From 5903ea9bae2d02f31080bed8ea5b71846fd80733 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Sep 2024 16:51:23 -0700 Subject: [PATCH 001/622] [NFC] Avoid wasted LocalGraph work in MergeLocals (#6908) We computed both get and set influences, but getGetInfluences() was never called, so that work was entirely pointless. This makes the pass 20% faster. --- src/passes/MergeLocals.cpp | 2 +- test/lit/passes/merge-locals.wast | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/merge-locals.wast diff --git a/src/passes/MergeLocals.cpp b/src/passes/MergeLocals.cpp index e6cf71538a4..9402e0669b3 100644 --- a/src/passes/MergeLocals.cpp +++ b/src/passes/MergeLocals.cpp @@ -108,7 +108,7 @@ struct MergeLocals // compute all dependencies auto* func = getFunction(); LocalGraph preGraph(func, getModule()); - preGraph.computeInfluences(); + preGraph.computeSetInfluences(); // optimize each copy std::unordered_map optimizedToCopy, optimizedToTrivial; diff --git a/test/lit/passes/merge-locals.wast b/test/lit/passes/merge-locals.wast new file mode 100644 index 00000000000..0b1e45d9d72 --- /dev/null +++ b/test/lit/passes/merge-locals.wast @@ -0,0 +1,30 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s --merge-locals -all -S -o - | filecheck %s + +(module + ;; CHECK: (func $between-unreachable (type $0) (result i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $y i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $between-unreachable (result i32) + (local $x i32) + (local $y i32) + (select + (unreachable) + ;; The local copy here is in between unreachables. We should not error. + (local.tee $x + (local.get $y) + ) + (unreachable) + ) + ) +) + From 509d18323cc9513b513bebdc0443e3fd416db692 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 5 Sep 2024 17:09:52 -0700 Subject: [PATCH 002/622] Avoid conflicts with public rec groups in MinimizeRecGroups (#6911) As with all type optimizations, MinimizeRecGroups only changes private types, which are the only types that are safe to modify. However, it is important for correctness that MinimimizeRecGroups maintain separate type identities for all types, whether public or private, to ensure that casts that should differentiate two types cannot change behavior. Previously the pass worked exclusively on private types, so there was nothing preventing it from constructing a minimial rec group that happened to have the same shape, and therefore type identity, as a public rec group. #6886 exhibits a fuzzer test case where this happens and changes the behavior of the program. Fix the bug by recording all public rec group shapes and resolve conflicts with these shapes by updating the shape of the conflicting non-public type. Fixes #6886. --- src/passes/MinimizeRecGroups.cpp | 80 +++++++++++++++++++++--- test/lit/passes/minimize-rec-groups.wast | 80 ++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 7 deletions(-) diff --git a/src/passes/MinimizeRecGroups.cpp b/src/passes/MinimizeRecGroups.cpp index 3a61e1e0b22..8b38f9e4a14 100644 --- a/src/passes/MinimizeRecGroups.cpp +++ b/src/passes/MinimizeRecGroups.cpp @@ -350,6 +350,14 @@ struct MinimizeRecGroups : Pass { // For each shape of rec group we have created, its index in `groups`. std::unordered_map groupShapeIndices; + // A sentinel index for public group shapes, which are recorded in + // `groupShapeIndices` but not in `groups`. + static constexpr Index PublicGroupIndex = -1; + + // Since we won't store public groups in `groups`, we need this separate place + // to store their types referred to by their `RecGroupShape`s. + std::vector> publicGroupTypes; + // When we find that two groups are isomorphic to (i.e. permutations of) each // other, we combine their equivalence classes and choose a new class // representative to use to disambiguate the groups and any further groups @@ -370,9 +378,32 @@ struct MinimizeRecGroups : Pass { initBrandOptions(); - types = ModuleUtils::getPrivateHeapTypes(*module); - for (auto type : ModuleUtils::collectHeapTypes(*module)) { - typeIndices.insert({type, typeIndices.size()}); + auto typeInfo = ModuleUtils::collectHeapTypeInfo( + *module, + ModuleUtils::TypeInclusion::AllTypes, + ModuleUtils::VisibilityHandling::FindVisibility); + + types.reserve(typeInfo.size()); + + // We cannot optimize public types, but we do need to make sure we don't + // generate new groups with the same shape. + std::unordered_set publicGroups; + for (auto& [type, info] : typeInfo) { + if (info.visibility == ModuleUtils::Visibility::Private) { + // We can optimize private types. + types.push_back(type); + typeIndices.insert({type, typeIndices.size()}); + } else { + publicGroups.insert(type.getRecGroup()); + } + } + + publicGroupTypes.reserve(publicGroups.size()); + for (auto group : publicGroups) { + publicGroupTypes.emplace_back(group.begin(), group.end()); + [[maybe_unused]] auto [_, inserted] = groupShapeIndices.insert( + {RecGroupShape(publicGroupTypes.back()), PublicGroupIndex}); + assert(inserted); } // The number of types to optimize is an upper bound on the number of @@ -428,7 +459,15 @@ struct MinimizeRecGroups : Pass { return; } - // We have a conflict. There are five possibilities: + // We have a conflict. There are seven possibilities: + // + // 0. We have a conflict with a public group and... + // + // A. We are trying to insert the next permutation of an existing + // equivalence class. + // + // B. We are inserting a new group not yet affiliated with a nontrivial + // equivalence class. // // 1. We are trying to insert the next permutation of an existing // equivalence class and have found that... @@ -454,12 +493,39 @@ struct MinimizeRecGroups : Pass { // // These possibilities are handled in order below. + auto& groupInfo = groups[group]; + Index groupRep = equivalenceClasses.getRoot(group); + Index other = it->second; - auto& groupInfo = groups[group]; - auto& otherInfo = groups[other]; + if (other == PublicGroupIndex) { + // The group currently has the same shape as some public group. We can't + // change the public group, so we need to change the shape of the current + // group. + // + // Case 0A: Since we already have a nontrivial equivalence class, we can + // try the next permutation to avoid conflict with the public group. + if (groups[groupRep].classInfo) { + // We have everything we need to generate the next permutation of this + // group. + auto& classInfo = *groups[groupRep].classInfo; + classInfo.advance(); + classInfo.permute(groupInfo); + shapesToUpdate.push_back(group); + return; + } - Index groupRep = equivalenceClasses.getRoot(group); + // Case 0B: There is no nontrivial equivalence class yet. Create one so + // we have all the information we need to try a different permutation. + assert(group == groupRep); + groupInfo.permutation = getCanonicalPermutation(groupInfo.group); + groupInfo.classInfo.emplace(groupInfo); + groupInfo.classInfo->permute(groupInfo); + shapesToUpdate.push_back(group); + return; + } + + auto& otherInfo = groups[other]; Index otherRep = equivalenceClasses.getRoot(other); // Case 1A: We have found a permutation of this group that has the same diff --git a/test/lit/passes/minimize-rec-groups.wast b/test/lit/passes/minimize-rec-groups.wast index b1c1b1467cb..3483d1c9de3 100644 --- a/test/lit/passes/minimize-rec-groups.wast +++ b/test/lit/passes/minimize-rec-groups.wast @@ -484,3 +484,83 @@ ;; CHECK: (global $a3 (ref null $a3) (ref.null none)) (global $a3 (ref null $a3) (ref.null none)) ) + +;; We must avoid conflicts with public types. +(module + ;; CHECK: (type $public (struct)) + (type $public (struct)) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $1 (array (mut i8))) + + ;; CHECK: (type $private (struct)) + (type $private (struct)) + (type $other (struct (field (ref null $private)))) + ) + + ;; CHECK: (global $public (ref null $public) (ref.null none)) + (global $public (export "g") (ref null $public) (ref.null none)) + + ;; CHECK: (global $private (ref null $private) (ref.null none)) + (global $private (ref null $private) (ref.null none)) +) + +;; Same as above, but now the public types are more complicated. +;; CHECK: (export "g" (global $public)) +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $publicA (struct (field (ref null $publicB)))) + (type $publicA (struct (field (ref null $publicB)))) + ;; CHECK: (type $publicB (struct (field (ref null $publicA)))) + (type $publicB (struct (field (ref null $publicA)))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $2 (struct)) + + ;; CHECK: (type $privateB (struct (field (ref null $privateA)))) + + ;; CHECK: (type $privateA (struct (field (ref null $privateB)))) + (type $privateA (struct (field (ref null $privateB)))) + (type $privateB (struct (field (ref null $privateA)))) + (type $other (struct (field i32))) + ) + + ;; CHECK: (global $public (ref null $publicA) (ref.null none)) + (global $public (export "g") (ref null $publicA) (ref.null none)) + ;; CHECK: (global $private (ref null $privateA) (ref.null none)) + (global $private (ref null $privateA) (ref.null none)) +) + +;; Now the conflict with the public type does not arise until we try to resolve +;; a conflict between the private types. +;; CHECK: (export "g" (global $public)) +(module + (rec + ;; CHECK: (type $privateA (struct (field i32) (field i64))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $publicBrand (struct)) + (type $publicBrand (struct)) + ;; CHECK: (type $public (struct (field i32) (field i64))) + (type $public (struct (field i32 i64))) + ) + (rec + (type $privateA (struct (field i32 i64))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $privateB (struct (field i32) (field i64))) + (type $privateB (struct (field i32 i64))) + ) + + ;; CHECK: (type $4 (struct)) + + ;; CHECK: (global $public (ref null $public) (ref.null none)) + (global $public (export "g") (ref null $public) (ref.null none)) + + ;; CHECK: (global $privateA (ref null $privateA) (ref.null none)) + (global $privateA (ref null $privateA) (ref.null none)) + ;; CHECK: (global $privateB (ref null $privateB) (ref.null none)) + (global $privateB (ref null $privateB) (ref.null none)) +) +;; CHECK: (export "g" (global $public)) From 655eab84019236d02032ceb61570f4f34ee8ac0d Mon Sep 17 00:00:00 2001 From: Roberto Lublinerman Date: Fri, 6 Sep 2024 19:30:00 -0300 Subject: [PATCH 003/622] Adds a J2CL specific pass that moves itable entries to vtables (#6888) This allows to remove a reference field from all Java objects reducing the per object memory and initialization overhead. The pass is designed to run direclty on the J2CL output before other optimizations since it relies on invariants that might get lost in optimization. If the invariants don't hold the pass aborts. --- src/passes/CMakeLists.txt | 1 + src/passes/J2CLItableMerging.cpp | 344 ++++++++++++++++++++++++ src/passes/pass.cpp | 4 + src/passes/passes.h | 1 + test/lit/help/wasm-metadce.test | 4 + test/lit/help/wasm-opt.test | 4 + test/lit/help/wasm2js.test | 4 + test/lit/passes/j2cl-merge-itables.wast | 230 ++++++++++++++++ 8 files changed, 592 insertions(+) create mode 100644 src/passes/J2CLItableMerging.cpp create mode 100644 test/lit/passes/j2cl-merge-itables.wast diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index b95017baca5..a219438b410 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -53,6 +53,7 @@ set(passes_SOURCES InstrumentLocals.cpp InstrumentMemory.cpp Intrinsics.cpp + J2CLItableMerging.cpp J2CLOpts.cpp JSPI.cpp LegalizeJSInterface.cpp diff --git a/src/passes/J2CLItableMerging.cpp b/src/passes/J2CLItableMerging.cpp new file mode 100644 index 00000000000..472d18b7e86 --- /dev/null +++ b/src/passes/J2CLItableMerging.cpp @@ -0,0 +1,344 @@ +/* + * Copyright 2024 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. + */ + +// This is a J2CL specific pass that merges itables into vtables. It is meant +// to be run at the beginning before structs corresponding to Java classes are +// optimized. +// +// The motivation for embedding itables into vtables is to reduce memory usage. +// +// The pass makes the following transformation on the structs related to Java +// classes. For given type `Foo` with `Foo[vtable] = { m1, m2, m3, ... }` +// and `Foo[itable] = { i1, i2, ...}`, this pass transforms it to +// `Foo[vtable] = { i1, i2, ...., m1, m2, m3, ... }`, and fixes all accesses +// and initializations accordingly. + +#include +#include + +#include "ir/effects.h" +#include "ir/localize.h" +#include "ir/ordering.h" +#include "ir/struct-utils.h" +#include "ir/subtypes.h" +#include "ir/type-updating.h" +#include "ir/utils.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm-type.h" +#include "wasm.h" + +namespace wasm { + +namespace { + +// Information about the structs that have vtables and itables. +struct StructInfo { + HeapType javaClass; + HeapType vtable; + HeapType itable; +}; + +struct J2CLItableMerging : public Pass { + // Keep track of all the structInfos so that they will be automatically + // released after the pass is done. + std::list structInfos; + + // Globals that hold vtables and itables indexed by their heap type. + // There is exactly 1 global for each vtable/itable type. + std::unordered_map tableGlobalsByType; + std::unordered_map structInfoByVtableType; + std::unordered_map structInfoByITableType; + + unsigned long itableSize = 0; + + void run(Module* module) override { + if (!module->features.hasGC()) { + return; + } + + if (!getPassOptions().closedWorld) { + Fatal() << "--merge-j2cl-itables requires --closed-world"; + } + + collectVtableAndItableTypes(*module); + // Update the indices to access the functions in the vtables and update + // the construction of the vtable instances. + updateVtableFieldsAccesses(*module); + // And now we can transform the accesses to the itable fields into their + // corresponding vtable fields. Needs to be done after + // updateVtableFieldsAccesses. + rerouteItableAccess(*module); + // The type structures are updated last since types are used as keys in + // the maps used above. + updateTypes(*module); + + // Since now vtables are initialized with `global.get` of the interface + // vtable instances, we need to reorder the globals. + PassRunner runner(module); + runner.add("reorder-globals-always"); + runner.setIsNested(true); + runner.run(); + } + + // Collects all structs corresponding to Java classes, their vtables and + // their itables. This is very tied to the way j2cl emits these constructs. + void collectVtableAndItableTypes(Module& wasm) { + // 1. Collect all structs that correspond that a Java type. + for (auto [heapType, typeNameInfo] : wasm.typeNames) { + + if (!heapType.isStruct()) { + continue; + } + + auto type = heapType.getStruct(); + if (typeNameInfo.fieldNames.empty() || + !typeNameInfo.fieldNames[0].equals("vtable")) { + continue; + } + if (typeNameInfo.fieldNames.size() < 1 || + !typeNameInfo.fieldNames[1].equals("itable")) { + continue; + } + + auto vtabletype = type.fields[0].type.getHeapType(); + auto itabletype = type.fields[1].type.getHeapType(); + + auto structItableSize = itabletype.getStruct().fields.size(); + + if (itableSize != 0 && itableSize != structItableSize) { + Fatal() << "--merge-j2cl-itables needs to be the first pass to run " + << "on j2cl output. (found itables with different sizes)"; + } + + itableSize = structItableSize; + + // Add a new StructInfo to the list by value so that its memory gets + // reclaimed automatically on exit. + structInfos.push_back(StructInfo{heapType, vtabletype, itabletype}); + // Point to the StructInfo just added to the list to be able to look it + // up by its vtable and itable types. + structInfoByVtableType[vtabletype] = &structInfos.back(); + structInfoByITableType[itabletype] = &structInfos.back(); + } + + // 2. Collect the globals for vtables and itables. + for (auto& g : wasm.globals) { + if (!g->type.isStruct()) { + continue; + } + if (structInfoByVtableType.count(g->type.getHeapType())) { + tableGlobalsByType[g->type.getHeapType()] = g.get(); + } else if (structInfoByITableType.count(g->type.getHeapType())) { + tableGlobalsByType[g->type.getHeapType()] = g.get(); + } + } + + if (itableSize == 0) { + Fatal() << "--merge-j2cl-itables needs to be the first pass to run " + << "on j2cl output. (no Java classes found)"; + } + } + + // Fix the indexes of `struct.get` for vtable fields, and prepend the + // initializers for the itable fields to `struct.new`. + // Note that there isn't any `struct.set` because the vtable fields are + // immutable. + void updateVtableFieldsAccesses(Module& wasm) { + struct Reindexer : public WalkerPass> { + bool isFunctionParallel() override { return true; } + + J2CLItableMerging& parent; + + Reindexer(J2CLItableMerging& parent) : parent(parent) {} + + std::unique_ptr create() override { + return std::make_unique(parent); + } + + void visitStructGet(StructGet* curr) { + if (curr->ref->type == Type::unreachable) { + return; + } + + if (!parent.structInfoByVtableType.count( + curr->ref->type.getHeapType())) { + return; + } + // This is a struct.get on the vtable. + // It is ok to just change the index since the field has moved but + // the type is the same. + curr->index += parent.itableSize; + } + + void visitStructNew(StructNew* curr) { + if (curr->type == Type::unreachable) { + return; + } + + auto it = parent.structInfoByVtableType.find(curr->type.getHeapType()); + if (it == parent.structInfoByVtableType.end()) { + return; + } + // The struct.new is for a vtable type and structInfo has the + // information relating the struct types for the Java class, its vtable + // and its itable. + auto structInfo = it->second; + + // Get the global that holds the corresponding itable instance. + auto* itableGlobal = parent.tableGlobalsByType[structInfo->itable]; + StructNew* itableStructNew = nullptr; + + if (itableGlobal && itableGlobal->init) { + if (itableGlobal->init->is()) { + // The global might get initialized with the shared empty itable, + // obtain the itable struct.new from the global.init. + auto* globalGet = itableGlobal->init->dynCast(); + auto* global = getModule()->getGlobal(globalGet->name); + itableStructNew = global->init->dynCast(); + } else { + // The global is initialized with a struct.new of the itable. + itableStructNew = itableGlobal->init->dynCast(); + } + } + + if (!itableStructNew) { + Fatal() << "--merge-j2cl-itables needs to be the first pass to run " + << "on j2cl output. (itable initializer not found)"; + } + auto& itableFieldInitializers = itableStructNew->operands; + + // Add the initialization for the itable fields. + for (Index i = parent.itableSize; i > 0; i--) { + if (itableFieldInitializers.size() >= i) { + // The itable was initialized with a struct.new, copy the + // initialization values. + curr->operands.insertAt( + 0, + ExpressionManipulator::copy(itableFieldInitializers[i - 1], + *getModule())); + } else { + // The itable was initialized with struct.new_default. So use + // null values to initialize the itable fields. + Builder builder(*getModule()); + curr->operands.insertAt( + 0, + builder.makeRefNull(itableStructNew->type.getHeapType() + .getStruct() + .fields[i - 1] + .type.getHeapType())); + } + } + } + }; + + Reindexer reindexer(*this); + reindexer.run(getPassRunner(), &wasm); + reindexer.runOnModuleCode(getPassRunner(), &wasm); + } + + // Redirects all itable access by changing `struct.get` of the `itable` field + // to `struct.get` on the to `vtable` field. + void rerouteItableAccess(Module& wasm) { + struct Rerouter : public WalkerPass> { + bool isFunctionParallel() override { return true; } + + J2CLItableMerging& parent; + + Rerouter(J2CLItableMerging& parent) : parent(parent) {} + + std::unique_ptr create() override { + return std::make_unique(parent); + } + + void visitStructGet(StructGet* curr) { + if (curr->ref->type == Type::unreachable) { + return; + } + + if (!curr->type.isStruct() || + !parent.structInfoByITableType.count(curr->type.getHeapType())) { + return; + } + + // This is a struct.get that returns an itable type; + // Change to return the corresponding vtable type. + Builder builder(*getModule()); + replaceCurrent(builder.makeStructGet( + 0, + curr->ref, + parent.structInfoByITableType[curr->type.getHeapType()] + ->javaClass.getStruct() + .fields[0] + .type)); + } + }; + + Rerouter rerouter(*this); + rerouter.run(getPassRunner(), &wasm); + rerouter.runOnModuleCode(getPassRunner(), &wasm); + } + + // Modify the struct definitions adding the itable fields to the vtable and + // preserving the vtable field names. + void updateTypes(Module& wasm) { + class TypeRewriter : public GlobalTypeRewriter { + J2CLItableMerging& parent; + + public: + TypeRewriter(Module& wasm, J2CLItableMerging& parent) + : GlobalTypeRewriter(wasm), parent(parent) {} + + void modifyStruct(HeapType oldStructType, Struct& struct_) override { + if (parent.structInfoByVtableType.count(oldStructType)) { + auto& newFields = struct_.fields; + + auto structInfo = parent.structInfoByVtableType[oldStructType]; + // Add the itable fields to the beginning of the vtable. + auto it = structInfo->itable.getStruct().fields.rbegin(); + while (it != structInfo->itable.getStruct().fields.rend()) { + newFields.insert(newFields.begin(), *it++); + newFields[0].type = getTempType(newFields[0].type); + } + + // Update field names as well. The Type Rewriter cannot do this for + // us, as it does not know which old fields map to which new ones + // (it just keeps the names in sequence). + auto& nameInfo = wasm.typeNames[oldStructType]; + + // Make a copy of the old ones before clearing them. + auto oldFieldNames = nameInfo.fieldNames; + + // Clear the old names and write the new ones. + nameInfo.fieldNames.clear(); + // Only need to preserve the field names for the vtable fields; the + // itable fields do not have names (in the original .wat file they + // are accessed by index). + for (Index i = 0; i < oldFieldNames.size(); i++) { + nameInfo.fieldNames[i + parent.itableSize] = oldFieldNames[i]; + } + } + } + }; + + TypeRewriter(wasm, *this).update(); + } +}; + +} // anonymous namespace + +Pass* createJ2CLItableMergingPass() { return new J2CLItableMerging(); } +} // namespace wasm \ No newline at end of file diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index eb499a4ad1b..d55e22111df 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -204,6 +204,10 @@ void PassRegistry::registerPasses() { createGUFAOptimizingPass); registerPass( "optimize-j2cl", "optimizes J2CL specific constructs.", createJ2CLOptsPass); + registerPass( + "merge-j2cl-itables", + "Merges itable structures into vtables to make types more compact", + createJ2CLItableMergingPass); registerPass("type-refining", "apply more specific subtypes to type fields where possible", createTypeRefiningPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index e4de733da31..c100cd2f954 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -67,6 +67,7 @@ Pass* createI64ToI32LoweringPass(); Pass* createInlineMainPass(); Pass* createInliningPass(); Pass* createInliningOptimizingPass(); +Pass* createJ2CLItableMergingPass(); Pass* createJSPIPass(); Pass* createJ2CLOptsPass(); Pass* createLegalizeAndPruneJSInterfacePass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 41a43bf1b03..4ddf5520397 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -248,6 +248,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-blocks merges blocks to their parents ;; CHECK-NEXT: +;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into +;; CHECK-NEXT: vtables to make types more +;; CHECK-NEXT: compact +;; CHECK-NEXT: ;; CHECK-NEXT: --merge-locals merges locals when beneficial ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-similar-functions merges similar functions when diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index e5b30e3046c..f55798253e7 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -257,6 +257,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-blocks merges blocks to their parents ;; CHECK-NEXT: +;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into +;; CHECK-NEXT: vtables to make types more +;; CHECK-NEXT: compact +;; CHECK-NEXT: ;; CHECK-NEXT: --merge-locals merges locals when beneficial ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-similar-functions merges similar functions when diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 2d933984e24..39c4d5f5ebb 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -211,6 +211,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-blocks merges blocks to their parents ;; CHECK-NEXT: +;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into +;; CHECK-NEXT: vtables to make types more +;; CHECK-NEXT: compact +;; CHECK-NEXT: ;; CHECK-NEXT: --merge-locals merges locals when beneficial ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-similar-functions merges similar functions when diff --git a/test/lit/passes/j2cl-merge-itables.wast b/test/lit/passes/j2cl-merge-itables.wast new file mode 100644 index 00000000000..f578eb65c5c --- /dev/null +++ b/test/lit/passes/j2cl-merge-itables.wast @@ -0,0 +1,230 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --closed-world --merge-j2cl-itables -all -S -o - | filecheck %s + +;; Shared itable instance. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $Object (sub (struct (field $vtable (ref $Object.vtable)) (field $itable (ref $Object.itable))))) + (type $Object (sub (struct + (field $vtable (ref $Object.vtable)) + (field $itable (ref $Object.itable))))) + + ;; CHECK: (type $SubObject (sub $Object (struct (field $vtable (ref $SubObject.vtable)) (field $itable (ref $Object.itable))))) + (type $SubObject (sub $Object (struct + (field $vtable (ref $SubObject.vtable)) + (field $itable (ref $Object.itable))))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $function (func)) + (type $function (func)) + + ;; The $Object.itable field (a structref) will be added as a field to + ;; the beginning of this vtable. + ;; CHECK: (type $Object.vtable (sub (struct (field structref)))) + (type $Object.vtable (sub (struct))) + + ;; The $Object.itable field (a structref) will be added as a field to + ;; the beginning of this vtable. + ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (struct (field structref) (field (ref $function))))) + (type $SubObject.vtable (sub $Object.vtable (struct + (field (ref $function))))) + + ;; CHECK: (type $Object.itable (struct (field structref))) + (type $Object.itable (struct + (field (ref null struct)))) + ) + + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) + (global $Object.itable (ref $Object.itable) + (struct.new_default $Object.itable)) + + ;; CHECK: (global $SubObject.itable (ref $Object.itable) (global.get $Object.itable)) + (global $SubObject.itable (ref $Object.itable) + (global.get $Object.itable)) ;; uses shared empty itable instance. + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $SubObject.vtable (ref $SubObject.vtable) (struct.new $SubObject.vtable + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.func $SubObject.f) + ;; CHECK-NEXT: )) + (global $SubObject.vtable (ref $SubObject.vtable) + (struct.new $SubObject.vtable (ref.func $SubObject.f))) + + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $Object.vtable (ref $Object.vtable) (struct.new $Object.vtable + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $Object.vtable (ref $Object.vtable) + (struct.new $Object.vtable)) + + ;; CHECK: (func $SubObject.f (type $function) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $SubObject.f + (type $function) + ) + + ;; CHECK: (func $usages (type $2) + ;; CHECK-NEXT: (local $o (ref null $SubObject)) + ;; CHECK-NEXT: (local.set $o + ;; CHECK-NEXT: (struct.new $SubObject + ;; CHECK-NEXT: (global.get $SubObject.vtable) + ;; CHECK-NEXT: (global.get $SubObject.itable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 + ;; CHECK-NEXT: (struct.get $SubObject $vtable + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 + ;; CHECK-NEXT: (struct.get $SubObject $vtable + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $usages + (local $o (ref null $SubObject)) + (local.set $o + (struct.new $SubObject + (global.get $SubObject.vtable) + (global.get $SubObject.itable))) + (drop + ;; The access to vtable field 0 is offset but the itable size and + ;; will be an access to field 1. + (struct.get $SubObject.vtable 0 + (struct.get $SubObject $vtable + (local.get $o)))) + (drop + ;; The access to itable field 0 will be rerouted to be an access to + ;; vtable field 0. + (struct.get $Object.itable 0 + (struct.get $SubObject $itable + (local.get $o)))) + ) +) + +;; Each type has its own itable. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $Object (sub (struct (field $vtable (ref $Object.vtable)) (field $itable (ref $Object.itable))))) + (type $Object (sub (struct + (field $vtable (ref $Object.vtable)) + (field $itable (ref $Object.itable))))) + + ;; CHECK: (type $SubObject (sub $Object (struct (field $vtable (ref $SubObject.vtable)) (field $itable (ref $SubObject.itable))))) + (type $SubObject (sub $Object (struct + (field $vtable (ref $SubObject.vtable)) + (field $itable (ref $SubObject.itable))))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $function (func)) + (type $function (func)) + + ;; CHECK: (type $Object.itable (sub (struct (field structref)))) + (type $Object.itable (sub (struct + (field (ref null struct))))) + + ;; CHECK: (type $SubObject.itable (sub $Object.itable (struct (field structref)))) + (type $SubObject.itable (sub $Object.itable + (struct (field (ref null struct))))) + + ;; The $Object.itable field (a structref) will be added as a field to + ;; the beginning of this vtable. + ;; CHECK: (type $Object.vtable (sub (struct (field structref)))) + (type $Object.vtable (sub (struct))) + + ;; The $SubObject.itable field (a structref) will be added as a field to + ;; the beginning of this vtable. + ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (struct (field structref) (field (ref $function))))) + (type $SubObject.vtable (sub $Object.vtable + (struct (field (ref $function))))) + ) + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $SubObject.vtable (ref $SubObject.vtable) (struct.new $SubObject.vtable + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.func $SubObject.f) + ;; CHECK-NEXT: )) + (global $SubObject.vtable (ref $SubObject.vtable) + (struct.new $SubObject.vtable (ref.func $SubObject.f))) + + ;; CHECK: (global $SubObject.itable (ref $SubObject.itable) (struct.new_default $SubObject.itable)) + (global $SubObject.itable (ref $SubObject.itable) + (struct.new_default $SubObject.itable)) + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $Object.vtable (ref $Object.vtable) (struct.new $Object.vtable + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $Object.vtable (ref $Object.vtable) + (struct.new $Object.vtable)) + + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) + (global $Object.itable (ref $Object.itable) + (struct.new_default $Object.itable)) + + ;; CHECK: (func $SubObject.f (type $function) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $SubObject.f + (type $function) + ) + + ;; CHECK: (func $usages (type $2) + ;; CHECK-NEXT: (local $o (ref null $SubObject)) + ;; CHECK-NEXT: (local.set $o + ;; CHECK-NEXT: (struct.new $SubObject + ;; CHECK-NEXT: (global.get $SubObject.vtable) + ;; CHECK-NEXT: (global.get $SubObject.itable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 + ;; CHECK-NEXT: (struct.get $SubObject $vtable + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 + ;; CHECK-NEXT: (struct.get $SubObject $vtable + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $usages + (local $o (ref null $SubObject)) + (local.set $o + (struct.new $SubObject + (global.get $SubObject.vtable) + (global.get $SubObject.itable))) + (drop + ;; The access to vtable field 0 is offset but the itable size and + ;; will be an access to field 1. + (struct.get $SubObject.vtable 0 + (struct.get $SubObject $vtable + (local.get $o)))) + (drop + ;; The access to itable field 0 will be rerouted to be an access to + ;; vtable field 0. + (struct.get $Object.itable 0 + (struct.get $SubObject $itable + (local.get $o)))) + ) +) From 4d58e2770b121e319025196c0cc2622864c47098 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 6 Sep 2024 15:37:39 -0700 Subject: [PATCH 004/622] [FP16] Fix replace lane for F16x8. (#6906) Before this change, replace lane was converting all the F16 lanes to F32 and then replacing one lane with the F16 (I32 representation) value, but it did not then convert all the other lanes back to F16 (I32). To fix this we can just leave the lanes as I32 and replace the one lane. Note: Previous replace lane tests didn't catch this since they started with vectors with all zeros so the F32->I32 didn't matter. Also, other operations don't run into this issue since they iterate over all lanes and convert the F32's back to F16 (I32). --------- Co-authored-by: Alon Zakai --- src/wasm/literal.cpp | 5 ++++- test/spec/f16.wast | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index e332db30582..e8641cbd7f2 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -1834,7 +1834,10 @@ Literal Literal::replaceLaneI64x2(const Literal& other, uint8_t index) const { return replace<2, &Literal::getLanesI64x2>(*this, other, index); } Literal Literal::replaceLaneF16x8(const Literal& other, uint8_t index) const { - return replace<8, &Literal::getLanesF16x8>( + // For F16 lane replacement we do not need to convert all the values to F32, + // instead keep the lanes as I32, and just replace the one lane with the + // integer value of the F32. + return replace<8, &Literal::getLanesUI16x8>( *this, other.convertF32ToF16(), index); } Literal Literal::replaceLaneF32x4(const Literal& other, uint8_t index) const { diff --git a/test/spec/f16.wast b/test/spec/f16.wast index d5de0c0e85a..f0d2b067827 100644 --- a/test/spec/f16.wast +++ b/test/spec/f16.wast @@ -34,6 +34,9 @@ (func (export "f16x8.nearest") (param $0 v128) (result v128) (f16x8.nearest (local.get $0))) (func (export "f16x8.relaxed_madd") (param $0 v128) (param $1 v128) (param $2 v128) (result v128) (f16x8.relaxed_madd (local.get $0) (local.get $1) (local.get $2))) (func (export "f16x8.relaxed_nmadd") (param $0 v128) (param $1 v128) (param $2 v128) (result v128) (f16x8.relaxed_nmadd (local.get $0) (local.get $1) (local.get $2))) + ;; Multiple operation tests: + (func (export "splat_replace") (result v128) (f16x8.replace_lane 0 (f16x8.splat (f32.const 1)) (f32.const 99)) + ) ) (assert_return (invoke "f32.load_f16") (f32.const 42.0)) @@ -216,3 +219,7 @@ (v128.const i16x8 0x7c00 0x7c00 0xbc00 0 0x3c00 0x4000 0x3c00 0xbc00)) ;; inf inf -2 0 0 -0.25 9 -2 (v128.const i16x8 0x7c00 0x7c00 0xc000 0 0 0xb400 0x4880 0xc000)) + +(assert_return (invoke "splat_replace") + (v128.const i16x8 0x5630 0x3c00 0x3c00 0x3c00 0x3c00 0x3c00 0x3c00 0x3c00) +) From 2d70ee0d1d2620a676bdf82779187cf0ee5bd0cf Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Sep 2024 17:24:52 -0700 Subject: [PATCH 005/622] [NFC] Rename the old topological sort utility (#6914) This will allow both the old and new topological sort utilities to be included into the same .cpp file while we phase out the old utility. --- src/ir/subtypes.h | 4 ++-- src/ir/type-updating.cpp | 1 - ...opological_sort.h => old_topological_sort.h} | 17 +++++++++-------- src/tools/wasm-ctor-eval.cpp | 4 ++-- src/wasm-type-ordering.h | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) rename src/support/{topological_sort.h => old_topological_sort.h} (88%) diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index 20377205586..47ee51ac3a0 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -18,7 +18,7 @@ #define wasm_ir_subtypes_h #include "ir/module-utils.h" -#include "support/topological_sort.h" +#include "support/old_topological_sort.h" #include "wasm.h" namespace wasm { @@ -80,7 +80,7 @@ struct SubTypes { // A topological sort that visits subtypes first. auto getSubTypesFirstSort() const { - struct SubTypesFirstSort : TopologicalSort { + struct SubTypesFirstSort : OldTopologicalSort { const SubTypes& parent; SubTypesFirstSort(const SubTypes& parent) : parent(parent) { diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 123b1311941..17437cf2e35 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -20,7 +20,6 @@ #include "ir/module-utils.h" #include "ir/names.h" #include "ir/utils.h" -#include "support/topological_sort.h" #include "wasm-type-ordering.h" #include "wasm-type.h" #include "wasm.h" diff --git a/src/support/topological_sort.h b/src/support/old_topological_sort.h similarity index 88% rename from src/support/topological_sort.h rename to src/support/old_topological_sort.h index 3594617ebd5..2f72774123b 100644 --- a/src/support/topological_sort.h +++ b/src/support/old_topological_sort.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef wasm_support_topological_sort_h -#define wasm_support_topological_sort_h +#ifndef wasm_support_old_topological_sort_h +#define wasm_support_old_topological_sort_h #include #include @@ -35,7 +35,7 @@ namespace wasm { // the immediate predecessors of `item`. // // Cycles in the graph are not detected and will result in an infinite loop. -template struct TopologicalSort { +template struct OldTopologicalSort { private: // The DFS work list. std::vector workStack; @@ -45,9 +45,10 @@ template struct TopologicalSort { // Should be overridden by `Subtype`. void pushPredecessors(T item) { - static_assert(&TopologicalSort::pushPredecessors != - &Subtype::pushPredecessors, - "TopologicalSort subclass must implement `pushPredecessors`"); + static_assert( + &OldTopologicalSort::pushPredecessors != + &Subtype::pushPredecessors, + "OldTopologicalSort subclass must implement `pushPredecessors`"); } // Pop until the stack is empty or it has an unfinished item on top. @@ -90,7 +91,7 @@ template struct TopologicalSort { using pointer = T*; using iterator_category = std::input_iterator_tag; - TopologicalSort* parent; + OldTopologicalSort* parent; bool isEnd() const { return !parent || parent->workStack.empty(); } bool operator==(Iterator& other) const { return isEnd() == other.isEnd(); } @@ -115,4 +116,4 @@ template struct TopologicalSort { } // namespace wasm -#endif // wasm_support_topological_sort_h +#endif // wasm_support_old_topological_sort_h diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 9cf552450bd..6809fa32afb 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -36,9 +36,9 @@ #include "support/colors.h" #include "support/file.h" #include "support/insert_ordered.h" +#include "support/old_topological_sort.h" #include "support/small_set.h" #include "support/string.h" -#include "support/topological_sort.h" #include "tool-options.h" #include "wasm-builder.h" #include "wasm-interpreter.h" @@ -678,7 +678,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { if (!mustBeAfter.empty()) { // We found constraints that require reordering, so do so. - struct MustBeAfterSort : TopologicalSort { + struct MustBeAfterSort : OldTopologicalSort { MustBeAfter& mustBeAfter; MustBeAfterSort(MustBeAfter& mustBeAfter) : mustBeAfter(mustBeAfter) { diff --git a/src/wasm-type-ordering.h b/src/wasm-type-ordering.h index 981ac004d23..9ccf4c568d4 100644 --- a/src/wasm-type-ordering.h +++ b/src/wasm-type-ordering.h @@ -20,7 +20,7 @@ #include #include "support/insert_ordered.h" -#include "support/topological_sort.h" +#include "support/old_topological_sort.h" #include "wasm-type.h" namespace wasm::HeapTypeOrdering { @@ -30,7 +30,7 @@ namespace wasm::HeapTypeOrdering { // visited. template struct SupertypesFirstBase - : TopologicalSort> { + : OldTopologicalSort> { // For each type in the input collection, whether it is a supertype. Used to // track membership in the input collection. InsertOrderedMap typeSet; From 7e117284029bfbfc2b638caa53335e1b2c53490e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Sep 2024 17:57:18 -0700 Subject: [PATCH 006/622] [NFC] Rename topological_orders.h to topological_sort.h (#6915) Now that the header includes topological sort utilities in addition to the utility that iterates over all topological orders, it makes more sense for it to be named topological_sort.h --- src/ir/module-utils.cpp | 2 +- src/passes/MinimizeRecGroups.cpp | 2 +- src/passes/ReorderGlobals.cpp | 2 +- src/support/{topological_orders.h => topological_sort.h} | 6 +++--- test/gtest/CMakeLists.txt | 2 +- test/gtest/{topological-orders.cpp => topological-sort.cpp} | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) rename src/support/{topological_orders.h => topological_sort.h} (98%) rename test/gtest/{topological-orders.cpp => topological-sort.cpp} (99%) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 3c81d6679f4..58f7d4637c1 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -20,7 +20,7 @@ #include "ir/manipulation.h" #include "ir/properties.h" #include "support/insert_ordered.h" -#include "support/topological_orders.h" +#include "support/topological_sort.h" namespace wasm::ModuleUtils { diff --git a/src/passes/MinimizeRecGroups.cpp b/src/passes/MinimizeRecGroups.cpp index 8b38f9e4a14..e89d9844219 100644 --- a/src/passes/MinimizeRecGroups.cpp +++ b/src/passes/MinimizeRecGroups.cpp @@ -75,7 +75,7 @@ #include "pass.h" #include "support/disjoint_sets.h" #include "support/strongly_connected_components.h" -#include "support/topological_orders.h" +#include "support/topological_sort.h" #include "wasm-type-shape.h" #include "wasm.h" diff --git a/src/passes/ReorderGlobals.cpp b/src/passes/ReorderGlobals.cpp index af0b9572a0d..f95f567268a 100644 --- a/src/passes/ReorderGlobals.cpp +++ b/src/passes/ReorderGlobals.cpp @@ -35,7 +35,7 @@ #include "ir/find_all.h" #include "pass.h" -#include "support/topological_orders.h" +#include "support/topological_sort.h" #include "wasm.h" namespace wasm { diff --git a/src/support/topological_orders.h b/src/support/topological_sort.h similarity index 98% rename from src/support/topological_orders.h rename to src/support/topological_sort.h index bc6609ae0c2..b75f6b78dd7 100644 --- a/src/support/topological_orders.h +++ b/src/support/topological_sort.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef wasm_support_topological_orders_h -#define wasm_support_topological_orders_h +#ifndef wasm_support_topological_sort_h +#define wasm_support_topological_sort_h #include #include @@ -300,4 +300,4 @@ template std::vector minSort(const Graph& graph, Cmp cmp) { } // namespace wasm -#endif // wasm_support_topological_orders_h +#endif // wasm_support_topological_sort_h diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 17e880a4efb..7ef72db135d 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -13,7 +13,7 @@ set(unittest_SOURCES scc.cpp stringify.cpp suffix_tree.cpp - topological-orders.cpp + topological-sort.cpp type-builder.cpp wat-lexer.cpp validator.cpp diff --git a/test/gtest/topological-orders.cpp b/test/gtest/topological-sort.cpp similarity index 99% rename from test/gtest/topological-orders.cpp rename to test/gtest/topological-sort.cpp index 67c56cbdb38..6a04a71ff47 100644 --- a/test/gtest/topological-orders.cpp +++ b/test/gtest/topological-sort.cpp @@ -18,7 +18,7 @@ #include #include -#include "support/topological_orders.h" +#include "support/topological_sort.h" #include "gtest/gtest.h" using namespace wasm; From 2525389276699787f8a22a71f19275909e2bb40d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Sep 2024 11:12:46 -0700 Subject: [PATCH 007/622] [NFC] LazyLocalGraph: Add getSetInfluences() (#6909) This new API on lazy local graphs allows us to use laziness in another place, StackIR opts. This makes writing the binary (which includes StackIR opts, when those are enabled), 10% faster. --- src/ir/LocalGraph.cpp | 68 ++++++++++++++++++++++++++++++++---- src/ir/local-graph.h | 41 ++++++++++++++++------ src/passes/MergeLocals.cpp | 8 ++++- src/wasm/wasm-stack-opts.cpp | 16 ++++----- 4 files changed, 107 insertions(+), 26 deletions(-) diff --git a/src/ir/LocalGraph.cpp b/src/ir/LocalGraph.cpp index fa7c976a4b2..11965040c96 100644 --- a/src/ir/LocalGraph.cpp +++ b/src/ir/LocalGraph.cpp @@ -308,26 +308,41 @@ struct LocalGraphFlower }; std::unordered_map getLocations; - // Set up getLocations using the flow blocks, so that we are ready to handle - // later lazy requests for the sets of particular gets. This is done in lazy - // mode. + // In lazy mode we also need to categorize gets and sets by their index. + std::vector> getsByIndex; + std::vector> setsByIndex; + + // Prepare for all later lazy work. void prepareLaziness() { prepareFlowBlocks(); + // Set up getLocations, getsByIndex, and setsByIndex. + auto numLocals = func->getNumLocals(); + getsByIndex.resize(numLocals); + setsByIndex.resize(numLocals); + for (auto& block : flowBlocks) { const auto& actions = block.actions; for (Index i = 0; i < actions.size(); i++) { if (auto* get = actions[i]->dynCast()) { getLocations[get] = BlockLocation{&block, i}; + getsByIndex[get->index].push_back(get); + } else if (auto* set = actions[i]->dynCast()) { + setsByIndex[set->index].push_back(set); + } else { + WASM_UNREACHABLE("bad action"); } } } } - // Flow a specific get. This is done in lazy mode. - void flowGet(LocalGet* get) { + // Flow a specific get to its sets. This is done in lazy mode. + void computeGetSets(LocalGet* get) { auto index = get->index; + // We must never repeat work. + assert(!getSetsMap.count(get)); + // Regardless of what we do below, ensure an entry for this get, so that we // know we computed it. auto& sets = getSetsMap[get]; @@ -390,6 +405,43 @@ struct LocalGraphFlower // We must do an inter-block flow. flowBackFromStartOfBlock(block, index, gets); } + + void computeSetInfluences(LocalSet* set, + LocalGraphBase::SetInfluencesMap& setInfluences) { + auto index = set->index; + + // We must never repeat work. + assert(!setInfluences.count(set)); + + // In theory we could flow the set forward, but to keep things simple we + // reuse the logic for flowing gets backwards: We flow all the gets of the + // set's index, thus fully computing that index and all its sets, including + // this one. This is not 100% lazy, but still avoids extra work by never + // doing work for local indexes we don't care about. + for (auto* get : getsByIndex[index]) { + // Don't repeat work. + if (!getSetsMap.count(get)) { + computeGetSets(get); + } + } + + // Ensure empty entries for each set of this index, to mark them as + // computed. + for (auto* set : setsByIndex[index]) { + setInfluences[set]; + } + + // Also ensure |set| itself, that we were originally asked about. It may be + // in unreachable code, which means it is not listed in setsByIndex. + setInfluences[set]; + + // Apply the info from the gets to the sets. + for (auto* get : getsByIndex[index]) { + for (auto* set : getSetsMap[get]) { + setInfluences[set].insert(get); + } + } + } }; // LocalGraph implementation @@ -526,7 +578,11 @@ LazyLocalGraph::~LazyLocalGraph() { } void LazyLocalGraph::computeGetSets(LocalGet* get) const { - flower->flowGet(get); + flower->computeGetSets(get); +} + +void LazyLocalGraph::computeSetInfluences(LocalSet* set) const { + flower->computeSetInfluences(set, setInfluences); } } // namespace wasm diff --git a/src/ir/local-graph.h b/src/ir/local-graph.h index 1c36fc1dc23..7fe9adedc24 100644 --- a/src/ir/local-graph.h +++ b/src/ir/local-graph.h @@ -64,26 +64,21 @@ struct LocalGraphBase { using Locations = std::map; Locations locations; + // A map of each get to the sets relevant to it (i.e., that it can read from). + using GetSetsMap = std::unordered_map; + // Sets of gets or sets, that are influenced, returned from get*Influences(). using SetInfluences = std::unordered_set; using GetInfluences = std::unordered_set; - // Defined publicly as other utilities need similar data layouts. - using GetSetsMap = std::unordered_map; + using SetInfluencesMap = std::unordered_map; + using GetInfluencesMap = std::unordered_map; protected: Function* func; Module* module; std::set SSAIndexes; - - // A map of each get to the sets relevant to it. This is mutable so that - // getSets() can be const in LazyLocalGraph (which does memoization, see - // below). - mutable GetSetsMap getSetsMap; - - std::unordered_map setInfluences; - std::unordered_map getInfluences; }; struct LocalGraph : public LocalGraphBase { @@ -167,6 +162,12 @@ struct LocalGraph : public LocalGraphBase { void computeSSAIndexes(); bool isSSA(Index x); + +private: + GetSetsMap getSetsMap; + + SetInfluencesMap setInfluences; + GetInfluencesMap getInfluences; }; // The internal implementation of the flow analysis used to compute things. This @@ -178,20 +179,38 @@ struct LazyLocalGraph : public LocalGraphBase { LazyLocalGraph(Function* func, Module* module = nullptr); ~LazyLocalGraph(); + // Similar APIs as in LocalGraph, but lazy versions. Each of them does a + // lookup, and if there is a missing entry then we did not do the computation + // yet, and then we do it and memoize it. const Sets& getSets(LocalGet* get) const { auto iter = getSetsMap.find(get); if (iter == getSetsMap.end()) { - // A missing entry means we did not do the computation yet. Do it now. computeGetSets(get); iter = getSetsMap.find(get); assert(iter != getSetsMap.end()); } return iter->second; } + const SetInfluences& getSetInfluences(LocalSet* set) const { + auto iter = setInfluences.find(set); + if (iter == setInfluences.end()) { + computeSetInfluences(set); + iter = setInfluences.find(set); + assert(iter != setInfluences.end()); + } + return iter->second; + } private: + // These data structures are mutable so that we can memoize. + mutable GetSetsMap getSetsMap; + + mutable SetInfluencesMap setInfluences; + // Compute the sets for a get and store them on getSetsMap. void computeGetSets(LocalGet* get) const; + // Compute influences for a set and store them on setInfluences. + void computeSetInfluences(LocalSet* set) const; // This remains alive as long as we are, so that we can compute things lazily. std::unique_ptr flower; diff --git a/src/passes/MergeLocals.cpp b/src/passes/MergeLocals.cpp index 9402e0669b3..b669c20d3a6 100644 --- a/src/passes/MergeLocals.cpp +++ b/src/passes/MergeLocals.cpp @@ -105,10 +105,16 @@ struct MergeLocals if (copies.empty()) { return; } - // compute all dependencies auto* func = getFunction(); + + // Compute the local graph. Note that we *cannot* do this lazily, as we want + // to read from the original state of the function while we are doing + // changes on it. That is, using an eager graph makes a snapshot of the + // initial state, which is what we want. If we can avoid that, this pass can + // be sped up by around 25%. LocalGraph preGraph(func, getModule()); preGraph.computeSetInfluences(); + // optimize each copy std::unordered_map optimizedToCopy, optimizedToTrivial; diff --git a/src/wasm/wasm-stack-opts.cpp b/src/wasm/wasm-stack-opts.cpp index a39b82b984d..4559705bf49 100644 --- a/src/wasm/wasm-stack-opts.cpp +++ b/src/wasm/wasm-stack-opts.cpp @@ -131,14 +131,14 @@ void StackIROptimizer::vacuum() { // no control flow branching out, we can remove both the set // and the get. void StackIROptimizer::local2Stack() { - // We use the localGraph to tell us if a get-set pair is indeed - // a set that is read by that get, and only that get. Note that we run - // this on the Binaryen IR, so we are assuming that no previous opt - // has changed the interaction of local operations. - // TODO: we can do this a lot faster, as we just care about linear - // control flow. - LocalGraph localGraph(func); - localGraph.computeSetInfluences(); + // We use the localGraph to tell us if a get-set pair is indeed a set that is + // read by that get, and only that get. Note that we run this on Binaryen IR, + // so we are assuming that no previous opt has changed the interaction of + // local operations. + // + // We use a lazy graph here as we only query in the rare case when we find a + // set/get pair that looks optimizable. + LazyLocalGraph localGraph(func); // The binary writing of StringWTF16Get and StringSliceWTF is optimized to use // fewer scratch locals when their operands are already LocalGets. To avoid // interfering with that optimization, we have to avoid removing such From 0b07c1b125715ec599c01e182db6729d550bd329 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Mon, 9 Sep 2024 16:35:39 -0400 Subject: [PATCH 008/622] Fix a warning under gcc 14 (#6912) Fixes: https://github.com/WebAssembly/binaryen/issues/6779 --- src/parser/wat-parser.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/parser/wat-parser.h b/src/parser/wat-parser.h index 041ba1d58b5..0bfb829cb8a 100644 --- a/src/parser/wat-parser.h +++ b/src/parser/wat-parser.h @@ -34,12 +34,17 @@ Result<> parseModule(Module& wasm, Lexer& lexer); Result parseConst(Lexer& lexer); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + struct InvokeAction { std::optional base; Name name; Literals args; }; +#pragma GCC diagnostic pop + struct GetAction { std::optional base; Name name; From 2467e70524c96481c34e5ac23b9f068eb60abcbf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Sep 2024 09:53:32 -0700 Subject: [PATCH 009/622] [NFC] Make LazyLocalGraph even lazier (#6919) Do not even construct the Flower helper class until we actually need it. This avoids even scanning the function and building the internal CFG if we never get any API call that needs it. This speeds up LICM by 50% (as now we never construct the CFG if we don't find a loop), and Stack IR-enabled binary writing by 10% (as many functions do not have locals in positions that can be optimized using LocalGraph). This moves |locations| from the base class to LocalGraph. It is not needed in the lazy version, so that makes sense for now (we can't keep it in the base, as then it would need to be mutable, which only makes sense for laziness). --- src/ir/LocalGraph.cpp | 15 ++++++++++++++- src/ir/local-graph.h | 17 ++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/ir/LocalGraph.cpp b/src/ir/LocalGraph.cpp index 11965040c96..af13707314e 100644 --- a/src/ir/LocalGraph.cpp +++ b/src/ir/LocalGraph.cpp @@ -552,7 +552,14 @@ bool LocalGraph::isSSA(Index x) { return SSAIndexes.count(x); } // LazyLocalGraph LazyLocalGraph::LazyLocalGraph(Function* func, Module* module) - : LocalGraphBase(func, module) { + : LocalGraphBase(func, module) {} + +void LazyLocalGraph::makeFlower() const { + // Lazy graphs do not provide |locations| publicly. TODO: perhaps refactor to + // avoid filling in this dummy data structure, but we may want to add a lazy + // version of it too, so see which makes sense first. + LocalGraph::Locations locations; + flower = std::make_unique(getSetsMap, locations, func, module); @@ -578,10 +585,16 @@ LazyLocalGraph::~LazyLocalGraph() { } void LazyLocalGraph::computeGetSets(LocalGet* get) const { + if (!flower) { + makeFlower(); + } flower->computeGetSets(get); } void LazyLocalGraph::computeSetInfluences(LocalSet* set) const { + if (!flower) { + makeFlower(); + } flower->computeSetInfluences(set, setInfluences); } diff --git a/src/ir/local-graph.h b/src/ir/local-graph.h index 7fe9adedc24..46bc135e0bd 100644 --- a/src/ir/local-graph.h +++ b/src/ir/local-graph.h @@ -58,11 +58,8 @@ struct LocalGraphBase { // get. Typically only one or two apply there, so this is a small set. using Sets = SmallSet; - // Where each get and set is. We compute this while doing the main computation - // and make it accessible for users, for easy replacing of things without - // extra work. + // Where each get and set is. using Locations = std::map; - Locations locations; // A map of each get to the sets relevant to it (i.e., that it can read from). using GetSetsMap = std::unordered_map; @@ -104,6 +101,11 @@ struct LocalGraph : public LocalGraphBase { return iter->second; } + // We compute the locations of gets and sets while doing the main computation + // and make it accessible for users, for easy replacing of things without + // extra work. + Locations locations; + // Checks if two gets are equivalent, that is, definitely have the same // value. bool equivalent(LocalGet* a, LocalGet* b); @@ -213,7 +215,12 @@ struct LazyLocalGraph : public LocalGraphBase { void computeSetInfluences(LocalSet* set) const; // This remains alive as long as we are, so that we can compute things lazily. - std::unique_ptr flower; + // It is mutable as when we construct this is an internal detail, that does + // not cause observable differences in API calls. + mutable std::unique_ptr flower; + + // We create |flower| lazily. + void makeFlower() const; }; } // namespace wasm From 203dcd5c47d6ea784e613f647f8addd9815a3d5b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Sep 2024 09:54:51 -0700 Subject: [PATCH 010/622] [NFC-ish] Remove LocalGraph from LocalSubtyping (#6921) The LocalGraph there was used for two purposes: 1. Get the list of gets and sets. 2. Get only the reachable gets and sets. It is trivial to get all the gets and sets in a much faster way, by just walking the code as this PR does. The downside is that we also consider unreachable gets and sets, so unreachable code can prevent us from optimizing, but that seems worthwhile as many passes make that assumption (and they all become maximally effective after --dce). That is the only non-NFC part here. Removing LocalGraph + the fixup code for unreachability makes this significantly shorter, and also 2-3x faster. --- src/passes/LocalSubtyping.cpp | 111 ++++++++++----------------- test/lit/passes/local-subtyping.wast | 23 ++---- 2 files changed, 48 insertions(+), 86 deletions(-) diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp index ad0cfa7d2be..7b30e35387f 100644 --- a/src/passes/LocalSubtyping.cpp +++ b/src/passes/LocalSubtyping.cpp @@ -50,32 +50,49 @@ struct LocalSubtyping : public WalkerPass> { return; } - auto numLocals = func->getNumLocals(); + // Compute the list of gets and sets for each local. + struct Scanner : public PostWalker { + // Which locals are relevant for us (we can ignore non-references). + std::vector relevant; + + // The lists of gets and sets. + std::vector> setsForLocal; + std::vector> getsForLocal; + + Scanner(Function* func) { + auto numLocals = func->getNumLocals(); + relevant.resize(numLocals); + setsForLocal.resize(numLocals); + getsForLocal.resize(numLocals); + + for (Index i = 0; i < numLocals; i++) { + // TODO: Ignore params here? That may require changes below. + if (func->getLocalType(i).isRef()) { + relevant[i] = true; + } + } - // Compute the local graph. We need to get the list of gets and sets for - // each local, so that we can do the analysis. For non-nullable locals, we - // also need to know when the default value of a local is used: if so then - // we cannot change that type, as if we change the local type to - // non-nullable then we'd be accessing the default, which is not allowed. - // - // TODO: Optimize this, as LocalGraph computes more than we need, and on - // more locals than we need. - LocalGraph localGraph(func, getModule()); - - // For each local index, compute all the the sets and gets. - std::vector> setsForLocal(numLocals); - std::vector> getsForLocal(numLocals); - - for (auto& [curr, _] : localGraph.locations) { - if (auto* set = curr->dynCast()) { - setsForLocal[set->index].push_back(set); - } else { - auto* get = curr->cast(); - getsForLocal[get->index].push_back(get); + walk(func->body); } - } - // Find which vars can be non-nullable. + void visitLocalGet(LocalGet* curr) { + if (relevant[curr->index]) { + getsForLocal[curr->index].push_back(curr); + } + } + + void visitLocalSet(LocalSet* curr) { + if (relevant[curr->index]) { + setsForLocal[curr->index].push_back(curr); + } + } + } scanner(func); + + auto& setsForLocal = scanner.setsForLocal; + auto& getsForLocal = scanner.getsForLocal; + + // Find which vars can be non-nullable (if a null is written, or the default + // null is used, then a local cannot become non-nullable). std::unordered_set cannotBeNonNullable; // All gets must be dominated structurally by sets for the local to be non- @@ -98,7 +115,8 @@ struct LocalSubtyping : public WalkerPass> { // TODO: handle cycles of X -> Y -> X etc. bool more; - bool optimized = false; + + auto numLocals = func->getNumLocals(); do { more = false; @@ -148,7 +166,6 @@ struct LocalSubtyping : public WalkerPass> { assert(Type::isSubType(newType, oldType)); func->vars[i - varBase] = newType; more = true; - optimized = true; // Update gets and tees. for (auto* get : getsForLocal[i]) { @@ -166,50 +183,6 @@ struct LocalSubtyping : public WalkerPass> { } } } while (more); - - // If we ever optimized, then we also need to do a final pass to update any - // unreachable gets and tees. They are not seen or updated in the above - // analysis, but must be fixed up for validation to work. - if (optimized) { - for (auto* get : FindAll(func->body).list) { - get->type = func->getLocalType(get->index); - } - for (auto* set : FindAll(func->body).list) { - auto newType = func->getLocalType(set->index); - if (set->isTee()) { - set->type = newType; - set->finalize(); - } - - // If this set was not processed earlier - that is, if it is in - // unreachable code - then it may have an incompatible type. That is, - // If we saw a reachable set that writes type A, and this set writes - // type B, we may have specialized the local type to A, but the value - // of type B in this unreachable set is no longer valid to write to - // that local. In such a case we must do additional work. - if (!Type::isSubType(set->value->type, newType)) { - // The type is incompatible. To fix this, replace - // - // (set (bad-value)) - // - // with - // - // (set (block - // (drop (bad-value)) - // (unreachable) - // )) - // - // (We cannot just ignore the bad value, as it may contain a break to - // a target that is necessary for validation.) - Builder builder(*getModule()); - set->value = builder.makeSequence(builder.makeDrop(set->value), - builder.makeUnreachable()); - } - } - - // Also update their parents. - ReFinalize().walkFunctionInModule(func, getModule()); - } } }; diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 922d350c58b..d5fc3d63d71 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -397,28 +397,18 @@ ) ;; CHECK: (func $incompatible-sets (type $1) (result i32) - ;; CHECK-NEXT: (local $temp (ref $1)) + ;; CHECK-NEXT: (local $temp (ref null $1)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (ref.func $incompatible-sets) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $temp - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null nofunc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $temp - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null nofunc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -431,9 +421,8 @@ ;; Make all code unreachable from here. (unreachable) ;; In unreachable code, assign values that are not compatible with the more - ;; specific type we will optimize to. Those cannot be left as they are, and - ;; will be fixed up so that they validate. (All we need is validation, as - ;; their contents do not matter, given they are not reached.) + ;; specific type we will optimize to. This prevents optimization here (we + ;; will optimize better after --dce is run). (drop (local.tee $temp (ref.null func) From 9d3f8e5603c42fdb2517d15d865f5cee06d21db5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Sep 2024 09:55:13 -0700 Subject: [PATCH 011/622] [NFC] Standardize Super:: over super:: (#6920) As the name of a class, uppercase seems better here. --- src/pass.h | 2 +- src/passes/CoalesceLocals.cpp | 2 +- src/passes/CodeFolding.cpp | 2 +- src/passes/DeadCodeElimination.cpp | 2 +- src/passes/LogExecution.cpp | 2 +- src/passes/LoopInvariantCodeMotion.cpp | 2 +- src/passes/Memory64Lowering.cpp | 2 +- src/passes/MemoryPacking.cpp | 2 +- src/passes/MergeLocals.cpp | 2 +- src/passes/MultiMemoryLowering.cpp | 2 +- src/passes/OptimizeAddedConstants.cpp | 2 +- src/passes/OptimizeInstructions.cpp | 2 +- src/passes/Precompute.cpp | 4 ++-- src/passes/RemoveUnusedBrs.cpp | 4 ++-- src/passes/SignExtLowering.cpp | 2 +- src/passes/SimplifyGlobals.cpp | 4 ++-- src/passes/SpillPointers.cpp | 2 +- src/passes/Table64Lowering.cpp | 2 +- src/passes/TranslateEH.cpp | 2 +- src/passes/TrapMode.cpp | 2 +- src/passes/TupleOptimization.cpp | 2 +- src/passes/TypeGeneralizing.cpp | 2 +- 22 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/pass.h b/src/pass.h index 9352319ad65..8d81fce8e8b 100644 --- a/src/pass.h +++ b/src/pass.h @@ -530,7 +530,7 @@ template class WalkerPass : public Pass, public WalkerType { protected: - using super = WalkerPass; + using Super = WalkerPass; public: void run(Module* module) override { diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp index 313bbc03aa0..78b8264955a 100644 --- a/src/passes/CoalesceLocals.cpp +++ b/src/passes/CoalesceLocals.cpp @@ -112,7 +112,7 @@ struct CoalesceLocals }; void CoalesceLocals::doWalkFunction(Function* func) { - super::doWalkFunction(func); + Super::doWalkFunction(func); // prioritize back edges increaseBackEdgePriorities(); // use liveness to find interference diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp index 2d17f6d3188..21527da6b18 100644 --- a/src/passes/CodeFolding.cpp +++ b/src/passes/CodeFolding.cpp @@ -293,7 +293,7 @@ struct CodeFolding : public WalkerPass> { while (anotherPass) { anotherPass = false; needEHFixups = false; - super::doWalkFunction(func); + Super::doWalkFunction(func); optimizeTerminatingTails(unreachableTails); // optimize returns at the end, so we can benefit from a fallthrough if // there is a value TODO: separate passes for them? diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp index f8acde7b0fc..a3667376f98 100644 --- a/src/passes/DeadCodeElimination.cpp +++ b/src/passes/DeadCodeElimination.cpp @@ -61,7 +61,7 @@ struct DeadCodeElimination if (old == expression) { return expression; } - super::replaceCurrent(expression); + Super::replaceCurrent(expression); // also update the type updater typeUpdater.noteReplacement(old, expression); return expression; diff --git a/src/passes/LogExecution.cpp b/src/passes/LogExecution.cpp index aa7948963bc..98b74197ad1 100644 --- a/src/passes/LogExecution.cpp +++ b/src/passes/LogExecution.cpp @@ -47,7 +47,7 @@ struct LogExecution : public WalkerPass> { void run(Module* module) override { loggerModule = getArgumentOrDefault("log-execution", ""); - super::run(module); + Super::run(module); } void visitLoop(Loop* curr) { curr->body = makeLogCall(curr->body); } diff --git a/src/passes/LoopInvariantCodeMotion.cpp b/src/passes/LoopInvariantCodeMotion.cpp index 1277c1a3483..6c7aecbc71f 100644 --- a/src/passes/LoopInvariantCodeMotion.cpp +++ b/src/passes/LoopInvariantCodeMotion.cpp @@ -53,7 +53,7 @@ struct LoopInvariantCodeMotion LazyLocalGraph localGraphInstance(func, getModule()); localGraph = &localGraphInstance; // Traverse the function. - super::doWalkFunction(func); + Super::doWalkFunction(func); } void visitLoop(Loop* loop) { diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index 879858b71b0..0a19d11c812 100644 --- a/src/passes/Memory64Lowering.cpp +++ b/src/passes/Memory64Lowering.cpp @@ -181,7 +181,7 @@ struct Memory64Lowering : public WalkerPass> { if (!module->features.has(FeatureSet::Memory64)) { return; } - super::run(module); + Super::run(module); // Don't modify the memories themselves until after the traversal since we // that would require memories to be the last thing that get visited, and // we don't want to depend on that specific ordering. diff --git a/src/passes/MemoryPacking.cpp b/src/passes/MemoryPacking.cpp index 064d74e36f5..ad562faf49a 100644 --- a/src/passes/MemoryPacking.cpp +++ b/src/passes/MemoryPacking.cpp @@ -500,7 +500,7 @@ void MemoryPacking::optimizeSegmentOps(Module* module) { } void doWalkFunction(Function* func) { needsRefinalizing = false; - super::doWalkFunction(func); + Super::doWalkFunction(func); if (needsRefinalizing) { ReFinalize().walkFunctionInModule(func, getModule()); } diff --git a/src/passes/MergeLocals.cpp b/src/passes/MergeLocals.cpp index b669c20d3a6..94710d64159 100644 --- a/src/passes/MergeLocals.cpp +++ b/src/passes/MergeLocals.cpp @@ -81,7 +81,7 @@ struct MergeLocals // have a new assignment of $y at the location of the copy, // which makes it easy for us to see if the value if $y // is still used after that point - super::doWalkFunction(func); + Super::doWalkFunction(func); // optimize the copies, merging when we can, and removing // the trivial assigns we added temporarily diff --git a/src/passes/MultiMemoryLowering.cpp b/src/passes/MultiMemoryLowering.cpp index 0367afa98fb..b214959f710 100644 --- a/src/passes/MultiMemoryLowering.cpp +++ b/src/passes/MultiMemoryLowering.cpp @@ -109,7 +109,7 @@ struct MultiMemoryLowering : public Pass { return; } } - super::walkFunction(func); + Super::walkFunction(func); } void visitMemoryGrow(MemoryGrow* curr) { diff --git a/src/passes/OptimizeAddedConstants.cpp b/src/passes/OptimizeAddedConstants.cpp index 8efbcca2648..c76711d8a1c 100644 --- a/src/passes/OptimizeAddedConstants.cpp +++ b/src/passes/OptimizeAddedConstants.cpp @@ -304,7 +304,7 @@ struct OptimizeAddedConstants localGraph->computeSSAIndexes(); findPropagatable(); } - super::doWalkFunction(func); + Super::doWalkFunction(func); if (!helperIndexes.empty()) { createHelperIndexes(); } diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 21bd0c5ea4b..fff18b925c4 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -238,7 +238,7 @@ struct OptimizeInstructions } // Main walk. - super::doWalkFunction(func); + Super::doWalkFunction(func); if (refinalize) { ReFinalize().walkFunctionInModule(func, getModule()); diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 0d2df04d79c..780945b46b8 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -250,7 +250,7 @@ struct Precompute canPartiallyPrecompute = getPassOptions().optimizeLevel >= 2; // Walk the function and precompute things. - super::doWalkFunction(func); + Super::doWalkFunction(func); partiallyPrecompute(func); if (!propagate) { return; @@ -264,7 +264,7 @@ struct Precompute // We found constants to propagate and entered them in getValues. Do // another walk to apply them and perhaps other optimizations that are // unlocked. - super::doWalkFunction(func); + Super::doWalkFunction(func); // We could also try to partially precompute again, but that is a somewhat // heavy operation, so we only do it the first time, and leave such things // for later runs of this pass and for --converge. diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index fcb4ea56a38..7fe169bdbb7 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -485,7 +485,7 @@ struct RemoveUnusedBrs : public WalkerPass> { self->pushTask(clear, currp); // clear all flow after the condition self->pushTask(scan, &iff->condition); } else { - super::scan(self, currp); + Super::scan(self, currp); } } @@ -921,7 +921,7 @@ struct RemoveUnusedBrs : public WalkerPass> { // multiple cycles may be needed do { anotherCycle = false; - super::doWalkFunction(func); + Super::doWalkFunction(func); assert(ifStack.empty()); // flows may contain returns, which are flowing out and so can be // optimized diff --git a/src/passes/SignExtLowering.cpp b/src/passes/SignExtLowering.cpp index beb36494f23..659dc4b51da 100644 --- a/src/passes/SignExtLowering.cpp +++ b/src/passes/SignExtLowering.cpp @@ -73,7 +73,7 @@ struct SignExtLowering : public WalkerPass> { if (!module->features.has(FeatureSet::SignExt)) { return; } - super::run(module); + Super::run(module); module->features.disable(FeatureSet::SignExt); } }; diff --git a/src/passes/SimplifyGlobals.cpp b/src/passes/SimplifyGlobals.cpp index 461fe2890e8..ccfee5e6530 100644 --- a/src/passes/SimplifyGlobals.cpp +++ b/src/passes/SimplifyGlobals.cpp @@ -336,7 +336,7 @@ struct ConstantGlobalApplier : public WalkerPass< LinearExecutionWalker>> { - using super = WalkerPass< + using Super = WalkerPass< LinearExecutionWalker>>; @@ -361,7 +361,7 @@ struct ConstantGlobalApplier // This operation will change the type, so refinalize. refinalize = true; } - super::replaceCurrent(rep); + Super::replaceCurrent(rep); } void visitExpression(Expression* curr) { diff --git a/src/passes/SpillPointers.cpp b/src/passes/SpillPointers.cpp index 3af7039cdd3..db7fca85abf 100644 --- a/src/passes/SpillPointers.cpp +++ b/src/passes/SpillPointers.cpp @@ -69,7 +69,7 @@ struct SpillPointers // main entry point void doWalkFunction(Function* func) { - super::doWalkFunction(func); + Super::doWalkFunction(func); spillPointers(); } diff --git a/src/passes/Table64Lowering.cpp b/src/passes/Table64Lowering.cpp index 71fc6a6fe6d..b4a538df9a9 100644 --- a/src/passes/Table64Lowering.cpp +++ b/src/passes/Table64Lowering.cpp @@ -138,7 +138,7 @@ struct Table64Lowering : public WalkerPass> { } void run(Module* module) override { - super::run(module); + Super::run(module); // Don't modify the tables themselves until after the traversal since we // that would require tables to be the last thing that get visited, and // we don't want to depend on that specific ordering. diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index 5c34a902ba2..a3411cda967 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -806,7 +806,7 @@ struct TranslateToExnref : public WalkerPass> { delegateTargetToTrampoline[target] = labels->getUnique(target.toString()); } - super::doWalkFunction(func); + Super::doWalkFunction(func); // Similar to processDelegateTarget(), but for the caller target. if (delegateTargetToTrampoline.find(DELEGATE_CALLER_TARGET) != diff --git a/src/passes/TrapMode.cpp b/src/passes/TrapMode.cpp index 3b62b0573d0..f269f081fd4 100644 --- a/src/passes/TrapMode.cpp +++ b/src/passes/TrapMode.cpp @@ -323,7 +323,7 @@ struct TrapModePass : public WalkerPass> { void doWalkModule(Module* module) { trappingFunctions = std::make_unique(mode, *module); - super::doWalkModule(module); + Super::doWalkModule(module); } private: diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index ca704f1a3d7..c4fd51b16da 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -102,7 +102,7 @@ struct TupleOptimization : public WalkerPass> { copiedIndexes.resize(numLocals); // Walk the code to collect info. - super::doWalkFunction(func); + Super::doWalkFunction(func); // Analyze and optimize. optimize(func); diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index fad55e50699..c0359b897c3 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -908,7 +908,7 @@ struct TypeGeneralizing : WalkerPass> { } // Update gets and sets accordingly. - super::runOnFunction(wasm, func); + Super::runOnFunction(wasm, func); if (refinalize) { ReFinalize().walkFunctionInModule(func, wasm); From b4a34d20c957404206875242781e61dc84a1cd28 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 10 Sep 2024 12:01:00 -0700 Subject: [PATCH 012/622] Add a --preserve-type-order option (#6916) Unlike other module elements, types are not stored on the `Module`. Instead, they are collected by traversing the IR before printing and binary writing. The code that collects the types tries to optimize the order of rec groups based on the number of times each type is used. As a result, the output order of types generally has no relation to the input order of types. In addition, most type optimizations rewrite the types into a single large rec group, and the order of types in that group is essentially arbitrary. Changes to the code for counting type uses, sorting types, or sorting rec groups can yield very large changes in the output order of types, producing test diffs that are hard to review and potentially harming the readability of tests by moving output types away from the corresponding input types. To help make test output more stable and readable, introduce a tool option that causes the order of output types to match the order of input types as closely as possible. It is implemented by having the parsers record the indices of the input types on the `Module` just like they already record the type names. The `GlobalTypeRewriter` infrastructure used by type optimizations associates the new types with the old indices just like it already does for names and also respects the input order when rewriting types into a large recursion group. By default, wasm-opt and other tools clear the recorded type indices after parsing the module, so their default behavior is not modified by this change. Follow-on PRs will use the new flag in more tests, which will generate large diffs but leave the tests in stable, more readable states that will no longer change due to other changes to the optimizing type sorting logic. --- src/binaryen-c.cpp | 3 + src/ir/module-utils.cpp | 32 ++++ src/ir/type-updating.cpp | 155 ++++++++++++++---- src/ir/type-updating.h | 5 +- src/parser/parse-3-implicit-types.cpp | 4 + src/passes/MinimizeRecGroups.cpp | 2 +- src/passes/RoundTrip.cpp | 9 + src/support/topological_sort.h | 27 ++- src/tools/tool-options.h | 19 ++- src/tools/wasm-as.cpp | 4 +- src/tools/wasm-ctor-eval.cpp | 4 +- src/tools/wasm-dis.cpp | 4 +- src/tools/wasm-emscripten-finalize.cpp | 4 +- src/tools/wasm-merge.cpp | 4 +- src/tools/wasm-metadce.cpp | 4 +- src/tools/wasm-opt.cpp | 4 +- src/tools/wasm-reduce.cpp | 8 +- src/tools/wasm-split/wasm-split.cpp | 4 +- src/tools/wasm2js.cpp | 10 +- src/wasm.h | 1 + src/wasm/wasm-binary.cpp | 7 +- test/example/module-splitting.txt | 16 +- test/lit/help/wasm-as.test | 3 + test/lit/help/wasm-ctor-eval.test | 3 + test/lit/help/wasm-dis.test | 3 + test/lit/help/wasm-emscripten-finalize.test | 3 + test/lit/help/wasm-merge.test | 3 + test/lit/help/wasm-metadce.test | 8 +- test/lit/help/wasm-opt.test | 6 +- test/lit/help/wasm-reduce.test | 3 + test/lit/help/wasm-split.test | 3 + test/lit/help/wasm2js.test | 6 +- .../remove-unused-types-preserve-order.wast | 81 +++++++++ 33 files changed, 381 insertions(+), 71 deletions(-) create mode 100644 test/lit/passes/remove-unused-types-preserve-order.wast diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 31378ce22de..64462c35465 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -5647,6 +5647,9 @@ BinaryenModuleRef BinaryenModuleReadWithFeatures(char* input, p.dump(std::cerr); Fatal() << "error in parsing wasm binary"; } + // Do not regress code size by maintaining type order. TODO: Add an option to + // control this. + wasm->typeIndices.clear(); return wasm; } diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 58f7d4637c1..a16592d15a7 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -764,7 +764,39 @@ IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { } } + // If we've preserved the input type order on the module, we have to respect + // that first. Use the index of the first type from each group. In principle + // we could try to do something more robust like take the minimum index of all + // the types in the group, but if the groups haven't been preserved, then we + // won't be able to perfectly preserve the order anyway. + std::vector> groupTypeIndices; + if (wasm.typeIndices.empty()) { + groupTypeIndices.resize(groups.size()); + } else { + groupTypeIndices.reserve(groups.size()); + for (auto group : groups) { + groupTypeIndices.emplace_back(); + if (auto it = wasm.typeIndices.find(group[0]); + it != wasm.typeIndices.end()) { + groupTypeIndices.back() = it->second; + } + } + } + auto order = TopologicalSort::minSort(deps, [&](size_t a, size_t b) { + auto indexA = groupTypeIndices[a]; + auto indexB = groupTypeIndices[b]; + // Groups with indices must be sorted before groups without indices to + // ensure transitivity of this comparison relation. + if (indexA.has_value() != indexB.has_value()) { + return indexA.has_value(); + } + // Sort by preserved index if we can. + if (indexA && *indexA != *indexB) { + return *indexA < *indexB; + } + // Otherwise sort by weight and break ties by the arbitrary deterministic + // order in which we've collected types. auto weightA = weights[a]; auto weightB = weights[b]; if (weightA != weightB) { diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 17437cf2e35..467971989f0 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -20,6 +20,7 @@ #include "ir/module-utils.h" #include "ir/names.h" #include "ir/utils.h" +#include "support/topological_sort.h" #include "wasm-type-ordering.h" #include "wasm-type.h" #include "wasm.h" @@ -38,44 +39,125 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( // Find the heap types that are not publicly observable. Even in a closed // world scenario, don't modify public types because we assume that they may // be reflected on or used for linking. Figure out where each private type - // will be located in the builder. Sort the private types so that supertypes - // come before their subtypes. - Index i = 0; - auto privateTypes = ModuleUtils::getPrivateHeapTypes(wasm); + // will be located in the builder. + // + // There are two code paths here: a new one that is used when there are type + // indices to preserve and an old one that is used otherwise. The old code + // path is kept around to avoid unnecessary changes to test outputs while we + // incrementally add --preserve-type-order to tests that could benefit from + // it. Once we are done adding --preserve-type-order to tests, we should + // remove the old code path here since the new code path is strictly better. + if (wasm.typeIndices.size()) { + // New code path, currently used only with --preserve-type-order. + auto typeInfo = ModuleUtils::collectHeapTypeInfo( + wasm, + ModuleUtils::TypeInclusion::UsedIRTypes, + ModuleUtils::VisibilityHandling::FindVisibility); + + std::unordered_set additionalSet(additionalPrivateTypes.begin(), + additionalPrivateTypes.end()); + + std::vector>> + privateSupertypes; + privateSupertypes.reserve(typeInfo.size()); + for (auto& [type, info] : typeInfo) { + if (info.visibility != ModuleUtils::Visibility::Private && + !additionalSet.count(type)) { + continue; + } + privateSupertypes.push_back({type, {}}); + + if (auto super = getDeclaredSuperType(type)) { + auto it = typeInfo.find(*super); + // Record the supertype only if it is among the private types. + if ((it != typeInfo.end() && + it->second.visibility == ModuleUtils::Visibility::Private) || + additionalSet.count(*super)) { + privateSupertypes.back().second.push_back(*super); + } + } + } + + // Topological sort to have subtypes first. This is the opposite of the + // order we need, so the comparison is the opposite of what we ultimately + // want. + std::vector sorted; + if (wasm.typeIndices.empty()) { + sorted = TopologicalSort::sortOf(privateSupertypes.begin(), + privateSupertypes.end()); + } else { + sorted = + TopologicalSort::minSortOf(privateSupertypes.begin(), + privateSupertypes.end(), + [&](Index a, Index b) { + auto typeA = privateSupertypes[a].first; + auto typeB = privateSupertypes[b].first; + // Preserve type order. + auto itA = wasm.typeIndices.find(typeA); + auto itB = wasm.typeIndices.find(typeB); + bool hasA = itA != wasm.typeIndices.end(); + bool hasB = itB != wasm.typeIndices.end(); + if (hasA != hasB) { + // Types with preserved indices must be + // sorted before (after in this reversed + // comparison) types without indices to + // maintain transitivity. + return !hasA; + } + if (hasA && *itA != *itB) { + return !(itA->second < itB->second); + } + // Break ties by the arbitrary order we + // have collected the types in. + return a > b; + }); + } + std::reverse(sorted.begin(), sorted.end()); + Index i = 0; + for (auto type : sorted) { + typeIndices[type] = i++; + } + } else { + // Old code path. - if (!additionalPrivateTypes.empty()) { - // Only add additional private types that are not already in the list. - std::unordered_set privateTypesSet(privateTypes.begin(), - privateTypes.end()); + auto privateTypes = ModuleUtils::getPrivateHeapTypes(wasm); - for (auto t : additionalPrivateTypes) { - if (!privateTypesSet.count(t)) { - privateTypes.push_back(t); - privateTypesSet.insert(t); + if (!additionalPrivateTypes.empty()) { + // Only add additional private types that are not already in the list. + std::unordered_set privateTypesSet(privateTypes.begin(), + privateTypes.end()); + + for (auto t : additionalPrivateTypes) { + if (!privateTypesSet.count(t)) { + privateTypes.push_back(t); + privateTypesSet.insert(t); + } } } - } - // Topological sort to have supertypes first, but we have to account for the - // fact that we may be replacing the supertypes to get the order correct. - struct SupertypesFirst - : HeapTypeOrdering::SupertypesFirstBase { - GlobalTypeRewriter& parent; + // Topological sort to have supertypes first, but we have to account for the + // fact that we may be replacing the supertypes to get the order correct. + struct SupertypesFirst + : HeapTypeOrdering::SupertypesFirstBase { + GlobalTypeRewriter& parent; - SupertypesFirst(GlobalTypeRewriter& parent) : parent(parent) {} - std::optional getDeclaredSuperType(HeapType type) { - return parent.getDeclaredSuperType(type); - } - }; + SupertypesFirst(GlobalTypeRewriter& parent) : parent(parent) {} + std::optional getDeclaredSuperType(HeapType type) { + return parent.getDeclaredSuperType(type); + } + }; - SupertypesFirst sortedTypes(*this); - for (auto type : sortedTypes.sort(privateTypes)) { - typeIndices[type] = i++; + SupertypesFirst sortedTypes(*this); + Index i = 0; + for (auto type : sortedTypes.sort(privateTypes)) { + typeIndices[type] = i++; + } } if (typeIndices.size() == 0) { return {}; } + typeBuilder.grow(typeIndices.size()); // All the input types are distinct, so we need to make sure the output types @@ -86,7 +168,7 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( typeBuilder.createRecGroup(0, typeBuilder.size()); // Create the temporary heap types. - i = 0; + Index i = 0; auto map = [&](HeapType type) -> HeapType { if (auto it = typeIndices.find(type); it != typeIndices.end()) { return typeBuilder[it->second]; @@ -144,7 +226,7 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( for (auto [type, index] : typeIndices) { oldToNewTypes[type] = newTypes[index]; } - mapTypeNames(oldToNewTypes); + mapTypeNamesAndIndices(oldToNewTypes); return oldToNewTypes; } @@ -268,7 +350,7 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { } } -void GlobalTypeRewriter::mapTypeNames(const TypeMap& oldToNewTypes) { +void GlobalTypeRewriter::mapTypeNamesAndIndices(const TypeMap& oldToNewTypes) { // Update type names to avoid duplicates. std::unordered_set typeNames; for (auto& [type, info] : wasm.typeNames) { @@ -281,16 +363,21 @@ void GlobalTypeRewriter::mapTypeNames(const TypeMap& oldToNewTypes) { } if (auto it = wasm.typeNames.find(old); it != wasm.typeNames.end()) { - wasm.typeNames[new_] = wasm.typeNames[old]; + auto& oldNames = it->second; + wasm.typeNames[new_] = oldNames; // Use the existing name in the new type, as usually it completely // replaces the old. Rename the old name in a unique way to avoid // confusion in the case that it remains used. - auto deduped = - Names::getValidName(wasm.typeNames[old].name, - [&](Name test) { return !typeNames.count(test); }); - wasm.typeNames[old].name = deduped; + auto deduped = Names::getValidName( + oldNames.name, [&](Name test) { return !typeNames.count(test); }); + oldNames.name = deduped; typeNames.insert(deduped); } + if (auto it = wasm.typeIndices.find(old); it != wasm.typeIndices.end()) { + // It's ok if we end up with duplicate indices. Ties will be resolved in + // some arbitrary manner. + wasm.typeIndices[new_] = it->second; + } } } diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h index a8e071fb647..fe1cd2806aa 100644 --- a/src/ir/type-updating.h +++ b/src/ir/type-updating.h @@ -371,8 +371,9 @@ class GlobalTypeRewriter { // Users of `mapTypes` may want to update the type names according to their // mapping. This is not done automatically in `mapTypes` because other users - // may want the names to reflect that types have been replaced. - void mapTypeNames(const TypeMap& oldToNewTypes); + // may want the names to reflect that types have been replaced. Do the same + // mapping for recorded type indices. + void mapTypeNamesAndIndices(const TypeMap& oldToNewTypes); // Subclasses can implement these methods to modify the new set of types that // we map to. By default, we simply copy over the types, and these functions diff --git a/src/parser/parse-3-implicit-types.cpp b/src/parser/parse-3-implicit-types.cpp index 3a3a867e151..cf13ae0f7e2 100644 --- a/src/parser/parse-3-implicit-types.cpp +++ b/src/parser/parse-3-implicit-types.cpp @@ -29,6 +29,10 @@ parseImplicitTypeDefs(ParseDeclsCtx& decls, WithPosition with(ctx, pos); CHECK_ERR(typeuse(ctx)); } + // Record type indices now that all the types have been parsed. + for (Index i = 0; i < types.size(); ++i) { + decls.wasm.typeIndices.insert({types[i], i}); + } return Ok{}; } diff --git a/src/passes/MinimizeRecGroups.cpp b/src/passes/MinimizeRecGroups.cpp index e89d9844219..0faf297f545 100644 --- a/src/passes/MinimizeRecGroups.cpp +++ b/src/passes/MinimizeRecGroups.cpp @@ -857,7 +857,7 @@ struct MinimizeRecGroups : Pass { } GlobalTypeRewriter rewriter(wasm); rewriter.mapTypes(oldToNew); - rewriter.mapTypeNames(oldToNew); + rewriter.mapTypeNamesAndIndices(oldToNew); } }; diff --git a/src/passes/RoundTrip.cpp b/src/passes/RoundTrip.cpp index 129b89ac818..123752f793a 100644 --- a/src/passes/RoundTrip.cpp +++ b/src/passes/RoundTrip.cpp @@ -40,6 +40,11 @@ struct RoundTrip : public Pass { // the target features section has been stripped. We also need them in order // to tell the builder which features to build with. auto features = module->features; + + // We need to know whether we should preserve the type order when we read + // the module back in. + bool preserveTypeOrder = !module->typeIndices.empty(); + // Write, clear, and read the module WasmBinaryWriter(module, buffer, getPassOptions()).write(); ModuleUtils::clearModule(*module); @@ -53,6 +58,10 @@ struct RoundTrip : public Pass { std::cerr << '\n'; Fatal() << "error in parsing wasm binary"; } + + if (!preserveTypeOrder) { + module->typeIndices.clear(); + } } }; diff --git a/src/support/topological_sort.h b/src/support/topological_sort.h index b75f6b78dd7..be5e49b8c8b 100644 --- a/src/support/topological_sort.h +++ b/src/support/topological_sort.h @@ -42,10 +42,17 @@ using Graph = std::vector>; // Return a topological sort of the vertices in the given adjacency graph. inline std::vector sort(const Graph& graph); +// Return the topological sort of the vertices in the given adjacency graph that +// is lexicographically minimal with respect to the provided comparator on +// vertex indices. Implemented using a min-heap internally. +template> +std::vector minSort(const Graph& graph, F cmp = std::less{}); + // A utility that finds a topological sort of a graph with arbitrary element // types. The provided iterators must be to pairs of elements and collections of // their children. -template decltype(auto) sortOf(It begin, It end) { +template +decltype(auto) sortOfImpl(It begin, It end, Args... args) { using T = std::remove_cv_t; std::unordered_map indices; std::vector elements; @@ -67,17 +74,23 @@ template decltype(auto) sortOf(It begin, It end) { // Compute the topological order and convert back to original elements. std::vector order; order.reserve(elements.size()); - for (auto i : sort(indexGraph)) { + for (auto i : Sort(indexGraph, std::forward(args)...)) { order.emplace_back(std::move(elements[i])); } return order; } -// Return the topological sort of the vertices in the given adjacency graph that -// is lexicographically minimal with respect to the provided comparator on -// vertex indices. Implemented using a min-heap internally. -template> -std::vector minSort(const Graph& graph, F cmp = std::less{}); +template decltype(auto) sortOf(It begin, It end) { + return sortOfImpl (&)(const Graph&), sort>(begin, end); +} + +template +decltype(auto) minSortOf(It begin, It end, Cmp cmp) { + return sortOfImpl (&)(const Graph&, Cmp), + minSort, + Cmp>(begin, end, cmp); +} } // namespace TopologicalSort diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index bfa9a9b5c40..f900d76ba48 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -31,6 +31,7 @@ struct ToolOptions : public Options { PassOptions passOptions; bool quiet = false; + bool preserveTypeOrder = false; IRProfile profile = IRProfile::Normal; constexpr static const char* ToolOptionsCategory = "Tool options"; @@ -158,6 +159,16 @@ struct ToolOptions : public Options { [this](Options*, const std::string&) { passOptions.closedWorld = true; }) + .add( + "--preserve-type-order", + "", + "Preserve the order of types from the input (useful for debugging and " + "testing)", + ToolOptionsCategory, + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { + preserveTypeOrder = true; + }) .add("--generate-stack-ir", "", "generate StackIR during writing", @@ -213,11 +224,17 @@ struct ToolOptions : public Options { return *this; } - void applyFeatures(Module& module) const { + void applyOptionsBeforeParse(Module& module) const { module.features.enable(enabledFeatures); module.features.disable(disabledFeatures); } + void applyOptionsAfterParse(Module& module) const { + if (!preserveTypeOrder) { + module.typeIndices.clear(); + } + } + virtual void addPassArg(const std::string& key, const std::string& value) { passOptions.arguments[key] = value; } diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp index a767e6908e4..73ae8213432 100644 --- a/src/tools/wasm-as.cpp +++ b/src/tools/wasm-as.cpp @@ -107,13 +107,15 @@ int main(int argc, const char* argv[]) { auto input(read_file(options.extra["infile"], Flags::Text)); Module wasm; - options.applyFeatures(wasm); + options.applyOptionsBeforeParse(wasm); auto parsed = WATParser::parseModule(wasm, input); if (auto* err = parsed.getErr()) { Fatal() << err->msg; } + options.applyOptionsAfterParse(wasm); + if (options.extra["validate"] != "none") { if (options.debug) { std::cerr << "Validating..." << std::endl; diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 6809fa32afb..d74790b2ab0 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -1445,7 +1445,7 @@ int main(int argc, const char* argv[]) { options.parse(argc, argv); Module wasm; - options.applyFeatures(wasm); + options.applyOptionsBeforeParse(wasm); { if (options.debug) { @@ -1460,6 +1460,8 @@ int main(int argc, const char* argv[]) { } } + options.applyOptionsAfterParse(wasm); + if (!WasmValidator().validate(wasm)) { std::cout << wasm << '\n'; Fatal() << "error in validating input"; diff --git a/src/tools/wasm-dis.cpp b/src/tools/wasm-dis.cpp index f9f30335963..1603736cea9 100644 --- a/src/tools/wasm-dis.cpp +++ b/src/tools/wasm-dis.cpp @@ -64,7 +64,7 @@ int main(int argc, const char* argv[]) { std::cerr << "parsing binary..." << std::endl; } Module wasm; - options.applyFeatures(wasm); + options.applyOptionsBeforeParse(wasm); try { ModuleReader().readBinary(options.extra["infile"], wasm, sourceMapFilename); } catch (ParseException& p) { @@ -82,6 +82,8 @@ int main(int argc, const char* argv[]) { Fatal() << "error in parsing wasm source mapping"; } + options.applyOptionsAfterParse(wasm); + // TODO: Validation. However, validating would mean that users are forced to // run with wasm-dis -all or such, to enable the features (unless the // features section is present, but that's rare in general). It would be diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp index 6b4e994ac83..505e783496d 100644 --- a/src/tools/wasm-emscripten-finalize.cpp +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -196,7 +196,7 @@ int main(int argc, const char* argv[]) { auto writeOutput = outfile.size() > 0 || !emitBinary; Module wasm; - options.applyFeatures(wasm); + options.applyOptionsBeforeParse(wasm); ModuleReader reader; // If we are not writing output then we definitely don't need to read debug // info. However, if we emit output then definitely load the names section so @@ -226,6 +226,8 @@ int main(int argc, const char* argv[]) { Fatal() << "error in parsing wasm source map"; } + options.applyOptionsAfterParse(wasm); + BYN_TRACE_WITH_TYPE("emscripten-dump", "Module before:\n"); BYN_DEBUG_WITH_TYPE("emscripten-dump", std::cerr << &wasm); diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index 449f0cfdb40..3de2283502c 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -695,7 +695,7 @@ Input source maps can be specified by adding an -ism option right after the modu currModule = laterInput.get(); } - options.applyFeatures(*currModule); + options.applyOptionsBeforeParse(*currModule); ModuleReader reader; try { @@ -705,6 +705,8 @@ Input source maps can be specified by adding an -ism option right after the modu Fatal() << "error in parsing wasm input: " << inputFile; } + options.applyOptionsAfterParse(*currModule); + if (options.passOptions.validate) { if (!WasmValidator().validate(*currModule)) { std::cout << *currModule << '\n'; diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index 9cc06375ec7..41dcf6ad4c5 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -486,7 +486,7 @@ int main(int argc, const char* argv[]) { } Module wasm; - options.applyFeatures(wasm); + options.applyOptionsBeforeParse(wasm); { if (options.debug) { @@ -502,6 +502,8 @@ int main(int argc, const char* argv[]) { } } + options.applyOptionsAfterParse(wasm); + if (options.passOptions.validate) { if (!WasmValidator().validate(wasm)) { std::cout << wasm << '\n'; diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index d488171fb85..3e11521790d 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -243,7 +243,7 @@ int main(int argc, const char* argv[]) { options.parse(argc, argv); Module wasm; - options.applyFeatures(wasm); + options.applyOptionsBeforeParse(wasm); BYN_TRACE("reading...\n"); @@ -294,6 +294,8 @@ int main(int argc, const char* argv[]) { "request for silly amounts of memory)"; } + options.applyOptionsAfterParse(wasm); + if (options.passOptions.validate) { if (!WasmValidator().validate(wasm, options.passOptions)) { exitOnInvalidWasm("error validating input"); diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index c276296ad48..8d9858b7829 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -362,6 +362,9 @@ struct Reducer void loadWorking() { module = std::make_unique(); + + toolOptions.applyOptionsBeforeParse(*module); + ModuleReader reader; try { reader.read(working, *module); @@ -371,15 +374,14 @@ struct Reducer Fatal() << "error in parsing working wasm binary"; } + toolOptions.applyOptionsAfterParse(*module); + // If there is no features section, assume we may need them all (without // this, a module with no features section but that uses e.g. atomics and // bulk memory would not work). if (!module->hasFeaturesSection) { module->features = FeatureSet::All; } - // Apply features the user passed on the commandline. - toolOptions.applyFeatures(*module); - builder = std::make_unique(*module); setModule(module.get()); } diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index eed26f1e15e..c1ec6052f58 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -38,7 +38,7 @@ using namespace wasm; namespace { void parseInput(Module& wasm, const WasmSplitOptions& options) { - options.applyFeatures(wasm); + options.applyOptionsBeforeParse(wasm); ModuleReader reader; reader.setProfile(options.profile); try { @@ -52,6 +52,8 @@ void parseInput(Module& wasm, const WasmSplitOptions& options) { "request for silly amounts of memory)"; } + options.applyOptionsAfterParse(wasm); + if (options.passOptions.validate && !WasmValidator().validate(wasm)) { Fatal() << "error validating input"; } diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp index 286f8989079..2c48e5be064 100644 --- a/src/tools/wasm2js.cpp +++ b/src/tools/wasm2js.cpp @@ -786,7 +786,9 @@ void AssertionEmitter::emit() { if (auto* mod = std::get_if(&cmd)) { if (auto* w = std::get_if>(mod)) { wasm = *w; - options.applyFeatures(*wasm); + // We have already done the parse, but we still do this to apply the + // features from the command line. + options.applyOptionsBeforeParse(*wasm); std::stringstream funcNameS; funcNameS << ASM_FUNC << i; std::stringstream moduleNameS; @@ -928,6 +930,7 @@ int main(int argc, const char* argv[]) { // is defined. if (binaryInput) { wasm = std::make_shared(); + options.applyOptionsBeforeParse(*wasm); ModuleReader reader; reader.read(input, *wasm, ""); } else { @@ -946,6 +949,9 @@ int main(int argc, const char* argv[]) { if (auto* mod = std::get_if(&(*script)[0].cmd)) { if (auto* w = std::get_if>(mod)) { wasm = *w; + // This isn't actually before the parse, but we can't apply the + // feature options any earlier. FIXME. + options.applyOptionsBeforeParse(*wasm); } } if (!wasm) { @@ -965,7 +971,7 @@ int main(int argc, const char* argv[]) { Fatal() << "error: modules with multiple tables are not supported yet."; } - options.applyFeatures(*wasm); + options.applyOptionsAfterParse(*wasm); if (options.passOptions.validate) { if (!WasmValidator().validate(*wasm)) { std::cout << *wasm << '\n'; diff --git a/src/wasm.h b/src/wasm.h index a7ad6ec6cb6..a86f7701371 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2308,6 +2308,7 @@ class Module { Name name; std::unordered_map typeNames; + std::unordered_map typeIndices; MixedArena allocator; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 3c8cc86df7a..151a7f91189 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2464,7 +2464,12 @@ void WasmBinaryReader::readTypes() { if (auto* err = result.getError()) { Fatal() << "Invalid type: " << err->reason << " at index " << err->index; } - types = *result; + types = std::move(*result); + + // Record the type indices. + for (Index i = 0; i < types.size(); ++i) { + wasm.typeIndices.insert({types[i], i}); + } } Name WasmBinaryReader::getFunctionName(Index index) { diff --git a/test/example/module-splitting.txt b/test/example/module-splitting.txt index 69fabc81665..b72867dda97 100644 --- a/test/example/module-splitting.txt +++ b/test/example/module-splitting.txt @@ -629,10 +629,10 @@ Before: Keeping: null After: (module - (type $0 (func (param i32) (result i32))) - (type $1 (func)) + (type $0 (func)) + (type $1 (func (param i32) (result i32))) (import "env" "base" (global $base i32)) - (import "placeholder" "0" (func $placeholder_0 (type $0) (param i32) (result i32))) + (import "placeholder" "0" (func $placeholder_0 (type $1) (param i32) (result i32))) (table $table 1000 funcref) (table $0 1 funcref) (elem $0 (table $table) (global.get $base) func $null $trampoline_foo) @@ -641,17 +641,17 @@ After: (export "%table" (table $table)) (export "%table_2" (table $0)) (export "%global" (global $base)) - (func $null (type $1) + (func $null (type $0) (nop) ) - (func $foo (type $0) (param $0 i32) (result i32) - (call_indirect $0 (type $0) + (func $foo (type $1) (param $0 i32) (result i32) + (call_indirect $0 (type $1) (local.get $0) (i32.const 0) ) ) - (func $trampoline_foo (type $0) (param $0 i32) (result i32) - (call_indirect $0 (type $0) + (func $trampoline_foo (type $1) (param $0 i32) (result i32) + (call_indirect $0 (type $1) (local.get $0) (i32.const 0) ) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index a2ee3d45e52..5dd4b151573 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -139,6 +139,9 @@ ;; CHECK-NEXT: them and pass them back in, but not ;; CHECK-NEXT: inspect their contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from the +;; CHECK-NEXT: input (useful for debugging and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index 4ad75a8d061..abb80b12eb6 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -146,6 +146,9 @@ ;; CHECK-NEXT: them and pass them back in, but not ;; CHECK-NEXT: inspect their contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from the +;; CHECK-NEXT: input (useful for debugging and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index 96e999c2c7b..90bc12b9918 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -132,6 +132,9 @@ ;; CHECK-NEXT: them and pass them back in, but not ;; CHECK-NEXT: inspect their contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from the +;; CHECK-NEXT: input (useful for debugging and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index fbf86209c69..cdd378d4082 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -174,6 +174,9 @@ ;; CHECK-NEXT: them and pass them back in, but not ;; CHECK-NEXT: inspect their contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from the +;; CHECK-NEXT: input (useful for debugging and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index 108fd367373..ee1fed13a92 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -162,6 +162,9 @@ ;; CHECK-NEXT: them and pass them back in, but not ;; CHECK-NEXT: inspect their contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from the +;; CHECK-NEXT: input (useful for debugging and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 4ddf5520397..50295b1f02d 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -248,9 +248,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-blocks merges blocks to their parents ;; CHECK-NEXT: -;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into +;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into ;; CHECK-NEXT: vtables to make types more -;; CHECK-NEXT: compact +;; CHECK-NEXT: compact ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-locals merges locals when beneficial ;; CHECK-NEXT: @@ -771,6 +771,10 @@ ;; CHECK-NEXT: in, but not inspect their ;; CHECK-NEXT: contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from +;; CHECK-NEXT: the input (useful for debugging +;; CHECK-NEXT: and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index f55798253e7..0691d1c1839 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -257,7 +257,7 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-blocks merges blocks to their parents ;; CHECK-NEXT: -;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into +;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into ;; CHECK-NEXT: vtables to make types more ;; CHECK-NEXT: compact ;; CHECK-NEXT: @@ -780,6 +780,10 @@ ;; CHECK-NEXT: in, but not inspect their ;; CHECK-NEXT: contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from +;; CHECK-NEXT: the input (useful for debugging +;; CHECK-NEXT: and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 61e171ba91d..cc75aed2b11 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -168,6 +168,9 @@ ;; CHECK-NEXT: them and pass them back in, but not ;; CHECK-NEXT: inspect their contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from the +;; CHECK-NEXT: input (useful for debugging and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index a6074c85df8..dc521a82f49 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -249,6 +249,9 @@ ;; CHECK-NEXT: them and pass them back in, but not ;; CHECK-NEXT: inspect their contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from the +;; CHECK-NEXT: input (useful for debugging and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 39c4d5f5ebb..89dcaa0280d 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -211,7 +211,7 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --merge-blocks merges blocks to their parents ;; CHECK-NEXT: -;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into +;; CHECK-NEXT: --merge-j2cl-itables Merges itable structures into ;; CHECK-NEXT: vtables to make types more ;; CHECK-NEXT: compact ;; CHECK-NEXT: @@ -734,6 +734,10 @@ ;; CHECK-NEXT: in, but not inspect their ;; CHECK-NEXT: contents or call them. ;; CHECK-NEXT: +;; CHECK-NEXT: --preserve-type-order Preserve the order of types from +;; CHECK-NEXT: the input (useful for debugging +;; CHECK-NEXT: and testing) +;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate StackIR during writing ;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-stack-ir optimize StackIR during writing diff --git a/test/lit/passes/remove-unused-types-preserve-order.wast b/test/lit/passes/remove-unused-types-preserve-order.wast new file mode 100644 index 00000000000..12f467e5848 --- /dev/null +++ b/test/lit/passes/remove-unused-types-preserve-order.wast @@ -0,0 +1,81 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all --closed-world --preserve-type-order \ +;; RUN: --remove-unused-types -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --closed-world --preserve-type-order \ +;; RUN: --remove-unused-types --roundtrip -S -o - | filecheck %s + +(module + (rec + (type $unused-1 (struct)) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct)) + (type $A (struct)) + (type $unused-2 (struct)) + ) + ;; CHECK: (type $B (struct)) + (type $B (struct)) + (type $unused-3 (struct (field i32))) + (rec + ;; CHECK: (type $C (struct (field (ref null $D)))) + (type $C (struct (field (ref null $D)))) + (type $unused-4 (struct)) + ;; CHECK: (type $D (struct (field (ref null $C)))) + (type $D (struct (field (ref null $C)))) + ) + ;; CHECK: (type $E (struct (field (ref null $E)))) + (type $E (struct (field (ref null $E)))) + + ;; Use the types in a shuffled order, using later types the most. If we + ;; weren't deliberately and correctly preserving type order, we would end up + ;; with some other order. + + ;; CHECK: (global $E1 (ref null $E) (ref.null none)) + (global $E1 (ref null $E) (ref.null none)) + ;; CHECK: (global $E2 (ref null $E) (ref.null none)) + (global $E2 (ref null $E) (ref.null none)) + ;; CHECK: (global $E3 (ref null $E) (ref.null none)) + (global $E3 (ref null $E) (ref.null none)) + ;; CHECK: (global $E4 (ref null $E) (ref.null none)) + (global $E4 (ref null $E) (ref.null none)) + ;; CHECK: (global $E5 (ref null $E) (ref.null none)) + (global $E5 (ref null $E) (ref.null none)) + ;; CHECK: (global $E6 (ref null $E) (ref.null none)) + (global $E6 (ref null $E) (ref.null none)) + ;; CHECK: (global $E7 (ref null $E) (ref.null none)) + (global $E7 (ref null $E) (ref.null none)) + ;; CHECK: (global $E8 (ref null $E) (ref.null none)) + (global $E8 (ref null $E) (ref.null none)) + ;; CHECK: (global $E9 (ref null $E) (ref.null none)) + (global $E9 (ref null $E) (ref.null none)) + + ;; CHECK: (global $C1 (ref null $C) (ref.null none)) + (global $C1 (ref null $C) (ref.null none)) + ;; CHECK: (global $C2 (ref null $C) (ref.null none)) + (global $C2 (ref null $C) (ref.null none)) + ;; CHECK: (global $C3 (ref null $C) (ref.null none)) + (global $C3 (ref null $C) (ref.null none)) + ;; CHECK: (global $C4 (ref null $C) (ref.null none)) + (global $C4 (ref null $C) (ref.null none)) + ;; CHECK: (global $C5 (ref null $C) (ref.null none)) + (global $C5 (ref null $C) (ref.null none)) + ;; CHECK: (global $C6 (ref null $C) (ref.null none)) + (global $C6 (ref null $C) (ref.null none)) + + ;; CHECK: (global $A1 (ref null $A) (ref.null none)) + (global $A1 (ref null $A) (ref.null none)) + ;; CHECK: (global $A2 (ref null $A) (ref.null none)) + (global $A2 (ref null $A) (ref.null none)) + ;; CHECK: (global $A3 (ref null $A) (ref.null none)) + (global $A3 (ref null $A) (ref.null none)) + ;; CHECK: (global $A4 (ref null $A) (ref.null none)) + (global $A4 (ref null $A) (ref.null none)) + + ;; CHECK: (global $D1 (ref null $D) (ref.null none)) + (global $D1 (ref null $D) (ref.null none)) + ;; CHECK: (global $D2 (ref null $D) (ref.null none)) + (global $D2 (ref null $D) (ref.null none)) + + ;; CHECK: (global $B1 (ref null $B) (ref.null none)) + (global $B1 (ref null $B) (ref.null none)) +) From 801518be793b0fc6ff8043cfdf64e4fd6c6813cd Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 10 Sep 2024 12:01:22 -0700 Subject: [PATCH 013/622] Use --preserve-type-order in select tests (#6917) These are the tests that would otherwise have the largest diffs when changing the topological sort used to sort types. signature-refining_gto.wat also cannot be automatically updated, so there is extra benefit to making sure it has stable output. --- test/lit/passes/abstract-type-refining.wast | 148 ++++++++++---------- test/lit/passes/signature-refining_gto.wat | 11 +- test/lit/passes/type-merging.wast | 126 ++++++----------- test/lit/passes/unsubtyping.wast | 116 +++++++-------- 4 files changed, 177 insertions(+), 224 deletions(-) diff --git a/test/lit/passes/abstract-type-refining.wast b/test/lit/passes/abstract-type-refining.wast index c0a2f12b3cf..814f5c1f005 100644 --- a/test/lit/passes/abstract-type-refining.wast +++ b/test/lit/passes/abstract-type-refining.wast @@ -1,7 +1,9 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --abstract-type-refining --remove-unused-types --traps-never-happen -all --closed-world -S -o - | filecheck %s --check-prefix=YESTNH -;; RUN: foreach %s %t wasm-opt --abstract-type-refining --remove-unused-types -all --closed-world -S -o - | filecheck %s --check-prefix=NO_TNH +;; RUN: foreach %s %t wasm-opt --abstract-type-refining --remove-unused-types --traps-never-happen \ +;; RUN: -all --closed-world --preserve-type-order -S -o - | filecheck %s --check-prefix=YESTNH +;; RUN: foreach %s %t wasm-opt --abstract-type-refining --remove-unused-types \ +;; RUN: -all --closed-world --preserve-type-order -S -o - | filecheck %s --check-prefix=NO_TNH ;; Run in both TNH and non-TNH mode. @@ -12,15 +14,11 @@ ;; TNH mode $A and $D will also not be emitted in the output anymore. (module ;; NO_TNH: (rec - ;; NO_TNH-NEXT: (type $0 (func)) - - ;; NO_TNH: (type $A (sub (struct))) + ;; NO_TNH-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $0 (func)) - - ;; YESTNH: (type $B (sub (struct))) + ;; YESTNH-NEXT: (type $B (sub (struct))) ;; NO_TNH: (type $B (sub $A (struct))) (type $B (sub $A (struct))) @@ -35,15 +33,19 @@ ;; NO_TNH: (type $E (sub $D (struct))) (type $E (sub $D (struct))) - ;; YESTNH: (type $4 (func (param anyref))) + ;; YESTNH: (type $3 (func (param anyref))) + + ;; YESTNH: (type $4 (func)) ;; YESTNH: (global $global anyref (struct.new_default $B)) - ;; NO_TNH: (type $6 (func (param anyref))) + ;; NO_TNH: (type $5 (func (param anyref))) + + ;; NO_TNH: (type $6 (func)) ;; NO_TNH: (global $global anyref (struct.new_default $B)) (global $global anyref (struct.new $B)) - ;; YESTNH: (func $new (type $4) (param $x anyref) + ;; YESTNH: (func $new (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (struct.new_default $C) ;; YESTNH-NEXT: ) @@ -51,7 +53,7 @@ ;; YESTNH-NEXT: (struct.new_default $E) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $new (type $6) (param $x anyref) + ;; NO_TNH: (func $new (type $5) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (struct.new_default $C) ;; NO_TNH-NEXT: ) @@ -68,7 +70,7 @@ ) ) - ;; YESTNH: (func $ref.cast (type $4) (param $x anyref) + ;; YESTNH: (func $ref.cast (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $B) ;; YESTNH-NEXT: (local.get $x) @@ -95,7 +97,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $ref.cast (type $6) (param $x anyref) + ;; NO_TNH: (func $ref.cast (type $5) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast (ref $A) ;; NO_TNH-NEXT: (local.get $x) @@ -152,14 +154,14 @@ ) ) - ;; YESTNH: (func $ref.test (type $4) (param $x anyref) + ;; YESTNH: (func $ref.test (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.test (ref $B) ;; YESTNH-NEXT: (local.get $x) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $ref.test (type $6) (param $x anyref) + ;; NO_TNH: (func $ref.test (type $5) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.test (ref $A) ;; NO_TNH-NEXT: (local.get $x) @@ -174,7 +176,7 @@ ) ) - ;; YESTNH: (func $br_on (type $4) (param $x anyref) + ;; YESTNH: (func $br_on (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (block $block (result (ref $B)) ;; YESTNH-NEXT: (drop @@ -186,7 +188,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $br_on (type $6) (param $x anyref) + ;; NO_TNH: (func $br_on (type $5) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block $block (result anyref) ;; NO_TNH-NEXT: (drop @@ -211,14 +213,14 @@ ) ) - ;; YESTNH: (func $basic (type $4) (param $x anyref) + ;; YESTNH: (func $basic (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref struct) ;; YESTNH-NEXT: (local.get $x) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $basic (type $6) (param $x anyref) + ;; NO_TNH: (func $basic (type $5) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast (ref struct) ;; NO_TNH-NEXT: (local.get $x) @@ -234,7 +236,7 @@ ) ) - ;; YESTNH: (func $locals (type $0) + ;; YESTNH: (func $locals (type $4) ;; YESTNH-NEXT: (local $A (ref $B)) ;; YESTNH-NEXT: (local $B (ref $B)) ;; YESTNH-NEXT: (local $C (ref $C)) @@ -242,7 +244,7 @@ ;; YESTNH-NEXT: (local $E (ref $E)) ;; YESTNH-NEXT: (nop) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $locals (type $0) + ;; NO_TNH: (func $locals (type $6) ;; NO_TNH-NEXT: (local $A (ref $A)) ;; NO_TNH-NEXT: (local $B (ref $B)) ;; NO_TNH-NEXT: (local $C (ref $C)) @@ -269,31 +271,29 @@ ;; NO_TNH-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) - ;; YESTNH: (type $B1 (sub $A (struct))) - - ;; YESTNH: (type $2 (func (param anyref))) - ;; YESTNH: (type $B (sub $A (struct))) - ;; NO_TNH: (type $B1 (sub $A (struct))) - - ;; NO_TNH: (type $2 (func (param anyref))) - ;; NO_TNH: (type $B (sub $A (struct))) (type $B (sub $A (struct))) + ;; YESTNH: (type $B1 (sub $A (struct))) + ;; NO_TNH: (type $B1 (sub $A (struct))) (type $B1 (sub $A (struct))) ;; this is a new type ) + ;; YESTNH: (type $3 (func (param anyref))) + ;; YESTNH: (global $global anyref (struct.new_default $B)) + ;; NO_TNH: (type $3 (func (param anyref))) + ;; NO_TNH: (global $global anyref (struct.new_default $B)) (global $global anyref (struct.new $B)) - ;; YESTNH: (func $new (type $2) (param $x anyref) + ;; YESTNH: (func $new (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (struct.new_default $B1) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $new (type $2) (param $x anyref) + ;; NO_TNH: (func $new (type $3) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (struct.new_default $B1) ;; NO_TNH-NEXT: ) @@ -304,7 +304,7 @@ ) ) - ;; YESTNH: (func $ref.cast (type $2) (param $x anyref) + ;; YESTNH: (func $ref.cast (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $A) ;; YESTNH-NEXT: (local.get $x) @@ -321,7 +321,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $ref.cast (type $2) (param $x anyref) + ;; NO_TNH: (func $ref.cast (type $3) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast (ref $A) ;; NO_TNH-NEXT: (local.get $x) @@ -547,11 +547,11 @@ ) ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $0 (func)) + ;; YESTNH-NEXT: (type $0 (func (param anyref))) - ;; YESTNH: (type $1 (func (param anyref))) + ;; YESTNH: (type $1 (func)) - ;; YESTNH: (func $ref.cast (type $1) (param $x anyref) + ;; YESTNH: (func $ref.cast (type $0) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref none) ;; YESTNH-NEXT: (local.get $x) @@ -574,11 +574,11 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; NO_TNH: (rec - ;; NO_TNH-NEXT: (type $0 (func)) + ;; NO_TNH-NEXT: (type $0 (func (param anyref))) - ;; NO_TNH: (type $1 (func (param anyref))) + ;; NO_TNH: (type $1 (func)) - ;; NO_TNH: (func $ref.cast (type $1) (param $x anyref) + ;; NO_TNH: (func $ref.cast (type $0) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast (ref none) ;; NO_TNH-NEXT: (local.get $x) @@ -624,7 +624,7 @@ ) ) - ;; YESTNH: (func $ref.cast.null (type $1) (param $x anyref) + ;; YESTNH: (func $ref.cast.null (type $0) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast nullref ;; YESTNH-NEXT: (local.get $x) @@ -646,7 +646,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $ref.cast.null (type $1) (param $x anyref) + ;; NO_TNH: (func $ref.cast.null (type $0) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast nullref ;; NO_TNH-NEXT: (local.get $x) @@ -692,7 +692,7 @@ ) ) - ;; YESTNH: (func $ref.test (type $1) (param $x anyref) + ;; YESTNH: (func $ref.test (type $0) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.test (ref none) ;; YESTNH-NEXT: (local.get $x) @@ -704,7 +704,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $ref.test (type $1) (param $x anyref) + ;; NO_TNH: (func $ref.test (type $0) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.test (ref none) ;; NO_TNH-NEXT: (local.get $x) @@ -731,7 +731,7 @@ ) ) - ;; YESTNH: (func $br_on (type $1) (param $x anyref) + ;; YESTNH: (func $br_on (type $0) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (block $block (result (ref none)) ;; YESTNH-NEXT: (drop @@ -751,7 +751,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $br_on (type $1) (param $x anyref) + ;; NO_TNH: (func $br_on (type $0) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block $block (result (ref none)) ;; NO_TNH-NEXT: (drop @@ -794,14 +794,14 @@ ) ) - ;; YESTNH: (func $locals (type $0) + ;; YESTNH: (func $locals (type $1) ;; YESTNH-NEXT: (local $A (ref none)) ;; YESTNH-NEXT: (local $B (ref none)) ;; YESTNH-NEXT: (local $C1 (ref none)) ;; YESTNH-NEXT: (local $C2 nullref) ;; YESTNH-NEXT: (nop) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $locals (type $0) + ;; NO_TNH: (func $locals (type $1) ;; NO_TNH-NEXT: (local $A (ref none)) ;; NO_TNH-NEXT: (local $B (ref none)) ;; NO_TNH-NEXT: (local $C1 (ref none)) @@ -822,29 +822,29 @@ (module (rec ;; NO_TNH: (rec - ;; NO_TNH-NEXT: (type $0 (func (param anyref))) - - ;; NO_TNH: (type $A (sub (struct))) + ;; NO_TNH-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) ;; NO_TNH: (type $B (sub $A (struct))) (type $B (sub $A (struct))) ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $0 (func (param anyref))) - - ;; YESTNH: (type $C1 (sub (struct))) + ;; YESTNH-NEXT: (type $C1 (sub (struct))) ;; NO_TNH: (type $C1 (sub $B (struct))) (type $C1 (sub $B (struct))) (type $C2 (sub $B (struct))) ) + ;; YESTNH: (type $1 (func (param anyref))) + ;; YESTNH: (global $global anyref (struct.new_default $C1)) + ;; NO_TNH: (type $3 (func (param anyref))) + ;; NO_TNH: (global $global anyref (struct.new_default $C1)) (global $global anyref (struct.new $C1)) - ;; YESTNH: (func $ref.cast (type $0) (param $x anyref) + ;; YESTNH: (func $ref.cast (type $1) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $C1) ;; YESTNH-NEXT: (local.get $x) @@ -866,7 +866,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $ref.cast (type $0) (param $x anyref) + ;; NO_TNH: (func $ref.cast (type $3) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast (ref $A) ;; NO_TNH-NEXT: (local.get $x) @@ -913,7 +913,7 @@ ) ) - ;; YESTNH: (func $ref.cast.null (type $0) (param $x anyref) + ;; YESTNH: (func $ref.cast.null (type $1) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref null $C1) ;; YESTNH-NEXT: (local.get $x) @@ -935,7 +935,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $ref.cast.null (type $0) (param $x anyref) + ;; NO_TNH: (func $ref.cast.null (type $3) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast (ref null $A) ;; NO_TNH-NEXT: (local.get $x) @@ -1084,19 +1084,17 @@ (type $A (sub (func))) ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $1 (func (param funcref))) - - ;; YESTNH: (type $B (sub $A (func))) + ;; YESTNH-NEXT: (type $B (sub $A (func))) ;; NO_TNH: (rec - ;; NO_TNH-NEXT: (type $1 (func (param funcref))) - - ;; NO_TNH: (type $B (sub $A (func))) + ;; NO_TNH-NEXT: (type $B (sub $A (func))) (type $B (sub $A (func))) ;; YESTNH: (type $C (sub $B (func))) ;; NO_TNH: (type $C (sub $B (func))) (type $C (sub $B (func))) + ;; YESTNH: (type $3 (func (param funcref))) + ;; YESTNH: (elem declare func $A $C) ;; YESTNH: (export "A" (func $A)) @@ -1106,6 +1104,8 @@ ;; YESTNH-NEXT: (ref.func $A) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) + ;; NO_TNH: (type $3 (func (param funcref))) + ;; NO_TNH: (elem declare func $A $C) ;; NO_TNH: (export "A" (func $A)) @@ -1138,7 +1138,7 @@ ) ) - ;; YESTNH: (func $casts (type $1) (param $x funcref) + ;; YESTNH: (func $casts (type $3) (param $x funcref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $A) ;; YESTNH-NEXT: (local.get $x) @@ -1155,7 +1155,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $casts (type $1) (param $x funcref) + ;; NO_TNH: (func $casts (type $3) (param $x funcref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast (ref $A) ;; NO_TNH-NEXT: (local.get $x) @@ -1196,13 +1196,9 @@ ;; Array subtyping, which is a TODO - for now we do nothing. (module ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $0 (func (param anyref))) - - ;; YESTNH: (type $A (sub (array (mut i32)))) + ;; YESTNH-NEXT: (type $A (sub (array (mut i32)))) ;; NO_TNH: (rec - ;; NO_TNH-NEXT: (type $0 (func (param anyref))) - - ;; NO_TNH: (type $A (sub (array (mut i32)))) + ;; NO_TNH-NEXT: (type $A (sub (array (mut i32)))) (type $A (sub (array (mut i32)))) ;; YESTNH: (type $B (sub $A (array (mut i32)))) @@ -1213,10 +1209,14 @@ ;; NO_TNH: (type $C (sub $B (array (mut i32)))) (type $C (sub $B (array (mut i32)))) + ;; YESTNH: (type $3 (func (param anyref))) + ;; YESTNH: (global $A (ref $A) (array.new $A ;; YESTNH-NEXT: (i32.const 10) ;; YESTNH-NEXT: (i32.const 20) ;; YESTNH-NEXT: )) + ;; NO_TNH: (type $3 (func (param anyref))) + ;; NO_TNH: (global $A (ref $A) (array.new $A ;; NO_TNH-NEXT: (i32.const 10) ;; NO_TNH-NEXT: (i32.const 20) @@ -1252,7 +1252,7 @@ (i32.const 20) )) - ;; YESTNH: (func $casts (type $0) (param $x anyref) + ;; YESTNH: (func $casts (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $A) ;; YESTNH-NEXT: (local.get $x) @@ -1269,7 +1269,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $casts (type $0) (param $x anyref) + ;; NO_TNH: (func $casts (type $3) (param $x anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast (ref $A) ;; NO_TNH-NEXT: (local.get $x) diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat index 3876afcbda0..ec2b517b14b 100644 --- a/test/lit/passes/signature-refining_gto.wat +++ b/test/lit/passes/signature-refining_gto.wat @@ -1,4 +1,5 @@ -;; RUN: wasm-opt %s --closed-world --signature-refining --gto --remove-unused-types --roundtrip -all -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --closed-world --preserve-type-order \ +;; RUN: --signature-refining --gto --remove-unused-types --roundtrip -S -o - | filecheck %s ;; Check that type $A is not included in the final binary after the signature ;; refining optimization and an additional --remove-unused-types pass. @@ -8,11 +9,11 @@ ;; CHECK-NOT: (type $A (type $A (struct (field (mut (ref null $A))))) - ;; CHECK: (type $0 (func (param funcref i32))) + ;; CHECK: (type $0 (func (param (ref none)))) - ;; CHECK: (type $1 (func (param (ref none)))) + ;; CHECK: (type $1 (func (param funcref i32))) - ;; CHECK: (func $struct.get (type $1) (param $0 (ref none)) + ;; CHECK: (func $struct.get (type $0) (param $0 (ref none)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -31,7 +32,7 @@ ) ) - ;; CHECK: (func $caller (type $0) (param $0 funcref) (param $1 i32) + ;; CHECK: (func $caller (type $1) (param $0 funcref) (param $1 i32) ;; CHECK-NEXT: (call $struct.get ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null none) diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast index fcf3f4332a6..d6d117a05ab 100644 --- a/test/lit/passes/type-merging.wast +++ b/test/lit/passes/type-merging.wast @@ -1,5 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --closed-world --type-merging --remove-unused-types -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-merging --remove-unused-types -S -o - | filecheck %s (module (rec @@ -7,19 +8,15 @@ ;; CHECK-NEXT: (type $A (sub (struct (field anyref)))) (type $A (sub (struct (field anyref)))) (type $B (sub $A (struct (field anyref)))) - ;; CHECK: (type $G (sub final $A (struct (field anyref)))) - - ;; CHECK: (type $F (sub $A (struct (field anyref)))) - - ;; CHECK: (type $E (sub $A (struct (field eqref)))) - - ;; CHECK: (type $D (sub $A (struct (field (ref any))))) - ;; CHECK: (type $C (sub $A (struct (field anyref) (field f64)))) (type $C (sub $A (struct (field anyref) (field f64)))) + ;; CHECK: (type $D (sub $A (struct (field (ref any))))) (type $D (sub $A (struct (field (ref any))))) + ;; CHECK: (type $E (sub $A (struct (field eqref)))) (type $E (sub $A (struct (field eqref)))) + ;; CHECK: (type $F (sub $A (struct (field anyref)))) (type $F (sub $A (struct (field anyref)))) + ;; CHECK: (type $G (sub final $A (struct (field anyref)))) (type $G (sub final $A (struct (field anyref)))) ) @@ -208,11 +205,10 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $X (sub (struct (field (ref null $A)) (field f32)))) - - ;; CHECK: (type $A (sub (struct (field (ref null $X)) (field i32)))) + ;; CHECK-NEXT: (type $A (sub (struct (field (ref null $X)) (field i32)))) (type $A (sub (struct (ref null $X) i32))) (type $B (sub $A (struct (ref null $Y) i32))) + ;; CHECK: (type $X (sub (struct (field (ref null $A)) (field f32)))) (type $X (sub (struct (ref null $A) f32))) (type $Y (sub $X (struct (ref null $B) f32))) ) @@ -269,11 +265,10 @@ (rec (type $A (struct (ref null $X) i32)) ;; CHECK: (rec - ;; CHECK-NEXT: (type $Y (struct (field (ref null $B)) (field f32))) - - ;; CHECK: (type $B (struct (field (ref null $Y)) (field i32))) + ;; CHECK-NEXT: (type $B (struct (field (ref null $Y)) (field i32))) (type $B (struct (ref null $Y) i32)) (type $X (struct (ref null $A) f32)) + ;; CHECK: (type $Y (struct (field (ref null $B)) (field f32))) (type $Y (struct (ref null $B) f32)) ) ;; CHECK: (type $2 (func)) @@ -325,11 +320,10 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $X (sub (struct (field (ref null $A))))) - - ;; CHECK: (type $A (sub (struct (field (ref null $X))))) + ;; CHECK-NEXT: (type $A (sub (struct (field (ref null $X))))) (type $A (sub (struct (ref null $X)))) (type $B (sub $A (struct (ref null $Y)))) + ;; CHECK: (type $X (sub (struct (field (ref null $A))))) (type $X (sub (struct (ref null $A)))) (type $Y (sub $X (struct (ref null $B)))) ) @@ -366,55 +360,43 @@ (module ;; Check that a diversity of root types are merged correctly. ;; CHECK: (rec - ;; CHECK-NEXT: (type $M (struct (field i32) (field i32))) - - ;; CHECK: (type $L (struct (field i32))) - - ;; CHECK: (type $K (func (param i32 i32 i32) (result i32 i32))) - - ;; CHECK: (type $J (func (param i32 i32) (result i32 i32 i32))) - - ;; CHECK: (type $I (array (ref $A))) - - ;; CHECK: (type $H (array (ref null $A))) - - ;; CHECK: (type $G (array (ref any))) - - ;; CHECK: (type $F (array anyref)) - - ;; CHECK: (type $E (array i64)) - - ;; CHECK: (type $D (array i32)) - - ;; CHECK: (type $C (array i16)) - - ;; CHECK: (type $B (array (mut i32))) - - ;; CHECK: (type $A (array i8)) + ;; CHECK-NEXT: (type $A (array i8)) (type $A (array i8)) (type $A' (array i8)) + ;; CHECK: (type $C (array i16)) (type $C (array i16)) (type $C' (array i16)) + ;; CHECK: (type $D (array i32)) (type $D (array i32)) (type $D' (array i32)) + ;; CHECK: (type $B (array (mut i32))) (type $B (array (mut i32))) (type $B' (array (mut i32))) + ;; CHECK: (type $E (array i64)) (type $E (array i64)) (type $E' (array i64)) + ;; CHECK: (type $F (array anyref)) (type $F (array anyref)) (type $F' (array anyref)) + ;; CHECK: (type $G (array (ref any))) (type $G (array (ref any))) (type $G' (array (ref any))) + ;; CHECK: (type $H (array (ref null $A))) (type $H (array (ref null $A))) (type $H' (array (ref null $A))) + ;; CHECK: (type $I (array (ref $A))) (type $I (array (ref $A))) (type $I' (array (ref $A))) + ;; CHECK: (type $J (func (param i32 i32) (result i32 i32 i32))) (type $J (func (param i32 i32) (result i32 i32 i32))) (type $J' (func (param i32 i32) (result i32 i32 i32))) + ;; CHECK: (type $K (func (param i32 i32 i32) (result i32 i32))) (type $K (func (param i32 i32 i32) (result i32 i32))) (type $K' (func (param i32 i32 i32) (result i32 i32))) + ;; CHECK: (type $L (struct (field i32))) (type $L (struct i32)) (type $L' (struct i32)) + ;; CHECK: (type $M (struct (field i32) (field i32))) (type $M (struct i32 i32)) (type $M' (struct i32 i32)) @@ -574,26 +556,23 @@ (rec ;; These will get merged in the initial supertype merging stage. ;; CHECK: (rec - ;; CHECK-NEXT: (type $B' (sub (struct (field (ref $A))))) - - ;; CHECK: (type $C (sub $B' (struct (field (ref $A)) (field i32)))) - - ;; CHECK: (type $D' (sub $C (struct (field (ref $A)) (field i32) (field i32)))) - - ;; CHECK: (type $A (sub (struct))) + ;; CHECK-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) (type $A' (sub $A (struct))) ;; These siblings will be merged only after $a and $a' are merged. (type $B (sub (struct (ref $A)))) + ;; CHECK: (type $B' (sub (struct (field (ref $A))))) (type $B' (sub (struct (ref $A')))) ;; These will get merged only after $b and $b' are merged. + ;; CHECK: (type $C (sub $B' (struct (field (ref $A)) (field i32)))) (type $C (sub $B (struct (ref $A) i32))) (type $C' (sub $B' (struct (ref $A') i32))) ;; These will get merged only after $c and $c' are merged. (type $D (sub $C (struct (ref $A) i32 i32))) + ;; CHECK: (type $D' (sub $C (struct (field (ref $A)) (field i32) (field i32)))) (type $D' (sub $C' (struct (ref $A') i32 i32))) ) @@ -653,24 +632,20 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $C (sub (struct (field (mut i32))))) - - ;; CHECK: (type $D (sub $C (struct (field (mut i32)) (field (mut i32))))) - - ;; CHECK: (type $H (sub $D (struct (field (mut i32)) (field (mut i32)) (field (mut (ref null $D)))))) - - ;; CHECK: (type $A (sub $H (struct (field (mut i32)) (field (mut i32)) (field (mut (ref null $D))) (field (mut i64)) (field (mut (ref null $I)))))) - - ;; CHECK: (type $I (array (mut (ref null $C)))) + ;; CHECK-NEXT: (type $I (array (mut (ref null $C)))) (type $I (array (mut (ref null $C)))) + ;; CHECK: (type $C (sub (struct (field (mut i32))))) (type $C (sub (struct (field (mut i32))))) + ;; CHECK: (type $D (sub $C (struct (field (mut i32)) (field (mut i32))))) (type $D (sub $C (struct (field (mut i32)) (field (mut i32))))) (type $E (sub $D (struct (field (mut i32)) (field (mut i32))))) (type $F (sub $E (struct (field (mut i32)) (field (mut i32))))) (type $D$to-merge (sub $F (struct (field (mut i32)) (field (mut i32))))) ;; CHECK: (type $G (func (param (ref $C)) (result (ref $D)))) (type $G (func (param (ref $C)) (result (ref $D)))) + ;; CHECK: (type $H (sub $D (struct (field (mut i32)) (field (mut i32)) (field (mut (ref null $D)))))) (type $H (sub $D (struct (field (mut i32)) (field (mut i32)) (field (mut (ref null $E)))))) + ;; CHECK: (type $A (sub $H (struct (field (mut i32)) (field (mut i32)) (field (mut (ref null $D))) (field (mut i64)) (field (mut (ref null $I)))))) (type $A (sub $H (struct (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) (field (mut i64)) (field (mut (ref null $I)))))) (type $A$to-merge (sub $A (struct (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) (field (mut i64)) (field (mut (ref null $I)))))) ) @@ -706,16 +681,14 @@ ;; Arrays (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $refarray (sub (array anyref))) - - ;; CHECK: (type $sub-refarray-nn (sub $refarray (array (ref any)))) - - ;; CHECK: (type $intarray (sub (array (mut i32)))) + ;; CHECK-NEXT: (type $intarray (sub (array (mut i32)))) (type $intarray (sub (array (mut i32)))) (type $sub-intarray (sub $intarray (array (mut i32)))) + ;; CHECK: (type $refarray (sub (array anyref))) (type $refarray (sub (array (ref null any)))) (type $sub-refarray (sub $refarray (array (ref null any)))) + ;; CHECK: (type $sub-refarray-nn (sub $refarray (array (ref any)))) (type $sub-refarray-nn (sub $refarray (array (ref any)))) ;; CHECK: (type $3 (func)) @@ -872,11 +845,7 @@ ;; $x and $y are structurally identical, but won't be merged because there is ;; a cast to $y. ;; CHECK: (rec - ;; CHECK-NEXT: (type $b (sub (struct (field (ref null $x))))) - - ;; CHECK: (type $b1 (sub $b (struct (field (ref null $y))))) - - ;; CHECK: (type $x (sub (struct (field anyref)))) + ;; CHECK-NEXT: (type $x (sub (struct (field anyref)))) (type $x (sub (struct anyref))) ;; CHECK: (type $y (sub $x (struct (field anyref)))) (type $y (sub $x (struct anyref))) @@ -887,7 +856,9 @@ ;; subtype of $b. ;; CHECK: (type $a (struct (field (ref null $y)))) (type $a (struct (ref null $y))) + ;; CHECK: (type $b (sub (struct (field (ref null $x))))) (type $b (sub (struct (ref null $x)))) + ;; CHECK: (type $b1 (sub $b (struct (field (ref null $y))))) (type $b1 (sub $b (struct (ref null $y)))) ) @@ -927,10 +898,9 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $B (sub (func))) - - ;; CHECK: (type $A (sub (func (result (ref any) (ref $B))))) + ;; CHECK-NEXT: (type $A (sub (func (result (ref any) (ref $B))))) (type $A (sub (func (result (ref any) (ref $C))))) + ;; CHECK: (type $B (sub (func))) (type $B (sub (func))) (type $C (sub $B (func))) ;; CHECK: (type $D (sub final $A (func (result (ref any) (ref $B))))) @@ -966,15 +936,13 @@ (rec ;; CHECK: (type $A (sub (struct (field (ref null $A)) (field (ref null $I))))) (type $A (sub (struct (ref null $A) (ref null $I)))) - ;; CHECK: (type $C (sub $A (struct (field (ref null $A)) (field (ref null $K))))) - - ;; CHECK: (type $D2 (sub $C (struct (field (ref null $B)) (field (ref null $K))))) - ;; CHECK: (type $B (sub $A (struct (field (ref null $B)) (field (ref null $J))))) (type $B (sub $A (struct (ref null $B) (ref null $J)))) + ;; CHECK: (type $C (sub $A (struct (field (ref null $A)) (field (ref null $K))))) (type $C (sub $A (struct (ref null $A) (ref null $K)))) ;; CHECK: (type $D1 (sub $B (struct (field (ref null $B)) (field (ref null $K))))) (type $D1 (sub $B (struct (ref null $B) (ref null $K)))) + ;; CHECK: (type $D2 (sub $C (struct (field (ref null $B)) (field (ref null $K))))) (type $D2 (sub $C (struct (ref null $B) (ref null $K)))) ) @@ -997,18 +965,16 @@ ;; CHECK: (type $A (sub (struct (field (ref null $A)) (field (ref null $I))))) (type $A (sub (struct (ref null $A) (ref null $I)))) (type $A' (sub $A (struct (ref null $A) (ref null $I)))) - ;; CHECK: (type $C (sub $A (struct (field (ref null $A)) (field (ref null $K))))) - - ;; CHECK: (type $D2 (sub $C (struct (field (ref null $B)) (field (ref null $K))))) - ;; CHECK: (type $B (sub $A (struct (field (ref null $B)) (field (ref null $J))))) (type $B (sub $A' (struct (ref null $B) (ref null $J)))) (type $B' (sub $B (struct (ref null $B) (ref null $J)))) + ;; CHECK: (type $C (sub $A (struct (field (ref null $A)) (field (ref null $K))))) (type $C (sub $A' (struct (ref null $A) (ref null $K)))) (type $C' (sub $C (struct (ref null $A) (ref null $K)))) ;; CHECK: (type $D1 (sub $B (struct (field (ref null $B)) (field (ref null $K))))) (type $D1 (sub $B' (struct (ref null $B) (ref null $K)))) (type $D1' (sub $D1 (struct (ref null $B) (ref null $K)))) + ;; CHECK: (type $D2 (sub $C (struct (field (ref null $B)) (field (ref null $K))))) (type $D2 (sub $C' (struct (ref null $B) (ref null $K)))) (type $D2' (sub $D2 (struct (ref null $B) (ref null $K)))) ) diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index 590cc5ae104..0d4e11e122b 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -1,14 +1,14 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --closed-world --unsubtyping --remove-unused-types -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --unsubtyping --remove-unused-types -all -S -o - | filecheck %s (module ;; $sub1 and $sub2 should become parent types and $super should be removed. (type $super (sub (struct))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $sub2 (sub (struct (field f32)))) - - ;; CHECK: (type $sub1 (sub (struct (field i32)))) + ;; CHECK-NEXT: (type $sub1 (sub (struct (field i32)))) (type $sub1 (sub $super (struct i32))) + ;; CHECK: (type $sub2 (sub (struct (field f32)))) (type $sub2 (sub $super (struct f32))) ;; CHECK: (global $sub1 (ref $sub1) (struct.new_default $sub1)) @@ -21,10 +21,9 @@ ;; Same result, but we start with $sub2 <: $sub1. (type $super (sub (struct))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $sub2 (sub (struct (field i32) (field i32)))) - - ;; CHECK: (type $sub1 (sub (struct (field i32)))) + ;; CHECK-NEXT: (type $sub1 (sub (struct (field i32)))) (type $sub1 (sub $super (struct i32))) + ;; CHECK: (type $sub2 (sub (struct (field i32) (field i32)))) (type $sub2 (sub $sub1 (struct i32 i32))) ;; CHECK: (global $sub1 (ref $sub1) (struct.new_default $sub1)) @@ -214,12 +213,11 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $opt (sub (struct (field i32)))) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $opt (sub (struct (field i32)))) (type $opt (sub $super (struct i32))) ;; CHECK: (type $3 (func)) @@ -439,11 +437,11 @@ ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) - ;; CHECK: (type $2 (func (result (ref $sub)))) + ;; CHECK: (type $2 (func (result (ref $super)))) - ;; CHECK: (type $3 (func (result (ref $super)))) + ;; CHECK: (type $3 (func (result (ref $sub)))) - ;; CHECK: (func $return-call (type $3) (result (ref $super)) + ;; CHECK: (func $return-call (type $2) (result (ref $super)) ;; CHECK-NEXT: (return_call $callee) ;; CHECK-NEXT: ) (func $return-call (result (ref $super)) @@ -451,7 +449,7 @@ (return_call $callee) ) - ;; CHECK: (func $callee (type $2) (result (ref $sub)) + ;; CHECK: (func $callee (type $3) (result (ref $sub)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $callee (result (ref $sub)) @@ -493,15 +491,15 @@ ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) - ;; CHECK: (type $2 (func (result (ref $sub)))) + ;; CHECK: (type $2 (func (result (ref $super)))) - ;; CHECK: (type $3 (func (result (ref $super)))) + ;; CHECK: (type $3 (func (result (ref $sub)))) ;; CHECK: (table $t 1 1 funcref) (table $t 1 1 funcref) - ;; CHECK: (func $return-call-indirect (type $3) (result (ref $super)) - ;; CHECK-NEXT: (return_call_indirect $t (type $2) + ;; CHECK: (func $return-call-indirect (type $2) (result (ref $super)) + ;; CHECK-NEXT: (return_call_indirect $t (type $3) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -515,10 +513,9 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $sub (sub (func))) - - ;; CHECK: (type $super (sub (func))) + ;; CHECK-NEXT: (type $super (sub (func))) (type $super (sub (func))) + ;; CHECK: (type $sub (sub (func))) (type $sub (sub $super (func))) ;; CHECK: (table $t 1 1 (ref null $super)) @@ -620,10 +617,9 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) - ;; CHECK: (type $sub2 (sub $super (struct (field i32)))) - ;; CHECK: (type $sub1 (sub $super (struct))) (type $sub1 (sub $super (struct))) + ;; CHECK: (type $sub2 (sub $super (struct (field i32)))) (type $sub2 (sub $super (struct i32))) ;; CHECK: (type $3 (func)) @@ -673,10 +669,9 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $sub (sub (struct))) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (struct))) (type $sub (sub $super (struct))) ;; CHECK: (type $2 (func)) @@ -697,15 +692,13 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $super2 (sub (struct))) - - ;; CHECK: (type $sub2 (sub $super2 (struct))) - - ;; CHECK: (type $super1 (sub (struct))) + ;; CHECK-NEXT: (type $super1 (sub (struct))) (type $super1 (sub (struct))) + ;; CHECK: (type $super2 (sub (struct))) (type $super2 (sub (struct))) ;; CHECK: (type $sub1 (sub $super1 (struct))) (type $sub1 (sub $super1 (struct))) + ;; CHECK: (type $sub2 (sub $super2 (struct))) (type $sub2 (sub $super2 (struct))) ) @@ -788,20 +781,20 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $0 (func)) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $2 (func)) + ;; CHECK: (table $super 1 1 (ref null $super)) (table $super 1 1 (ref null $super)) ;; CHECK: (table $sub 1 1 (ref null $sub)) (table $sub 1 1 (ref null $sub)) - ;; CHECK: (func $table-copy (type $0) + ;; CHECK: (func $table-copy (type $2) ;; CHECK-NEXT: (table.copy $super $sub ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 0) @@ -820,20 +813,20 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $0 (func)) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $2 (func)) + ;; CHECK: (table $super 1 1 (ref null $super)) (table $super 1 1 (ref null $super)) ;; CHECK: (elem $sub (ref null $sub)) (elem $sub (ref null $sub)) - ;; CHECK: (func $table-copy (type $0) + ;; CHECK: (func $table-copy (type $2) ;; CHECK-NEXT: (table.init $super $sub ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 0) @@ -1134,13 +1127,12 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $struct (sub (struct (field (ref null $super))))) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $struct (sub (struct (field (ref null $super))))) (type $struct (sub (struct (ref null $super)))) ;; CHECK: (type $3 (func)) @@ -1181,13 +1173,12 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $struct (sub (struct (field (mut (ref null $super)))))) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $struct (sub (struct (field (mut (ref null $super)))))) (type $struct (sub (struct (mut (ref null $super))))) ;; CHECK: (type $3 (func (param (ref null $struct)))) @@ -1237,13 +1228,12 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $array (sub (array (ref null $super)))) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $array (sub (array (ref null $super)))) (type $array (sub (array (ref null $super)))) ;; CHECK: (type $3 (func)) @@ -1298,21 +1288,20 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $array (sub (array (ref null $super)))) - - ;; CHECK: (type $1 (func)) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $array (sub (array (ref null $super)))) (type $array (sub (array (ref null $super)))) + ;; CHECK: (type $3 (func)) + ;; CHECK: (elem $e (ref null $sub)) (elem $e (ref null $sub)) - ;; CHECK: (func $array-new-elem (type $1) + ;; CHECK: (func $array-new-elem (type $3) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.new_elem $array $e ;; CHECK-NEXT: (i32.const 0) @@ -1347,13 +1336,12 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $array (sub (array (ref null $super)))) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $array (sub (array (ref null $super)))) (type $array (sub (array (ref null $super)))) ;; CHECK: (type $3 (func)) @@ -1457,10 +1445,9 @@ ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) - ;; CHECK: (type $sub-array (sub (array (mut (ref null $sub))))) - ;; CHECK: (type $super-array (sub (array (mut (ref null $super))))) (type $super-array (sub (array (mut (ref null $super))))) + ;; CHECK: (type $sub-array (sub (array (mut (ref null $sub))))) (type $sub-array (sub (array (mut (ref null $sub))))) ;; CHECK: (type $4 (func)) @@ -1615,21 +1602,20 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $array (sub (array (mut (ref null $super))))) - - ;; CHECK: (type $1 (func)) - - ;; CHECK: (type $super (sub (struct))) + ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $array (sub (array (mut (ref null $super))))) (type $array (sub (array (mut (ref null $super))))) + ;; CHECK: (type $3 (func)) + ;; CHECK: (elem $e (ref null $sub)) (elem $e (ref null $sub)) - ;; CHECK: (func $array-init-elem (type $1) + ;; CHECK: (func $array-init-elem (type $3) ;; CHECK-NEXT: (array.init_elem $array $e ;; CHECK-NEXT: (array.new_fixed $array 0) ;; CHECK-NEXT: (i32.const 0) From 7ce8484a8cf426a30d392634e6eefae1c98097f5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 10 Sep 2024 12:01:35 -0700 Subject: [PATCH 014/622] Use --preserve-type-order in more tests (#6923) Update the remaining tests whose readability will be affected by the removal of the old topological sort in #6902, no matter how small their diffs would have been. --- test/lit/passes/j2cl-merge-itables.wast | 15 ++-- .../passes/string-lowering-instructions.wast | 76 +++++++++---------- test/lit/passes/type-refining.wast | 62 +++++++-------- test/lit/passes/unsubtyping-casts.wast | 3 +- 4 files changed, 76 insertions(+), 80 deletions(-) diff --git a/test/lit/passes/j2cl-merge-itables.wast b/test/lit/passes/j2cl-merge-itables.wast index f578eb65c5c..499f598e127 100644 --- a/test/lit/passes/j2cl-merge-itables.wast +++ b/test/lit/passes/j2cl-merge-itables.wast @@ -1,6 +1,7 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --closed-world --merge-j2cl-itables -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --merge-j2cl-itables -all -S -o - | filecheck %s ;; Shared itable instance. (module @@ -16,8 +17,6 @@ (field $vtable (ref $SubObject.vtable)) (field $itable (ref $Object.itable))))) - ;; CHECK: (type $2 (func)) - ;; CHECK: (type $function (func)) (type $function (func)) @@ -37,6 +36,8 @@ (field (ref null struct)))) ) + ;; CHECK: (type $6 (func)) + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) @@ -70,7 +71,7 @@ (type $function) ) - ;; CHECK: (func $usages (type $2) + ;; CHECK: (func $usages (type $6) ;; CHECK-NEXT: (local $o (ref null $SubObject)) ;; CHECK-NEXT: (local.set $o ;; CHECK-NEXT: (struct.new $SubObject @@ -128,8 +129,6 @@ (field $vtable (ref $SubObject.vtable)) (field $itable (ref $SubObject.itable))))) - ;; CHECK: (type $2 (func)) - ;; CHECK: (type $function (func)) (type $function (func)) @@ -155,6 +154,8 @@ ;; The initialization for the itable field (null struct) will be added to this ;; vtable instance. + ;; CHECK: (type $7 (func)) + ;; CHECK: (global $SubObject.vtable (ref $SubObject.vtable) (struct.new $SubObject.vtable ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.func $SubObject.f) @@ -185,7 +186,7 @@ (type $function) ) - ;; CHECK: (func $usages (type $2) + ;; CHECK: (func $usages (type $7) ;; CHECK-NEXT: (local $o (ref null $SubObject)) ;; CHECK-NEXT: (local.set $o ;; CHECK-NEXT: (struct.new $SubObject diff --git a/test/lit/passes/string-lowering-instructions.wast b/test/lit/passes/string-lowering-instructions.wast index 459f1817019..d8ba996b2f0 100644 --- a/test/lit/passes/string-lowering-instructions.wast +++ b/test/lit/passes/string-lowering-instructions.wast @@ -1,24 +1,18 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --string-lowering -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --preserve-type-order --string-lowering -S -o - | filecheck %s (module (rec - ;; CHECK: (type $0 (array (mut i16))) + ;; CHECK: (type $0 (func)) - ;; CHECK: (type $1 (func)) - - ;; CHECK: (type $2 (func (result externref))) - - ;; CHECK: (type $3 (func (param externref externref) (result i32))) + ;; CHECK: (type $1 (array (mut i16))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $4 (func (param externref))) - - ;; CHECK: (type $struct-of-string (struct (field externref) (field i32) (field anyref))) + ;; CHECK-NEXT: (type $struct-of-string (struct (field externref) (field i32) (field anyref))) (type $struct-of-string (struct (field stringref) (field i32) (field anyref))) - ;; CHECK: (type $struct-of-array (struct (field (ref $0)))) + ;; CHECK: (type $struct-of-array (struct (field (ref $1)))) (type $struct-of-array (struct (field (ref $array16)))) ;; CHECK: (type $array16-imm (array i32)) @@ -37,27 +31,33 @@ (type $array16 (array (mut i16))) ) - ;; CHECK: (type $12 (func (param externref) (result externref))) + ;; CHECK: (type $9 (func (param (ref $1)))) + + ;; CHECK: (type $10 (func (param externref externref) (result (ref extern)))) + + ;; CHECK: (type $11 (func (param externref (ref $1)) (result i32))) + + ;; CHECK: (type $12 (func (param externref externref) (result i32))) ;; CHECK: (type $13 (func (param externref) (result i32))) - ;; CHECK: (type $14 (func (param externref externref) (result i32))) + ;; CHECK: (type $14 (func (param externref) (result externref))) - ;; CHECK: (type $15 (func (param externref (ref $0)) (result i32))) + ;; CHECK: (type $15 (func (param externref))) - ;; CHECK: (type $16 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $16 (func (result externref))) - ;; CHECK: (type $17 (func (param (ref $0)))) + ;; CHECK: (type $17 (func (param externref externref) (result i32))) ;; CHECK: (type $18 (func (param externref i32 externref))) - ;; CHECK: (type $19 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $19 (func (param (ref null $1) i32 i32) (result (ref extern)))) ;; CHECK: (type $20 (func (param i32) (result (ref extern)))) ;; CHECK: (type $21 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $22 (func (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (type $22 (func (param externref (ref null $1) i32) (result i32))) ;; CHECK: (type $23 (func (param externref) (result i32))) @@ -69,21 +69,21 @@ ;; CHECK: (import "string.const" "1" (global $string.const_value (ref extern))) - ;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $1))) + ;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $0))) (import "colliding" "name" (func $fromCodePoint)) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $19) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $19) (param (ref null $1) i32 i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_18 (type $20) (param i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $21) (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $22) (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $22) (param externref (ref null $1) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $3) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $17) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $3) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $17) (param externref externref) (result i32))) ;; CHECK: (import "wasm:js-string" "length" (func $length (type $23) (param externref) (result i32))) @@ -98,7 +98,7 @@ ;; CHECK: (export "export.2" (func $exported-string-receiver)) - ;; CHECK: (func $string.new.gc (type $17) (param $array16 (ref $0)) + ;; CHECK: (func $string.new.gc (type $9) (param $array16 (ref $1)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $fromCharCodeArray ;; CHECK-NEXT: (local.get $array16) @@ -117,7 +117,7 @@ ) ) - ;; CHECK: (func $string.from_code_point (type $2) (result externref) + ;; CHECK: (func $string.from_code_point (type $16) (result externref) ;; CHECK-NEXT: (call $fromCodePoint_18 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -128,7 +128,7 @@ ) ) - ;; CHECK: (func $string.concat (type $16) (param $0 externref) (param $1 externref) (result (ref extern)) + ;; CHECK: (func $string.concat (type $10) (param $0 externref) (param $1 externref) (result (ref extern)) ;; CHECK-NEXT: (call $concat ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -141,7 +141,7 @@ ) ) - ;; CHECK: (func $string.encode (type $15) (param $ref externref) (param $array16 (ref $0)) (result i32) + ;; CHECK: (func $string.encode (type $11) (param $ref externref) (param $array16 (ref $1)) (result i32) ;; CHECK-NEXT: (call $intoCharCodeArray ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $array16) @@ -156,7 +156,7 @@ ) ) - ;; CHECK: (func $string.eq (type $14) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.eq (type $12) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (call $equals ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -169,7 +169,7 @@ ) ) - ;; CHECK: (func $string.compare (type $14) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.compare (type $12) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (call $compare ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -206,7 +206,7 @@ ) ) - ;; CHECK: (func $string.slice (type $12) (param $ref externref) (result externref) + ;; CHECK: (func $string.slice (type $14) (param $ref externref) (result externref) ;; CHECK-NEXT: (call $substring ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 2) @@ -221,7 +221,7 @@ ) ) - ;; CHECK: (func $if.string (type $12) (param $ref externref) (result externref) + ;; CHECK: (func $if.string (type $14) (param $ref externref) (result externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -244,7 +244,7 @@ ) ) - ;; CHECK: (func $if.string.flip (type $12) (param $ref externref) (result externref) + ;; CHECK: (func $if.string.flip (type $14) (param $ref externref) (result externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -268,7 +268,7 @@ ) ) - ;; CHECK: (func $exported-string-returner (type $2) (result externref) + ;; CHECK: (func $exported-string-returner (type $16) (result externref) ;; CHECK-NEXT: (global.get $string.const_exported) ;; CHECK-NEXT: ) (func $exported-string-returner (export "export.1") (result stringref) @@ -302,8 +302,8 @@ ) ) - ;; CHECK: (func $use-struct-of-array (type $1) - ;; CHECK-NEXT: (local $array16 (ref $0)) + ;; CHECK: (func $use-struct-of-array (type $0) + ;; CHECK-NEXT: (local $array16 (ref $1)) ;; CHECK-NEXT: (local $open (ref $array16-open)) ;; CHECK-NEXT: (local $child (ref $array16-child)) ;; CHECK-NEXT: (local $32 (ref $array32)) @@ -312,7 +312,7 @@ ;; CHECK-NEXT: (call $fromCharCodeArray ;; CHECK-NEXT: (struct.get $struct-of-array 0 ;; CHECK-NEXT: (struct.new $struct-of-array - ;; CHECK-NEXT: (array.new_fixed $0 2 + ;; CHECK-NEXT: (array.new_fixed $1 2 ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) @@ -358,7 +358,7 @@ ) ) - ;; CHECK: (func $struct-of-string (type $1) + ;; CHECK: (func $struct-of-string (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $struct-of-string ;; CHECK-NEXT: (ref.null noextern) @@ -398,7 +398,7 @@ ) ) - ;; CHECK: (func $call-param-null (type $4) (param $str externref) + ;; CHECK: (func $call-param-null (type $15) (param $str externref) ;; CHECK-NEXT: (call $call-param-null ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index ab82aeb3608..5689ff3dc59 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1,5 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --closed-world --type-refining -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-refining -S -o - | filecheck %s (module ;; A struct with three fields. The first will have no writes, the second one @@ -93,11 +94,10 @@ ;; CHECK-NEXT: (type $struct (sub (struct (field (mut (ref $struct)))))) (type $struct (sub (struct (field (mut structref))))) - ;; CHECK: (type $child-B (sub $struct (struct (field (mut (ref $struct)))))) - ;; CHECK: (type $child-A (sub $struct (struct (field (mut (ref $struct)))))) (type $child-A (sub $struct (struct (field (mut structref))))) + ;; CHECK: (type $child-B (sub $struct (struct (field (mut (ref $struct)))))) (type $child-B (sub $struct (struct (field (mut structref))))) ) @@ -141,11 +141,11 @@ (type $child-B (sub $struct (struct (field (mut structref))))) ) - ;; CHECK: (type $3 (func)) + ;; CHECK: (type $3 (func (param (ref $struct) (ref $child-A)))) - ;; CHECK: (type $4 (func (param (ref $struct) (ref $child-A)))) + ;; CHECK: (type $4 (func)) - ;; CHECK: (func $work (type $4) (param $struct (ref $struct)) (param $child-A (ref $child-A)) + ;; CHECK: (func $work (type $3) (param $struct (ref $struct)) (param $child-A (ref $child-A)) ;; CHECK-NEXT: (struct.set $struct 0 ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (local.get $child-A) @@ -166,7 +166,7 @@ ) ) - ;; CHECK: (func $keepalive (type $3) + ;; CHECK: (func $keepalive (type $4) ;; CHECK-NEXT: (local $temp (ref null $child-B)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) @@ -455,21 +455,19 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (struct (field (ref $Y))))) - - ;; CHECK: (type $B (sub $A (struct (field (ref $Y))))) - - ;; CHECK: (type $X (sub (struct))) + ;; CHECK-NEXT: (type $X (sub (struct))) (type $X (sub (struct))) ;; CHECK: (type $Y (sub $X (struct))) (type $Y (sub $X (struct))) + ;; CHECK: (type $A (sub (struct (field (ref $Y))))) (type $A (sub (struct (field (ref $X))))) ;; CHECK: (type $C (sub $A (struct (field (ref $Y))))) (type $C (sub $A (struct (field (ref $X))))) + ;; CHECK: (type $B (sub $A (struct (field (ref $Y))))) (type $B (sub $A (struct (field (ref $X))))) ) @@ -545,19 +543,18 @@ ;; CHECK: (type $X (sub (struct))) (type $X (sub (struct))) - ;; CHECK: (type $A (sub (struct (field (ref $X))))) - ;; CHECK: (type $Y (sub $X (struct))) (type $Y (sub $X (struct))) + ;; CHECK: (type $A (sub (struct (field (ref $X))))) (type $A (sub (struct (field (ref $X))))) - ;; CHECK: (type $3 (func)) - ;; CHECK: (type $B (sub $A (struct (field (ref $Y))))) (type $B (sub $A (struct (field (ref $Y))))) - ;; CHECK: (func $foo (type $3) + ;; CHECK: (type $4 (func)) + + ;; CHECK: (func $foo (type $4) ;; CHECK-NEXT: (local $unused2 (ref null $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $A @@ -825,19 +822,16 @@ ;; CHECK: (type $Leaf1-Inner (sub $Root-Inner (struct (field i32)))) (type $Leaf1-Inner (sub $Root-Inner (struct (field i32)))) - ;; CHECK: (type $Root-Outer (sub (struct (field (ref $Leaf2-Inner))))) - - ;; CHECK: (type $Leaf1-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner))))) - - ;; CHECK: (type $Leaf2-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner))))) - ;; CHECK: (type $Leaf2-Inner (sub $Root-Inner (struct))) (type $Leaf2-Inner (sub $Root-Inner (struct ))) + ;; CHECK: (type $Root-Outer (sub (struct (field (ref $Leaf2-Inner))))) (type $Root-Outer (sub (struct (field (ref $Root-Inner))))) + ;; CHECK: (type $Leaf1-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner))))) (type $Leaf1-Outer (sub $Root-Outer (struct (field (ref $Leaf1-Inner))))) + ;; CHECK: (type $Leaf2-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner))))) (type $Leaf2-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner))))) ;; CHECK: (type $6 (func (param (ref null $Leaf1-Outer)))) @@ -1241,25 +1235,25 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $0 (func (param (ref noextern)))) + ;; CHECK-NEXT: (type $A (struct (field (mut (ref noextern))))) + (type $A (struct (field (mut externref)))) - ;; CHECK: (type $1 (func (param (ref none) (ref noextern)))) + ;; CHECK: (type $1 (func)) - ;; CHECK: (type $2 (func (param (ref $A) externref))) + ;; CHECK: (type $2 (func (param externref) (result anyref))) - ;; CHECK: (type $A (struct (field (mut (ref noextern))))) - (type $A (struct (field (mut externref)))) + ;; CHECK: (type $3 (func (param (ref $A) externref))) - ;; CHECK: (type $4 (func (param externref) (result anyref))) + ;; CHECK: (type $4 (func (param (ref none) (ref noextern)))) - ;; CHECK: (type $5 (func)) + ;; CHECK: (type $5 (func (param (ref noextern)))) ;; CHECK: (type $6 (func)) ;; CHECK: (tag $tag) (tag $tag) - ;; CHECK: (func $struct.new (type $4) (param $extern externref) (result anyref) + ;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref) ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.cast (ref noextern) ;; CHECK-NEXT: (try (result externref) @@ -1310,7 +1304,7 @@ ) ) - ;; CHECK: (func $struct.set (type $2) (param $ref (ref $A)) (param $extern externref) + ;; CHECK: (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (ref.cast (ref noextern) @@ -1351,7 +1345,7 @@ ) ) - ;; CHECK: (func $bottom.type (type $1) (param $ref (ref none)) (param $value (ref noextern)) + ;; CHECK: (func $bottom.type (type $4) (param $ref (ref none)) (param $value (ref noextern)) ;; CHECK-NEXT: (block ;; (replaces unreachable StructSet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $ref) @@ -1370,7 +1364,7 @@ ) ) - ;; CHECK: (func $unreachable (type $0) (param $value (ref noextern)) + ;; CHECK: (func $unreachable (type $5) (param $value (ref noextern)) ;; CHECK-NEXT: (block ;; (replaces unreachable StructSet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) diff --git a/test/lit/passes/unsubtyping-casts.wast b/test/lit/passes/unsubtyping-casts.wast index 95394bdd05d..9852fe4705b 100644 --- a/test/lit/passes/unsubtyping-casts.wast +++ b/test/lit/passes/unsubtyping-casts.wast @@ -1,5 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --closed-world --unsubtyping --remove-unused-types -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --unsubtyping --remove-unused-types -S -o - | filecheck %s (module ;; CHECK: (rec From 10216e8fdc841f53408bc10caac2709f16b80030 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Sep 2024 13:23:53 -0700 Subject: [PATCH 015/622] [NFC] OptimizeAddedConstants: Early exit if there are no memories (#6926) The pass optimizes loads and stores, so without a memory there is nothing to do. This only helps if the user set --low-memory-unused and also has no memory, which is likely rare, but it's a trivial change so it seems worthwhile. In particular this pass constructs a LocalGraph, so if we can avoid work it can be substantial. --- src/passes/OptimizeAddedConstants.cpp | 5 +++++ .../optimize-added-constants-nomemory.wast | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 test/lit/passes/optimize-added-constants-nomemory.wast diff --git a/src/passes/OptimizeAddedConstants.cpp b/src/passes/OptimizeAddedConstants.cpp index c76711d8a1c..f03109223e2 100644 --- a/src/passes/OptimizeAddedConstants.cpp +++ b/src/passes/OptimizeAddedConstants.cpp @@ -290,6 +290,11 @@ struct OptimizeAddedConstants << "--low-memory-unused flag is set."; } + if (getModule()->memories.empty()) { + // There can be no loads and stores without a memory. + return; + } + // Multiple passes may be needed if we have x + 4 + 8 etc. (nested structs // in C can cause this, but it's rare). Note that we only need that for the // propagation case (as 4 + 8 would be optimized directly if it were diff --git a/test/lit/passes/optimize-added-constants-nomemory.wast b/test/lit/passes/optimize-added-constants-nomemory.wast new file mode 100644 index 00000000000..75b9e112cad --- /dev/null +++ b/test/lit/passes/optimize-added-constants-nomemory.wast @@ -0,0 +1,21 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --optimize-added-constants --low-memory-unused -S -o - | filecheck %s + +;; Check we do not error on modules with no memories, which the pass has +;; nothing to do on. + +(module + ;; CHECK: (func $do-stuff-without-a-memory (type $0) (param $x i32) (result i32) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $do-stuff-without-a-memory (param $x i32) (result i32) + (i32.add + (local.get $x) + (i32.const 42) + ) + ) +) From 34ee8342f8148e4c25bd1990e518cd8a9a502f80 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Sep 2024 13:24:20 -0700 Subject: [PATCH 016/622] [NFC] Use a lazy LocalGraph in Heap2Local (#6925) That pass only cares about reference locals, and even among them, only ones that we see a struct.new/array.new that flows through locals. This makes the pass 40% faster. --- src/passes/Heap2Local.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 82a5e90bb72..09a50eea8d8 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -198,15 +198,17 @@ enum class ParentChildInteraction : int8_t { struct EscapeAnalyzer { // To find what escapes, we need to follow where values flow, both up to // parents, and via branches, and through locals. - // TODO: for efficiency, only scan reference types in LocalGraph - const LocalGraph& localGraph; + // + // We use a lazy graph here because we only need this for reference locals, + // and even among them, only ones we see an allocation is stored to. + const LazyLocalGraph& localGraph; const Parents& parents; const BranchUtils::BranchTargets& branchTargets; const PassOptions& passOptions; Module& wasm; - EscapeAnalyzer(const LocalGraph& localGraph, + EscapeAnalyzer(const LazyLocalGraph& localGraph, const Parents& parents, const BranchUtils::BranchTargets& branchTargets, const PassOptions& passOptions, @@ -1139,17 +1141,13 @@ struct Heap2Local { Module& wasm; const PassOptions& passOptions; - // TODO: construct this LocalGraph on demand - LocalGraph localGraph; + LazyLocalGraph localGraph; Parents parents; BranchUtils::BranchTargets branchTargets; Heap2Local(Function* func, Module& wasm, const PassOptions& passOptions) : func(func), wasm(wasm), passOptions(passOptions), localGraph(func, &wasm), parents(func->body), branchTargets(func->body) { - // We need to track what each set influences, to see where its value can - // flow to. - localGraph.computeSetInfluences(); // Find all the relevant allocations in the function: StructNew, ArrayNew, // ArrayNewFixed. From b0c955d4e5d1454dd9d6036d25ec9118146eee4c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Sep 2024 14:22:41 -0700 Subject: [PATCH 017/622] [NFC] Remove excessive debug logging from binary reading (#6927) We were doing a debug logging for every LEB byte. It turns out that the isDebugEnabled() calls are expensive when called so frequently: in a release+assertion build, even with debug disabled, these checks are the highest thing in the profile. This PR removes the checks, which makes binary reading 12% faster. --- src/passes/PostEmscripten.cpp | 1 + src/wasm-binary.h | 45 +-------- src/wasm/wasm-binary.cpp | 183 ++-------------------------------- 3 files changed, 8 insertions(+), 221 deletions(-) diff --git a/src/passes/PostEmscripten.cpp b/src/passes/PostEmscripten.cpp index 0e2c268dec0..20f26e211b6 100644 --- a/src/passes/PostEmscripten.cpp +++ b/src/passes/PostEmscripten.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/src/wasm-binary.h b/src/wasm-binary.h index d7fde59134d..fe740d56a76 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -28,14 +28,11 @@ #include "ir/import-utils.h" #include "ir/module-utils.h" #include "parsing.h" -#include "support/debug.h" #include "wasm-builder.h" #include "wasm-traversal.h" #include "wasm-validator.h" #include "wasm.h" -#define DEBUG_TYPE "binary" - namespace wasm { enum { @@ -158,18 +155,15 @@ class BufferWithRandomAccess : public std::vector { BufferWithRandomAccess() = default; BufferWithRandomAccess& operator<<(int8_t x) { - BYN_TRACE("writeInt8: " << (int)(uint8_t)x << " (at " << size() << ")\n"); push_back(x); return *this; } BufferWithRandomAccess& operator<<(int16_t x) { - BYN_TRACE("writeInt16: " << x << " (at " << size() << ")\n"); push_back(x & 0xff); push_back(x >> 8); return *this; } BufferWithRandomAccess& operator<<(int32_t x) { - BYN_TRACE("writeInt32: " << x << " (at " << size() << ")\n"); push_back(x & 0xff); x >>= 8; push_back(x & 0xff); @@ -180,7 +174,6 @@ class BufferWithRandomAccess : public std::vector { return *this; } BufferWithRandomAccess& operator<<(int64_t x) { - BYN_TRACE("writeInt64: " << x << " (at " << size() << ")\n"); push_back(x & 0xff); x >>= 8; push_back(x & 0xff); @@ -199,47 +192,19 @@ class BufferWithRandomAccess : public std::vector { return *this; } BufferWithRandomAccess& operator<<(U32LEB x) { - [[maybe_unused]] size_t before = -1; - BYN_DEBUG(before = size(); std::cerr << "writeU32LEB: " << x.value - << " (at " << before << ")" - << std::endl;); x.write(this); - BYN_DEBUG(for (size_t i = before; i < size(); i++) { - std::cerr << " " << (int)at(i) << " (at " << i << ")\n"; - }); return *this; } BufferWithRandomAccess& operator<<(U64LEB x) { - [[maybe_unused]] size_t before = -1; - BYN_DEBUG(before = size(); std::cerr << "writeU64LEB: " << x.value - << " (at " << before << ")" - << std::endl;); x.write(this); - BYN_DEBUG(for (size_t i = before; i < size(); i++) { - std::cerr << " " << (int)at(i) << " (at " << i << ")\n"; - }); return *this; } BufferWithRandomAccess& operator<<(S32LEB x) { - [[maybe_unused]] size_t before = -1; - BYN_DEBUG(before = size(); std::cerr << "writeS32LEB: " << x.value - << " (at " << before << ")" - << std::endl;); x.write(this); - BYN_DEBUG(for (size_t i = before; i < size(); i++) { - std::cerr << " " << (int)at(i) << " (at " << i << ")\n"; - }); return *this; } BufferWithRandomAccess& operator<<(S64LEB x) { - [[maybe_unused]] size_t before = -1; - BYN_DEBUG(before = size(); std::cerr << "writeS64LEB: " << x.value - << " (at " << before << ")" - << std::endl;); x.write(this); - BYN_DEBUG(for (size_t i = before; i < size(); i++) { - std::cerr << " " << (int)at(i) << " (at " << i << ")\n"; - }); return *this; } @@ -249,21 +214,17 @@ class BufferWithRandomAccess : public std::vector { BufferWithRandomAccess& operator<<(uint64_t x) { return *this << (int64_t)x; } BufferWithRandomAccess& operator<<(float x) { - BYN_TRACE("writeFloat32: " << x << " (at " << size() << ")\n"); return *this << Literal(x).reinterpreti32(); } BufferWithRandomAccess& operator<<(double x) { - BYN_TRACE("writeFloat64: " << x << " (at " << size() << ")\n"); return *this << Literal(x).reinterpreti64(); } void writeAt(size_t i, uint16_t x) { - BYN_TRACE("backpatchInt16: " << x << " (at " << i << ")\n"); (*this)[i] = x & 0xff; (*this)[i + 1] = x >> 8; } void writeAt(size_t i, uint32_t x) { - BYN_TRACE("backpatchInt32: " << x << " (at " << i << ")\n"); (*this)[i] = x & 0xff; x >>= 8; (*this)[i + 1] = x & 0xff; @@ -276,16 +237,12 @@ class BufferWithRandomAccess : public std::vector { // writes out an LEB to an arbitrary location. this writes the LEB as a full // 5 bytes, the fixed amount that can easily be set aside ahead of time void writeAtFullFixedSize(size_t i, U32LEB x) { - BYN_TRACE("backpatchU32LEB: " << x.value << " (at " << i << ")\n"); // fill all 5 bytes, we have to do this when backpatching x.writeAt(this, i, MaxLEB32Bytes); } // writes out an LEB of normal size // returns how many bytes were written - size_t writeAt(size_t i, U32LEB x) { - BYN_TRACE("writeAtU32LEB: " << x.value << " (at " << i << ")\n"); - return x.writeAt(this, i); - } + size_t writeAt(size_t i, U32LEB x) { return x.writeAt(this, i); } template void writeTo(T& o) { for (auto c : *this) { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 151a7f91189..d8a310103da 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -101,7 +101,6 @@ void WasmBinaryWriter::write() { } void WasmBinaryWriter::writeHeader() { - BYN_TRACE("== writeHeader\n"); o << int32_t(BinaryConsts::Magic); // magic number \0asm o << int32_t(BinaryConsts::Version); } @@ -207,7 +206,6 @@ void WasmBinaryWriter::writeStart() { if (!wasm->start.is()) { return; } - BYN_TRACE("== writeStart\n"); auto start = startSection(BinaryConsts::Section::Start); o << U32LEB(getFunctionIndex(wasm->start.str)); finishSection(start); @@ -217,7 +215,6 @@ void WasmBinaryWriter::writeMemories() { if (importInfo->getNumDefinedMemories() == 0) { return; } - BYN_TRACE("== writeMemories\n"); auto start = startSection(BinaryConsts::Section::Memory); auto num = importInfo->getNumDefinedMemories(); o << U32LEB(num); @@ -259,7 +256,6 @@ void WasmBinaryWriter::writeTypes() { } } - BYN_TRACE("== writeTypes\n"); auto start = startSection(BinaryConsts::Section::Type); o << U32LEB(numGroups); std::optional lastGroup = std::nullopt; @@ -273,7 +269,6 @@ void WasmBinaryWriter::writeTypes() { } lastGroup = currGroup; // Emit the type definition. - BYN_TRACE("write " << type << std::endl); auto super = type.getDeclaredSuperType(); if (super || type.isOpen()) { if (type.isOpen()) { @@ -332,7 +327,6 @@ void WasmBinaryWriter::writeImports() { if (num == 0) { return; } - BYN_TRACE("== writeImports\n"); auto start = startSection(BinaryConsts::Section::Import); o << U32LEB(num); auto writeImportHeader = [&](Importable* import) { @@ -340,27 +334,23 @@ void WasmBinaryWriter::writeImports() { writeInlineString(import->base.str); }; ModuleUtils::iterImportedFunctions(*wasm, [&](Function* func) { - BYN_TRACE("write one function\n"); writeImportHeader(func); o << U32LEB(int32_t(ExternalKind::Function)); o << U32LEB(getTypeIndex(func->type)); }); ModuleUtils::iterImportedGlobals(*wasm, [&](Global* global) { - BYN_TRACE("write one global\n"); writeImportHeader(global); o << U32LEB(int32_t(ExternalKind::Global)); writeType(global->type); o << U32LEB(global->mutable_); }); ModuleUtils::iterImportedTags(*wasm, [&](Tag* tag) { - BYN_TRACE("write one tag\n"); writeImportHeader(tag); o << U32LEB(int32_t(ExternalKind::Tag)); o << uint8_t(0); // Reserved 'attribute' field. Always 0. o << U32LEB(getTypeIndex(tag->sig)); }); ModuleUtils::iterImportedMemories(*wasm, [&](Memory* memory) { - BYN_TRACE("write one memory\n"); writeImportHeader(memory); o << U32LEB(int32_t(ExternalKind::Memory)); writeResizableLimits(memory->initial, @@ -370,7 +360,6 @@ void WasmBinaryWriter::writeImports() { memory->is64()); }); ModuleUtils::iterImportedTables(*wasm, [&](Table* table) { - BYN_TRACE("write one table\n"); writeImportHeader(table); o << U32LEB(int32_t(ExternalKind::Table)); writeType(table->type); @@ -387,13 +376,10 @@ void WasmBinaryWriter::writeFunctionSignatures() { if (importInfo->getNumDefinedFunctions() == 0) { return; } - BYN_TRACE("== writeFunctionSignatures\n"); auto start = startSection(BinaryConsts::Section::Function); o << U32LEB(importInfo->getNumDefinedFunctions()); - ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) { - BYN_TRACE("write one\n"); - o << U32LEB(getTypeIndex(func->type)); - }); + ModuleUtils::iterDefinedFunctions( + *wasm, [&](Function* func) { o << U32LEB(getTypeIndex(func->type)); }); finishSection(start); } @@ -411,33 +397,28 @@ void WasmBinaryWriter::writeFunctions() { moduleStackIR.emplace(*wasm, options); } - BYN_TRACE("== writeFunctions\n"); auto sectionStart = startSection(BinaryConsts::Section::Code); o << U32LEB(importInfo->getNumDefinedFunctions()); bool DWARF = Debug::hasDWARFSections(*getModule()); ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) { assert(binaryLocationTrackedExpressionsForFunc.empty()); - BYN_TRACE("write one at" << o.size() << std::endl); // Do not smear any debug location from the previous function. writeNoDebugLocation(); size_t sourceMapLocationsSizeAtFunctionStart = sourceMapLocations.size(); size_t sizePos = writeU32LEBPlaceholder(); size_t start = o.size(); - BYN_TRACE("writing" << func->name << std::endl); // Emit Stack IR if present. StackIR* stackIR = nullptr; if (moduleStackIR) { stackIR = moduleStackIR->getStackIROrNull(func); } if (stackIR) { - BYN_TRACE("write Stack IR\n"); StackIRToBinaryWriter writer(*this, o, func, *stackIR, sourceMap, DWARF); writer.write(); if (debugInfo) { funcMappedLocals[func->name] = std::move(writer.getMappedLocals()); } } else { - BYN_TRACE("write Binaryen IR\n"); BinaryenIRToBinaryWriter writer(*this, o, func, sourceMap, DWARF); writer.write(); if (debugInfo) { @@ -446,8 +427,6 @@ void WasmBinaryWriter::writeFunctions() { } size_t size = o.size() - start; assert(size <= std::numeric_limits::max()); - BYN_TRACE("body size: " << size << ", writing at " << sizePos - << ", next starts at " << o.size() << "\n"); auto sizeFieldSize = o.writeAt(sizePos, U32LEB(size)); // We can move things back if the actual LEB for the size doesn't use the // maximum 5 bytes. In that case we need to adjust offsets after we move @@ -569,7 +548,6 @@ void WasmBinaryWriter::writeGlobals() { if (importInfo->getNumDefinedGlobals() == 0) { return; } - BYN_TRACE("== writeglobals\n"); auto start = startSection(BinaryConsts::Section::Global); // Count and emit the total number of globals after tuple globals have been // expanded into their constituent parts. @@ -578,7 +556,6 @@ void WasmBinaryWriter::writeGlobals() { *wasm, [&num](Global* global) { num += global->type.size(); }); o << U32LEB(num); ModuleUtils::iterDefinedGlobals(*wasm, [&](Global* global) { - BYN_TRACE("write one\n"); size_t i = 0; for (const auto& t : global->type) { writeType(t); @@ -614,11 +591,9 @@ void WasmBinaryWriter::writeExports() { if (wasm->exports.size() == 0) { return; } - BYN_TRACE("== writeexports\n"); auto start = startSection(BinaryConsts::Section::Export); o << U32LEB(wasm->exports.size()); for (auto& curr : wasm->exports) { - BYN_TRACE("write one\n"); writeInlineString(curr->name.str); o << U32LEB(int32_t(curr->kind)); switch (curr->kind) { @@ -764,7 +739,6 @@ void WasmBinaryWriter::writeTableDeclarations() { // defined tables found. skipping" << std::endl; return; } - BYN_TRACE("== writeTableDeclarations\n"); auto start = startSection(BinaryConsts::Section::Table); auto num = importInfo->getNumDefinedTables(); o << U32LEB(num); @@ -789,7 +763,6 @@ void WasmBinaryWriter::writeElementSegments() { return; } - BYN_TRACE("== writeElementSegments\n"); auto start = startSection(BinaryConsts::Section::Element); o << U32LEB(elemCount); @@ -871,12 +844,10 @@ void WasmBinaryWriter::writeTags() { if (importInfo->getNumDefinedTags() == 0) { return; } - BYN_TRACE("== writeTags\n"); auto start = startSection(BinaryConsts::Section::Tag); auto num = importInfo->getNumDefinedTags(); o << U32LEB(num); ModuleUtils::iterDefinedTags(*wasm, [&](Tag* tag) { - BYN_TRACE("write one\n"); o << uint8_t(0); // Reserved 'attribute' field. Always 0. o << U32LEB(getTypeIndex(tag->sig)); }); @@ -885,7 +856,6 @@ void WasmBinaryWriter::writeTags() { } void WasmBinaryWriter::writeNames() { - BYN_TRACE("== writeNames\n"); auto start = startSection(BinaryConsts::Section::Custom); writeInlineString(BinaryConsts::CustomSections::Name); @@ -1198,7 +1168,6 @@ void WasmBinaryWriter::writeNames() { } void WasmBinaryWriter::writeSourceMapUrl() { - BYN_TRACE("== writeSourceMapUrl\n"); auto start = startSection(BinaryConsts::Section::Custom); writeInlineString(BinaryConsts::CustomSections::SourceMapUrl); writeInlineString(sourceMapUrl.c_str()); @@ -1880,7 +1849,6 @@ void WasmBinaryReader::read() { } void WasmBinaryReader::readCustomSection(size_t payloadLen) { - BYN_TRACE("== readCustomSection\n"); auto oldPos = pos; Name sectionName = getInlineString(); size_t read = pos - oldPos; @@ -1927,103 +1895,77 @@ uint8_t WasmBinaryReader::getInt8() { if (!more()) { throwError("unexpected end of input"); } - BYN_TRACE("getInt8: " << (int)(uint8_t)input[pos] << " (at " << pos << ")\n"); return input[pos++]; } uint16_t WasmBinaryReader::getInt16() { - BYN_TRACE("<==\n"); auto ret = uint16_t(getInt8()); ret |= uint16_t(getInt8()) << 8; - BYN_TRACE("getInt16: " << ret << "/0x" << std::hex << ret << std::dec - << " ==>\n"); return ret; } uint32_t WasmBinaryReader::getInt32() { - BYN_TRACE("<==\n"); auto ret = uint32_t(getInt16()); ret |= uint32_t(getInt16()) << 16; - BYN_TRACE("getInt32: " << ret << "/0x" << std::hex << ret << std::dec - << " ==>\n"); return ret; } uint64_t WasmBinaryReader::getInt64() { - BYN_TRACE("<==\n"); auto ret = uint64_t(getInt32()); ret |= uint64_t(getInt32()) << 32; - BYN_TRACE("getInt64: " << ret << "/0x" << std::hex << ret << std::dec - << " ==>\n"); return ret; } uint8_t WasmBinaryReader::getLaneIndex(size_t lanes) { - BYN_TRACE("<==\n"); auto ret = getInt8(); if (ret >= lanes) { throwError("Illegal lane index"); } - BYN_TRACE("getLaneIndex(" << lanes << "): " << ret << " ==>" << std::endl); return ret; } Literal WasmBinaryReader::getFloat32Literal() { - BYN_TRACE("<==\n"); auto ret = Literal(getInt32()); ret = ret.castToF32(); - BYN_TRACE("getFloat32: " << ret << " ==>\n"); return ret; } Literal WasmBinaryReader::getFloat64Literal() { - BYN_TRACE("<==\n"); auto ret = Literal(getInt64()); ret = ret.castToF64(); - BYN_TRACE("getFloat64: " << ret << " ==>\n"); return ret; } Literal WasmBinaryReader::getVec128Literal() { - BYN_TRACE("<==\n"); std::array bytes; for (auto i = 0; i < 16; ++i) { bytes[i] = getInt8(); } auto ret = Literal(bytes.data()); - BYN_TRACE("getVec128: " << ret << " ==>\n"); return ret; } uint32_t WasmBinaryReader::getU32LEB() { - BYN_TRACE("<==\n"); U32LEB ret; ret.read([&]() { return getInt8(); }); - BYN_TRACE("getU32LEB: " << ret.value << " ==>\n"); return ret.value; } uint64_t WasmBinaryReader::getU64LEB() { - BYN_TRACE("<==\n"); U64LEB ret; ret.read([&]() { return getInt8(); }); - BYN_TRACE("getU64LEB: " << ret.value << " ==>\n"); return ret.value; } int32_t WasmBinaryReader::getS32LEB() { - BYN_TRACE("<==\n"); S32LEB ret; ret.read([&]() { return (int8_t)getInt8(); }); - BYN_TRACE("getS32LEB: " << ret.value << " ==>\n"); return ret.value; } int64_t WasmBinaryReader::getS64LEB() { - BYN_TRACE("<==\n"); S64LEB ret; ret.read([&]() { return (int8_t)getInt8(); }); - BYN_TRACE("getS64LEB: " << ret.value << " ==>\n"); return ret.value; } @@ -2216,13 +2158,11 @@ Type WasmBinaryReader::getConcreteType() { } Name WasmBinaryReader::getInlineString(bool requireValid) { - BYN_TRACE("<==\n"); auto len = getU32LEB(); auto data = getByteView(len); if (requireValid && !String::isUTF8(data)) { throwError("invalid UTF-8 string"); } - BYN_TRACE("getInlineString: " << data << " ==>\n"); return Name(data); } @@ -2255,7 +2195,6 @@ void WasmBinaryReader::verifyInt64(int64_t x) { } void WasmBinaryReader::readHeader() { - BYN_TRACE("== readHeader\n"); verifyInt32(BinaryConsts::Magic); auto version = getInt32(); if (version != BinaryConsts::Version) { @@ -2268,21 +2207,15 @@ void WasmBinaryReader::readHeader() { } } -void WasmBinaryReader::readStart() { - BYN_TRACE("== readStart\n"); - startIndex = getU32LEB(); -} +void WasmBinaryReader::readStart() { startIndex = getU32LEB(); } static Name makeName(std::string prefix, size_t counter) { return Name(prefix + std::to_string(counter)); } void WasmBinaryReader::readMemories() { - BYN_TRACE("== readMemories\n"); auto num = getU32LEB(); - BYN_TRACE("num: " << num << std::endl); for (size_t i = 0; i < num; i++) { - BYN_TRACE("read one\n"); auto memory = Builder::makeMemory(makeName("", i)); getResizableLimits(memory->initial, memory->max, @@ -2294,9 +2227,7 @@ void WasmBinaryReader::readMemories() { } void WasmBinaryReader::readTypes() { - BYN_TRACE("== readTypes\n"); TypeBuilder builder(getU32LEB()); - BYN_TRACE("num: " << builder.size() << std::endl); auto readHeapType = [&]() -> HeapType { int64_t htCode = getS64LEB(); // TODO: Actually s33 @@ -2345,12 +2276,10 @@ void WasmBinaryReader::readTypes() { std::vector params; std::vector results; size_t numParams = getU32LEB(); - BYN_TRACE("num params: " << numParams << std::endl); for (size_t j = 0; j < numParams; j++) { params.push_back(readType()); } auto numResults = getU32LEB(); - BYN_TRACE("num results: " << numResults << std::endl); for (size_t j = 0; j < numResults; j++) { results.push_back(readType()); } @@ -2398,7 +2327,6 @@ void WasmBinaryReader::readTypes() { auto readStructDef = [&]() { FieldList fields; size_t numFields = getU32LEB(); - BYN_TRACE("num fields: " << numFields << std::endl); for (size_t j = 0; j < numFields; j++) { fields.push_back(readFieldDef()); } @@ -2406,7 +2334,6 @@ void WasmBinaryReader::readTypes() { }; for (size_t i = 0; i < builder.size(); i++) { - BYN_TRACE("read one\n"); auto form = getInt8(); if (form == BinaryConsts::EncodedType::Rec) { uint32_t groupSize = getU32LEB(); @@ -2558,9 +2485,7 @@ void WasmBinaryReader::getResizableLimits(Address& initial, } void WasmBinaryReader::readImports() { - BYN_TRACE("== readImports\n"); size_t num = getU32LEB(); - BYN_TRACE("num: " << num << std::endl); Builder builder(wasm); size_t tableCounter = 0; size_t memoryCounter = 0; @@ -2568,7 +2493,6 @@ void WasmBinaryReader::readImports() { size_t globalCounter = 0; size_t tagCounter = 0; for (size_t i = 0; i < num; i++) { - BYN_TRACE("read one\n"); auto module = getInlineString(); auto base = getInlineString(); auto kind = (ExternalKind)getU32LEB(); @@ -2668,11 +2592,8 @@ void WasmBinaryReader::requireFunctionContext(const char* error) { } void WasmBinaryReader::readFunctionSignatures() { - BYN_TRACE("== readFunctionSignatures\n"); size_t num = getU32LEB(); - BYN_TRACE("num: " << num << std::endl); for (size_t i = 0; i < num; i++) { - BYN_TRACE("read one\n"); auto index = getU32LEB(); functionTypes.push_back(getTypeByIndex(index)); // Check that the type is a signature. @@ -2712,14 +2633,12 @@ Signature WasmBinaryReader::getSignatureByFunctionIndex(Index index) { } void WasmBinaryReader::readFunctions() { - BYN_TRACE("== readFunctions\n"); auto numImports = wasm.functions.size(); size_t total = getU32LEB(); if (total != functionTypes.size() - numImports) { throwError("invalid function section size, must equal types"); } for (size_t i = 0; i < total; i++) { - BYN_TRACE("read one at " << pos << std::endl); auto sizePos = pos; size_t size = getU32LEB(); if (size == 0) { @@ -2741,14 +2660,11 @@ void WasmBinaryReader::readFunctions() { readNextDebugLocation(); - BYN_TRACE("reading " << i << std::endl); - readVars(); func->prologLocation = debugLocation; { // process the function body - BYN_TRACE("processing function: " << i << std::endl); nextLabel = 0; willBeIgnored = false; // process body @@ -2801,7 +2717,6 @@ void WasmBinaryReader::readFunctions() { debugLocation.clear(); wasm.addFunction(std::move(func)); } - BYN_TRACE(" end function bodies\n"); } void WasmBinaryReader::readVars() { @@ -2825,12 +2740,9 @@ void WasmBinaryReader::readVars() { } void WasmBinaryReader::readExports() { - BYN_TRACE("== readExports\n"); size_t num = getU32LEB(); - BYN_TRACE("num: " << num << std::endl); std::unordered_set names; for (size_t i = 0; i < num; i++) { - BYN_TRACE("read one\n"); auto curr = std::make_unique(); curr->name = getInlineString(); if (!names.emplace(curr->name).second) { @@ -3081,11 +2993,8 @@ void WasmBinaryReader::readStrings() { } void WasmBinaryReader::readGlobals() { - BYN_TRACE("== readGlobals\n"); size_t num = getU32LEB(); - BYN_TRACE("num: " << num << std::endl); for (size_t i = 0; i < num; i++) { - BYN_TRACE("read one\n"); auto type = getConcreteType(); auto mutable_ = getU32LEB(); if (mutable_ & ~1) { @@ -3101,14 +3010,12 @@ void WasmBinaryReader::readGlobals() { } void WasmBinaryReader::processExpressions() { - BYN_TRACE("== processExpressions\n"); unreachableInTheWasmSense = false; while (1) { Expression* curr; auto ret = readExpression(curr); if (!curr) { lastSeparator = ret; - BYN_TRACE("== processExpressions finished\n"); return; } pushExpression(curr); @@ -3129,8 +3036,6 @@ void WasmBinaryReader::processExpressions() { peek == BinaryConsts::Catch_Legacy || peek == BinaryConsts::CatchAll_Legacy || peek == BinaryConsts::Delegate) { - BYN_TRACE("== processExpressions finished with unreachable" - << std::endl); lastSeparator = BinaryConsts::ASTNodes(peek); // Read the byte we peeked at. No new instruction is generated for it. Expression* dummy = nullptr; @@ -3146,7 +3051,6 @@ void WasmBinaryReader::processExpressions() { } void WasmBinaryReader::skipUnreachableCode() { - BYN_TRACE("== skipUnreachableCode\n"); // preserve the stack, and restore it. it contains the instruction that made // us unreachable, and we can ignore anything after it. things after it may // pop, we want to undo that @@ -3166,7 +3070,6 @@ void WasmBinaryReader::skipUnreachableCode() { Expression* curr; auto ret = readExpression(curr); if (!curr) { - BYN_TRACE("== skipUnreachableCode finished\n"); lastSeparator = ret; unreachableInTheWasmSense = false; willBeIgnored = before; @@ -3202,12 +3105,10 @@ void WasmBinaryReader::pushExpression(Expression* curr) { } Expression* WasmBinaryReader::popExpression() { - BYN_TRACE("== popExpression\n"); if (expressionStack.empty()) { if (unreachableInTheWasmSense) { // in unreachable code, trying to pop past the polymorphic stack // area results in receiving unreachables - BYN_TRACE("== popping unreachable from polymorphic stack" << std::endl); return allocator.alloc(); } throwError( @@ -3370,13 +3271,11 @@ void WasmBinaryReader::processNames() { } void WasmBinaryReader::readDataSegmentCount() { - BYN_TRACE("== readDataSegmentCount\n"); hasDataCount = true; dataCount = getU32LEB(); } void WasmBinaryReader::readDataSegments() { - BYN_TRACE("== readDataSegments\n"); auto num = getU32LEB(); for (size_t i = 0; i < num; i++) { auto curr = Builder::makeDataSegment(); @@ -3406,7 +3305,6 @@ void WasmBinaryReader::readDataSegments() { } void WasmBinaryReader::readTableDeclarations() { - BYN_TRACE("== readTableDeclarations\n"); auto numTables = getU32LEB(); for (size_t i = 0; i < numTables; i++) { @@ -3429,7 +3327,6 @@ void WasmBinaryReader::readTableDeclarations() { } void WasmBinaryReader::readElementSegments() { - BYN_TRACE("== readElementSegments\n"); auto numSegments = getU32LEB(); if (numSegments >= Table::kMaxSize) { throwError("Too many segments"); @@ -3507,11 +3404,8 @@ void WasmBinaryReader::readElementSegments() { } void WasmBinaryReader::readTags() { - BYN_TRACE("== readTags\n"); size_t numTags = getU32LEB(); - BYN_TRACE("num: " << numTags << std::endl); for (size_t i = 0; i < numTags; i++) { - BYN_TRACE("read one\n"); getInt8(); // Reserved 'attribute' field auto typeIndex = getU32LEB(); wasm.addTag(Builder::makeTag(makeName("tag$", i), @@ -3609,7 +3503,6 @@ class NameProcessor { } // anonymous namespace void WasmBinaryReader::readNames(size_t payloadLen) { - BYN_TRACE("== readNames\n"); auto sectionPos = pos; uint32_t lastType = 0; while (pos < sectionPos + payloadLen) { @@ -3943,7 +3836,6 @@ void WasmBinaryReader::readDylink(size_t payloadLen) { } void WasmBinaryReader::readDylink0(size_t payloadLen) { - BYN_TRACE("== readDylink0\n"); auto sectionPos = pos; uint32_t lastType = 0; @@ -3988,7 +3880,6 @@ BinaryConsts::ASTNodes WasmBinaryReader::readExpression(Expression*& curr) { if (pos == endOfFunction) { throwError("Reached function end without seeing End opcode"); } - BYN_TRACE("zz recurse into " << ++depth << " at " << pos << std::endl); readNextDebugLocation(); std::set currDebugLocation; if (debugLocation.size()) { @@ -3996,7 +3887,6 @@ BinaryConsts::ASTNodes WasmBinaryReader::readExpression(Expression*& curr) { } size_t startPos = pos; uint8_t code = getInt8(); - BYN_TRACE("readExpression seeing " << (int)code << std::endl); switch (code) { case BinaryConsts::Block: visitBlock((curr = allocator.alloc())->cast()); @@ -4410,7 +4300,6 @@ BinaryConsts::ASTNodes WasmBinaryReader::readExpression(Expression*& curr) { BinaryLocation(pos - codeSectionLocation)}; } } - BYN_TRACE("zz recurse from " << depth-- << " at " << pos << std::endl); return BinaryConsts::ASTNodes(code); } @@ -4459,7 +4348,6 @@ void WasmBinaryReader::pushBlockElements(Block* curr, Type type, size_t start) { } void WasmBinaryReader::visitBlock(Block* curr) { - BYN_TRACE("zz node: Block\n"); startControlFlow(curr); // special-case Block and de-recurse nested blocks in their first position, as // that is a common pattern that can be very highly nested. @@ -4540,7 +4428,6 @@ Expression* WasmBinaryReader::getBlockOrSingleton(Type type) { } void WasmBinaryReader::visitIf(If* curr) { - BYN_TRACE("zz node: If\n"); startControlFlow(curr); curr->type = getType(); curr->condition = popNonVoidExpression(); @@ -4555,7 +4442,6 @@ void WasmBinaryReader::visitIf(If* curr) { } void WasmBinaryReader::visitLoop(Loop* curr) { - BYN_TRACE("zz node: Loop\n"); startControlFlow(curr); curr->type = getType(); curr->name = getNextLabel(); @@ -4585,7 +4471,6 @@ void WasmBinaryReader::visitLoop(Loop* curr) { } WasmBinaryReader::BreakTarget WasmBinaryReader::getBreakTarget(int32_t offset) { - BYN_TRACE("getBreakTarget " << offset << std::endl); if (breakStack.size() < 1 + size_t(offset)) { throwError("bad breakindex (low)"); } @@ -4593,8 +4478,6 @@ WasmBinaryReader::BreakTarget WasmBinaryReader::getBreakTarget(int32_t offset) { if (index >= breakStack.size()) { throwError("bad breakindex (high)"); } - BYN_TRACE("breaktarget " << breakStack[index].name << " type " - << breakStack[index].type << std::endl); auto& ret = breakStack[index]; // if the break is in literally unreachable code, then we will not emit it // anyhow, so do not note that the target has breaks to it @@ -4605,7 +4488,6 @@ WasmBinaryReader::BreakTarget WasmBinaryReader::getBreakTarget(int32_t offset) { } Name WasmBinaryReader::getExceptionTargetName(int32_t offset) { - BYN_TRACE("getExceptionTarget " << offset << std::endl); // We always start parsing a function by creating a block label and pushing it // in breakStack in getBlockOrSingleton, so if a 'delegate''s target is that // block, it does not mean it targets that block; it throws to the caller. @@ -4616,7 +4498,6 @@ Name WasmBinaryReader::getExceptionTargetName(int32_t offset) { if (index > breakStack.size()) { throwError("bad try index (high)"); } - BYN_TRACE("exception target " << breakStack[index].name << std::endl); auto& ret = breakStack[index]; // if the delegate/rethrow is in literally unreachable code, then we will not // emit it anyhow, so do not note that the target has a reference to it @@ -4627,7 +4508,6 @@ Name WasmBinaryReader::getExceptionTargetName(int32_t offset) { } void WasmBinaryReader::visitBreak(Break* curr, uint8_t code) { - BYN_TRACE("zz node: Break, code " << int32_t(code) << std::endl); BreakTarget target = getBreakTarget(getU32LEB()); curr->name = target.name; if (code == BinaryConsts::BrIf) { @@ -4640,16 +4520,13 @@ void WasmBinaryReader::visitBreak(Break* curr, uint8_t code) { } void WasmBinaryReader::visitSwitch(Switch* curr) { - BYN_TRACE("zz node: Switch\n"); curr->condition = popNonVoidExpression(); auto numTargets = getU32LEB(); - BYN_TRACE("targets: " << numTargets << std::endl); for (size_t i = 0; i < numTargets; i++) { curr->targets.push_back(getBreakTarget(getU32LEB()).name); } auto defaultTarget = getBreakTarget(getU32LEB()); curr->default_ = defaultTarget.name; - BYN_TRACE("default: " << curr->default_ << "\n"); if (defaultTarget.type.isConcrete()) { curr->value = popTypedExpression(defaultTarget.type); } @@ -4657,7 +4534,6 @@ void WasmBinaryReader::visitSwitch(Switch* curr) { } void WasmBinaryReader::visitCall(Call* curr) { - BYN_TRACE("zz node: Call\n"); auto index = getU32LEB(); auto sig = getSignatureByFunctionIndex(index); auto num = sig.params.size(); @@ -4672,7 +4548,6 @@ void WasmBinaryReader::visitCall(Call* curr) { } void WasmBinaryReader::visitCallIndirect(CallIndirect* curr) { - BYN_TRACE("zz node: CallIndirect\n"); auto index = getU32LEB(); curr->heapType = getTypeByIndex(index); Index tableIdx = getU32LEB(); @@ -4689,7 +4564,6 @@ void WasmBinaryReader::visitCallIndirect(CallIndirect* curr) { } void WasmBinaryReader::visitLocalGet(LocalGet* curr) { - BYN_TRACE("zz node: LocalGet " << pos << std::endl); requireFunctionContext("local.get"); curr->index = getU32LEB(); if (curr->index >= currFunction->getNumLocals()) { @@ -4700,7 +4574,6 @@ void WasmBinaryReader::visitLocalGet(LocalGet* curr) { } void WasmBinaryReader::visitLocalSet(LocalSet* curr, uint8_t code) { - BYN_TRACE("zz node: Set|LocalTee\n"); requireFunctionContext("local.set outside of function"); curr->index = getU32LEB(); if (curr->index >= currFunction->getNumLocals()) { @@ -4716,7 +4589,6 @@ void WasmBinaryReader::visitLocalSet(LocalSet* curr, uint8_t code) { } void WasmBinaryReader::visitGlobalGet(GlobalGet* curr) { - BYN_TRACE("zz node: GlobalGet " << pos << std::endl); auto index = getU32LEB(); if (index >= wasm.globals.size()) { throwError("invalid global index"); @@ -4728,7 +4600,6 @@ void WasmBinaryReader::visitGlobalGet(GlobalGet* curr) { } void WasmBinaryReader::visitGlobalSet(GlobalSet* curr) { - BYN_TRACE("zz node: GlobalSet\n"); auto index = getU32LEB(); if (index >= wasm.globals.size()) { throwError("invalid global index"); @@ -4773,9 +4644,7 @@ bool WasmBinaryReader::maybeVisitLoad( uint8_t code, std::optional prefix) { Load* curr; - auto allocate = [&]() { - curr = allocator.alloc(); - }; + auto allocate = [&]() { curr = allocator.alloc(); }; if (!prefix) { switch (code) { case BinaryConsts::I32LoadMem8S: @@ -4856,7 +4725,6 @@ bool WasmBinaryReader::maybeVisitLoad( default: return false; } - BYN_TRACE("zz node: Load\n"); } else if (prefix == BinaryConsts::AtomicPrefix) { switch (code) { case BinaryConsts::I32AtomicLoad8U: @@ -4897,7 +4765,6 @@ bool WasmBinaryReader::maybeVisitLoad( default: return false; } - BYN_TRACE("zz node: AtomicLoad\n"); } else if (prefix == BinaryConsts::MiscPrefix) { switch (code) { case BinaryConsts::F32_F16LoadMem: @@ -4908,7 +4775,6 @@ bool WasmBinaryReader::maybeVisitLoad( default: return false; } - BYN_TRACE("zz node: Load\n"); } else { return false; } @@ -5032,7 +4898,6 @@ bool WasmBinaryReader::maybeVisitStore( } curr->isAtomic = prefix == BinaryConsts::AtomicPrefix; - BYN_TRACE("zz node: Store\n"); Index memIdx = readMemoryAccess(curr->align, curr->offset); memoryRefs[memIdx].push_back(&curr->memory); curr->value = popNonVoidExpression(); @@ -5092,7 +4957,6 @@ bool WasmBinaryReader::maybeVisitAtomicRMW(Expression*& out, uint8_t code) { #undef SET_FOR_OP #undef SET - BYN_TRACE("zz node: AtomicRMW\n"); Address readAlign; Index memIdx = readMemoryAccess(readAlign, curr->offset); memoryRefs[memIdx].push_back(&curr->memory); @@ -5144,7 +5008,6 @@ bool WasmBinaryReader::maybeVisitAtomicCmpxchg(Expression*& out, uint8_t code) { WASM_UNREACHABLE("unexpected opcode"); } - BYN_TRACE("zz node: AtomicCmpxchg\n"); Address readAlign; Index memIdx = readMemoryAccess(readAlign, curr->offset); memoryRefs[memIdx].push_back(&curr->memory); @@ -5177,7 +5040,6 @@ bool WasmBinaryReader::maybeVisitAtomicWait(Expression*& out, uint8_t code) { WASM_UNREACHABLE("unexpected opcode"); } curr->type = Type::i32; - BYN_TRACE("zz node: AtomicWait\n"); curr->timeout = popNonVoidExpression(); curr->expected = popNonVoidExpression(); curr->ptr = popNonVoidExpression(); @@ -5197,7 +5059,6 @@ bool WasmBinaryReader::maybeVisitAtomicNotify(Expression*& out, uint8_t code) { return false; } auto* curr = allocator.alloc(); - BYN_TRACE("zz node: AtomicNotify\n"); curr->type = Type::i32; curr->notifyCount = popNonVoidExpression(); @@ -5218,7 +5079,6 @@ bool WasmBinaryReader::maybeVisitAtomicFence(Expression*& out, uint8_t code) { return false; } auto* curr = allocator.alloc(); - BYN_TRACE("zz node: AtomicFence\n"); curr->order = getU32LEB(); curr->finalize(); out = curr; @@ -5227,7 +5087,6 @@ bool WasmBinaryReader::maybeVisitAtomicFence(Expression*& out, uint8_t code) { bool WasmBinaryReader::maybeVisitConst(Expression*& out, uint8_t code) { Const* curr; - BYN_TRACE("zz node: Const, code " << code << std::endl); switch (code) { case BinaryConsts::I32Const: curr = allocator.alloc(); @@ -5474,7 +5333,6 @@ bool WasmBinaryReader::maybeVisitUnary(Expression*& out, uint8_t code) { default: return false; } - BYN_TRACE("zz node: Unary\n"); curr->value = popNonVoidExpression(); curr->finalize(); out = curr; @@ -5519,7 +5377,6 @@ bool WasmBinaryReader::maybeVisitTruncSat(Expression*& out, uint32_t code) { default: return false; } - BYN_TRACE("zz node: Unary (nontrapping float-to-int)\n"); curr->value = popNonVoidExpression(); curr->finalize(); out = curr; @@ -5750,7 +5607,6 @@ bool WasmBinaryReader::maybeVisitBinary(Expression*& out, uint8_t code) { default: return false; } - BYN_TRACE("zz node: Binary\n"); curr->right = popNonVoidExpression(); curr->left = popNonVoidExpression(); curr->finalize(); @@ -6331,7 +6187,6 @@ bool WasmBinaryReader::maybeVisitSIMDBinary(Expression*& out, uint32_t code) { default: return false; } - BYN_TRACE("zz node: Binary\n"); curr->right = popNonVoidExpression(); curr->left = popNonVoidExpression(); curr->finalize(); @@ -7056,7 +6911,6 @@ bool WasmBinaryReader::maybeVisitSIMDLoadStoreLane(Expression*& out, } void WasmBinaryReader::visitSelect(Select* curr, uint8_t code) { - BYN_TRACE("zz node: Select, code " << int32_t(code) << std::endl); if (code == BinaryConsts::SelectWithType) { size_t numTypes = getU32LEB(); std::vector types; @@ -7080,7 +6934,6 @@ void WasmBinaryReader::visitSelect(Select* curr, uint8_t code) { } void WasmBinaryReader::visitReturn(Return* curr) { - BYN_TRACE("zz node: Return\n"); requireFunctionContext("return"); Type type = currFunction->getResults(); if (type.isConcrete()) { @@ -7090,7 +6943,6 @@ void WasmBinaryReader::visitReturn(Return* curr) { } void WasmBinaryReader::visitMemorySize(MemorySize* curr) { - BYN_TRACE("zz node: MemorySize\n"); Index index = getU32LEB(); if (getMemory(index)->is64()) { curr->type = Type::i64; @@ -7100,7 +6952,6 @@ void WasmBinaryReader::visitMemorySize(MemorySize* curr) { } void WasmBinaryReader::visitMemoryGrow(MemoryGrow* curr) { - BYN_TRACE("zz node: MemoryGrow\n"); curr->delta = popNonVoidExpression(); Index index = getU32LEB(); if (getMemory(index)->is64()) { @@ -7109,31 +6960,25 @@ void WasmBinaryReader::visitMemoryGrow(MemoryGrow* curr) { memoryRefs[index].push_back(&curr->memory); } -void WasmBinaryReader::visitNop(Nop* curr) { BYN_TRACE("zz node: Nop\n"); } +void WasmBinaryReader::visitNop(Nop* curr) {} -void WasmBinaryReader::visitUnreachable(Unreachable* curr) { - BYN_TRACE("zz node: Unreachable\n"); -} +void WasmBinaryReader::visitUnreachable(Unreachable* curr) {} void WasmBinaryReader::visitDrop(Drop* curr) { - BYN_TRACE("zz node: Drop\n"); curr->value = popNonVoidExpression(); curr->finalize(); } void WasmBinaryReader::visitRefNull(RefNull* curr) { - BYN_TRACE("zz node: RefNull\n"); curr->finalize(getHeapType().getBottom()); } void WasmBinaryReader::visitRefIsNull(RefIsNull* curr) { - BYN_TRACE("zz node: RefIsNull\n"); curr->value = popNonVoidExpression(); curr->finalize(); } void WasmBinaryReader::visitRefFunc(RefFunc* curr) { - BYN_TRACE("zz node: RefFunc\n"); Index index = getU32LEB(); // We don't know function names yet, so record this use to be updated later. // Note that we do not need to check that 'index' is in bounds, as that will @@ -7147,14 +6992,12 @@ void WasmBinaryReader::visitRefFunc(RefFunc* curr) { } void WasmBinaryReader::visitRefEq(RefEq* curr) { - BYN_TRACE("zz node: RefEq\n"); curr->right = popNonVoidExpression(); curr->left = popNonVoidExpression(); curr->finalize(); } void WasmBinaryReader::visitTableGet(TableGet* curr) { - BYN_TRACE("zz node: TableGet\n"); Index tableIdx = getU32LEB(); if (tableIdx >= wasm.tables.size()) { throwError("bad table index"); @@ -7167,7 +7010,6 @@ void WasmBinaryReader::visitTableGet(TableGet* curr) { } void WasmBinaryReader::visitTableSet(TableSet* curr) { - BYN_TRACE("zz node: TableSet\n"); Index tableIdx = getU32LEB(); if (tableIdx >= wasm.tables.size()) { throwError("bad table index"); @@ -7180,7 +7022,6 @@ void WasmBinaryReader::visitTableSet(TableSet* curr) { } void WasmBinaryReader::visitTryOrTryInBlock(Expression*& out) { - BYN_TRACE("zz node: Try\n"); auto* curr = allocator.alloc(); startControlFlow(curr); // For simplicity of implementation, like if scopes, we create a hidden block @@ -7333,7 +7174,6 @@ void WasmBinaryReader::visitTryOrTryInBlock(Expression*& out) { } void WasmBinaryReader::visitTryTable(TryTable* curr) { - BYN_TRACE("zz node: TryTable\n"); // For simplicity of implementation, like if scopes, we create a hidden block // within each try-body, and let branches target those inner blocks instead. @@ -7378,7 +7218,6 @@ void WasmBinaryReader::visitTryTable(TryTable* curr) { } void WasmBinaryReader::visitThrow(Throw* curr) { - BYN_TRACE("zz node: Throw\n"); auto index = getU32LEB(); if (index >= wasm.tags.size()) { throwError("bad tag index"); @@ -7395,7 +7234,6 @@ void WasmBinaryReader::visitThrow(Throw* curr) { } void WasmBinaryReader::visitRethrow(Rethrow* curr) { - BYN_TRACE("zz node: Rethrow\n"); curr->target = getExceptionTargetName(getU32LEB()); // This special target is valid only for delegates if (curr->target == DELEGATE_CALLER_TARGET) { @@ -7406,13 +7244,11 @@ void WasmBinaryReader::visitRethrow(Rethrow* curr) { } void WasmBinaryReader::visitThrowRef(ThrowRef* curr) { - BYN_TRACE("zz node: ThrowRef\n"); curr->exnref = popNonVoidExpression(); curr->finalize(); } void WasmBinaryReader::visitCallRef(CallRef* curr) { - BYN_TRACE("zz node: CallRef\n"); curr->target = popNonVoidExpression(); HeapType heapType = getTypeByIndex(getU32LEB()); if (!Type::isSubType(curr->target->type, Type(heapType, Nullable))) { @@ -7922,7 +7758,6 @@ bool WasmBinaryReader::maybeVisitStringSliceWTF(Expression*& out, } void WasmBinaryReader::visitRefAs(RefAs* curr, uint8_t code) { - BYN_TRACE("zz node: RefAs\n"); switch (code) { case BinaryConsts::RefAsNonNull: curr->op = RefAsNonNull; @@ -7944,7 +7779,6 @@ void WasmBinaryReader::visitRefAs(RefAs* curr, uint8_t code) { } void WasmBinaryReader::visitContBind(ContBind* curr) { - BYN_TRACE("zz node: ContBind\n"); auto contTypeBeforeIndex = getU32LEB(); curr->contTypeBefore = getTypeByIndex(contTypeBeforeIndex); @@ -7981,7 +7815,6 @@ void WasmBinaryReader::visitContBind(ContBind* curr) { } void WasmBinaryReader::visitContNew(ContNew* curr) { - BYN_TRACE("zz node: ContNew\n"); auto contTypeIndex = getU32LEB(); curr->contType = getTypeByIndex(contTypeIndex); @@ -7995,7 +7828,6 @@ void WasmBinaryReader::visitContNew(ContNew* curr) { } void WasmBinaryReader::visitResume(Resume* curr) { - BYN_TRACE("zz node: Resume\n"); auto contTypeIndex = getU32LEB(); curr->contType = getTypeByIndex(contTypeIndex); @@ -8012,9 +7844,7 @@ void WasmBinaryReader::visitResume(Resume* curr) { curr->handlerTags.resize(numHandlers); curr->handlerBlocks.resize(numHandlers); - BYN_TRACE("handler num: " << numHandlers << std::endl); for (size_t i = 0; i < numHandlers; i++) { - BYN_TRACE("read one tag handler pair \n"); auto tagIndex = getU32LEB(); auto tag = getTagName(tagIndex); @@ -8041,7 +7871,6 @@ void WasmBinaryReader::visitResume(Resume* curr) { } void WasmBinaryReader::visitSuspend(Suspend* curr) { - BYN_TRACE("zz node: Suspend\n"); auto tagIndex = getU32LEB(); if (tagIndex >= wasm.tags.size()) { From 1a2d26f4092897f88f8fc60fc7a4dee2083ae531 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 10 Sep 2024 15:24:16 -0700 Subject: [PATCH 018/622] Replace the old topological sort everywhere (#6902) To avoid having two separate topological sort utilities in the code base, replace remaining uses of the old DFS-based, CRTP topological sort with the newer Kahn's algorithm implementation. This would be NFC, except that the new topological sort produces a different order than the old topological sort, so the output of some passes is reordered. --- src/ir/subtypes.h | 36 ++-- src/ir/type-updating.cpp | 170 ++++++--------- src/passes/GlobalTypeOptimization.cpp | 4 +- src/passes/TypeMerging.cpp | 33 ++- src/tools/wasm-ctor-eval.cpp | 36 +--- src/wasm-type-ordering.h | 66 ++---- test/ctor-eval/gc-2.wast.out | 10 +- test/lit/ctor-eval/gc-cycle-multi.wast | 27 ++- test/lit/ctor-eval/gc-cycle.wast | 252 +++++++++++----------- test/lit/passes/gto-mutability.wast | 8 +- test/lit/passes/signature-pruning.wast | 6 +- test/lit/passes/signature-refining.wast | 8 +- test/lit/passes/type-merging.wast | 64 +++--- test/lit/passes/type-ssa_and_merging.wast | 10 +- 14 files changed, 311 insertions(+), 419 deletions(-) diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index 47ee51ac3a0..c69250043b8 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -18,7 +18,7 @@ #define wasm_ir_subtypes_h #include "ir/module-utils.h" -#include "support/old_topological_sort.h" +#include "support/topological_sort.h" #include "wasm.h" namespace wasm { @@ -79,29 +79,19 @@ struct SubTypes { } // A topological sort that visits subtypes first. - auto getSubTypesFirstSort() const { - struct SubTypesFirstSort : OldTopologicalSort { - const SubTypes& parent; - - SubTypesFirstSort(const SubTypes& parent) : parent(parent) { - for (auto type : parent.types) { - // The roots are types with no supertype. - if (!type.getDeclaredSuperType()) { - push(type); - } - } - } - - void pushPredecessors(HeapType type) { - // Things we need to process before each type are its subtypes. Once we - // know their depth, we can easily compute our own. - for (auto pred : parent.getImmediateSubTypes(type)) { - push(pred); - } + std::vector getSubTypesFirstSort() const { + std::vector>> graph; + graph.reserve(types.size()); + for (auto type : types) { + if (auto it = typeSubTypes.find(type); it != typeSubTypes.end()) { + graph.emplace_back(*it); + } else { + graph.emplace_back(type, std::vector{}); } - }; - - return SubTypesFirstSort(*this); + } + auto sorted = TopologicalSort::sortOf(graph.begin(), graph.end()); + std::reverse(sorted.begin(), sorted.end()); + return sorted; } // Computes the depth of children for each type. This is 0 if the type has no diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 467971989f0..dedbb631697 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -40,118 +40,72 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( // world scenario, don't modify public types because we assume that they may // be reflected on or used for linking. Figure out where each private type // will be located in the builder. - // - // There are two code paths here: a new one that is used when there are type - // indices to preserve and an old one that is used otherwise. The old code - // path is kept around to avoid unnecessary changes to test outputs while we - // incrementally add --preserve-type-order to tests that could benefit from - // it. Once we are done adding --preserve-type-order to tests, we should - // remove the old code path here since the new code path is strictly better. - if (wasm.typeIndices.size()) { - // New code path, currently used only with --preserve-type-order. - auto typeInfo = ModuleUtils::collectHeapTypeInfo( - wasm, - ModuleUtils::TypeInclusion::UsedIRTypes, - ModuleUtils::VisibilityHandling::FindVisibility); - - std::unordered_set additionalSet(additionalPrivateTypes.begin(), - additionalPrivateTypes.end()); - - std::vector>> - privateSupertypes; - privateSupertypes.reserve(typeInfo.size()); - for (auto& [type, info] : typeInfo) { - if (info.visibility != ModuleUtils::Visibility::Private && - !additionalSet.count(type)) { - continue; - } - privateSupertypes.push_back({type, {}}); - - if (auto super = getDeclaredSuperType(type)) { - auto it = typeInfo.find(*super); - // Record the supertype only if it is among the private types. - if ((it != typeInfo.end() && - it->second.visibility == ModuleUtils::Visibility::Private) || - additionalSet.count(*super)) { - privateSupertypes.back().second.push_back(*super); - } - } - } - - // Topological sort to have subtypes first. This is the opposite of the - // order we need, so the comparison is the opposite of what we ultimately - // want. - std::vector sorted; - if (wasm.typeIndices.empty()) { - sorted = TopologicalSort::sortOf(privateSupertypes.begin(), - privateSupertypes.end()); - } else { - sorted = - TopologicalSort::minSortOf(privateSupertypes.begin(), - privateSupertypes.end(), - [&](Index a, Index b) { - auto typeA = privateSupertypes[a].first; - auto typeB = privateSupertypes[b].first; - // Preserve type order. - auto itA = wasm.typeIndices.find(typeA); - auto itB = wasm.typeIndices.find(typeB); - bool hasA = itA != wasm.typeIndices.end(); - bool hasB = itB != wasm.typeIndices.end(); - if (hasA != hasB) { - // Types with preserved indices must be - // sorted before (after in this reversed - // comparison) types without indices to - // maintain transitivity. - return !hasA; - } - if (hasA && *itA != *itB) { - return !(itA->second < itB->second); - } - // Break ties by the arbitrary order we - // have collected the types in. - return a > b; - }); - } - std::reverse(sorted.begin(), sorted.end()); - Index i = 0; - for (auto type : sorted) { - typeIndices[type] = i++; + auto typeInfo = ModuleUtils::collectHeapTypeInfo( + wasm, + ModuleUtils::TypeInclusion::UsedIRTypes, + ModuleUtils::VisibilityHandling::FindVisibility); + + std::unordered_set additionalSet(additionalPrivateTypes.begin(), + additionalPrivateTypes.end()); + + std::vector>> privateSupertypes; + privateSupertypes.reserve(typeInfo.size()); + for (auto& [type, info] : typeInfo) { + if (info.visibility != ModuleUtils::Visibility::Private && + !additionalSet.count(type)) { + continue; } - } else { - // Old code path. - - auto privateTypes = ModuleUtils::getPrivateHeapTypes(wasm); - - if (!additionalPrivateTypes.empty()) { - // Only add additional private types that are not already in the list. - std::unordered_set privateTypesSet(privateTypes.begin(), - privateTypes.end()); + privateSupertypes.push_back({type, {}}); - for (auto t : additionalPrivateTypes) { - if (!privateTypesSet.count(t)) { - privateTypes.push_back(t); - privateTypesSet.insert(t); - } + if (auto super = getDeclaredSuperType(type)) { + auto it = typeInfo.find(*super); + // Record the supertype only if it is among the private types. + if ((it != typeInfo.end() && + it->second.visibility == ModuleUtils::Visibility::Private) || + additionalSet.count(*super)) { + privateSupertypes.back().second.push_back(*super); } } + } - // Topological sort to have supertypes first, but we have to account for the - // fact that we may be replacing the supertypes to get the order correct. - struct SupertypesFirst - : HeapTypeOrdering::SupertypesFirstBase { - GlobalTypeRewriter& parent; - - SupertypesFirst(GlobalTypeRewriter& parent) : parent(parent) {} - std::optional getDeclaredSuperType(HeapType type) { - return parent.getDeclaredSuperType(type); - } - }; - - SupertypesFirst sortedTypes(*this); - Index i = 0; - for (auto type : sortedTypes.sort(privateTypes)) { - typeIndices[type] = i++; - } + // Topological sort to have subtypes first. This is the opposite of the + // order we need, so the comparison is the opposite of what we ultimately + // want. + std::vector sorted; + if (wasm.typeIndices.empty()) { + sorted = TopologicalSort::sortOf(privateSupertypes.begin(), + privateSupertypes.end()); + } else { + sorted = + TopologicalSort::minSortOf(privateSupertypes.begin(), + privateSupertypes.end(), + [&](Index a, Index b) { + auto typeA = privateSupertypes[a].first; + auto typeB = privateSupertypes[b].first; + // Preserve type order. + auto itA = wasm.typeIndices.find(typeA); + auto itB = wasm.typeIndices.find(typeB); + bool hasA = itA != wasm.typeIndices.end(); + bool hasB = itB != wasm.typeIndices.end(); + if (hasA != hasB) { + // Types with preserved indices must be + // sorted before (after in this reversed + // comparison) types without indices to + // maintain transitivity. + return !hasA; + } + if (hasA && *itA != *itB) { + return !(itA->second < itB->second); + } + // Break ties by the arbitrary order we + // have collected the types in. + return a > b; + }); + } + std::reverse(sorted.begin(), sorted.end()); + Index i = 0; + for (auto type : sorted) { + typeIndices[type] = i++; } if (typeIndices.size() == 0) { @@ -168,7 +122,7 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( typeBuilder.createRecGroup(0, typeBuilder.size()); // Create the temporary heap types. - Index i = 0; + i = 0; auto map = [&](HeapType type) -> HeapType { if (auto it = typeIndices.find(type); it != typeIndices.end()) { return typeBuilder[it->second]; diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index eec8e206d7d..dac6fd7b659 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -171,8 +171,8 @@ struct GlobalTypeOptimization : public Pass { // fields in a supertype is a constraint on what subtypes can do. That is, // we decide for each supertype what the optimal order is, and consider that // fixed, and then subtypes can decide how to sort fields that they append. - HeapTypeOrdering::SupertypesFirst sorted; - for (auto type : sorted.sort(propagator.subTypes.types)) { + for (auto type : + HeapTypeOrdering::supertypesFirst(propagator.subTypes.types)) { if (!type.isStruct()) { continue; } diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 1cac7732f4e..206addd2fed 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -142,6 +142,17 @@ struct TypeMerging : public Pass { return type; } + std::vector + mergeableSupertypesFirst(const std::vector& types) { + return HeapTypeOrdering::supertypesFirst( + types, [&](HeapType type) -> std::optional { + if (auto super = type.getDeclaredSuperType()) { + return getMerged(*super); + } + return std::nullopt; + }); + } + void run(Module* module_) override; // We will do two different kinds of merging: First, we will merge types into @@ -163,20 +174,6 @@ struct TypeMerging : public Pass { void applyMerges(); }; -struct MergeableSupertypesFirst - : HeapTypeOrdering::SupertypesFirstBase { - TypeMerging& merging; - - MergeableSupertypesFirst(TypeMerging& merging) : merging(merging) {} - - std::optional getDeclaredSuperType(HeapType type) { - if (auto super = type.getDeclaredSuperType()) { - return merging.getMerged(*super); - } - return std::nullopt; - } -}; - // Hash and equality-compare HeapTypes based on their top-level structure (i.e. // "shape"), ignoring nontrivial heap type children that will not be // differentiated between until we run the DFA partition refinement. @@ -305,8 +302,7 @@ bool TypeMerging::merge(MergeKind kind) { // For each type, either create a new partition or add to its supertype's // partition. - MergeableSupertypesFirst sortedTypes(*this); - for (auto type : sortedTypes.sort(mergeable)) { + for (auto type : mergeableSupertypesFirst(mergeable)) { // We need partitions for any public children of this type since those // children will participate in the DFA we're creating. for (auto child : getPublicChildren(type)) { @@ -414,7 +410,7 @@ bool TypeMerging::merge(MergeKind kind) { std::vector newMergeable; bool merged = false; for (const auto& partition : refinedPartitions) { - auto target = *MergeableSupertypesFirst(*this).sort(partition).begin(); + auto target = mergeableSupertypesFirst(partition).front(); newMergeable.push_back(target); for (auto type : partition) { if (type != target) { @@ -452,8 +448,7 @@ TypeMerging::splitSupertypePartition(const std::vector& types) { std::unordered_set includedTypes(types.begin(), types.end()); std::vector> partitions; std::unordered_map partitionIndices; - MergeableSupertypesFirst sortedTypes(*this); - for (auto type : sortedTypes.sort(types)) { + for (auto type : mergeableSupertypesFirst(types)) { auto super = type.getDeclaredSuperType(); if (super && includedTypes.count(*super)) { // We must already have a partition for the supertype we can add to. diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index d74790b2ab0..464654d4f07 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -36,9 +36,9 @@ #include "support/colors.h" #include "support/file.h" #include "support/insert_ordered.h" -#include "support/old_topological_sort.h" #include "support/small_set.h" #include "support/string.h" +#include "support/topological_sort.h" #include "tool-options.h" #include "wasm-builder.h" #include "wasm-interpreter.h" @@ -609,9 +609,9 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { Builder builder(*wasm); // First, find what constraints we have on the ordering of the globals. We - // will build up a map of each global to the globals it must be after. - using MustBeAfter = InsertOrderedMap>; - MustBeAfter mustBeAfter; + // will build up a map of each global to the globals it must be before. + using MustBeBefore = InsertOrderedMap>; + MustBeBefore mustBeBefore; for (auto& global : wasm->globals) { if (!global->init) { @@ -672,31 +672,12 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // Any global.gets that cannot be fixed up are constraints. for (auto* get : scanner.unfixableGets) { - mustBeAfter[global->name].insert(get->name); + mustBeBefore[global->name]; + mustBeBefore[get->name].insert(global->name); } } - if (!mustBeAfter.empty()) { - // We found constraints that require reordering, so do so. - struct MustBeAfterSort : OldTopologicalSort { - MustBeAfter& mustBeAfter; - - MustBeAfterSort(MustBeAfter& mustBeAfter) : mustBeAfter(mustBeAfter) { - for (auto& [global, _] : mustBeAfter) { - push(global); - } - } - - void pushPredecessors(Name global) { - auto iter = mustBeAfter.find(global); - if (iter != mustBeAfter.end()) { - for (auto other : iter->second) { - push(other); - } - } - } - }; - + if (!mustBeBefore.empty()) { auto oldGlobals = std::move(wasm->globals); // After clearing the globals vector, clear the map as well. wasm->updateMaps(); @@ -706,7 +687,8 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { globalIndexes[oldGlobals[i]->name] = i; } // Add the globals that had an important ordering, in the right order. - for (auto global : MustBeAfterSort(mustBeAfter)) { + for (auto global : + TopologicalSort::sortOf(mustBeBefore.begin(), mustBeBefore.end())) { wasm->addGlobal(std::move(oldGlobals[globalIndexes[global]])); } // Add all other globals after them. diff --git a/src/wasm-type-ordering.h b/src/wasm-type-ordering.h index 9ccf4c568d4..0f23b495308 100644 --- a/src/wasm-type-ordering.h +++ b/src/wasm-type-ordering.h @@ -20,58 +20,34 @@ #include #include "support/insert_ordered.h" -#include "support/old_topological_sort.h" +#include "support/topological_sort.h" #include "wasm-type.h" namespace wasm::HeapTypeOrdering { -// Given a collection of types, iterate through it such that each type in the -// collection is visited only after its immediate supertype in the collection is -// visited. -template -struct SupertypesFirstBase - : OldTopologicalSort> { - // For each type in the input collection, whether it is a supertype. Used to - // track membership in the input collection. - InsertOrderedMap typeSet; - - SupertypeProvider& self() { return *static_cast(this); } - - template SupertypeProvider& sort(const T& types) { - for (auto type : types) { - typeSet[type] = false; - } - // Find the supertypes that are in the collection. - for (auto [type, _] : typeSet) { - if (auto super = self().getDeclaredSuperType(type)) { - if (auto it = typeSet.find(*super); it != typeSet.end()) { - it->second = true; - } - } - } - // Types that are not supertypes of others are the roots. - for (auto [type, isSuper] : typeSet) { - if (!isSuper) { - this->push(type); - } - } - return self(); +// Given a collection of types, return a sequence of the types such that each +// type in the sequence comes only after its immediate supertype in the +// collection is visited. +template +std::vector supertypesFirst( + const T& types, + std::function(HeapType)> getSuper = + [](HeapType type) { return type.getDeclaredSuperType(); }) { + + InsertOrderedMap> subtypes; + for (auto type : types) { + subtypes.insert({type, {}}); } - - void pushPredecessors(HeapType type) { - // Do not visit types that weren't in the input collection. - if (auto super = self().getDeclaredSuperType(type); - super && typeSet.count(*super)) { - this->push(*super); + // Find the supertypes that are in the collection. + for (auto [type, _] : subtypes) { + if (auto super = getSuper(type)) { + if (auto it = subtypes.find(*super); it != subtypes.end()) { + it->second.push_back(type); + } } } -}; - -struct SupertypesFirst : SupertypesFirstBase { - std::optional getDeclaredSuperType(HeapType type) { - return type.getDeclaredSuperType(); - } -}; + return TopologicalSort::sortOf(subtypes.begin(), subtypes.end()); +} } // namespace wasm::HeapTypeOrdering diff --git a/test/ctor-eval/gc-2.wast.out b/test/ctor-eval/gc-2.wast.out index 4d80f764e36..ebac3765344 100644 --- a/test/ctor-eval/gc-2.wast.out +++ b/test/ctor-eval/gc-2.wast.out @@ -1,15 +1,15 @@ (module (type $struct (sub (struct (field i32)))) (type $1 (func (result i32))) - (global $ctor-eval$global_4 (ref $struct) (struct.new $struct - (i32.const 9999) - )) - (global $global3 (ref $struct) (global.get $ctor-eval$global_4)) - (global $global2 (mut (ref null $struct)) (global.get $ctor-eval$global_4)) (global $ctor-eval$global (ref $struct) (struct.new $struct (i32.const 1337) )) + (global $ctor-eval$global_4 (ref $struct) (struct.new $struct + (i32.const 9999) + )) (global $global1 (ref any) (global.get $ctor-eval$global)) + (global $global2 (mut (ref null $struct)) (global.get $ctor-eval$global_4)) + (global $global3 (ref $struct) (global.get $ctor-eval$global_4)) (export "keepalive" (func $keepalive)) (func $keepalive (type $1) (result i32) (select diff --git a/test/lit/ctor-eval/gc-cycle-multi.wast b/test/lit/ctor-eval/gc-cycle-multi.wast index 705b76f2fd4..07029143260 100644 --- a/test/lit/ctor-eval/gc-cycle-multi.wast +++ b/test/lit/ctor-eval/gc-cycle-multi.wast @@ -13,27 +13,26 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_6 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: )) - ;; CHECK: (global $c (mut (ref null $A)) (global.get $ctor-eval$global_8)) - ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_7)) - (global $a (mut (ref null $A)) (ref.null $A)) - ;; CHECK: (global $ctor-eval$global_6 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: )) - ;; CHECK: (global $b (mut (ref null $A)) (global.get $ctor-eval$global_6)) + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_6)) + (global $a (mut (ref null $A)) (ref.null $A)) + ;; CHECK: (global $b (mut (ref null $A)) (global.get $ctor-eval$global_7)) (global $b (mut (ref null $A)) (ref.null $A)) + ;; CHECK: (global $c (mut (ref null $A)) (global.get $ctor-eval$global_8)) (global $c (mut (ref null $A)) (ref.null $A)) (func $makeCycle (param $i i32) (result (ref $A)) @@ -118,16 +117,16 @@ ) ;; CHECK: (func $start (type $1) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_8) -;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: (global.get $ctor-eval$global_6) +;; CHECK-NEXT: (global.get $ctor-eval$global_6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_6) -;; CHECK-NEXT: (global.get $ctor-eval$global_6) +;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/ctor-eval/gc-cycle.wast b/test/lit/ctor-eval/gc-cycle.wast index 36333a8bdc2..a342ba3c93f 100644 --- a/test/lit/ctor-eval/gc-cycle.wast +++ b/test/lit/ctor-eval/gc-cycle.wast @@ -151,20 +151,20 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) - (global $a (mut (ref null $A)) (ref.null $A)) - - ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A - ;; CHECK-NEXT: (global.get $ctor-eval$global_8) + ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) - ;; CHECK: (global $b (mut (ref null $A)) (global.get $ctor-eval$global_7)) + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_7)) + (global $a (mut (ref null $A)) (ref.null $A)) + + ;; CHECK: (global $b (mut (ref null $A)) (global.get $ctor-eval$global_8)) (global $b (mut (ref null $A)) (ref.null $A)) (func $test (export "test") @@ -223,8 +223,8 @@ ;; CHECK: (func $start (type $1) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -250,20 +250,20 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) - (global $a (mut (ref null $A)) (ref.null $A)) - - ;; CHECK: (global $ctor-eval$global_7 (ref $B) (struct.new $B - ;; CHECK-NEXT: (global.get $ctor-eval$global_8) + ;; CHECK: (global $ctor-eval$global_8 (ref $B) (struct.new $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) - ;; CHECK: (global $b (mut (ref null $B)) (global.get $ctor-eval$global_7)) + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_7)) + (global $a (mut (ref null $A)) (ref.null $A)) + + ;; CHECK: (global $b (mut (ref null $B)) (global.get $ctor-eval$global_8)) (global $b (mut (ref null $B)) (ref.null $B)) (func $test (export "test") @@ -321,8 +321,8 @@ ;; CHECK: (func $start (type $2) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -348,19 +348,19 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) - - ;; CHECK: (global $ctor-eval$global_7 (ref $B) (struct.new $B - ;; CHECK-NEXT: (global.get $ctor-eval$global_8) + ;; CHECK: (global $ctor-eval$global_8 (ref $B) (struct.new $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) - ;; CHECK: (global $b (mut (ref null $B)) (global.get $ctor-eval$global_7)) + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_7)) + + ;; CHECK: (global $b (mut (ref null $B)) (global.get $ctor-eval$global_8)) (global $b (mut (ref null $B)) (ref.null $B)) (global $a (mut (ref null $A)) (ref.null $A)) @@ -420,8 +420,8 @@ ;; CHECK: (func $start (type $2) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -446,12 +446,17 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) + ;; CHECK: (global $ctor-eval$global_8 (ref $B) (struct.new $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_7) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_7)) (global $a (mut (ref null $A)) (ref.null $A)) (global $b (mut (ref null $B)) (ref.null $B)) @@ -481,11 +486,6 @@ ) ) - ;; CHECK: (global $ctor-eval$global_7 (ref $B) (struct.new $B - ;; CHECK-NEXT: (global.get $ctor-eval$global_8) - ;; CHECK-NEXT: (i32.const 1337) - ;; CHECK-NEXT: )) - ;; CHECK: (export "test" (func $test_3)) ;; CHECK: (export "keepalive" (func $keepalive)) @@ -506,8 +506,8 @@ ;; CHECK: (func $start (type $2) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -536,12 +536,17 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) + ;; CHECK: (global $ctor-eval$global_8 (ref $B) (struct.new $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_7) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_7)) (global $a (mut (ref null $A)) (ref.null $A)) (func $test (export "test") @@ -569,11 +574,6 @@ ) ) - ;; CHECK: (global $ctor-eval$global_7 (ref $B) (struct.new $B - ;; CHECK-NEXT: (global.get $ctor-eval$global_8) - ;; CHECK-NEXT: (i32.const 1337) - ;; CHECK-NEXT: )) - ;; CHECK: (export "test" (func $test_3)) ;; CHECK: (export "keepalive" (func $keepalive)) @@ -594,8 +594,8 @@ ;; CHECK: (func $start (type $2) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: (global.get $ctor-eval$global_8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -614,17 +614,22 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_13 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_12 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) + ;; CHECK: (global $ctor-eval$global_13 (ref $A) (struct.new $A + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (i32.const 99999) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_12)) (global $a (mut (ref null $A)) (ref.null $A)) (global $b (mut (ref null $A)) (ref.null $A)) @@ -665,11 +670,6 @@ ) ) - ;; CHECK: (global $ctor-eval$global_12 (ref $A) (struct.new $A - ;; CHECK-NEXT: (global.get $ctor-eval$global_13) - ;; CHECK-NEXT: (i32.const 99999) - ;; CHECK-NEXT: )) - ;; CHECK: (export "test" (func $test_3)) ;; CHECK: (export "keepalive" (func $keepalive)) @@ -690,12 +690,8 @@ ;; CHECK: (func $start (type $1) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_13) -;; CHECK-NEXT: (global.get $ctor-eval$global_14) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) +;; CHECK-NEXT: (global.get $ctor-eval$global_13) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -729,25 +725,25 @@ ;; CHECK: (type $4 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_12 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_13 (ref $B) (array.new_fixed $B 10 - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK: (global $ctor-eval$global_14 (ref $B) (array.new_fixed $B 10 + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_12)) (global $a (mut (ref null $A)) (ref.null $A)) (func $test (export "test") @@ -784,9 +780,9 @@ ) ) - ;; CHECK: (global $ctor-eval$global_12 (ref $C) (array.new_fixed $C 2 - ;; CHECK-NEXT: (global.get $ctor-eval$global_13) + ;; CHECK: (global $ctor-eval$global_13 (ref $C) (array.new_fixed $C 2 ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: )) ;; CHECK: (export "test" (func $test_3)) @@ -809,8 +805,8 @@ ;; CHECK: (func $start (type $3) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) +;; CHECK-NEXT: (global.get $ctor-eval$global_13) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -837,25 +833,25 @@ ;; CHECK: (type $4 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_12 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_13 (ref $B) (array.new_fixed $B 10 - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) - ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK: (global $ctor-eval$global_14 (ref $B) (array.new_fixed $B 10 + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_12)) (global $a (mut (ref null $A)) (ref.null $A)) (global $b (mut (ref null $B)) (ref.null $B)) @@ -896,9 +892,9 @@ ) ) - ;; CHECK: (global $ctor-eval$global_12 (ref $C) (array.new_fixed $C 2 - ;; CHECK-NEXT: (global.get $ctor-eval$global_13) + ;; CHECK: (global $ctor-eval$global_13 (ref $C) (array.new_fixed $C 2 ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: )) ;; CHECK: (export "test" (func $test_3)) @@ -921,8 +917,8 @@ ;; CHECK: (func $start (type $3) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) +;; CHECK-NEXT: (global.get $ctor-eval$global_13) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -948,13 +944,25 @@ ;; CHECK: (type $3 (func (result anyref))) - ;; CHECK: (global $ctor-eval$global_16 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_17 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_16)) + ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_18 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) (global $a (mut (ref null $A)) (ref.null $A)) (global $b (mut (ref null $B)) (ref.null $B)) @@ -986,27 +994,15 @@ ) ) - ;; CHECK: (global $ctor-eval$global_19 (ref $A) (struct.new $A - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: )) - - ;; CHECK: (global $ctor-eval$global_15 (ref $A) (struct.new $A - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: )) - - ;; CHECK: (global $ctor-eval$global_14 (ref $B) (array.new_fixed $B 3 - ;; CHECK-NEXT: (global.get $ctor-eval$global_15) - ;; CHECK-NEXT: (global.get $ctor-eval$global_16) - ;; CHECK-NEXT: (global.get $ctor-eval$global_19) + ;; CHECK: (global $ctor-eval$global_16 (ref $B) (array.new_fixed $B 3 + ;; CHECK-NEXT: (global.get $ctor-eval$global_17) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_18) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_17 (ref $B) (array.new_fixed $B 0)) + ;; CHECK: (global $ctor-eval$global_15 (ref $B) (array.new_fixed $B 0)) - ;; CHECK: (global $ctor-eval$global_18 (ref $B) (array.new_fixed $B 0)) + ;; CHECK: (global $ctor-eval$global_19 (ref $B) (array.new_fixed $B 0)) ;; CHECK: (export "test" (func $test_3)) @@ -1028,16 +1024,16 @@ ;; CHECK: (func $start (type $2) ;; CHECK-NEXT: (struct.set $A 0 -;; CHECK-NEXT: (global.get $ctor-eval$global_16) -;; CHECK-NEXT: (global.get $ctor-eval$global_17) +;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: (global.get $ctor-eval$global_15) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $A 1 -;; CHECK-NEXT: (global.get $ctor-eval$global_16) ;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: (global.get $ctor-eval$global_16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $A 2 -;; CHECK-NEXT: (global.get $ctor-eval$global_16) -;; CHECK-NEXT: (global.get $ctor-eval$global_18) +;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: (global.get $ctor-eval$global_19) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1060,23 +1056,23 @@ ;; CHECK: (type $3 (func (result anyref))) - ;; CHECK: (global $ctor-eval$global_16 (ref $B) (array.new_fixed $B 3 + ;; CHECK: (global $ctor-eval$global_17 (ref $B) (array.new_fixed $B 0)) + + ;; CHECK: (global $ctor-eval$global_14 (ref $B) (array.new_fixed $B 3 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_19 (ref $B) (array.new_fixed $B 0)) - - ;; CHECK: (global $ctor-eval$global_15 (ref $B) (array.new_fixed $B 0)) + ;; CHECK: (global $ctor-eval$global_18 (ref $B) (array.new_fixed $B 0)) - ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A - ;; CHECK-NEXT: (global.get $ctor-eval$global_15) - ;; CHECK-NEXT: (global.get $ctor-eval$global_16) - ;; CHECK-NEXT: (global.get $ctor-eval$global_19) + ;; CHECK: (global $ctor-eval$global_16 (ref $A) (struct.new $A + ;; CHECK-NEXT: (global.get $ctor-eval$global_17) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_18) ;; CHECK-NEXT: )) - ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_16)) (global $a (mut (ref null $A)) (ref.null $A)) (global $b (mut (ref null $B)) (ref.null $B)) @@ -1109,13 +1105,13 @@ ) ) - ;; CHECK: (global $ctor-eval$global_17 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_15 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_18 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_19 (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) @@ -1141,19 +1137,19 @@ ;; CHECK: (func $start (type $2) ;; CHECK-NEXT: (array.set $B -;; CHECK-NEXT: (global.get $ctor-eval$global_16) +;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (i32.const 0) -;; CHECK-NEXT: (global.get $ctor-eval$global_17) +;; CHECK-NEXT: (global.get $ctor-eval$global_15) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.set $B -;; CHECK-NEXT: (global.get $ctor-eval$global_16) -;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: (global.get $ctor-eval$global_16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.set $B -;; CHECK-NEXT: (global.get $ctor-eval$global_16) +;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (i32.const 2) -;; CHECK-NEXT: (global.get $ctor-eval$global_18) +;; CHECK-NEXT: (global.get $ctor-eval$global_19) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/gto-mutability.wast b/test/lit/passes/gto-mutability.wast index 47a9e231537..13b416b1bdd 100644 --- a/test/lit/passes/gto-mutability.wast +++ b/test/lit/passes/gto-mutability.wast @@ -429,10 +429,10 @@ ;; optimize the field to be immutable. ;; CHECK: (rec - ;; CHECK-NEXT: (type $0 (func (param (ref null $super) (ref null $sub)))) - - ;; CHECK: (type $super (sub (struct (field i32)))) + ;; CHECK-NEXT: (type $super (sub (struct (field i32)))) (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $1 (func (param (ref null $super) (ref null $sub)))) + ;; CHECK: (type $sub (sub $super (struct (field i32)))) (type $sub (sub $super (struct (field (mut i32))))) @@ -464,7 +464,7 @@ ) ) - ;; CHECK: (func $field-keepalive (type $0) (param $super (ref null $super)) (param $sub (ref null $sub)) + ;; CHECK: (func $field-keepalive (type $1) (param $super (ref null $super)) (param $sub (ref null $sub)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $super 0 ;; CHECK-NEXT: (local.get $super) diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast index 8754c8189f8..57dd783622c 100644 --- a/test/lit/passes/signature-pruning.wast +++ b/test/lit/passes/signature-pruning.wast @@ -1161,9 +1161,9 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $much (func)) + ;; CHECK-NEXT: (type $0 (func)) - ;; CHECK: (type $1 (func)) + ;; CHECK: (type $much (func)) ;; CHECK: (rec ;; CHECK-NEXT: (type $none (func)) @@ -1191,7 +1191,7 @@ (func $unused-param (type $much) (param $param i32) ) - ;; CHECK: (func $caller (type $1) + ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $unused-param) ;; CHECK-NEXT: ) (func $caller diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast index fdddbe4ed8f..7a5020e29e0 100644 --- a/test/lit/passes/signature-refining.wast +++ b/test/lit/passes/signature-refining.wast @@ -1068,11 +1068,11 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $0 (func (param (ref $B)))) - - ;; CHECK: (type $A (sub (struct))) + ;; CHECK-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) + ;; CHECK: (type $1 (func (param (ref $B)))) + ;; CHECK: (type $B (sub $A (struct))) (type $B (sub $A (struct))) ) @@ -1108,7 +1108,7 @@ ) ) - ;; CHECK: (func $target (type $0) (param $x (ref $B)) + ;; CHECK: (func $target (type $1) (param $x (ref $B)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $target (param $x (ref $A)) diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast index d6d117a05ab..b8d60286346 100644 --- a/test/lit/passes/type-merging.wast +++ b/test/lit/passes/type-merging.wast @@ -234,10 +234,10 @@ (module (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (struct (field (ref null $A))))) (type $A (sub (struct (ref null $X)))) (type $B (sub $A (struct (ref null $Y)))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $X (sub (struct (field (ref null $X))))) (type $X (sub (struct (ref null $A)))) (type $Y (sub $X (struct (ref null $B)))) ) @@ -245,10 +245,10 @@ ;; CHECK: (type $1 (func)) ;; CHECK: (func $foo (type $1) - ;; CHECK-NEXT: (local $a (ref null $A)) - ;; CHECK-NEXT: (local $b (ref null $A)) - ;; CHECK-NEXT: (local $x (ref null $A)) - ;; CHECK-NEXT: (local $y (ref null $A)) + ;; CHECK-NEXT: (local $a (ref null $X)) + ;; CHECK-NEXT: (local $b (ref null $X)) + ;; CHECK-NEXT: (local $x (ref null $X)) + ;; CHECK-NEXT: (local $y (ref null $X)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo @@ -263,21 +263,21 @@ (module (rec - (type $A (struct (ref null $X) i32)) ;; CHECK: (rec - ;; CHECK-NEXT: (type $B (struct (field (ref null $Y)) (field i32))) + ;; CHECK-NEXT: (type $A (struct (field (ref null $X)) (field i32))) + (type $A (struct (ref null $X) i32)) (type $B (struct (ref null $Y) i32)) + ;; CHECK: (type $X (struct (field (ref null $A)) (field f32))) (type $X (struct (ref null $A) f32)) - ;; CHECK: (type $Y (struct (field (ref null $B)) (field f32))) (type $Y (struct (ref null $B) f32)) ) ;; CHECK: (type $2 (func)) ;; CHECK: (func $foo (type $2) - ;; CHECK-NEXT: (local $a (ref null $B)) - ;; CHECK-NEXT: (local $b (ref null $B)) - ;; CHECK-NEXT: (local $x (ref null $Y)) - ;; CHECK-NEXT: (local $y (ref null $Y)) + ;; CHECK-NEXT: (local $a (ref null $A)) + ;; CHECK-NEXT: (local $b (ref null $A)) + ;; CHECK-NEXT: (local $x (ref null $X)) + ;; CHECK-NEXT: (local $y (ref null $X)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo @@ -293,19 +293,19 @@ (module (rec (type $A (struct (ref null $X))) - ;; CHECK: (rec - ;; CHECK-NEXT: (type $B (struct (field (ref null $B)))) (type $B (struct (ref null $Y))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $X (struct (field (ref null $X)))) (type $X (struct (ref null $A))) (type $Y (struct (ref null $B))) ) ;; CHECK: (type $1 (func)) ;; CHECK: (func $foo (type $1) - ;; CHECK-NEXT: (local $a (ref null $B)) - ;; CHECK-NEXT: (local $b (ref null $B)) - ;; CHECK-NEXT: (local $x (ref null $B)) - ;; CHECK-NEXT: (local $y (ref null $B)) + ;; CHECK-NEXT: (local $a (ref null $X)) + ;; CHECK-NEXT: (local $b (ref null $X)) + ;; CHECK-NEXT: (local $x (ref null $X)) + ;; CHECK-NEXT: (local $y (ref null $X)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo @@ -495,8 +495,8 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (struct (field anyref)))) (type $A (sub (struct anyref))) + ;; CHECK: (type $B (sub $A (struct (field eqref)))) (type $B (sub $A (struct eqref))) - ;; CHECK: (type $C (sub $A (struct (field eqref)))) (type $C (sub $A (struct eqref))) ) @@ -504,8 +504,8 @@ ;; CHECK: (func $foo (type $2) ;; CHECK-NEXT: (local $a (ref null $A)) - ;; CHECK-NEXT: (local $b (ref null $C)) - ;; CHECK-NEXT: (local $c (ref null $C)) + ;; CHECK-NEXT: (local $b (ref null $B)) + ;; CHECK-NEXT: (local $c (ref null $B)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo @@ -524,8 +524,8 @@ (type $A (sub (struct anyref))) (type $B (sub $A (struct anyref))) (type $C (sub $A (struct anyref))) + ;; CHECK: (type $D (sub $A (struct (field eqref)))) (type $D (sub $B (struct eqref))) - ;; CHECK: (type $E (sub $A (struct (field eqref)))) (type $E (sub $C (struct eqref))) ) @@ -535,8 +535,8 @@ ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (local $b (ref null $A)) ;; CHECK-NEXT: (local $c (ref null $A)) - ;; CHECK-NEXT: (local $d (ref null $E)) - ;; CHECK-NEXT: (local $e (ref null $E)) + ;; CHECK-NEXT: (local $d (ref null $D)) + ;; CHECK-NEXT: (local $e (ref null $D)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo @@ -561,18 +561,18 @@ (type $A' (sub $A (struct))) ;; These siblings will be merged only after $a and $a' are merged. + ;; CHECK: (type $B (sub (struct (field (ref $A))))) (type $B (sub (struct (ref $A)))) - ;; CHECK: (type $B' (sub (struct (field (ref $A))))) (type $B' (sub (struct (ref $A')))) ;; These will get merged only after $b and $b' are merged. - ;; CHECK: (type $C (sub $B' (struct (field (ref $A)) (field i32)))) + ;; CHECK: (type $C (sub $B (struct (field (ref $A)) (field i32)))) (type $C (sub $B (struct (ref $A) i32))) (type $C' (sub $B' (struct (ref $A') i32))) ;; These will get merged only after $c and $c' are merged. + ;; CHECK: (type $D (sub $C (struct (field (ref $A)) (field i32) (field i32)))) (type $D (sub $C (struct (ref $A) i32 i32))) - ;; CHECK: (type $D' (sub $C (struct (field (ref $A)) (field i32) (field i32)))) (type $D' (sub $C' (struct (ref $A') i32 i32))) ) @@ -581,12 +581,12 @@ ;; CHECK: (func $foo (type $4) ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (local $a' (ref null $A)) - ;; CHECK-NEXT: (local $b (ref null $B')) - ;; CHECK-NEXT: (local $b' (ref null $B')) + ;; CHECK-NEXT: (local $b (ref null $B)) + ;; CHECK-NEXT: (local $b' (ref null $B)) ;; CHECK-NEXT: (local $c (ref null $C)) ;; CHECK-NEXT: (local $c' (ref null $C)) - ;; CHECK-NEXT: (local $d (ref null $D')) - ;; CHECK-NEXT: (local $d' (ref null $D')) + ;; CHECK-NEXT: (local $d (ref null $D)) + ;; CHECK-NEXT: (local $d' (ref null $D)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo diff --git a/test/lit/passes/type-ssa_and_merging.wast b/test/lit/passes/type-ssa_and_merging.wast index 8e84cf5c489..4a772763b18 100644 --- a/test/lit/passes/type-ssa_and_merging.wast +++ b/test/lit/passes/type-ssa_and_merging.wast @@ -14,14 +14,14 @@ ;; YES: (type $0 (func (result i32))) ;; YES: (rec - ;; YES-NEXT: (type $1 (func (param (ref $A)))) - - ;; YES: (type $A (sub (struct))) + ;; YES-NEXT: (type $A (sub (struct))) (type $A (sub (struct (field (mut i32))))) ;; NOP: (type $2 (func (result i32))) ;; NOP: (import "a" "b" (func $import (type $2) (result i32))) + ;; YES: (type $2 (func (param (ref $A)))) + ;; YES: (type $A_2 (sub $A (struct))) ;; YES: (type $A_1 (sub $A (struct))) @@ -92,7 +92,7 @@ ;; NOP-NEXT: (local.get $0) ;; NOP-NEXT: ) ;; NOP-NEXT: ) - ;; YES: (func $get-a-1 (type $1) (param $0 (ref $A)) + ;; YES: (func $get-a-1 (type $2) (param $0 (ref $A)) ;; YES-NEXT: (if ;; YES-NEXT: (call $import) ;; YES-NEXT: (then @@ -134,7 +134,7 @@ ;; NOP-NEXT: (local.get $0) ;; NOP-NEXT: ) ;; NOP-NEXT: ) - ;; YES: (func $get-a-2 (type $1) (param $0 (ref $A)) + ;; YES: (func $get-a-2 (type $2) (param $0 (ref $A)) ;; YES-NEXT: (if ;; YES-NEXT: (call $import) ;; YES-NEXT: (then From 432ed1ccc62aea8c04ac0d2f89a25acedde6c948 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 10 Sep 2024 19:13:19 -0700 Subject: [PATCH 019/622] [EH] Fix pop enclosed within a block in DCE (#6922) #6400 fixed this case but that handled only when a `pop` is an immediate child of the current expression, but a `pop` can be nested deeper down. We conservatively run the EH fixup whenever we have a `pop` and create `block`s in the optimization. We considered using `FindAll` to make it precise, but we decided the quadratic time plexity was not worth it. Fixes #6918. --- src/passes/DeadCodeElimination.cpp | 25 ++++++----- test/lit/passes/dce-eh-legacy.wast | 72 ++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp index a3667376f98..c82c73998ff 100644 --- a/src/passes/DeadCodeElimination.cpp +++ b/src/passes/DeadCodeElimination.cpp @@ -56,6 +56,10 @@ struct DeadCodeElimination // as we remove code, we must keep the types of other nodes valid TypeUpdater typeUpdater; + // Information used to decide whether we need EH fixups at the end + bool hasPop = false; // Do we have a 'pop' in this function? + bool addedBlock = false; // Have we added blocks in this function? + Expression* replaceCurrent(Expression* expression) { auto* old = getCurrent(); if (old == expression) { @@ -73,6 +77,10 @@ struct DeadCodeElimination } void visitExpression(Expression* curr) { + if (curr->is()) { + hasPop = true; + } + if (!Properties::isControlFlowStructure(curr)) { // Control flow structures require special handling, but others are // simple. @@ -90,11 +98,7 @@ struct DeadCodeElimination Builder builder(*getModule()); std::vector remainingChildren; bool afterUnreachable = false; - bool hasPop = false; for (auto* child : ChildIterator(curr)) { - if (child->is()) { - hasPop = true; - } if (afterUnreachable) { typeUpdater.noteRecursiveRemoval(child); continue; @@ -109,12 +113,8 @@ struct DeadCodeElimination if (remainingChildren.size() == 1) { replaceCurrent(remainingChildren[0]); } else { + addedBlock = true; replaceCurrent(builder.makeBlock(remainingChildren)); - if (hasPop) { - // We are moving a pop into a new block we just created, which - // means we may need to fix things up here. - needEHFixups = true; - } } } } @@ -196,10 +196,11 @@ struct DeadCodeElimination } } - bool needEHFixups = false; - void visitFunction(Function* curr) { - if (needEHFixups) { + // We conservatively run the EH pop fixup if this function has a 'pop' and + // if we have ever added blocks in the optimization, which may have moved + // pops into the blocks. + if (hasPop && addedBlock) { EHUtils::handleBlockNestedPops(curr, *getModule()); } } diff --git a/test/lit/passes/dce-eh-legacy.wast b/test/lit/passes/dce-eh-legacy.wast index ef6d569d69b..107c35609cd 100644 --- a/test/lit/passes/dce-eh-legacy.wast +++ b/test/lit/passes/dce-eh-legacy.wast @@ -1,14 +1,28 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: wasm-opt %s --dce -all -S -o - | filecheck %s ;; If either try body or catch body is reachable, the whole try construct is ;; reachable (module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (type $2 (func (param eqref))) + + ;; CHECK: (type $3 (func (param i32 i32))) + + ;; CHECK: (type $4 (func (result i32))) + + ;; CHECK: (type $struct (struct (field (mut eqref)))) + (type $struct (struct (field (mut (ref null eq))))) + ;; CHECK: (tag $e) (tag $e) - ;; CHECK: (tag $e-i32 (param i32)) (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-eqref (param eqref)) + (tag $e-eqref (param (ref null eq))) ;; CHECK: (func $foo (type $0) ;; CHECK-NEXT: (nop) @@ -149,11 +163,63 @@ ) ) - ;; CHECK: (func $helper-i32-i32 (type $2) (param $0 i32) (param $1 i32) + ;; CHECK: (func $helper-i32-i32 (type $3) (param $0 i32) (param $1 i32) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $helper-i32-i32 (param $0 i32) (param $1 i32) (nop) ) + + ;; CHECK: (func $pop-within-block (type $4) (result i32) + ;; CHECK-NEXT: (local $0 eqref) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-eqref + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-block (result i32) + (try (result i32) + (do + (i32.const 0) + ) + (catch $e-eqref + (drop + ;; Optimization moves the 'pop' inside a block, which needs to be + ;; extracted out of the block at the end. + ;; (block + ;; (drop + ;; (struct.new $struct.0 + ;; (pop eqref) + ;; ) + ;; ) + ;; (unreachable) + ;; ) + (ref.eq + (struct.new $struct + (pop (ref null eq)) + ) + (unreachable) + ) + ) + (i32.const 0) + ) + ) + ) ) From 0901ba7fff0bac1def4d457b15e4771368abf00d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 11 Sep 2024 15:07:10 -0700 Subject: [PATCH 020/622] [NFC] Optimize propagateLocals() (#6928) That is the slowest part of --precompute-propagate, which is one of our slowest passes. This makes that pass 25% faster. The main change is to make the maps consider missing elements as non-constant, rather than storing a None element in them. That saves allocating entries for the common case of a non-constant set/get. Also switch to a simple vector for the work queue, which is possible if we only add work when a get/set becomes constant (which can only happen once). Another benefit to adding work in that manner is that we don't start by adding every single get/set as "work" at the start. --- src/passes/Precompute.cpp | 272 ++++++++++++++++++++++---------------- 1 file changed, 156 insertions(+), 116 deletions(-) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 780945b46b8..379f6ee49c6 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -35,13 +35,16 @@ #include "ir/utils.h" #include "pass.h" #include "support/insert_ordered.h" -#include "support/unique_deferring_queue.h" +#include "support/small_vector.h" #include "wasm-builder.h" #include "wasm-interpreter.h" #include "wasm.h" namespace wasm { +// A map of gets to their constant values. If a get does not have a constant +// value then it does not appear in the map (that avoids allocating for the +// majority of gets). using GetValues = std::unordered_map; // A map of values on the heap. This maps the expressions that create the @@ -81,7 +84,7 @@ class PrecomputingExpressionRunner // Concrete values of gets computed during the pass, which the runner does not // know about since it only records values of sets it visits. - GetValues& getValues; + const GetValues& getValues; HeapValues& heapValues; @@ -99,7 +102,7 @@ class PrecomputingExpressionRunner public: PrecomputingExpressionRunner(Module* module, - GetValues& getValues, + const GetValues& getValues, HeapValues& heapValues, bool replaceExpression) : ConstantExpressionRunner( @@ -114,9 +117,8 @@ class PrecomputingExpressionRunner auto iter = getValues.find(curr); if (iter != getValues.end()) { auto values = iter->second; - if (values.isConcrete()) { - return Flow(values); - } + assert(values.isConcrete()); + return Flow(values); } return ConstantExpressionRunner< PrecomputingExpressionRunner>::visitLocalGet(curr); @@ -743,131 +745,169 @@ struct Precompute // Propagates values around. Returns whether we propagated. bool propagateLocals(Function* func) { bool propagated = false; - // using the graph of get-set interactions, do a constant-propagation type + + // Using the graph of get-set interactions, do a constant-propagation type // operation: note which sets are assigned locals, then see if that lets us // compute other sets as locals (since some of the gets they read may be - // constant). - // compute all dependencies + // constant). First, compute all influences/dependencies. LocalGraph localGraph(func, getModule()); localGraph.computeInfluences(); - // prepare the work list. we add things here that might change to a constant - // initially, that means everything - UniqueDeferredQueue work; - for (auto& [curr, _] : localGraph.locations) { - work.push(curr); - } - // the constant value, or none if not a constant + + // A map of sets to their constant values. If a set does not appear here + // then it is not constant, like |getValues|. std::unordered_map setValues; - // propagate constant values - while (!work.empty()) { - auto* curr = work.pop(); - // see if this set or get is actually a constant value, and if so, - // mark it as such and add everything it influences to the work list, - // as they may be constant too. - if (auto* set = curr->dynCast()) { - if (setValues[set].isConcrete()) { - continue; // already known constant - } - // Precompute the value. Note that this executes the code from scratch - // each time we reach this point, and so we need to be careful about - // repeating side effects if those side effects are expressed *in the - // value*. A case where that can happen is GC data (each struct.new - // creates a new, unique struct, even if the data is equal), and so - // PrecomputingExpressionRunner has special logic to make sure that - // reference identity is preserved properly. - // - // (Other side effects are fine; if an expression does a call and we - // somehow know the entire expression precomputes to a 42, then we can - // propagate that 42 along to the users, regardless of whatever the call - // did globally.) - auto values = precomputeValue(Properties::getFallthrough( - set->value, getPassOptions(), *getModule())); - // Fix up the value. The computation we just did was to look at the - // fallthrough, then precompute that; that looks through expressions - // that pass through the value. Normally that does not matter here, - // for example, (block .. (value)) returns the value unmodified. - // However, some things change the type, for example RefAsNonNull has - // a non-null type, while its input may be nullable. That does not - // matter either, as if we managed to precompute it then the value had - // the more specific (in this example, non-nullable) type. But there - // is a situation where this can cause an issue: RefCast. An attempt to - // perform a "bad" cast, say of a function to a struct, is a case where - // the fallthrough value's type is very different than the actually - // returned value's type. To handle that, if we precomputed a value and - // if it has the wrong type then precompute it again without looking - // through to the fallthrough. - if (values.isConcrete() && - !Type::isSubType(values.getType(), set->value->type)) { - values = precomputeValue(set->value); - } + + // The work list, which will contain sets and gets that have just been + // found to have a constant value. As we only add them to the list when they + // are found to be constant, each can only be added once, and a simple + // vector is enough here (which we can make a small vector to avoid any + // allocation in small-enough functions). + SmallVector work; + + // Given a set, see if it has a constant value. If so, note that on + // setValues and add to the work list. + auto checkConstantSet = [&](LocalSet* set) { + if (setValues.count(set)) { + // Already known to be constant. + return; + } + + // Precompute the value. Note that this executes the code from scratch + // each time we reach this point, and so we need to be careful about + // repeating side effects if those side effects are expressed *in the + // value*. A case where that can happen is GC data (each struct.new + // creates a new, unique struct, even if the data is equal), and so + // PrecomputingExpressionRunner has special logic to make sure that + // reference identity is preserved properly. + // + // (Other side effects are fine; if an expression does a call and we + // somehow know the entire expression precomputes to a 42, then we can + // propagate that 42 along to the users, regardless of whatever the call + // did globally.) + auto values = precomputeValue( + Properties::getFallthrough(set->value, getPassOptions(), *getModule())); + + // Fix up the value. The computation we just did was to look at the + // fallthrough, then precompute that; that looks through expressions + // that pass through the value. Normally that does not matter here, + // for example, (block .. (value)) returns the value unmodified. + // However, some things change the type, for example RefAsNonNull has + // a non-null type, while its input may be nullable. That does not + // matter either, as if we managed to precompute it then the value had + // the more specific (in this example, non-nullable) type. But there + // is a situation where this can cause an issue: RefCast. An attempt to + // perform a "bad" cast, say of a null to non-null, is a tricky case where + // the fallthrough value's type is very different than the actually + // returned value's type. To handle that, if we precomputed a value and + // if it has the wrong type then precompute it again without looking + // through to the fallthrough. + if (values.isConcrete() && + !Type::isSubType(values.getType(), set->value->type)) { + values = precomputeValue(set->value); + } + + if (values.isConcrete()) { setValues[set] = values; - if (values.isConcrete()) { - for (auto* get : localGraph.getSetInfluences(set)) { - work.push(get); - } - } - } else { - auto* get = curr->cast(); - if (getValues[get].size() >= 1) { - continue; // already known constant - } - // for this get to have constant value, all sets must agree - Literals values; - bool first = true; - for (auto* set : localGraph.getSets(get)) { - Literals curr; - if (set == nullptr) { - if (getFunction()->isVar(get->index)) { - auto localType = getFunction()->getLocalType(get->index); - if (!localType.isDefaultable()) { - // This is a nondefaultable local that seems to read the default - // value at the function entry. This is either an internal error - // or a case of unreachable code; the latter is possible as - // LocalGraph is not precise in unreachable code. - // - // We cannot set zeros here (as applying them, even in - // unreachable code, would not validate), so just mark this as - // a hopeless case to ignore. - values = {}; - } else { - curr = Literal::makeZeros(localType); - } + work.push_back(set); + } + }; + + // The same, for a get. + auto checkConstantGet = [&](LocalGet* get) { + if (getValues.count(get)) { + // Already known to be constant. + return; + } + + // For this get to have constant value, all sets must agree on a constant. + Literals values; + bool first = true; + for (auto* set : localGraph.getSets(get)) { + Literals curr; + if (set == nullptr) { + if (getFunction()->isVar(get->index)) { + auto localType = getFunction()->getLocalType(get->index); + if (!localType.isDefaultable()) { + // This is a nondefaultable local that seems to read the default + // value at the function entry. This is either an internal error + // or a case of unreachable code; the latter is possible as + // LocalGraph is not precise in unreachable code. Give up. + return; } else { - // it's a param, so it's hopeless - values = {}; - break; + curr = Literal::makeZeros(localType); } } else { - curr = setValues[set]; - } - if (curr.isNone()) { - // not a constant, give up - values = {}; - break; + // It's a param, so the value is non-constant. Give up. + return; } - // we found a concrete value. compare with the current one - if (first) { - values = curr; // this is the first - first = false; - } else { - if (values != curr) { - // not the same, give up - values = {}; - break; - } + } else { + // If there is an entry for the set, use that constant. Otherwise, the + // set is not constant, and we give up. + auto iter = setValues.find(set); + if (iter == setValues.end()) { + return; } + curr = iter->second; } - // we may have found a value - if (values.isConcrete()) { - // we did! - getValues[get] = values; - for (auto* set : localGraph.getGetInfluences(get)) { - work.push(set); - } - propagated = true; + + // We found a concrete value, so there is a chance, if it matches all + // the rest. + assert(curr.isConcrete()); + if (first) { + // This is the first ever value we see. All later ones must match it. + values = curr; + first = false; + } else if (values != curr) { + // This later value is not the same as before, give up. + return; + } + } + + if (values.isConcrete()) { + // We found a constant value! + getValues[get] = values; + work.push_back(get); + propagated = true; + } else { + // If it is not concrete then, since we early-exited before on any + // possible problem, there must be no sets for this get, which means it + // is in unreachable code. In that case, we never switched |first| from + // true to false. + assert(first == true); + // We could optimize using unreachability here, but we leave that for + // other passes. + } + }; + + // Check all gets and sets to find which are constant, mark them as such, + // and add propagation work based on that. + for (auto& [curr, _] : localGraph.locations) { + if (auto* set = curr->dynCast()) { + checkConstantSet(set); + } else { + checkConstantGet(curr->cast()); + } + } + + // Propagate constant values while work remains. + while (!work.empty()) { + auto* curr = work.back(); + work.pop_back(); + + // This get or set is a constant value. Check if the things it influences + // become constant. + if (auto* set = curr->dynCast()) { + for (auto* get : localGraph.getSetInfluences(set)) { + checkConstantGet(get); + } + } else { + auto* get = curr->cast(); + for (auto* set : localGraph.getGetInfluences(get)) { + checkConstantSet(set); } } } + return propagated; } From 0cbe03a13abd4a59dafce5042ac979adf151a702 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 11 Sep 2024 15:59:55 -0700 Subject: [PATCH 021/622] Fix parser error on block params (#6932) The error checking we had to report an error when the input contains block parameters was in a code path that is no longer executed under normal circumstances. Specifically, it was part of the `ParseModuleTypesCtx` phase of parsing, which no longer parses function bodies. Move the error checking to the `ParseDefsCtx` phase, which does parse function bodies. Fixes #6929. --- src/parser/contexts.h | 12 ++++++------ test/lit/parse-bad-block-params.wast | 12 ++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 test/lit/parse-bad-block-params.wast diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 7e70b67764a..387cb146eef 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1259,12 +1259,6 @@ struct ParseModuleTypesCtx : TypeParserCtx, } Result getBlockTypeFromTypeUse(Index pos, TypeUse use) { - assert(use.type.isSignature()); - if (use.type.getSignature().params != Type::none) { - return in.err(pos, "block parameters not yet supported"); - } - // TODO: Once we support block parameters, return an error here if any of - // them are named. return use.type; } @@ -1451,6 +1445,12 @@ struct ParseDefsCtx : TypeParserCtx { } Result getBlockTypeFromTypeUse(Index pos, HeapType type) { + assert(type.isSignature()); + if (type.getSignature().params != Type::none) { + return in.err(pos, "block parameters not yet supported"); + } + // TODO: Once we support block parameters, return an error here if any of + // them are named. return type; } diff --git a/test/lit/parse-bad-block-params.wast b/test/lit/parse-bad-block-params.wast new file mode 100644 index 00000000000..67e05989ce6 --- /dev/null +++ b/test/lit/parse-bad-block-params.wast @@ -0,0 +1,12 @@ +;; RUN: not wasm-opt %s -S -o - 2>&1 | filecheck %s + +;; CHECK: 8:11: error: block parameters not yet supported + +(module + (func + (i32.const 0) + (block (param i32) + (drop) + ) + ) +) From 43ed681debe7928c894f2ae57823f08988d23475 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 11 Sep 2024 17:08:24 -0700 Subject: [PATCH 022/622] [NFC] Ignore invalid precomputed fallthrough values (#6933) Followup to #6928. If the fallthrough precomputes to an invalid type, it makes no sense to precompute the non-fallthrough, as it can't return anything useful. --- src/passes/Precompute.cpp | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 379f6ee49c6..0b8ead056a8 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -787,26 +787,20 @@ struct Precompute auto values = precomputeValue( Properties::getFallthrough(set->value, getPassOptions(), *getModule())); - // Fix up the value. The computation we just did was to look at the - // fallthrough, then precompute that; that looks through expressions - // that pass through the value. Normally that does not matter here, - // for example, (block .. (value)) returns the value unmodified. - // However, some things change the type, for example RefAsNonNull has - // a non-null type, while its input may be nullable. That does not - // matter either, as if we managed to precompute it then the value had - // the more specific (in this example, non-nullable) type. But there - // is a situation where this can cause an issue: RefCast. An attempt to - // perform a "bad" cast, say of a null to non-null, is a tricky case where - // the fallthrough value's type is very different than the actually - // returned value's type. To handle that, if we precomputed a value and - // if it has the wrong type then precompute it again without looking - // through to the fallthrough. + // We precomputed the *fallthrough* value (which allows us to look through + // some things that would otherwise block us). But in some cases, like a + // ref.cast, the fallthrough value can have an incompatible type for the + // entire expression, which would be invalid for us to propagate, e.g.: + // + // (ref.cast (ref struct) + // (ref.null any) + // ) + // + // In such a case the value cannot actually fall through. Ignore such + // cases (which other optimizations can handle) by making sure that we + // only propagate a valid subtype. if (values.isConcrete() && - !Type::isSubType(values.getType(), set->value->type)) { - values = precomputeValue(set->value); - } - - if (values.isConcrete()) { + Type::isSubType(values.getType(), set->value->type)) { setValues[set] = values; work.push_back(set); } From 0888f9c245cd864b9386f65ff54331f9fbe993b0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 12 Sep 2024 10:26:25 -0700 Subject: [PATCH 023/622] [NFC] Make Precompute use a lazy LocalGraph (#6934) To do this, add locations and getInfluences to LazyLocalGraph. Both cannot really be computed in a fine-grained manner, so just compute them all on the first request. That is not as efficient as our lazy computation of getSets and setInfluences, but they are also less important, and this change makes the pass 20% faster. --- src/ir/LocalGraph.cpp | 52 ++++++++++++++++++++++++++++++++++----- src/ir/local-graph.h | 26 ++++++++++++++++++++ src/passes/Precompute.cpp | 8 +++--- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/src/ir/LocalGraph.cpp b/src/ir/LocalGraph.cpp index af13707314e..a5495b5b33f 100644 --- a/src/ir/LocalGraph.cpp +++ b/src/ir/LocalGraph.cpp @@ -281,6 +281,8 @@ struct LocalGraphFlower }); if (lastSet != pred->lastSets.end()) { // There is a set here, apply it, and stop the flow. + // TODO: If we find a computed get, apply its sets and stop? That + // could help but it requires more info on FlowBlock. for (auto* get : gets) { getSetsMap[get].insert(lastSet->second); } @@ -512,7 +514,9 @@ void LocalGraph::computeSetInfluences() { } } -void LocalGraph::computeGetInfluences() { +static void +doComputeGetInfluences(const LocalGraphBase::Locations& locations, + LocalGraphBase::GetInfluencesMap& getInfluences) { for (auto& [curr, _] : locations) { if (auto* set = curr->dynCast()) { FindAll findAll(set->value); @@ -523,6 +527,10 @@ void LocalGraph::computeGetInfluences() { } } +void LocalGraph::computeGetInfluences() { + doComputeGetInfluences(locations, getInfluences); +} + void LocalGraph::computeSSAIndexes() { std::unordered_map> indexSets; for (auto& [get, sets] : getSetsMap) { @@ -555,13 +563,12 @@ LazyLocalGraph::LazyLocalGraph(Function* func, Module* module) : LocalGraphBase(func, module) {} void LazyLocalGraph::makeFlower() const { - // Lazy graphs do not provide |locations| publicly. TODO: perhaps refactor to - // avoid filling in this dummy data structure, but we may want to add a lazy - // version of it too, so see which makes sense first. - LocalGraph::Locations locations; + // |locations| is set here and filled in by |flower|. + assert(!locations); + locations.emplace(); flower = - std::make_unique(getSetsMap, locations, func, module); + std::make_unique(getSetsMap, *locations, func, module); flower->prepareLaziness(); @@ -585,6 +592,9 @@ LazyLocalGraph::~LazyLocalGraph() { } void LazyLocalGraph::computeGetSets(LocalGet* get) const { + // We must never repeat work. + assert(!getSetsMap.count(get)); + if (!flower) { makeFlower(); } @@ -592,10 +602,40 @@ void LazyLocalGraph::computeGetSets(LocalGet* get) const { } void LazyLocalGraph::computeSetInfluences(LocalSet* set) const { + // We must never repeat work. + assert(!setInfluences.count(set)); + if (!flower) { makeFlower(); } flower->computeSetInfluences(set, setInfluences); } +void LazyLocalGraph::computeGetInfluences() const { + // We must never repeat work. + assert(!getInfluences); + + // We do not need any flow for this, but we do need |locations| to be filled + // in. + getLocations(); + assert(locations); + + getInfluences.emplace(); + doComputeGetInfluences(*locations, *getInfluences); +} + +void LazyLocalGraph::computeLocations() const { + // We must never repeat work. + assert(!locations); + + // |flower| fills in |locations| as it scans the function. + // + // In theory we could be even lazier here, but it is nice that flower will + // fill in the locations as it goes, avoiding an additional pass. And, in + // practice, if we ask for locations then we likely need other things anyhow. + if (!flower) { + makeFlower(); + } +} + } // namespace wasm diff --git a/src/ir/local-graph.h b/src/ir/local-graph.h index 46bc135e0bd..6f1e621cfdf 100644 --- a/src/ir/local-graph.h +++ b/src/ir/local-graph.h @@ -202,17 +202,43 @@ struct LazyLocalGraph : public LocalGraphBase { } return iter->second; } + const GetInfluences& getGetInfluences(LocalGet* get) const { + if (!getInfluences) { + computeGetInfluences(); + assert(getInfluences); + } + return (*getInfluences)[get]; + } + + const Locations& getLocations() const { + if (!locations) { + computeLocations(); + assert(locations); + } + return *locations; + } private: // These data structures are mutable so that we can memoize. mutable GetSetsMap getSetsMap; mutable SetInfluencesMap setInfluences; + // The entire |getInfluences| is computed once the first request for one + // arrives, so the entire thing is either present or not, unlike setInfluences + // which is fine-grained. The difference is that the influences of a get may + // include sets of other indexes, so there is no simple way to lazify that + // computation. + mutable std::optional getInfluences; + mutable std::optional locations; // Compute the sets for a get and store them on getSetsMap. void computeGetSets(LocalGet* get) const; // Compute influences for a set and store them on setInfluences. void computeSetInfluences(LocalSet* set) const; + // Compute influences for all gets and store them on getInfluences. + void computeGetInfluences() const; + // Compute locations and store them on getInfluences. + void computeLocations() const; // This remains alive as long as we are, so that we can compute things lazily. // It is mutable as when we construct this is an internal detail, that does diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 0b8ead056a8..709fd7d3d85 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -749,9 +749,9 @@ struct Precompute // Using the graph of get-set interactions, do a constant-propagation type // operation: note which sets are assigned locals, then see if that lets us // compute other sets as locals (since some of the gets they read may be - // constant). First, compute all influences/dependencies. - LocalGraph localGraph(func, getModule()); - localGraph.computeInfluences(); + // constant). We do this lazily as most locals do not end up with constant + // values that we can propagate. + LazyLocalGraph localGraph(func, getModule()); // A map of sets to their constant values. If a set does not appear here // then it is not constant, like |getValues|. @@ -875,7 +875,7 @@ struct Precompute // Check all gets and sets to find which are constant, mark them as such, // and add propagation work based on that. - for (auto& [curr, _] : localGraph.locations) { + for (auto& [curr, _] : localGraph.getLocations()) { if (auto* set = curr->dynCast()) { checkConstantSet(set); } else { From 29d30fabcb688b599920deef9d92a5413615eaab Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Fri, 13 Sep 2024 12:54:13 -0600 Subject: [PATCH 024/622] Add OCaml toolchain to binaryen readme (#6939) `wasm_of_ocaml` uses Binaryen's `wasm_as`, `wasm_merge` and `wasm_opt` as a part of its toolchain. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ef1bf03002c..3eaafb7f72a 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Toolchains using Binaryen as a **component** (typically running `wasm-opt`) incl * [`J2CL`](https://j2cl.io/) (Java; [`J2Wasm`](https://github.com/google/j2cl/tree/master/samples/wasm)) * [`Kotlin`](https://kotl.in/wasmgc) (Kotlin/Wasm) * [`Dart`](https://flutter.dev/wasm) (Flutter) + * [`wasm_of_ocaml`](https://github.com/ocaml-wasm/wasm_of_ocaml) (OCaml) For more on how some of those work, see the toolchain architecture parts of the [V8 WasmGC porting blogpost](https://v8.dev/blog/wasm-gc-porting). From 4913080cd040c0e0a1607ed73ac13bbbafe3a05d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 16 Sep 2024 12:42:46 -0700 Subject: [PATCH 025/622] Fix Heap2Local on pops inside of newly-created blocks (#6938) --- src/passes/Heap2Local.cpp | 17 +++++ test/lit/passes/heap2local.wast | 124 ++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 09a50eea8d8..c31bc8436b1 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -152,6 +152,7 @@ #include "ir/bits.h" #include "ir/branch-utils.h" +#include "ir/eh-utils.h" #include "ir/find_all.h" #include "ir/local-graph.h" #include "ir/parents.h" @@ -1191,9 +1192,16 @@ struct Heap2Local { // some cases, so to be careful here use a fairly small limit. return size < 20; } + + // Also note if a pop exists here, as they may require fixups. + bool hasPop = false; + + void visitPop(Pop* curr) { hasPop = true; } } finder; finder.walk(func->body); + bool optimized = false; + // First, lower non-escaping arrays into structs. That allows us to handle // arrays in a single place, and let all the rest of this pass assume we are // working on structs. We are in fact only optimizing struct-like arrays @@ -1215,6 +1223,7 @@ struct Heap2Local { auto* structNew = Array2Struct(allocation, analyzer, func, wasm).structNew; Struct2Local(structNew, analyzer, func, wasm); + optimized = true; } } @@ -1231,8 +1240,16 @@ struct Heap2Local { localGraph, parents, branchTargets, passOptions, wasm); if (!analyzer.escapes(allocation)) { Struct2Local(allocation, analyzer, func, wasm); + optimized = true; } } + + // We conservatively run the EH pop fixup if this function has a 'pop' and + // if we have ever optimized, as all of the things we do here involve + // creating blocks, so we might have moved pops into the blocks. + if (finder.hasPop && optimized) { + EHUtils::handleBlockNestedPops(func, wasm); + } } bool canHandleAsLocal(const Field& field) { diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 143b7033a9b..179437c3874 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -4485,3 +4485,127 @@ ) ) ) + +;; Pops require fixups. +(module + (type $struct (struct (field (mut i32)))) + + (type $array (array (mut i32))) + + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (type $2 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) + + ;; CHECK: (tag $tag (param i32)) + (tag $tag (param i32)) + + ;; CHECK: (func $struct-with-pop (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-with-pop + (try + (do + (nop) + ) + (catch $tag + (drop + ;; We create a block when we replace the struct with locals, which the + ;; pop must be moved out of. + (struct.new $struct + (pop i32) + ) + ) + ) + ) + ) + + ;; CHECK: (func $array-with-pop (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $2)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-with-pop + (try + (do + (nop) + ) + (catch $tag + (drop + ;; As above, but with an array + (array.new $array + (pop i32) + (i32.const 3) + ) + ) + ) + ) + ) +) From 2a4096512d514b2319ff1c9360f0fbc9ecc94e5d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 16 Sep 2024 14:12:13 -0700 Subject: [PATCH 026/622] Remove open "ignorable public" array types (#6940) There are a few heap types that are hard-coded to be considered public and therefore allowed on module boundaries even in --closed-world mode, specifically to support js-string-builtins. We previously considered both open and closed (i.e. final) mutable i8 arrays to be public in this manner, but js-string-builtins only uses the closed versions, so remove the open versions. This fixes a particular bug in which Unsubtyping optimized a private array type to be equivalent to an ignorable public array type, incorrectly changing the behavior of a cast, but it does not address the larger problem of optimizations producing types that are equivalent to public types. Add a TODO about that problem for now. Fixes #6935. --- src/ir/type-updating.cpp | 4 ++++ src/wasm/wasm-type.cpp | 10 +--------- test/lit/passes/unsubtyping.wast | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index dedbb631697..8b74ebfcd5d 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -175,6 +175,10 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( #endif auto& newTypes = *buildResults; + // TODO: It is possible that the newly built rec group matches some public rec + // group. If that is the case, we need to try a different permutation of the + // types or add a brand type to distinguish the private types. + // Map the old types to the new ones. TypeMap oldToNewTypes; for (auto [type, index] : typeIndices) { diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 1730f1b85b9..151d45a1d94 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -2887,17 +2887,9 @@ void TypeBuilder::dump() { std::unordered_set getIgnorablePublicTypes() { auto array8 = Array(Field(Field::i8, Mutable)); auto array16 = Array(Field(Field::i16, Mutable)); - TypeBuilder builder(4); - // We handle final and non-final here, but should remove one of them - // eventually TODO + TypeBuilder builder(2); builder[0] = array8; - builder[0].setOpen(false); builder[1] = array16; - builder[1].setOpen(false); - builder[2] = array8; - builder[2].setOpen(true); - builder[3] = array16; - builder[3].setOpen(true); auto result = builder.build(); assert(result); std::unordered_set ret; diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index 0d4e11e122b..aa4af720bf3 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -1747,3 +1747,35 @@ ;; CHECK: (global $g (ref $super) (struct.new_default $sub)) (global $g (ref $super) (struct.new_default $sub)) ) + +;; Regression test for a bug where we considered $super to be a public type +;; (because it was once in contention to appear in js-string-builtin +;; signatures), so we only updated $sub, but that caused $sub and $super to be +;; the same type, changing the behavior of the cast. +(module + ;; CHECK: (type $super (sub (array (mut i8)))) + (type $super (sub (array (mut i8)))) + (type $sub (sub $super (array (mut i8)))) + ;; CHECK: (type $1 (func)) + + ;; CHECK: (export "test" (func $0)) + + ;; CHECK: (func $0 (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref none) + ;; CHECK-NEXT: (array.new_default $super + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $0 (export "test") + (drop + (ref.cast (ref $sub) + (array.new_default $super + (i32.const 0) + ) + ) + ) + ) +) From 106f84b4f2dc373b540ace29139f850576f22b8a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 16 Sep 2024 14:12:42 -0700 Subject: [PATCH 027/622] [wasm-split] Add an option to skip importing placeholders (#6942) Wasm-split generally assumes that calls to secondary functions made before the secondary module has been loaded and instantiated should go to imported placeholder functions that can be responsible for loading the secondary module and forwarding the call to the loaded function. That scheme makes the loading entirely transparent from the application's point of view, which is not always a good thing. Other schemes would make it impossible for a secondary function to be called before the secondary module has been explicitly loaded, in which case the placeholder functions would never be called. To improve code size and simplify instantiation under these schemes, add a new `--no-placeholders` option that skips adding imported placeholder functions. --- src/ir/module-splitting.cpp | 55 +++++++++++++++--------- src/ir/module-splitting.h | 6 ++- src/tools/wasm-split/split-options.cpp | 9 ++++ src/tools/wasm-split/split-options.h | 1 + src/tools/wasm-split/wasm-split.cpp | 1 + test/lit/help/wasm-split.test | 5 +++ test/lit/wasm-split/no-placeholders.wast | 54 +++++++++++++++++++++++ 7 files changed, 109 insertions(+), 22 deletions(-) create mode 100644 test/lit/wasm-split/no-placeholders.wast diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 22f8b301f44..adda1f4a6da 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -94,10 +94,9 @@ template void forEachElement(Module& module, F f) { } else if (auto* g = segment->offset->dynCast()) { base = g->name; } - ElementUtils::iterElementSegmentFunctionNames( - segment, [&](Name& entry, Index i) { - f(segment->table, base, offset + i, entry); - }); + for (Index i = 0; i < segment->data.size(); ++i) { + f(segment->table, base, offset + i, segment->data[i]); + } }); } @@ -209,9 +208,12 @@ TableSlotManager::TableSlotManager(Module& module) : module(module) { } // Initialize funcIndices with the functions already in the table. - forEachElement(module, [&](Name table, Name base, Index offset, Name func) { - addSlot(func, {table, base, offset}); - }); + forEachElement(module, + [&](Name table, Name base, Index offset, Expression* elem) { + if (auto* func = elem->dynCast()) { + addSlot(func->func, {table, base, offset}); + } + }); } Table* TableSlotManager::makeTable() { @@ -693,21 +695,32 @@ void ModuleSplitter::setupTablePatching() { // Replace table references to secondary functions with an imported // placeholder that encodes the table index in its name: // `importNamespace`.`index`. - forEachElement(primary, [&](Name, Name, Index index, Name& elem) { - if (secondaryFuncs.count(elem)) { - placeholderMap[index] = elem; - auto* secondaryFunc = secondary.getFunction(elem); - replacedElems[index] = secondaryFunc; - auto placeholder = std::make_unique(); - placeholder->module = config.placeholderNamespace; - placeholder->base = std::to_string(index); - placeholder->name = Names::getValidFunctionName( - primary, std::string("placeholder_") + placeholder->base.toString()); - placeholder->hasExplicitName = true; - placeholder->type = secondaryFunc->type; - elem = placeholder->name; - primary.addFunction(std::move(placeholder)); + forEachElement(primary, [&](Name, Name, Index index, Expression*& elem) { + auto* ref = elem->dynCast(); + if (!ref) { + return; + } + if (!secondaryFuncs.count(ref->func)) { + return; + } + placeholderMap[index] = ref->func; + auto* secondaryFunc = secondary.getFunction(ref->func); + replacedElems[index] = secondaryFunc; + if (!config.usePlaceholders) { + // TODO: This can create active element segments with lots of nulls. We + // should optimize them like we do data segments with zeros. + elem = Builder(primary).makeRefNull(HeapType::nofunc); + return; } + auto placeholder = std::make_unique(); + placeholder->module = config.placeholderNamespace; + placeholder->base = std::to_string(index); + placeholder->name = Names::getValidFunctionName( + primary, std::string("placeholder_") + placeholder->base.toString()); + placeholder->hasExplicitName = true; + placeholder->type = secondaryFunc->type; + elem = Builder(primary).makeRefFunc(placeholder->name, placeholder->type); + primary.addFunction(std::move(placeholder)); }); if (replacedElems.size() == 0) { diff --git a/src/ir/module-splitting.h b/src/ir/module-splitting.h index dc5bb19984a..620993d2d88 100644 --- a/src/ir/module-splitting.h +++ b/src/ir/module-splitting.h @@ -52,11 +52,15 @@ struct Config { // exists. May or may not include imported functions, which are always kept in // the primary module regardless. std::set primaryFuncs; + // Whether to import placeholder functions into the primary module that will + // be called when a secondary function is called before the secondary module + // has been loaded. + bool usePlaceholders = true; // The namespace from which to import primary functions into the secondary // module. Name importNamespace = "primary"; // The namespace from which to import placeholder functions into the primary - // module. + // module. Ignored if `usePlaceholders` is false. Name placeholderNamespace = "placeholder"; // The prefix to attach to the name of any newly created exports. This can be // used to differentiate between "real" exports of the module and exports that diff --git a/src/tools/wasm-split/split-options.cpp b/src/tools/wasm-split/split-options.cpp index 9a93519983f..825efddd973 100644 --- a/src/tools/wasm-split/split-options.cpp +++ b/src/tools/wasm-split/split-options.cpp @@ -176,6 +176,15 @@ WasmSplitOptions::WasmSplitOptions() {Mode::Split}, Options::Arguments::Zero, [&](Options* o, const std::string& argument) { symbolMap = true; }) + .add( + "--no-placeholders", + "", + "Do not import placeholder functions. Calls to secondary functions will " + "fail before the secondary module has been instantiated.", + WasmSplitOption, + {Mode::Split}, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { usePlaceholders = false; }) .add( "--placeholdermap", "", diff --git a/src/tools/wasm-split/split-options.h b/src/tools/wasm-split/split-options.h index 6aa5b001117..b8129f29bf0 100644 --- a/src/tools/wasm-split/split-options.h +++ b/src/tools/wasm-split/split-options.h @@ -41,6 +41,7 @@ struct WasmSplitOptions : ToolOptions { }; StorageKind storageKind = StorageKind::InGlobals; + bool usePlaceholders = true; bool unescape = false; bool verbose = false; bool emitBinary = true; diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index c1ec6052f58..cb148090d64 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -329,6 +329,7 @@ void splitModule(const WasmSplitOptions& options) { if (options.exportPrefix.size()) { config.newExportPrefix = options.exportPrefix; } + config.usePlaceholders = options.usePlaceholders; config.minimizeNewExportNames = !options.passOptions.debugInfo; config.jspi = options.jspi; auto splitResults = ModuleSplitting::splitFunctions(wasm, config); diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index dc521a82f49..4fa534e43ca 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -52,6 +52,11 @@ ;; CHECK-NEXT: --symbolmap [split] Write a symbol map file for each ;; CHECK-NEXT: of the output modules. ;; CHECK-NEXT: +;; CHECK-NEXT: --no-placeholders [split] Do not import placeholder +;; CHECK-NEXT: functions. Calls to secondary functions +;; CHECK-NEXT: will fail before the secondary module has +;; CHECK-NEXT: been instantiated. +;; CHECK-NEXT: ;; CHECK-NEXT: --placeholdermap [split] Write a file mapping placeholder ;; CHECK-NEXT: indices to the function names. ;; CHECK-NEXT: diff --git a/test/lit/wasm-split/no-placeholders.wast b/test/lit/wasm-split/no-placeholders.wast new file mode 100644 index 00000000000..d3f8fd67652 --- /dev/null +++ b/test/lit/wasm-split/no-placeholders.wast @@ -0,0 +1,54 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-split %s -all --no-placeholders --split-funcs=bar,baz -g -o1 %t.1.wasm -o2 %t.2.wasm +;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY +;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY + +(module + ;; PRIMARY: (type $0 (func)) + + ;; PRIMARY: (table $0 2 funcref) + + ;; PRIMARY: (elem $0 (table $0) (i32.const 0) funcref (item (ref.null nofunc)) (item (ref.null nofunc))) + + ;; PRIMARY: (export "foo" (func $foo)) + + ;; PRIMARY: (export "table" (table $0)) + + ;; PRIMARY: (func $foo + ;; PRIMARY-NEXT: (call_indirect (type $0) + ;; PRIMARY-NEXT: (i32.const 0) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (call_indirect (type $0) + ;; PRIMARY-NEXT: (i32.const 1) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: ) + (func $foo + (call $bar) + (call $baz) + ) + ;; SECONDARY: (type $0 (func)) + + ;; SECONDARY: (import "primary" "table" (table $timport$0 2 funcref)) + + ;; SECONDARY: (import "primary" "foo" (func $foo)) + + ;; SECONDARY: (elem $0 (i32.const 0) $bar $baz) + + ;; SECONDARY: (func $bar + ;; SECONDARY-NEXT: (call $foo) + ;; SECONDARY-NEXT: (call $baz) + ;; SECONDARY-NEXT: ) + (func $bar + (call $foo) + (call $baz) + ) + ;; SECONDARY: (func $baz + ;; SECONDARY-NEXT: (call $foo) + ;; SECONDARY-NEXT: (call $bar) + ;; SECONDARY-NEXT: ) + (func $baz + (call $foo) + (call $bar) + ) +) From ed19e3f699ddb72d59f227a9f20846c9ce79e2c6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 16 Sep 2024 15:37:43 -0700 Subject: [PATCH 028/622] [NFC] Move enough of wasm-type.cpp into wasm-type.h to inline core is*() methods (#6936) This just moves code around. As a result, isRef() vanishes entirely from the profiling traces in #6931, since now the core isRef/Tuple/etc. methods are all inlineable. This also required some reordering of wasm-type.h, namely to move HeapType up front. No changes to that class otherwise. TypeInfo is now in the header. getTypeInfo is now a static method on Type. This has the downside of moving internal details into the header, and it may increase compile time a little. The upside is making the --precompute benchmark from #6931 significantly faster, 33%, and it will also help the many Type::isNonNullable() etc. calls we have scattered around the codebase in other passes too. --- src/wasm-type.h | 479 +++++++++++++++++++++++++---------------- src/wasm/wasm-type.cpp | 124 ++--------- 2 files changed, 300 insertions(+), 303 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 03c4f8e772c..5b2074e1c19 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -78,6 +78,227 @@ using HeapTypeNameGenerator = std::function; // HeapType. using TypeID = uint64_t; +enum Shareability { Shared, Unshared }; + +enum class HeapTypeKind { + Basic, + Func, + Struct, + Array, + Cont, +}; + +class HeapType { + // Unlike `Type`, which represents the types of values on the WebAssembly + // stack, `HeapType` is used to describe the structures that reference types + // refer to. HeapTypes are canonicalized and interned exactly like Types and + // should also be passed by value. + uintptr_t id; + +public: + // Bit zero indicates whether the type is `shared`, so we need to leave it + // free. + enum BasicHeapType : uint32_t { + ext = 0 << 1, + func = 1 << 1, + cont = 2 << 1, + any = 3 << 1, + eq = 4 << 1, + i31 = 5 << 1, + struct_ = 6 << 1, + array = 7 << 1, + exn = 8 << 1, + string = 9 << 1, + none = 10 << 1, + noext = 11 << 1, + nofunc = 12 << 1, + nocont = 13 << 1, + noexn = 14 << 1, + }; + static constexpr BasicHeapType _last_basic_type = BasicHeapType(noexn + 1); + + // BasicHeapType can be implicitly upgraded to HeapType + constexpr HeapType(BasicHeapType id) : id(id) {} + + // But converting raw TypeID is more dangerous, so make it explicit + explicit HeapType(TypeID id) : id(id) {} + + // Choose an arbitrary heap type as the default. + constexpr HeapType() : HeapType(func) {} + + // Construct a HeapType referring to the single canonical HeapType for the + // given signature. In nominal mode, this is the first HeapType created with + // this signature. + HeapType(Signature signature); + + HeapType(Continuation cont); + + // Create a HeapType with the given structure. In equirecursive mode, this may + // be the same as a previous HeapType created with the same contents. In + // nominal mode, this will be a fresh type distinct from all previously + // created HeapTypes. + // TODO: make these explicit to differentiate them. + HeapType(const Struct& struct_); + HeapType(Struct&& struct_); + HeapType(Array array); + + HeapTypeKind getKind() const; + + constexpr bool isBasic() const { return id <= _last_basic_type; } + bool isFunction() const { + return isMaybeShared(func) || getKind() == HeapTypeKind::Func; + } + bool isData() const { + auto kind = getKind(); + return isMaybeShared(string) || kind == HeapTypeKind::Struct || + kind == HeapTypeKind::Array; + } + bool isSignature() const { return getKind() == HeapTypeKind::Func; } + bool isContinuation() const { return getKind() == HeapTypeKind::Cont; } + bool isStruct() const { return getKind() == HeapTypeKind::Struct; } + bool isArray() const { return getKind() == HeapTypeKind::Array; } + bool isExn() const { return isMaybeShared(HeapType::exn); } + bool isString() const { return isMaybeShared(HeapType::string); } + bool isBottom() const; + bool isOpen() const; + bool isShared() const { return getShared() == Shared; } + + Shareability getShared() const; + + // Check if the type is a given basic heap type, while ignoring whether it is + // shared or not. + bool isMaybeShared(BasicHeapType type) const { + return isBasic() && getBasic(Unshared) == type; + } + + Signature getSignature() const; + Continuation getContinuation() const; + + const Struct& getStruct() const; + Array getArray() const; + + // If there is a nontrivial (i.e. non-basic, one that was declared by the + // module) nominal supertype, return it, else an empty optional. + std::optional getDeclaredSuperType() const; + + // As |getDeclaredSuperType|, but also handles basic types, that is, if the + // super is a basic type, then we return it here. Declared types are returned + // as well, just like |getDeclaredSuperType|. + std::optional getSuperType() const; + + // Return the depth of this heap type in the nominal type hierarchy, i.e. the + // number of supertypes in its supertype chain. + size_t getDepth() const; + + // Get the bottom heap type for this heap type's hierarchy. + BasicHeapType getUnsharedBottom() const; + BasicHeapType getBottom() const { + return HeapType(getUnsharedBottom()).getBasic(getShared()); + } + + // Get the top heap type for this heap type's hierarchy. + BasicHeapType getUnsharedTop() const; + BasicHeapType getTop() const { + return HeapType(getUnsharedTop()).getBasic(getShared()); + } + + // Get the recursion group for this non-basic type. + RecGroup getRecGroup() const; + + // Get the index of this non-basic type within its recursion group. + size_t getRecGroupIndex() const; + + constexpr TypeID getID() const { return id; } + + // Get the shared or unshared version of this basic heap type. + constexpr BasicHeapType getBasic(Shareability share) const { + assert(isBasic()); + return BasicHeapType(share == Shared ? (id | 1) : (id & ~1)); + } + + // (In)equality must be defined for both HeapType and BasicHeapType because it + // is otherwise ambiguous whether to convert both this and other to int or + // convert other to HeapType. + bool operator==(const HeapType& other) const { return id == other.id; } + bool operator==(const BasicHeapType& other) const { return id == other; } + bool operator!=(const HeapType& other) const { return id != other.id; } + bool operator!=(const BasicHeapType& other) const { return id != other; } + + // Returns true if left is a subtype of right. Subtype includes itself. + static bool isSubType(HeapType left, HeapType right); + + std::vector getTypeChildren() const; + + // Return the ordered HeapType children, looking through child Types. + std::vector getHeapTypeChildren() const; + + // Similar to `getHeapTypeChildren`, but also includes the supertype if it + // exists. + std::vector getReferencedHeapTypes() const; + + // Return the LUB of two HeapTypes, which may or may not exist. + static std::optional getLeastUpperBound(HeapType a, HeapType b); + + // Returns the feature set required to use this type. + FeatureSet getFeatures() const; + + // Helper allowing the value of `print(...)` to be sent to an ostream. Stores + // a `TypeID` because `Type` is incomplete at this point and using a reference + // makes it less convenient to use. + struct Printed { + TypeID typeID; + HeapTypeNameGenerator generateName; + }; + + // Given a function for generating HeapType names, print the definition of + // this HeapType to `os`. `generateName` should return the same + // name each time it is called with the same HeapType and it should return + // different names for different types. + Printed print(HeapTypeNameGenerator generateName) { + return Printed{getID(), generateName}; + } + + std::string toString() const; +}; + +// Internal only. +struct TypeInfo { + using type_t = Type; + // Used in assertions to ensure that temporary types don't leak into the + // global store. + bool isTemp = false; + enum Kind { + TupleKind, + RefKind, + } kind; + struct Ref { + HeapType heapType; + Nullability nullability; + }; + union { + Tuple tuple; + Ref ref; + }; + + TypeInfo(const Tuple& tuple) : kind(TupleKind), tuple(tuple) {} + TypeInfo(Tuple&& tuple) : kind(TupleKind), tuple(std::move(tuple)) {} + TypeInfo(HeapType heapType, Nullability nullable) + : kind(RefKind), ref{heapType, nullable} {} + TypeInfo(const TypeInfo& other); + ~TypeInfo(); + + constexpr bool isTuple() const { return kind == TupleKind; } + constexpr bool isRef() const { return kind == RefKind; } + + // If this TypeInfo represents a Type that can be represented more simply, + // return that simpler Type. For example, this handles eliminating singleton + // tuple types. + std::optional getCanonical() const; + + bool operator==(const TypeInfo& other) const; + bool operator!=(const TypeInfo& other) const { return !(*this == other); } +}; + class Type { // The `id` uniquely represents each type, so type equality is just a // comparison of the ids. For basic types the `id` is just the `BasicType` @@ -150,24 +371,72 @@ class Type { constexpr bool isFloat() const { return id == f32 || id == f64; } constexpr bool isVector() const { return id == v128; }; constexpr bool isNumber() const { return id >= i32 && id <= v128; } - bool isTuple() const; bool isSingle() const { return isConcrete() && !isTuple(); } - bool isRef() const; - bool isFunction() const; - // See literal.h. - bool isData() const; + + // Tuples, refs, etc. are quickly handled using isBasic(), leaving the non- + // basic case for the underlying implementation. + + bool isTuple() const { + if (isBasic()) { + return false; + } else { + return getTypeInfo(*this)->isTuple(); + } + } + + bool isRef() const { + if (isBasic()) { + return false; + } else { + return getTypeInfo(*this)->isRef(); + } + } + + bool isFunction() const { + if (isBasic()) { + return false; + } else { + auto* info = getTypeInfo(*this); + return info->isRef() && info->ref.heapType.isFunction(); + } + } + + bool isData() const { + if (isBasic()) { + return false; + } else { + auto* info = getTypeInfo(*this); + return info->isRef() && info->ref.heapType.isData(); + } + } + // Checks whether a type is a reference and is nullable. This returns false // for a value that is not a reference, that is, for which nullability is // irrelevant. - bool isNullable() const; + bool isNullable() const { + if (isRef()) { + return getTypeInfo(*this)->ref.nullability == Nullable; + } else { + return false; + } + } + // Checks whether a type is a reference and is non-nullable. This returns // false for a value that is not a reference, that is, for which nullability // is irrelevant. (For that reason, this is only the negation of isNullable() // on references, but both return false on non-references.) - bool isNonNullable() const; + bool isNonNullable() const { + if (isRef()) { + return getTypeInfo(*this)->ref.nullability == NonNullable; + } else { + return false; + } + } + + bool isSignature() const { return isRef() && getHeapType().isSignature(); } + // Whether this type is only inhabited by null values. bool isNull() const; - bool isSignature() const; bool isStruct() const; bool isArray() const; bool isExn() const; @@ -222,11 +491,17 @@ class Type { // Returns the tuple, assuming that this is a tuple type. Note that it is // normally simpler to use operator[] and size() on the Type directly. - const Tuple& getTuple() const; + HeapType getHeapType() const { + assert(isRef()); + return getTypeInfo(*this)->ref.heapType; + } // Gets the heap type corresponding to this type, assuming that it is a // reference type. - HeapType getHeapType() const; + const Tuple& getTuple() const { + assert(isTuple()); + return getTypeInfo(*this)->tuple; + } // Returns a number type based on its size in bytes and whether it is a float // type. @@ -308,189 +583,11 @@ class Type { return std::make_reverse_iterator(begin()); } const Type& operator[](size_t i) const { return *Iterator{{this, i}}; } -}; - -enum Shareability { Shared, Unshared }; - -enum class HeapTypeKind { - Basic, - Func, - Struct, - Array, - Cont, -}; - -class HeapType { - // Unlike `Type`, which represents the types of values on the WebAssembly - // stack, `HeapType` is used to describe the structures that reference types - // refer to. HeapTypes are canonicalized and interned exactly like Types and - // should also be passed by value. - uintptr_t id; - -public: - // Bit zero indicates whether the type is `shared`, so we need to leave it - // free. - enum BasicHeapType : uint32_t { - ext = 0 << 1, - func = 1 << 1, - cont = 2 << 1, - any = 3 << 1, - eq = 4 << 1, - i31 = 5 << 1, - struct_ = 6 << 1, - array = 7 << 1, - exn = 8 << 1, - string = 9 << 1, - none = 10 << 1, - noext = 11 << 1, - nofunc = 12 << 1, - nocont = 13 << 1, - noexn = 14 << 1, - }; - static constexpr BasicHeapType _last_basic_type = BasicHeapType(noexn + 1); - - // BasicHeapType can be implicitly upgraded to HeapType - constexpr HeapType(BasicHeapType id) : id(id) {} - - // But converting raw TypeID is more dangerous, so make it explicit - explicit HeapType(TypeID id) : id(id) {} - - // Choose an arbitrary heap type as the default. - constexpr HeapType() : HeapType(func) {} - - // Construct a HeapType referring to the single canonical HeapType for the - // given signature. In nominal mode, this is the first HeapType created with - // this signature. - HeapType(Signature signature); - - HeapType(Continuation cont); - - // Create a HeapType with the given structure. In equirecursive mode, this may - // be the same as a previous HeapType created with the same contents. In - // nominal mode, this will be a fresh type distinct from all previously - // created HeapTypes. - // TODO: make these explicit to differentiate them. - HeapType(const Struct& struct_); - HeapType(Struct&& struct_); - HeapType(Array array); - - HeapTypeKind getKind() const; - - constexpr bool isBasic() const { return id <= _last_basic_type; } - bool isFunction() const { - return isMaybeShared(func) || getKind() == HeapTypeKind::Func; - } - bool isData() const { - auto kind = getKind(); - return isMaybeShared(string) || kind == HeapTypeKind::Struct || - kind == HeapTypeKind::Array; - } - bool isSignature() const { return getKind() == HeapTypeKind::Func; } - bool isContinuation() const { return getKind() == HeapTypeKind::Cont; } - bool isStruct() const { return getKind() == HeapTypeKind::Struct; } - bool isArray() const { return getKind() == HeapTypeKind::Array; } - bool isExn() const { return isMaybeShared(HeapType::exn); } - bool isString() const { return isMaybeShared(HeapType::string); } - bool isBottom() const; - bool isOpen() const; - bool isShared() const { return getShared() == Shared; } - - Shareability getShared() const; - - // Check if the type is a given basic heap type, while ignoring whether it is - // shared or not. - bool isMaybeShared(BasicHeapType type) const { - return isBasic() && getBasic(Unshared) == type; - } - Signature getSignature() const; - Continuation getContinuation() const; - - const Struct& getStruct() const; - Array getArray() const; - - // If there is a nontrivial (i.e. non-basic, one that was declared by the - // module) nominal supertype, return it, else an empty optional. - std::optional getDeclaredSuperType() const; - - // As |getDeclaredSuperType|, but also handles basic types, that is, if the - // super is a basic type, then we return it here. Declared types are returned - // as well, just like |getDeclaredSuperType|. - std::optional getSuperType() const; - - // Return the depth of this heap type in the nominal type hierarchy, i.e. the - // number of supertypes in its supertype chain. - size_t getDepth() const; - - // Get the bottom heap type for this heap type's hierarchy. - BasicHeapType getUnsharedBottom() const; - BasicHeapType getBottom() const { - return HeapType(getUnsharedBottom()).getBasic(getShared()); - } - - // Get the top heap type for this heap type's hierarchy. - BasicHeapType getUnsharedTop() const; - BasicHeapType getTop() const { - return HeapType(getUnsharedTop()).getBasic(getShared()); - } - - // Get the recursion group for this non-basic type. - RecGroup getRecGroup() const; - - // Get the index of this non-basic type within its recursion group. - size_t getRecGroupIndex() const; - - constexpr TypeID getID() const { return id; } - - // Get the shared or unshared version of this basic heap type. - constexpr BasicHeapType getBasic(Shareability share) const { - assert(isBasic()); - return BasicHeapType(share == Shared ? (id | 1) : (id & ~1)); - } - - // (In)equality must be defined for both HeapType and BasicHeapType because it - // is otherwise ambiguous whether to convert both this and other to int or - // convert other to HeapType. - bool operator==(const HeapType& other) const { return id == other.id; } - bool operator==(const BasicHeapType& other) const { return id == other; } - bool operator!=(const HeapType& other) const { return id != other.id; } - bool operator!=(const BasicHeapType& other) const { return id != other; } - - // Returns true if left is a subtype of right. Subtype includes itself. - static bool isSubType(HeapType left, HeapType right); - - std::vector getTypeChildren() const; - - // Return the ordered HeapType children, looking through child Types. - std::vector getHeapTypeChildren() const; - - // Similar to `getHeapTypeChildren`, but also includes the supertype if it - // exists. - std::vector getReferencedHeapTypes() const; - - // Return the LUB of two HeapTypes, which may or may not exist. - static std::optional getLeastUpperBound(HeapType a, HeapType b); - - // Returns the feature set required to use this type. - FeatureSet getFeatures() const; - - // Helper allowing the value of `print(...)` to be sent to an ostream. Stores - // a `TypeID` because `Type` is incomplete at this point and using a reference - // makes it less convenient to use. - struct Printed { - TypeID typeID; - HeapTypeNameGenerator generateName; - }; - - // Given a function for generating HeapType names, print the definition of - // this HeapType to `os`. `generateName` should return the same - // name each time it is called with the same HeapType and it should return - // different names for different types. - Printed print(HeapTypeNameGenerator generateName) { - return Printed{getID(), generateName}; + static TypeInfo* getTypeInfo(Type type) { + assert(!type.isBasic()); + return (TypeInfo*)type.getID(); } - - std::string toString() const; }; inline bool Type::isNull() const { return isRef() && getHeapType().isBottom(); } diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 151d45a1d94..dad8bb669bc 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -41,43 +41,6 @@ namespace wasm { namespace { -struct TypeInfo { - using type_t = Type; - // Used in assertions to ensure that temporary types don't leak into the - // global store. - bool isTemp = false; - enum Kind { - TupleKind, - RefKind, - } kind; - struct Ref { - HeapType heapType; - Nullability nullability; - }; - union { - Tuple tuple; - Ref ref; - }; - - TypeInfo(const Tuple& tuple) : kind(TupleKind), tuple(tuple) {} - TypeInfo(Tuple&& tuple) : kind(TupleKind), tuple(std::move(tuple)) {} - TypeInfo(HeapType heapType, Nullability nullable) - : kind(RefKind), ref{heapType, nullable} {} - TypeInfo(const TypeInfo& other); - ~TypeInfo(); - - constexpr bool isTuple() const { return kind == TupleKind; } - constexpr bool isRef() const { return kind == RefKind; } - - // If this TypeInfo represents a Type that can be represented more simply, - // return that simpler Type. For example, this handles eliminating singleton - // tuple types. - std::optional getCanonical() const; - - bool operator==(const TypeInfo& other) const; - bool operator!=(const TypeInfo& other) const { return !(*this == other); } -}; - using RecGroupInfo = std::vector; struct HeapTypeInfo { @@ -403,11 +366,6 @@ template class equal_to> { namespace wasm { namespace { -TypeInfo* getTypeInfo(Type type) { - assert(!type.isBasic()); - return (TypeInfo*)type.getID(); -} - HeapTypeInfo* getHeapTypeInfo(HeapType ht) { assert(!ht.isBasic()); return (HeapTypeInfo*)ht.getID(); @@ -419,12 +377,14 @@ HeapType asHeapType(std::unique_ptr& info) { Type markTemp(Type type) { if (!type.isBasic()) { - getTypeInfo(type)->isTemp = true; + Type::getTypeInfo(type)->isTemp = true; } return type; } -bool isTemp(Type type) { return !type.isBasic() && getTypeInfo(type)->isTemp; } +bool isTemp(Type type) { + return !type.isBasic() && Type::getTypeInfo(type)->isTemp; +} bool isTemp(HeapType type) { return !type.isBasic() && getHeapTypeInfo(type)->isTemp; @@ -517,6 +477,8 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, return {lubUnshared.getBasic(share)}; } +} // anonymous namespace + TypeInfo::TypeInfo(const TypeInfo& other) { kind = other.kind; switch (kind) { @@ -588,6 +550,8 @@ HeapTypeInfo::~HeapTypeInfo() { WASM_UNREACHABLE("unexpected kind"); } +namespace { + struct TypeStore { std::recursive_mutex mutex; @@ -756,60 +720,6 @@ Type::Type(HeapType heapType, Nullability nullable) { new (this) Type(globalTypeStore.insert(TypeInfo(heapType, nullable))); } -bool Type::isTuple() const { - if (isBasic()) { - return false; - } else { - return getTypeInfo(*this)->isTuple(); - } -} - -bool Type::isRef() const { - if (isBasic()) { - return false; - } else { - return getTypeInfo(*this)->isRef(); - } -} - -bool Type::isFunction() const { - if (isBasic()) { - return false; - } else { - auto* info = getTypeInfo(*this); - return info->isRef() && info->ref.heapType.isFunction(); - } -} - -bool Type::isData() const { - if (isBasic()) { - return false; - } else { - auto* info = getTypeInfo(*this); - return info->isRef() && info->ref.heapType.isData(); - } -} - -bool Type::isNullable() const { - if (isRef()) { - return getTypeInfo(*this)->ref.nullability == Nullable; - } else { - return false; - } -} - -bool Type::isNonNullable() const { - if (isRef()) { - return getTypeInfo(*this)->ref.nullability == NonNullable; - } else { - return false; - } -} - -bool Type::isSignature() const { - return isRef() && getHeapType().isSignature(); -} - bool Type::isStruct() const { return isRef() && getHeapType().isStruct(); } bool Type::isArray() const { return isRef() && getHeapType().isArray(); } @@ -919,16 +829,6 @@ FeatureSet Type::getFeatures() const { return getSingleFeatures(*this); } -const Tuple& Type::getTuple() const { - assert(isTuple()); - return getTypeInfo(*this)->tuple; -} - -HeapType Type::getHeapType() const { - assert(isRef()); - return getTypeInfo(*this)->ref.heapType; -} - Type Type::get(unsigned byteSize, bool float_) { if (byteSize < 4) { return Type::i32; @@ -2111,7 +2011,7 @@ size_t RecGroupHasher::hash(Type type) const { if (type.isBasic()) { wasm::rehash(digest, type.getID()); } else { - hash_combine(digest, hash(*getTypeInfo(type))); + hash_combine(digest, hash(*Type::getTypeInfo(type))); } return digest; } @@ -2243,7 +2143,7 @@ bool RecGroupEquator::eq(Type a, Type b) const { if (a.isBasic() || b.isBasic()) { return a == b; } - return eq(*getTypeInfo(a), *getTypeInfo(b)); + return eq(*Type::getTypeInfo(a), *Type::getTypeInfo(b)); } bool RecGroupEquator::eq(HeapType a, HeapType b) const { @@ -2393,7 +2293,7 @@ template void TypeGraphWalkerBase::scanType(Type* type) { if (type->isBasic()) { return; } - auto* info = getTypeInfo(*type); + auto* info = Type::getTypeInfo(*type); switch (info->kind) { case TypeInfo::TupleKind: { auto& types = info->tuple; @@ -2799,7 +2699,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, auto canonicalizeTypes = [&](bool tuples) { for (auto& [original, uses] : locations.types) { if (original.isTuple() == tuples) { - Type canonical = globalTypeStore.insert(*getTypeInfo(original)); + Type canonical = globalTypeStore.insert(*Type::getTypeInfo(original)); for (Type* use : uses) { *use = canonical; } From 6c07a328e5ce0e1ac187a55d07faf6be642774a5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 16 Sep 2024 15:42:28 -0700 Subject: [PATCH 029/622] Require string-style identifiers to be UTF-8 (#6941) In the WebAssembly text format, strings can generally be arbitrary bytes, but identifiers must be valid UTF-8. Check for UTF-8 validity when parsing string-style identifiers in the lexer. Update StringLowering to generate valid UTF-8 global names even for strings that may not be valid UTF-8 and test that text round tripping works correctly after StringLowering. Fixes #6937. --- src/parser/lexer.cpp | 10 ++++++ src/passes/StringLowering.cpp | 7 ++-- test/lit/parse-bad-identifier.wast | 8 +++++ test/lit/passes/string-gathering.wast | 36 +++++++++---------- test/lit/passes/string-lowering-imports.wast | 26 ++++++++------ .../passes/string-lowering-instructions.wast | 8 ++--- 6 files changed, 61 insertions(+), 34 deletions(-) create mode 100644 test/lit/parse-bad-identifier.wast diff --git a/src/parser/lexer.cpp b/src/parser/lexer.cpp index bb6428e875f..44aecdc2bc1 100644 --- a/src/parser/lexer.cpp +++ b/src/parser/lexer.cpp @@ -280,6 +280,13 @@ struct LexStrResult : LexResult { // Allocate a string only if there are escape sequences, otherwise just use // the original string_view. std::optional str; + + std::string_view getStr() { + if (str) { + return *str; + } + return span; + } }; struct LexStrCtx : LexCtx { @@ -860,6 +867,9 @@ std::optional ident(std::string_view in) { return {}; } if (auto s = str(ctx.next())) { + if (!String::isUTF8(s->getStr())) { + return {}; + } ctx.isStr = true; ctx.str = s->str; ctx.take(*s); diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 349ba8cd00f..081db606876 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -153,9 +153,12 @@ struct StringGathering : public Pass { [[maybe_unused]] bool valid = String::convertWTF16ToWTF8(wtf8, string.str); assert(valid); - // TODO: Use wtf8.view() once we have C++20. + // Then escape it because identifiers must be valid UTF-8. + // TODO: Use wtf8.view() and escaped.view() once we have C++20. + std::stringstream escaped; + String::printEscaped(escaped, wtf8.str()); auto name = Names::getValidGlobalName( - *module, std::string("string.const_") + std::string(wtf8.str())); + *module, std::string("string.const_") + std::string(escaped.str())); globalName = name; newNames.insert(name); auto* stringConst = builder.makeStringConst(string); diff --git a/test/lit/parse-bad-identifier.wast b/test/lit/parse-bad-identifier.wast new file mode 100644 index 00000000000..984e7e2300e --- /dev/null +++ b/test/lit/parse-bad-identifier.wast @@ -0,0 +1,8 @@ +;; RUN: not wasm-opt %s -S -o - 2>&1 | filecheck %s + +;; CHECK: 7:10: error: expected valtype + +(module + ;; String identifiers must still be UTF-8. + (global $"\ff" i32 (i32.const 0)) +) diff --git a/test/lit/passes/string-gathering.wast b/test/lit/passes/string-gathering.wast index 524c6a1f0c1..a6243c2386b 100644 --- a/test/lit/passes/string-gathering.wast +++ b/test/lit/passes/string-gathering.wast @@ -17,14 +17,14 @@ ;; CHECK: (type $0 (func)) - ;; CHECK: (global $string.const_bar (ref string) (string.const "bar")) + ;; CHECK: (global $"string.const_\"bar\"" (ref string) (string.const "bar")) - ;; CHECK: (global $string.const_other (ref string) (string.const "other")) + ;; CHECK: (global $"string.const_\"other\"" (ref string) (string.const "other")) ;; CHECK: (global $global (ref string) (string.const "foo")) (global $global (ref string) (string.const "foo")) - ;; CHECK: (global $global2 stringref (global.get $string.const_bar)) + ;; CHECK: (global $global2 stringref (global.get $"string.const_\"bar\"")) ;; LOWER: (type $0 (array (mut i16))) ;; LOWER: (type $1 (func)) @@ -45,9 +45,9 @@ ;; LOWER: (type $9 (func (param externref i32 i32) (result (ref extern)))) - ;; LOWER: (import "string.const" "0" (global $string.const_bar (ref extern))) + ;; LOWER: (import "string.const" "0" (global $"string.const_\"bar\"" (ref extern))) - ;; LOWER: (import "string.const" "1" (global $string.const_other (ref extern))) + ;; LOWER: (import "string.const" "1" (global $"string.const_\"other\"" (ref extern))) ;; LOWER: (import "string.const" "2" (global $global (ref extern))) @@ -69,12 +69,12 @@ ;; LOWER: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) - ;; LOWER: (global $global2 externref (global.get $string.const_bar)) + ;; LOWER: (global $global2 externref (global.get $"string.const_\"bar\"")) (global $global2 (ref null string) (string.const "bar")) ;; CHECK: (func $a (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $string.const_bar) + ;; CHECK-NEXT: (global.get $"string.const_\"bar\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $global) @@ -82,7 +82,7 @@ ;; CHECK-NEXT: ) ;; LOWER: (func $a (type $1) ;; LOWER-NEXT: (drop - ;; LOWER-NEXT: (global.get $string.const_bar) + ;; LOWER-NEXT: (global.get $"string.const_\"bar\"") ;; LOWER-NEXT: ) ;; LOWER-NEXT: (drop ;; LOWER-NEXT: (global.get $global) @@ -99,10 +99,10 @@ ;; CHECK: (func $b (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $string.const_bar) + ;; CHECK-NEXT: (global.get $"string.const_\"bar\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $string.const_other) + ;; CHECK-NEXT: (global.get $"string.const_\"other\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $global) @@ -113,10 +113,10 @@ ;; CHECK-NEXT: ) ;; LOWER: (func $b (type $1) ;; LOWER-NEXT: (drop - ;; LOWER-NEXT: (global.get $string.const_bar) + ;; LOWER-NEXT: (global.get $"string.const_\"bar\"") ;; LOWER-NEXT: ) ;; LOWER-NEXT: (drop - ;; LOWER-NEXT: (global.get $string.const_other) + ;; LOWER-NEXT: (global.get $"string.const_\"other\"") ;; LOWER-NEXT: ) ;; LOWER-NEXT: (drop ;; LOWER-NEXT: (global.get $global) @@ -217,9 +217,9 @@ (module ;; CHECK: (type $0 (func)) - ;; CHECK: (global $string.const_foo (ref string) (string.const "foo")) + ;; CHECK: (global $"string.const_\"foo\"" (ref string) (string.const "foo")) - ;; CHECK: (global $global (mut (ref string)) (global.get $string.const_foo)) + ;; CHECK: (global $global (mut (ref string)) (global.get $"string.const_\"foo\"")) ;; LOWER: (type $0 (array (mut i16))) ;; LOWER: (type $1 (func (param externref externref) (result i32))) @@ -240,7 +240,7 @@ ;; LOWER: (type $9 (func (param externref i32 i32) (result (ref extern)))) - ;; LOWER: (import "string.const" "0" (global $string.const_foo (ref extern))) + ;; LOWER: (import "string.const" "0" (global $"string.const_\"foo\"" (ref extern))) ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) @@ -260,17 +260,17 @@ ;; LOWER: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) - ;; LOWER: (global $global (mut (ref extern)) (global.get $string.const_foo)) + ;; LOWER: (global $global (mut (ref extern)) (global.get $"string.const_\"foo\"")) (global $global (mut (ref string)) (string.const "foo")) ;; CHECK: (func $a (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $string.const_foo) + ;; CHECK-NEXT: (global.get $"string.const_\"foo\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; LOWER: (func $a (type $2) ;; LOWER-NEXT: (drop - ;; LOWER-NEXT: (global.get $string.const_foo) + ;; LOWER-NEXT: (global.get $"string.const_\"foo\"") ;; LOWER-NEXT: ) ;; LOWER-NEXT: ) (func $a diff --git a/test/lit/passes/string-lowering-imports.wast b/test/lit/passes/string-lowering-imports.wast index 6a908139e04..1c6d62be72d 100644 --- a/test/lit/passes/string-lowering-imports.wast +++ b/test/lit/passes/string-lowering-imports.wast @@ -1,38 +1,44 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: wasm-opt %s -all --string-lowering-magic-imports --remove-unused-module-elements -S -o - | filecheck %s + +;; Text round tripping +;; RUN: wasm-opt %s -all --string-lowering-magic-imports --remove-unused-module-elements -S -o - | wasm-opt -all -S -o - | filecheck %s + +;; Binary round tripping ;; RUN: wasm-opt %s -all --string-lowering-magic-imports --remove-unused-module-elements --roundtrip -S -o - | filecheck %s --check-prefix=RTRIP + (module ;; CHECK: (type $0 (func)) - ;; CHECK: (import "\'" "bar" (global $string.const_bar (ref extern))) + ;; CHECK: (import "\'" "bar" (global $"string.const_\"bar\"" (ref extern))) - ;; CHECK: (import "\'" "foo" (global $string.const_foo (ref extern))) + ;; CHECK: (import "\'" "foo" (global $"string.const_\"foo\"" (ref extern))) - ;; CHECK: (import "\'" "needs\tescaping\00.\'#%- .\r\n\\08\0c\n\r\t.\ea\99\ae" (global $"string.const_needs\tescaping\00.\'#%- .\r\n\\08\0c\n\r\t.\ea\99\ae" (ref extern))) + ;; CHECK: (import "\'" "needs\tescaping\00.\'#%- .\r\n\\08\0c\n\r\t.\ea\99\ae" (global $"string.const_\"needs\\tescaping\\00.\\\'#%- .\\r\\n\\\\08\\0c\\n\\r\\t.\\ea\\99\\ae\"" (ref extern))) - ;; CHECK: (import "string.const" "0" (global $"string.const_unpaired high surrogate \ed\a0\80 " (ref extern))) + ;; CHECK: (import "string.const" "0" (global $"string.const_\"unpaired high surrogate \\ed\\a0\\80 \"" (ref extern))) - ;; CHECK: (import "string.const" "1" (global $"string.const_unpaired low surrogate \ed\bd\88 " (ref extern))) + ;; CHECK: (import "string.const" "1" (global $"string.const_\"unpaired low surrogate \\ed\\bd\\88 \"" (ref extern))) ;; CHECK: (export "consts" (func $consts)) ;; CHECK: (func $consts (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $string.const_foo) + ;; CHECK-NEXT: (global.get $"string.const_\"foo\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $string.const_bar) + ;; CHECK-NEXT: (global.get $"string.const_\"bar\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $"string.const_needs\tescaping\00.\'#%- .\r\n\\08\0c\n\r\t.\ea\99\ae") + ;; CHECK-NEXT: (global.get $"string.const_\"needs\\tescaping\\00.\\\'#%- .\\r\\n\\\\08\\0c\\n\\r\\t.\\ea\\99\\ae\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $"string.const_unpaired high surrogate \ed\a0\80 ") + ;; CHECK-NEXT: (global.get $"string.const_\"unpaired high surrogate \\ed\\a0\\80 \"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $"string.const_unpaired low surrogate \ed\bd\88 ") + ;; CHECK-NEXT: (global.get $"string.const_\"unpaired low surrogate \\ed\\bd\\88 \"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; RTRIP: (type $0 (func)) diff --git a/test/lit/passes/string-lowering-instructions.wast b/test/lit/passes/string-lowering-instructions.wast index d8ba996b2f0..1fd470ff822 100644 --- a/test/lit/passes/string-lowering-instructions.wast +++ b/test/lit/passes/string-lowering-instructions.wast @@ -65,9 +65,9 @@ ;; CHECK: (type $25 (func (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (import "string.const" "0" (global $string.const_exported (ref extern))) + ;; CHECK: (import "string.const" "0" (global $"string.const_\"exported\"" (ref extern))) - ;; CHECK: (import "string.const" "1" (global $string.const_value (ref extern))) + ;; CHECK: (import "string.const" "1" (global $"string.const_\"value\"" (ref extern))) ;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $0))) (import "colliding" "name" (func $fromCodePoint)) @@ -269,7 +269,7 @@ ) ;; CHECK: (func $exported-string-returner (type $16) (result externref) - ;; CHECK-NEXT: (global.get $string.const_exported) + ;; CHECK-NEXT: (global.get $"string.const_\"exported\"") ;; CHECK-NEXT: ) (func $exported-string-returner (export "export.1") (result stringref) ;; We should update the signature of this function even though it is public @@ -368,7 +368,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $struct-of-string - ;; CHECK-NEXT: (global.get $string.const_value) + ;; CHECK-NEXT: (global.get $"string.const_\"value\"") ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) From 92923108b4bb5da059c0ddd46b254234a9d1c7a5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 16 Sep 2024 15:57:33 -0700 Subject: [PATCH 030/622] [wasm-split] Add a multi-split mode (#6943) Add a mode that splits a module into arbitrarily many parts based on a simple manifest file. This is currently implemented by splitting out one module at a time in a loop, but this could change in the future if splitting out all the modules at once would improve the quality of the output. --- src/tools/wasm-split/split-options.cpp | 36 ++- src/tools/wasm-split/split-options.h | 4 + src/tools/wasm-split/wasm-split.cpp | 84 +++++++ test/lit/help/wasm-split.test | 23 +- test/lit/wasm-split/multi-split.wast | 220 ++++++++++++++++++ test/lit/wasm-split/multi-split.wast.manifest | 8 + 6 files changed, 369 insertions(+), 6 deletions(-) create mode 100644 test/lit/wasm-split/multi-split.wast create mode 100644 test/lit/wasm-split/multi-split.wast.manifest diff --git a/src/tools/wasm-split/split-options.cpp b/src/tools/wasm-split/split-options.cpp index 825efddd973..e77957f1fb4 100644 --- a/src/tools/wasm-split/split-options.cpp +++ b/src/tools/wasm-split/split-options.cpp @@ -61,6 +61,9 @@ std::ostream& operator<<(std::ostream& o, WasmSplitOptions::Mode& mode) { case WasmSplitOptions::Mode::Split: o << "split"; break; + case WasmSplitOptions::Mode::MultiSplit: + o << "multi-split"; + break; case WasmSplitOptions::Mode::Instrument: o << "instrument"; break; @@ -91,7 +94,14 @@ WasmSplitOptions::WasmSplitOptions() "Split an input module into two output modules. The default mode.", WasmSplitOption, Options::Arguments::Zero, - [&](Options* o, const std::string& arugment) { mode = Mode::Split; }) + [&](Options* o, const std::string& argument) { mode = Mode::Split; }) + .add( + "--multi-split", + "", + "Split an input module into an arbitrary number of output modules.", + WasmSplitOption, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { mode = Mode::MultiSplit; }) .add( "--instrument", "", @@ -151,6 +161,25 @@ WasmSplitOptions::WasmSplitOptions() [&](Options* o, const std::string& argument) { splitFuncs = parseNameList(argument); }) + .add( + "--manifest", + "", + "File describing the functions to be split into each module. Each " + "section separated by a blank line begins with the base name of an " + "output module, which is followed by a list of functions to place in " + "that module, one per line.", + WasmSplitOption, + {Mode::MultiSplit}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { manifestFile = argument; }) + .add("--out-prefix", + "", + "Prefix prepended to module names in the manifest file to create " + "output file names.", + WasmSplitOption, + {Mode::MultiSplit}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { outPrefix = argument; }) .add("--primary-output", "-o1", "Output file for the primary module.", @@ -313,7 +342,7 @@ WasmSplitOptions::WasmSplitOptions() "-g", "Emit names section in wasm binary (or full debuginfo in wast)", WasmSplitOption, - {Mode::Split, Mode::Instrument}, + {Mode::Split, Mode::MultiSplit, Mode::Instrument}, Options::Arguments::Zero, [&](Options* o, const std::string& arguments) { passOptions.debugInfo = true; @@ -322,7 +351,7 @@ WasmSplitOptions::WasmSplitOptions() "-o", "Output file.", WasmSplitOption, - {Mode::Instrument, Mode::MergeProfiles}, + {Mode::Instrument, Mode::MergeProfiles, Mode::MultiSplit}, Options::Arguments::One, [&](Options* o, const std::string& argument) { output = argument; }) .add("--unescape", @@ -407,6 +436,7 @@ bool WasmSplitOptions::validate() { } switch (mode) { case Mode::Split: + case Mode::MultiSplit: case Mode::Instrument: if (inputFiles.size() > 1) { fail("Cannot have more than one input file."); diff --git a/src/tools/wasm-split/split-options.h b/src/tools/wasm-split/split-options.h index b8129f29bf0..105c90c80ee 100644 --- a/src/tools/wasm-split/split-options.h +++ b/src/tools/wasm-split/split-options.h @@ -26,6 +26,7 @@ const std::string DEFAULT_PROFILE_EXPORT("__write_profile"); struct WasmSplitOptions : ToolOptions { enum class Mode : unsigned { Split, + MultiSplit, Instrument, MergeProfiles, PrintProfile, @@ -68,6 +69,9 @@ struct WasmSplitOptions : ToolOptions { std::string secondaryMemoryName; std::string exportPrefix; + std::string manifestFile; + std::string outPrefix; + // A hack to ensure the split and instrumented modules have the same table // size when using Emscripten's SPLIT_MODULE mode with dynamic linking. TODO: // Figure out a more elegant solution for that use case and remove this. diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index cb148090d64..ea1734b6b09 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -362,6 +362,87 @@ void splitModule(const WasmSplitOptions& options) { writeModule(*secondary, options.secondaryOutput, options); } +void multiSplitModule(const WasmSplitOptions& options) { + if (options.manifestFile.empty()) { + Fatal() << "--multi-split requires --manifest"; + } + if (options.output.empty()) { + Fatal() << "--multi-split requires --output"; + } + + std::ifstream manifest(options.manifestFile); + if (!manifest.is_open()) { + Fatal() << "File not found: " << options.manifestFile; + } + + Module wasm; + parseInput(wasm, options); + + // Map module names to the functions that should be in the modules. + std::map> moduleFuncs; + // The module for which we are currently parsing a set of functions. + std::string currModule; + // The set of functions we are currently inserting into. + std::unordered_set* currFuncs = nullptr; + // Map functions to their modules to ensure no function is assigned to + // multiple modules. + std::unordered_map funcModules; + + std::string line; + bool newSection = true; + while (std::getline(manifest, line)) { + if (line.empty()) { + newSection = true; + continue; + } + if (newSection) { + currModule = line; + currFuncs = &moduleFuncs[line]; + newSection = false; + continue; + } + assert(currFuncs); + currFuncs->insert(line); + auto [it, inserted] = funcModules.insert({line, currModule}); + if (!inserted && it->second != currModule) { + Fatal() << "Function " << line << "cannot be assigned to module " + << currModule << "; it is already assigned to module " + << it->second << '\n'; + } + if (inserted && !options.quiet && !wasm.getFunctionOrNull(line)) { + std::cerr << "warning: Function " << line << " does not exist\n"; + } + } + + ModuleSplitting::Config config; + config.usePlaceholders = false; + config.importNamespace = ""; + config.minimizeNewExportNames = true; + for (auto& func : wasm.functions) { + config.primaryFuncs.insert(func->name); + } + for (auto& [mod, funcs] : moduleFuncs) { + if (options.verbose) { + std::cerr << "Splitting module " << mod << '\n'; + } + if (!options.quiet && funcs.empty()) { + std::cerr << "warning: Module " << mod << " will be empty\n"; + } + for (auto& func : funcs) { + config.primaryFuncs.erase(Name(func)); + } + auto splitResults = ModuleSplitting::splitFunctions(wasm, config); + // TODO: symbolMap, placeholderMap, emitModuleNames + // TODO: Support --emit-text and use .wast in that case. + auto moduleName = options.outPrefix + mod + ".wasm"; + PassRunner runner(&*splitResults.secondary); + runner.add("remove-unused-module-elements"); + runner.run(); + writeModule(*splitResults.secondary, moduleName, options); + } + writeModule(wasm, options.output, options); +} + void mergeProfiles(const WasmSplitOptions& options) { // Read the initial profile. We will merge other profiles into this one. ProfileData data = readProfile(options.inputFiles[0]); @@ -503,6 +584,9 @@ int main(int argc, const char* argv[]) { case WasmSplitOptions::Mode::Split: splitModule(options); break; + case WasmSplitOptions::Mode::MultiSplit: + multiSplitModule(options); + break; case WasmSplitOptions::Mode::Instrument: instrumentModule(options); break; diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 4fa534e43ca..e5e73656205 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -15,6 +15,9 @@ ;; CHECK-NEXT: --split Split an input module into two output ;; CHECK-NEXT: modules. The default mode. ;; CHECK-NEXT: +;; CHECK-NEXT: --multi-split Split an input module into an arbitrary +;; CHECK-NEXT: number of output modules. +;; CHECK-NEXT: ;; CHECK-NEXT: --instrument Instrument an input module to allow it to ;; CHECK-NEXT: generate a profile that can be used to ;; CHECK-NEXT: guide splitting. @@ -43,6 +46,18 @@ ;; CHECK-NEXT: can also pass a file with one function ;; CHECK-NEXT: per line by passing @filename. ;; CHECK-NEXT: +;; CHECK-NEXT: --manifest [multi-split] File describing the +;; CHECK-NEXT: functions to be split into each module. +;; CHECK-NEXT: Each section separated by a blank line +;; CHECK-NEXT: begins with the base name of an output +;; CHECK-NEXT: module, which is followed by a list of +;; CHECK-NEXT: functions to place in that module, one +;; CHECK-NEXT: per line. +;; CHECK-NEXT: +;; CHECK-NEXT: --out-prefix [multi-split] Prefix prepended to module +;; CHECK-NEXT: names in the manifest file to create +;; CHECK-NEXT: output file names. +;; CHECK-NEXT: ;; CHECK-NEXT: --primary-output,-o1 [split] Output file for the primary ;; CHECK-NEXT: module. ;; CHECK-NEXT: @@ -125,10 +140,12 @@ ;; CHECK-NEXT: --emit-text,-S [split, instrument] Emit text instead of ;; CHECK-NEXT: binary for the output file or files. ;; CHECK-NEXT: -;; CHECK-NEXT: --debuginfo,-g [split, instrument] Emit names section in -;; CHECK-NEXT: wasm binary (or full debuginfo in wast) +;; CHECK-NEXT: --debuginfo,-g [split, multi-split, instrument] Emit +;; CHECK-NEXT: names section in wasm binary (or full +;; CHECK-NEXT: debuginfo in wast) ;; CHECK-NEXT: -;; CHECK-NEXT: --output,-o [instrument, merge-profiles] Output file. +;; CHECK-NEXT: --output,-o [instrument, merge-profiles, multi-split] +;; CHECK-NEXT: Output file. ;; CHECK-NEXT: ;; CHECK-NEXT: --unescape,-u Un-escape function names (in ;; CHECK-NEXT: print-profile output) diff --git a/test/lit/wasm-split/multi-split.wast b/test/lit/wasm-split/multi-split.wast new file mode 100644 index 00000000000..9206e60eee5 --- /dev/null +++ b/test/lit/wasm-split/multi-split.wast @@ -0,0 +1,220 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-split -all -g --multi-split %s --manifest %s.manifest --out-prefix=%t -o %t.wasm +;; RUN: wasm-dis %t.wasm | filecheck %s --check-prefix=PRIMARY +;; RUN: wasm-dis %t1.wasm | filecheck %s --check-prefix=CHECK-A +;; RUN: wasm-dis %t2.wasm | filecheck %s --check-prefix=CHECK-B +;; RUN: wasm-dis %t3.wasm | filecheck %s --check-prefix=CHECK-C + +(module + (type $ret-i32 (func (result i32))) + ;; PRIMARY: (type $ret-i64 (func (result i64))) + (type $ret-i64 (func (result i64))) + ;; PRIMARY: (type $ret-f32 (func (result f32))) + (type $ret-f32 (func (result f32))) + ;; CHECK-A: (type $0 (func (result i64))) + + ;; CHECK-A: (type $1 (func (result f32))) + + ;; CHECK-A: (type $2 (func (result i32))) + + ;; CHECK-A: (import "" "table" (table $timport$0 1 funcref)) + + ;; CHECK-A: (import "" "a" (func $B (result i64))) + + ;; CHECK-A: (import "" "b" (func $C (result f32))) + + ;; CHECK-A: (elem $0 (i32.const 0) $A) + + ;; CHECK-A: (func $A (result i32) + ;; CHECK-A-NEXT: (drop + ;; CHECK-A-NEXT: (call_ref $2 + ;; CHECK-A-NEXT: (ref.func $A) + ;; CHECK-A-NEXT: ) + ;; CHECK-A-NEXT: ) + ;; CHECK-A-NEXT: (drop + ;; CHECK-A-NEXT: (call_ref $0 + ;; CHECK-A-NEXT: (ref.func $B) + ;; CHECK-A-NEXT: ) + ;; CHECK-A-NEXT: ) + ;; CHECK-A-NEXT: (drop + ;; CHECK-A-NEXT: (call_ref $1 + ;; CHECK-A-NEXT: (ref.func $C) + ;; CHECK-A-NEXT: ) + ;; CHECK-A-NEXT: ) + ;; CHECK-A-NEXT: (i32.const 0) + ;; CHECK-A-NEXT: ) + (func $A (type $ret-i32) (result i32) + (drop + (call_ref $ret-i32 + (ref.func $A) + ) + ) + (drop + (call_ref $ret-i64 + (ref.func $B) + ) + ) + (drop + (call_ref $ret-f32 + (ref.func $C) + ) + ) + (i32.const 0) + ) + ;; CHECK-B: (type $0 (func (result i32))) + + ;; CHECK-B: (type $1 (func (result f32))) + + ;; CHECK-B: (type $2 (func (result i64))) + + ;; CHECK-B: (import "" "table_3" (table $timport$0 2 funcref)) + + ;; CHECK-B: (import "" "table" (table $timport$1 1 funcref)) + + ;; CHECK-B: (import "" "b" (func $C (result f32))) + + ;; CHECK-B: (elem $0 (table $timport$0) (i32.const 0) func $B $1) + + ;; CHECK-B: (func $B (result i64) + ;; CHECK-B-NEXT: (drop + ;; CHECK-B-NEXT: (call_ref $0 + ;; CHECK-B-NEXT: (ref.func $1) + ;; CHECK-B-NEXT: ) + ;; CHECK-B-NEXT: ) + ;; CHECK-B-NEXT: (drop + ;; CHECK-B-NEXT: (call_ref $2 + ;; CHECK-B-NEXT: (ref.func $B) + ;; CHECK-B-NEXT: ) + ;; CHECK-B-NEXT: ) + ;; CHECK-B-NEXT: (drop + ;; CHECK-B-NEXT: (call_ref $1 + ;; CHECK-B-NEXT: (ref.func $C) + ;; CHECK-B-NEXT: ) + ;; CHECK-B-NEXT: ) + ;; CHECK-B-NEXT: (i64.const 0) + ;; CHECK-B-NEXT: ) + (func $B (type $ret-i64) (result i64) + (drop + (call_ref $ret-i32 + (ref.func $A) + ) + ) + (drop + (call_ref $ret-i64 + (ref.func $B) + ) + ) + (drop + (call_ref $ret-f32 + (ref.func $C) + ) + ) + (i64.const 0) + ) + ;; CHECK-C: (type $0 (func (result i64))) + + ;; CHECK-C: (type $1 (func (result i32))) + + ;; CHECK-C: (type $2 (func (result f32))) + + ;; CHECK-C: (import "" "table_4" (table $timport$0 2 funcref)) + + ;; CHECK-C: (import "" "table_3" (table $timport$1 2 funcref)) + + ;; CHECK-C: (elem $0 (table $timport$0) (i32.const 0) func $0 $C) + + ;; CHECK-C: (func $0 (result i64) + ;; CHECK-C-NEXT: (call_indirect (type $0) + ;; CHECK-C-NEXT: (i32.const 0) + ;; CHECK-C-NEXT: ) + ;; CHECK-C-NEXT: ) + + ;; CHECK-C: (func $C (result f32) + ;; CHECK-C-NEXT: (drop + ;; CHECK-C-NEXT: (call_ref $1 + ;; CHECK-C-NEXT: (ref.func $3) + ;; CHECK-C-NEXT: ) + ;; CHECK-C-NEXT: ) + ;; CHECK-C-NEXT: (drop + ;; CHECK-C-NEXT: (call_ref $0 + ;; CHECK-C-NEXT: (ref.func $2) + ;; CHECK-C-NEXT: ) + ;; CHECK-C-NEXT: ) + ;; CHECK-C-NEXT: (drop + ;; CHECK-C-NEXT: (call_ref $2 + ;; CHECK-C-NEXT: (ref.func $C) + ;; CHECK-C-NEXT: ) + ;; CHECK-C-NEXT: ) + ;; CHECK-C-NEXT: (f32.const 0) + ;; CHECK-C-NEXT: ) + (func $C (type $ret-f32) (result f32) + (drop + (call_ref $ret-i32 + (ref.func $A) + ) + ) + (drop + (call_ref $ret-i64 + (ref.func $B) + ) + ) + (drop + (call_ref $ret-f32 + (ref.func $C) + ) + ) + (f32.const 0) + ) +) +;; PRIMARY: (table $0 1 funcref) + +;; PRIMARY: (table $1 2 funcref) + +;; PRIMARY: (table $2 2 funcref) + +;; PRIMARY: (elem $0 (table $0) (i32.const 0) funcref (item (ref.null nofunc))) + +;; PRIMARY: (elem $1 (table $1) (i32.const 0) funcref (item (ref.null nofunc)) (item (ref.null nofunc))) + +;; PRIMARY: (elem $2 (table $2) (i32.const 0) funcref (item (ref.null nofunc)) (item (ref.null nofunc))) + +;; PRIMARY: (export "a" (func $0)) + +;; PRIMARY: (export "b" (func $1)) + +;; PRIMARY: (export "table" (table $0)) + +;; PRIMARY: (export "table_3" (table $1)) + +;; PRIMARY: (export "table_4" (table $2)) + +;; PRIMARY: (func $0 (result i64) +;; PRIMARY-NEXT: (call_indirect (type $ret-i64) +;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) + +;; PRIMARY: (func $1 (result f32) +;; PRIMARY-NEXT: (call_indirect (type $ret-f32) +;; PRIMARY-NEXT: (i32.const 1) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) + +;; CHECK-B: (func $1 (result i32) +;; CHECK-B-NEXT: (call_indirect (type $0) +;; CHECK-B-NEXT: (i32.const 0) +;; CHECK-B-NEXT: ) +;; CHECK-B-NEXT: ) + +;; CHECK-C: (func $2 (result i64) +;; CHECK-C-NEXT: (call_indirect (type $0) +;; CHECK-C-NEXT: (i32.const 0) +;; CHECK-C-NEXT: ) +;; CHECK-C-NEXT: ) + +;; CHECK-C: (func $3 (result i32) +;; CHECK-C-NEXT: (call_indirect (type $1) +;; CHECK-C-NEXT: (i32.const 1) +;; CHECK-C-NEXT: ) +;; CHECK-C-NEXT: ) diff --git a/test/lit/wasm-split/multi-split.wast.manifest b/test/lit/wasm-split/multi-split.wast.manifest new file mode 100644 index 00000000000..f6e710feda1 --- /dev/null +++ b/test/lit/wasm-split/multi-split.wast.manifest @@ -0,0 +1,8 @@ +1 +A + +2 +B + +3 +C From 34ad6a7598e662e9ff357987f2c81fde1e05c522 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 16 Sep 2024 17:22:22 -0700 Subject: [PATCH 031/622] [wasm-split] Run RemoveUnusedElements on secondary modules (#6945) Rather than analyze what module elements from the primary module a secondary module will need, the splitting logic conservatively imports all module elements from the primary module into the secondary module. Run RemoveUnusedElements on the secondary module to remove any of these imports that happen to be unnecessary. Leave a TODO mentioning the possibility of being more selective about which module elements get exported to reduce code size in the primary module, too. --- src/ir/module-splitting.cpp | 11 ++ src/tools/wasm-split/wasm-split.cpp | 11 +- test/example/module-splitting.txt | 105 ++---------------- test/lit/wasm-split/basic.wast | 1 - test/lit/wasm-split/initial-table-used.wast | 22 ++++ test/lit/wasm-split/initial-table.wast | 3 +- .../lit/wasm-split/jspi-secondary-export.wast | 2 - test/lit/wasm-split/jspi.wast | 2 - test/lit/wasm-split/minimized-exports.wast | 15 ++- test/lit/wasm-split/module-names.wast | 2 +- test/lit/wasm-split/name-collision.wast | 14 ++- test/lit/wasm-split/passive.wast | 4 +- test/lit/wasm-split/ref.func.wast | 8 +- test/lit/wasm-split/segments.wast | 31 +++++- 14 files changed, 110 insertions(+), 121 deletions(-) create mode 100644 test/lit/wasm-split/initial-table-used.wast diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index adda1f4a6da..777818689bd 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -316,6 +316,7 @@ struct ModuleSplitter { void exportImportCalledPrimaryFunctions(); void setupTablePatching(); void shareImportableItems(); + void removeUnusedSecondaryElements(); ModuleSplitter(Module& primary, const Config& config) : config(config), secondaryPtr(initSecondary(primary)), primary(primary), @@ -334,6 +335,7 @@ struct ModuleSplitter { exportImportCalledPrimaryFunctions(); setupTablePatching(); shareImportableItems(); + removeUnusedSecondaryElements(); } }; @@ -872,6 +874,15 @@ void ModuleSplitter::shareImportableItems() { } } +void ModuleSplitter::removeUnusedSecondaryElements() { + // TODO: It would be better to be more selective about only exporting and + // importing those items that the secondary module needs. This would reduce + // code size in the primary module as well. + PassRunner runner(&secondary); + runner.add("remove-unused-module-elements"); + runner.run(); +} + } // anonymous namespace Results splitFunctions(Module& primary, const Config& config) { diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index ea1734b6b09..2b66d116462 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -69,11 +69,15 @@ uint64_t hashFile(const std::string& filename) { return uint64_t(digest); } -void adjustTableSize(Module& wasm, int initialSize) { +void adjustTableSize(Module& wasm, int initialSize, bool secondary = false) { if (initialSize < 0) { return; } if (wasm.tables.empty()) { + if (secondary) { + // It's not a problem if the table is not used in the secondary module. + return; + } Fatal() << "--initial-table used but there is no table"; } @@ -336,7 +340,7 @@ void splitModule(const WasmSplitOptions& options) { auto& secondary = splitResults.secondary; adjustTableSize(wasm, options.initialTableSize); - adjustTableSize(*secondary, options.initialTableSize); + adjustTableSize(*secondary, options.initialTableSize, /*secondary=*/true); if (options.symbolMap) { writeSymbolMap(wasm, options.primaryOutput + ".symbols"); @@ -435,9 +439,6 @@ void multiSplitModule(const WasmSplitOptions& options) { // TODO: symbolMap, placeholderMap, emitModuleNames // TODO: Support --emit-text and use .wast in that case. auto moduleName = options.outPrefix + mod + ".wasm"; - PassRunner runner(&*splitResults.secondary); - runner.add("remove-unused-module-elements"); - runner.run(); writeModule(*splitResults.secondary, moduleName, options); } writeModule(wasm, options.output, options); diff --git a/test/example/module-splitting.txt b/test/example/module-splitting.txt index b72867dda97..fe878d739ab 100644 --- a/test/example/module-splitting.txt +++ b/test/example/module-splitting.txt @@ -33,11 +33,6 @@ After: ) Secondary: (module - (type $0 (func (param i32))) - (import "primary" "%memory" (memory $mem 3 42 shared)) - (import "primary" "%table" (table $tab 3 42 funcref)) - (import "primary" "%global" (global $glob (mut i32))) - (import "primary" "%tag" (tag $e (param i32))) ) @@ -64,11 +59,6 @@ After: ) Secondary: (module - (type $0 (func (param i32))) - (import "primary" "%memory" (memory $mem 3 42 shared)) - (import "primary" "%table" (table $tab 3 42 funcref)) - (import "primary" "%global" (global $glob (mut i32))) - (import "primary" "%tag" (tag $e (param i32))) ) @@ -99,11 +89,6 @@ After: ) Secondary: (module - (type $0 (func (param i32))) - (import "primary" "mem" (memory $mem 3 42 shared)) - (import "primary" "tab" (table $tab 3 42 funcref)) - (import "primary" "glob" (global $glob (mut i32))) - (import "primary" "e" (tag $e (param i32))) ) @@ -171,7 +156,6 @@ After: ) Secondary: (module - (import "primary" "%table" (table $table 1 funcref)) ) @@ -197,7 +181,6 @@ After: ) Secondary: (module - (import "primary" "%table" (table $table 2 funcref)) ) @@ -226,8 +209,6 @@ After: ) Secondary: (module - (import "primary" "%table" (table $table 1 funcref)) - (import "primary" "%global" (global $base i32)) ) @@ -256,8 +237,6 @@ After: ) Secondary: (module - (import "primary" "%table" (table $table 2 funcref)) - (import "primary" "%global" (global $base i32)) ) @@ -297,7 +276,6 @@ After: ) Secondary: (module - (import "primary" "%table" (table $table 1000 funcref)) ) @@ -324,8 +302,6 @@ After: ) Secondary: (module - (import "primary" "%table" (table $table 1000 funcref)) - (import "primary" "%global" (global $base i32)) ) @@ -342,10 +318,6 @@ After: ) Secondary: (module - (type $0 (func (param i32) (result i32))) - (func $foo (type $0) (param $0 i32) (result i32) - (local.get $0) - ) ) @@ -415,8 +387,7 @@ Secondary: (module (type $0 (func (param i32) (result i32))) (import "primary" "%table_1" (table $0 1 funcref)) - (import "primary" "%table" (table $table 1 funcref)) - (elem $0 (table $0) (i32.const 0) func $foo) + (elem $0 (i32.const 0) $foo) (func $foo (type $0) (param $0 i32) (result i32) (local.get $0) ) @@ -454,8 +425,7 @@ Secondary: (module (type $0 (func (param i32) (result i32))) (import "primary" "%table_1" (table $0 1 funcref)) - (import "primary" "%table" (table $table 2 funcref)) - (elem $0 (table $0) (i32.const 0) func $foo) + (elem $0 (i32.const 0) $foo) (func $foo (type $0) (param $0 i32) (result i32) (local.get $0) ) @@ -501,8 +471,7 @@ Secondary: (module (type $0 (func (param i32) (result i32))) (import "primary" "%table_2" (table $0 1 funcref)) - (import "primary" "%table" (table $table 1000 funcref)) - (elem $0 (table $0) (i32.const 0) func $foo) + (elem $0 (i32.const 0) $foo) (func $foo (type $0) (param $0 i32) (result i32) (local.get $0) ) @@ -551,9 +520,7 @@ Secondary: (module (type $0 (func (param i32) (result i32))) (import "primary" "%table_2" (table $0 1 funcref)) - (import "primary" "%table" (table $table 1000 funcref)) - (import "primary" "%global" (global $base i32)) - (elem $0 (table $0) (i32.const 0) func $foo) + (elem $0 (i32.const 0) $foo) (func $foo (type $0) (param $0 i32) (result i32) (local.get $0) ) @@ -602,9 +569,7 @@ Secondary: (module (type $0 (func (param i32) (result i32))) (import "primary" "%table_2" (table $0 1 funcref)) - (import "primary" "%table" (table $table 1000 funcref)) - (import "primary" "%global" (global $base i32)) - (elem $0 (table $0) (i32.const 0) func $foo) + (elem $0 (i32.const 0) $foo) (func $foo (type $0) (param $0 i32) (result i32) (local.get $0) ) @@ -661,9 +626,7 @@ Secondary: (module (type $0 (func (param i32) (result i32))) (import "primary" "%table_2" (table $0 1 funcref)) - (import "primary" "%table" (table $table 1000 funcref)) - (import "primary" "%global" (global $base i32)) - (elem $0 (table $0) (i32.const 0) func $foo) + (elem $0 (i32.const 0) $foo) (func $foo (type $0) (param $0 i32) (result i32) (local.get $0) ) @@ -717,11 +680,6 @@ After: ) Secondary: (module - (type $0 (func)) - (import "primary" "%bar" (func $bar (type $0))) - (func $foo (type $0) - (call $bar) - ) ) @@ -776,13 +734,6 @@ After: ) Secondary: (module - (type $0 (func)) - (func $bar (type $0) - (nop) - ) - (func $foo (type $0) - (call $bar) - ) ) @@ -879,8 +830,7 @@ Secondary: (module (type $0 (func)) (import "primary" "%table_1" (table $0 2 funcref)) - (import "primary" "%table" (table $table 4 funcref)) - (elem $0 (table $0) (i32.const 0) func $foo $baz) + (elem $0 (i32.const 0) $foo $baz) (func $baz (type $0) (nop) ) @@ -944,9 +894,7 @@ Secondary: (module (type $0 (func)) (import "primary" "%table_1" (table $0 2 funcref)) - (import "primary" "%table" (table $table 4 funcref)) - (import "primary" "%global" (global $base i32)) - (elem $0 (table $0) (i32.const 0) func $foo $baz) + (elem $0 (i32.const 0) $foo $baz) (func $baz (type $0) (nop) ) @@ -1010,8 +958,7 @@ Secondary: (module (type $0 (func)) (import "primary" "%table_1" (table $0 3 funcref)) - (import "primary" "%table" (table $table 4 funcref)) - (elem $0 (table $0) (i32.const 0) func $foo $bar $quux) + (elem $0 (i32.const 0) $foo $bar $quux) (func $bar (type $0) (nop) ) @@ -1081,9 +1028,7 @@ Secondary: (module (type $0 (func)) (import "primary" "%table_1" (table $0 3 funcref)) - (import "primary" "%table" (table $table 4 funcref)) - (import "primary" "%global" (global $base i32)) - (elem $0 (table $0) (i32.const 0) func $foo $bar $quux) + (elem $0 (i32.const 0) $foo $bar $quux) (func $bar (type $0) (nop) ) @@ -1136,10 +1081,8 @@ Secondary: (module (type $0 (func)) (import "primary" "%table_2" (table $0 1 funcref)) - (import "primary" "%table" (table $table 2 funcref)) - (import "primary" "%global" (global $base i32)) (import "primary" "%foo" (func $foo (type $0))) - (elem $0 (table $0) (i32.const 0) func $bar) + (elem $0 (i32.const 0) $bar) (func $bar (type $0) (call $foo) ) @@ -1185,9 +1128,8 @@ Secondary: (module (type $0 (func (param i32) (result i32))) (import "primary" "%table_2" (table $0 1 funcref)) - (import "primary" "%table" (table $table 1 1 funcref)) (import "primary" "%foo" (func $foo (type $0) (param i32) (result i32))) - (elem $0 (table $0) (i32.const 0) func $bar) + (elem $0 (i32.const 0) $bar) (func $bar (type $0) (param $0 i32) (result i32) (call $foo (i32.const 1) @@ -1279,28 +1221,5 @@ Minimized names primary: Minimized names secondary: (module - (type $0 (func)) - (import "primary" "%a" (func $0 (type $0))) - (import "primary" "%c" (func $1 (type $0))) - (import "primary" "%d" (func $2 (type $0))) - (import "primary" "already_exported" (func $3 (type $0))) - (import "primary" "%e" (func $4 (type $0))) - (import "primary" "%f" (func $5 (type $0))) - (import "primary" "%g" (func $6 (type $0))) - (import "primary" "%b" (func $7 (type $0))) - (import "primary" "%h" (func $8 (type $0))) - (import "primary" "%i" (func $9 (type $0))) - (func $call (type $0) - (call $0) - (call $1) - (call $2) - (call $3) - (call $4) - (call $5) - (call $6) - (call $7) - (call $8) - (call $9) - ) ) diff --git a/test/lit/wasm-split/basic.wast b/test/lit/wasm-split/basic.wast index 99bf17260e3..1390f1a6373 100644 --- a/test/lit/wasm-split/basic.wast +++ b/test/lit/wasm-split/basic.wast @@ -174,7 +174,6 @@ ;; KEEP-BOTH-PRIMARY-NEXT: ) ;; KEEP-BOTH-SECONDARY: (module -;; KEEP-BOTH-SECONDARY-NEXT: (import "primary" "%table" (table $table 1 1 funcref)) ;; KEEP-BOTH-SECONDARY-NEXT: ) ;; SPLIT-BAR-SUPERSEDE: warning: function bar was to be kept in primary module. However it will now be split out into secondary module. diff --git a/test/lit/wasm-split/initial-table-used.wast b/test/lit/wasm-split/initial-table-used.wast new file mode 100644 index 00000000000..23ae068c670 --- /dev/null +++ b/test/lit/wasm-split/initial-table-used.wast @@ -0,0 +1,22 @@ +;; Test that the --initial-table flag works as expected when the secondary +;; module uses the table. + +;; RUN: wasm-split %s --instrument --initial-table=1234 -S | filecheck %s + +;; RUN: wasm-split %s -g -o1 %t.1.wasm -o2 %t.2.wasm --split-funcs=use-table --initial-table=1234 +;; RUN: wasm-dis %t.1.wasm | filecheck %s +;; RUN: wasm-dis %t.2.wasm | filecheck %s + +;; CHECK: (table $table 1234 funcref) + +(module + (table $table 3 funcref) + (func $use-table + (call_indirect + (i32.const 0) + ) + ) + (func $use-use-table + (call $use-table) + ) +) diff --git a/test/lit/wasm-split/initial-table.wast b/test/lit/wasm-split/initial-table.wast index 534f7f3a956..cd2de4c55a9 100644 --- a/test/lit/wasm-split/initial-table.wast +++ b/test/lit/wasm-split/initial-table.wast @@ -1,10 +1,9 @@ -;; Test that the --initial-table flag works as expected +;; Test that the --initial-table flag works as expected. ;; RUN: wasm-split %s --instrument --initial-table=1234 -S | filecheck %s ;; RUN: wasm-split %s -g -o1 %t.1.wasm -o2 %t.2.wasm --initial-table=1234 ;; RUN: wasm-dis %t.1.wasm | filecheck %s -;; RUN: wasm-dis %t.2.wasm | filecheck %s ;; CHECK: (table $table 1234 funcref) diff --git a/test/lit/wasm-split/jspi-secondary-export.wast b/test/lit/wasm-split/jspi-secondary-export.wast index 553f0abd18a..6e89bab28b0 100644 --- a/test/lit/wasm-split/jspi-secondary-export.wast +++ b/test/lit/wasm-split/jspi-secondary-export.wast @@ -48,8 +48,6 @@ ;; SECONDARY: (import "primary" "%table" (table $timport$0 1 funcref)) - ;; SECONDARY: (import "primary" "load_secondary_module_status" (global $gimport$0 (mut i32))) - ;; SECONDARY: (elem $0 (i32.const 0) $bar) ;; SECONDARY: (func $bar (param $0 i32) (result i32) diff --git a/test/lit/wasm-split/jspi.wast b/test/lit/wasm-split/jspi.wast index e7d6814b7b9..d86775c8225 100644 --- a/test/lit/wasm-split/jspi.wast +++ b/test/lit/wasm-split/jspi.wast @@ -48,8 +48,6 @@ ;; SECONDARY: (import "primary" "%table" (table $timport$0 1 funcref)) - ;; SECONDARY: (import "primary" "load_secondary_module_status" (global $gimport$0 (mut i32))) - ;; SECONDARY: (import "primary" "foo" (func $foo (param i32) (result i32))) ;; SECONDARY: (elem $0 (i32.const 0) $bar) diff --git a/test/lit/wasm-split/minimized-exports.wast b/test/lit/wasm-split/minimized-exports.wast index 4ee73a35b47..e938f4ab69e 100644 --- a/test/lit/wasm-split/minimized-exports.wast +++ b/test/lit/wasm-split/minimized-exports.wast @@ -1,23 +1,34 @@ -;; RUN: wasm-split %s --keep-funcs=foo,bar --export-prefix='%' -o1 %t.1.wasm -o2 %t.2.wasm +;; RUN: wasm-split %s --keep-funcs=foo,bar --export-prefix='%' -o1 %t.1.wasm -o2 %t.2.wasm --no-placeholders ;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY ;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY ;; PRIMARY: (module ;; PRIMARY-NEXT: (type $0 (func)) +;; PRIMARY-NEXT: (table $0 1 funcref) +;; PRIMARY-NEXT: (elem $0 (table $0) (i32.const 0) funcref (item (ref.null nofunc))) +;; PRIMARY-NEXT: (export "baz" (func $2) ;; PRIMARY-NEXT: (export "%a" (func $1)) ;; PRIMARY-NEXT: (export "%b" (func $0)) +;; PRIMARY-NEXT: (export "%table" (table $0)) ;; PRIMARY-NEXT: (func $0 ;; PRIMARY-NEXT: (nop) ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: (func $1 ;; PRIMARY-NEXT: (nop) ;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (func $2 +;; PRIMARY-NEXT: (call_indirect (type $0) +;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) ;; SECONDARY: (module ;; SECONDARY-NEXT: (type $0 (func)) +;; SECONDARY-NEXT: (import "primary" "%table" (table $timport$0 1 funcref)) ;; SECONDARY-NEXT: (import "primary" "%a" (func $fimport$0)) ;; SECONDARY-NEXT: (import "primary" "%b" (func $fimport$1)) +;; SECONDARY-NEXT: (elem $0 (i32.const 0) $0) ;; SECONDARY-NEXT: (func $0 ;; SECONDARY-NEXT: (call $fimport$1) ;; SECONDARY-NEXT: (call $fimport$0) @@ -31,7 +42,7 @@ (func $bar (nop) ) - (func $baz + (func $baz (export "baz") (call $foo) (call $bar) ) diff --git a/test/lit/wasm-split/module-names.wast b/test/lit/wasm-split/module-names.wast index 9ed80ae7c6d..96dc96c3b0d 100644 --- a/test/lit/wasm-split/module-names.wast +++ b/test/lit/wasm-split/module-names.wast @@ -16,7 +16,7 @@ (module (func $foo - (nop) + (call $bar) ) (func $bar (nop) diff --git a/test/lit/wasm-split/name-collision.wast b/test/lit/wasm-split/name-collision.wast index ccb615906f5..bb227f41508 100644 --- a/test/lit/wasm-split/name-collision.wast +++ b/test/lit/wasm-split/name-collision.wast @@ -2,7 +2,7 @@ ;; non-function exports would result in the wrong import names being used in the ;; secondary module. -;; RUN: wasm-split %s -o1 %t.1.wasm -o2 %t.2.wasm +;; RUN: wasm-split %s -o1 %t.1.wasm -o2 %t.2.wasm --split-funcs=use ;; RUN: wasm-dis %t.2.wasm | filecheck %s ;; CHECK-NOT: (import "primary" "memory" (table @@ -13,4 +13,16 @@ (memory $collide 1 1) (export "table" (table $collide)) (export "memory" (memory $collide)) + (func $use + (call_indirect + (i32.const 0) + ) + (i32.store + (i32.const 0) + (i32.const 0) + ) + ) + (func $use-use + (call $use) + ) ) diff --git a/test/lit/wasm-split/passive.wast b/test/lit/wasm-split/passive.wast index 322288201b6..743d7d14e0c 100644 --- a/test/lit/wasm-split/passive.wast +++ b/test/lit/wasm-split/passive.wast @@ -37,9 +37,7 @@ ;; SECONDARY: (import "primary" "table_1" (table $timport$0 1 funcref)) - ;; SECONDARY: (import "primary" "table" (table $table 3 funcref)) - - ;; SECONDARY: (elem $0 (table $timport$0) (i32.const 0) func $second-in-table) + ;; SECONDARY: (elem $0 (i32.const 0) $second-in-table) ;; SECONDARY: (func $second-in-table (type $0) ;; SECONDARY-NEXT: (nop) diff --git a/test/lit/wasm-split/ref.func.wast b/test/lit/wasm-split/ref.func.wast index 39cce5dea82..d9a30890ac5 100644 --- a/test/lit/wasm-split/ref.func.wast +++ b/test/lit/wasm-split/ref.func.wast @@ -65,15 +65,9 @@ ;; SECONDARY: (import "primary" "table_2" (table $timport$0 2 funcref)) - ;; SECONDARY: (import "primary" "table" (table $table 1 1 funcref)) - - ;; SECONDARY: (import "primary" "global" (global $glob1 (ref func))) - - ;; SECONDARY: (import "primary" "global_4" (global $glob2 (ref func))) - ;; SECONDARY: (import "primary" "prime" (func $prime (type $0))) - ;; SECONDARY: (elem $0 (table $timport$0) (i32.const 0) func $second $second-in-table) + ;; SECONDARY: (elem $0 (i32.const 0) $second $second-in-table) ;; SECONDARY: (elem declare func $prime) diff --git a/test/lit/wasm-split/segments.wast b/test/lit/wasm-split/segments.wast index 7a14afc423b..215afd247fc 100644 --- a/test/lit/wasm-split/segments.wast +++ b/test/lit/wasm-split/segments.wast @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: wasm-split %s -all --keep-funcs=foo -g -o1 %t.1.wasm -o2 %t.2.wasm +;; RUN: wasm-split %s -all --keep-funcs=use-funcs -g -o1 %t.1.wasm -o2 %t.2.wasm ;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY ;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY @@ -14,17 +14,25 @@ ;; PRIMARY: (type $elem-array (array externref)) (type $elem-array (array externref)) + ;; PRIMARY: (import "placeholder" "0" (func $placeholder_0)) + ;; PRIMARY: (memory $mem 0) (memory $mem 0) ;; PRIMARY: (data $data "hello world") (data $data "hello world") + ;; PRIMARY: (table $0 1 funcref) + ;; PRIMARY: (elem $elem externref) (elem $elem externref) + ;; PRIMARY: (elem $1 (i32.const 0) $placeholder_0) + ;; PRIMARY: (export "memory" (memory $mem)) + ;; PRIMARY: (export "table" (table $0)) + ;; PRIMARY: (func $data.drop ;; PRIMARY-NEXT: (data.drop $data) ;; PRIMARY-NEXT: ) @@ -83,7 +91,9 @@ ;; SECONDARY: (type $0 (func)) - ;; SECONDARY: (import "primary" "memory" (memory $mem 0)) + ;; SECONDARY: (import "primary" "table" (table $timport$0 1 funcref)) + + ;; SECONDARY: (elem $0 (i32.const 0) $no-segment) ;; SECONDARY: (func $no-segment ;; SECONDARY-NEXT: (nop) @@ -91,4 +101,21 @@ (func $no-segment (nop) ) + + ;; PRIMARY: (func $use-funcs + ;; PRIMARY-NEXT: (call $data.drop) + ;; PRIMARY-NEXT: (call $memory.init) + ;; PRIMARY-NEXT: (call $array.new_data) + ;; PRIMARY-NEXT: (call $array.new_elem) + ;; PRIMARY-NEXT: (call_indirect (type $0) + ;; PRIMARY-NEXT: (i32.const 0) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: ) + (func $use-funcs + (call $data.drop) + (call $memory.init) + (call $array.new_data) + (call $array.new_elem) + (call $no-segment) + ) ) From 0da6d3e5b729e1fe7cc608720dc3d428fefcdb03 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 17 Sep 2024 11:25:18 -0700 Subject: [PATCH 032/622] Fix selects of packed fields in GlobalStructOptimization (#6947) We emit a select between two objects when only two objects exist of a particular type. However, if the field is packed, we did not handle truncating the written values. --- src/passes/GlobalStructInference.cpp | 6 ++-- test/lit/passes/gsi.wast | 49 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 7526cdb76b0..4158db05104 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -50,6 +50,7 @@ #include +#include "ir/bits.h" #include "ir/debuginfo.h" #include "ir/find_all.h" #include "ir/module-utils.h" @@ -421,8 +422,9 @@ struct GlobalStructInference : public Pass { Expression* ret; if (value.isConstant()) { // This is known to be a constant, so simply emit an expression for - // that constant. - ret = value.getConstant().makeExpression(wasm); + // that constant, and handle if the field is packed. + auto* expr = value.getConstant().makeExpression(wasm); + ret = Bits::makePackedFieldGet(expr, field, curr->signed_, wasm); } else { // Otherwise, this is non-constant, so we are in the situation where // we want to un-nest the value out of the struct.new it is in. Note diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast index 57d8137f493..299b19e00f2 100644 --- a/test/lit/passes/gsi.wast +++ b/test/lit/passes/gsi.wast @@ -1895,3 +1895,52 @@ ) ) ) + +;; Test packed fields. +(module + ;; CHECK: (type $struct (struct (field i8))) + (type $struct (struct (field i8))) + + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (global $A (ref $struct) (struct.new $struct + ;; CHECK-NEXT: (i32.const 257) + ;; CHECK-NEXT: )) + (global $A (ref $struct) (struct.new $struct + (i32.const 257) + )) + + ;; CHECK: (global $B (ref $struct) (struct.new $struct + ;; CHECK-NEXT: (i32.const 258) + ;; CHECK-NEXT: )) + (global $B (ref $struct) (struct.new $struct + (i32.const 258) + )) + + ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 257) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 258) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result i32) + ;; We can infer this value is one of two things since only two objects exist + ;; of this type. We must emit the proper truncated value for them, as the + ;; values are truncated into i8. + (struct.get_u $struct 0 + (global.get $A) + ) + ) +) From f9b64c8c5d9ad720304e101dc58790f3bbfdfc3c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Sep 2024 11:49:45 -0700 Subject: [PATCH 033/622] [wasm-split] Simplify handling of --keep-funcs and --split-funcs (#6948) Maintain the invariant that every defined functions belongs to either the set of kept functions or the set of split functions. Functions are kept by default except when --keep-funcs is specified without --split-funcs on the command line. This is mostly NFC except that it changes the default behavior when no arguments are specified on the command line to keep all functions. This will simplify a follow-on PR that switches from passing the kept functions to the module splitting utility to passing the split functions. --- src/tools/wasm-split/split-options.cpp | 2 + src/tools/wasm-split/split-options.h | 2 + src/tools/wasm-split/wasm-split.cpp | 130 +++++++++--------- test/lit/wasm-split/basic.wast | 12 +- .../lit/wasm-split/jspi-secondary-export.wast | 2 +- test/lit/wasm-split/profile-guided.wast | 1 - 6 files changed, 74 insertions(+), 75 deletions(-) diff --git a/src/tools/wasm-split/split-options.cpp b/src/tools/wasm-split/split-options.cpp index e77957f1fb4..cada7930b2e 100644 --- a/src/tools/wasm-split/split-options.cpp +++ b/src/tools/wasm-split/split-options.cpp @@ -147,6 +147,7 @@ WasmSplitOptions::WasmSplitOptions() Options::Arguments::One, [&](Options* o, const std::string& argument) { keepFuncs = parseNameList(argument); + hasKeepFuncs = true; }) .add("--split-funcs", "", @@ -160,6 +161,7 @@ WasmSplitOptions::WasmSplitOptions() Options::Arguments::One, [&](Options* o, const std::string& argument) { splitFuncs = parseNameList(argument); + hasSplitFuncs = true; }) .add( "--manifest", diff --git a/src/tools/wasm-split/split-options.h b/src/tools/wasm-split/split-options.h index 105c90c80ee..8f23928c322 100644 --- a/src/tools/wasm-split/split-options.h +++ b/src/tools/wasm-split/split-options.h @@ -58,6 +58,8 @@ struct WasmSplitOptions : ToolOptions { std::set keepFuncs; std::set splitFuncs; + bool hasKeepFuncs = false; + bool hasSplitFuncs = false; std::vector inputFiles; std::string output; diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 2b66d116462..1cfe5bad945 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -216,6 +216,7 @@ void splitModule(const WasmSplitOptions& options) { Module wasm; parseInput(wasm, options); + // All defined functions will be in one set or the other. std::set keepFuncs; std::set splitFuncs; @@ -224,57 +225,53 @@ void splitModule(const WasmSplitOptions& options) { uint64_t hash = hashFile(options.inputFiles[0]); getFunctionsToKeepAndSplit( wasm, hash, options.profileFile, keepFuncs, splitFuncs); + } else { + // Normally the default is to keep each function, but if --keep-funcs is the + // only thing specified, then all other functions will be split. + bool defaultSplit = options.hasKeepFuncs && !options.hasSplitFuncs; + if (defaultSplit) { + ModuleUtils::iterDefinedFunctions( + wasm, [&](Function* func) { splitFuncs.insert(func->name); }); + } else { + ModuleUtils::iterDefinedFunctions( + wasm, [&](Function* func) { keepFuncs.insert(func->name); }); + } } - if (options.keepFuncs.size()) { - // Use the explicitly provided `keepFuncs`. - for (auto& func : options.keepFuncs) { - if (!options.quiet && wasm.getFunctionOrNull(func) == nullptr) { + // Use the explicitly provided `keepFuncs`. + for (auto& func : options.keepFuncs) { + if (!wasm.getFunctionOrNull(func)) { + if (!options.quiet) { std::cerr << "warning: function " << func << " does not exist\n"; - continue; } - - keepFuncs.insert(func); - splitFuncs.erase(func); + continue; } + keepFuncs.insert(func); + splitFuncs.erase(func); } - if (options.splitFuncs.size()) { - // Use the explicitly provided `splitFuncs`. - for (auto& func : options.splitFuncs) { - auto* function = wasm.getFunctionOrNull(func); - if (!options.quiet && function == nullptr) { + // Use the explicitly provided `splitFuncs`. + for (auto& func : options.splitFuncs) { + auto* function = wasm.getFunctionOrNull(func); + if (!function) { + if (!options.quiet) { std::cerr << "warning: function " << func << " does not exist\n"; - continue; - } - if (function && function->imported()) { - if (!options.quiet) { - std::cerr << "warning: cannot split out imported function " << func - << "\n"; - } - } else { - if (!options.quiet && keepFuncs.count(func) > 0) { - std::cerr - << "warning: function " << func - << " was to be kept in primary module. " - << "However it will now be split out into secondary module.\n"; - } - - splitFuncs.insert(func); - keepFuncs.erase(func); } + continue; } - - if (keepFuncs.empty()) { - // could be the case where every function has been split out - // or when `splitFuncs` is used standalone, which is the case we'll cover - // here - for (auto& func : wasm.functions) { - if (splitFuncs.count(func->name) == 0) { - keepFuncs.insert(func->name); - } + if (function->imported()) { + if (!options.quiet) { + std::cerr << "warning: cannot split out imported function " << func + << "\n"; } + continue; } + if (!options.quiet && options.keepFuncs.count(func)) { + std::cerr << "warning: function " << func + << " was to be both kept and split. It will be split.\n"; + } + splitFuncs.insert(func); + keepFuncs.erase(func); } if (!options.quiet && keepFuncs.size() == 0) { @@ -284,43 +281,42 @@ void splitModule(const WasmSplitOptions& options) { if (options.jspi) { // The load secondary module function must be kept in the main module. keepFuncs.insert(ModuleSplitting::LOAD_SECONDARY_MODULE); + splitFuncs.erase(ModuleSplitting::LOAD_SECONDARY_MODULE); } // If warnings are enabled, check that any functions are being split out. - if (!options.quiet) { - std::set splitFuncs; - ModuleUtils::iterDefinedFunctions(wasm, [&](Function* func) { - if (keepFuncs.count(func->name) == 0) { - splitFuncs.insert(func->name); - } - }); - - if (splitFuncs.size() == 0) { - std::cerr - << "warning: not splitting any functions out to the secondary module\n"; - } + if (!options.quiet && splitFuncs.size() == 0) { + std::cerr + << "warning: not splitting any functions out to the secondary module\n"; + } - // Dump the kept and split functions if we are verbose - if (options.verbose) { - auto printCommaSeparated = [&](auto funcs) { - for (auto it = funcs.begin(); it != funcs.end(); ++it) { - if (it != funcs.begin()) { - std::cout << ", "; - } - std::cout << *it; + // Dump the kept and split functions if we are verbose. + if (options.verbose) { + auto printCommaSeparated = [&](auto funcs) { + for (auto it = funcs.begin(); it != funcs.end(); ++it) { + if (it != funcs.begin()) { + std::cout << ", "; } - }; + std::cout << *it; + } + }; - std::cout << "Keeping functions: "; - printCommaSeparated(keepFuncs); - std::cout << "\n"; + std::cout << "Keeping functions: "; + printCommaSeparated(keepFuncs); + std::cout << "\n"; - std::cout << "Splitting out functions: "; - printCommaSeparated(splitFuncs); - std::cout << "\n"; - } + std::cout << "Splitting out functions: "; + printCommaSeparated(splitFuncs); + std::cout << "\n"; } +#ifndef NDEBUG + // Check that all defined functions are in one set or the other. + ModuleUtils::iterDefinedFunctions(wasm, [&](Function* func) { + assert(keepFuncs.count(func->name) || splitFuncs.count(func->name)); + }); +#endif // NDEBUG + // Actually perform the splitting ModuleSplitting::Config config; config.primaryFuncs = std::move(keepFuncs); diff --git a/test/lit/wasm-split/basic.wast b/test/lit/wasm-split/basic.wast index 1390f1a6373..9af64daedfa 100644 --- a/test/lit/wasm-split/basic.wast +++ b/test/lit/wasm-split/basic.wast @@ -1,11 +1,6 @@ ;; RUN: not wasm-split %s --export-prefix='%' -g -o1 %t.none.1.wasm -o2 %t.none.2.wasm --keep-funcs=@failed-to-open.txt -v 2>&1 \ ;; RUN: | filecheck %s --check-prefix FAILED-TO-OPEN -;; RUN: wasm-split %s --export-prefix='%' -g -o1 %t.none.1.wasm -o2 %t.none.2.wasm -v 2>&1 \ -;; RUN: | filecheck %s --check-prefix KEEP-NONE -;; RUN: wasm-dis %t.none.1.wasm | filecheck %s --check-prefix KEEP-NONE-PRIMARY -;; RUN: wasm-dis %t.none.2.wasm | filecheck %s --check-prefix KEEP-NONE-SECONDARY - ;; RUN: wasm-split %s --export-prefix='%' -g -o1 %t.none.1.wasm -o2 %t.none.2.wasm --keep-funcs=@%S/none.txt -v 2>&1 \ ;; RUN: | filecheck %s --check-prefix KEEP-NONE ;; RUN: wasm-dis %t.none.1.wasm | filecheck %s --check-prefix KEEP-NONE-PRIMARY @@ -31,6 +26,11 @@ ;; RUN: wasm-dis %t.bar.1.wasm | filecheck %s --check-prefix KEEP-BAR-PRIMARY ;; RUN: wasm-dis %t.bar.2.wasm | filecheck %s --check-prefix KEEP-BAR-SECONDARY +;; RUN: wasm-split %s --export-prefix='%' -g -o1 %t.none.1.wasm -o2 %t.none.2.wasm -v 2>&1 \ +;; RUN: | filecheck %s --check-prefix KEEP-BOTH +;; RUN: wasm-dis %t.none.1.wasm | filecheck %s --check-prefix KEEP-BOTH-PRIMARY +;; RUN: wasm-dis %t.none.2.wasm | filecheck %s --check-prefix KEEP-BOTH-SECONDARY + ;; RUN: wasm-split %s --export-prefix='%' -g -o1 %t.both.1.wasm -o2 %t.both.2.wasm --keep-funcs=foo,bar -v 2>&1 \ ;; RUN: | filecheck %s --check-prefix KEEP-BOTH ;; RUN: wasm-dis %t.both.1.wasm | filecheck %s --check-prefix KEEP-BOTH-PRIMARY @@ -176,6 +176,6 @@ ;; KEEP-BOTH-SECONDARY: (module ;; KEEP-BOTH-SECONDARY-NEXT: ) -;; SPLIT-BAR-SUPERSEDE: warning: function bar was to be kept in primary module. However it will now be split out into secondary module. +;; SPLIT-BAR-SUPERSEDE: warning: function bar was to be both kept and split. It will be split. ;; SPLIT-BAR-SUPERSEDE: Keeping functions: foo{{$}} ;; SPLIT-BAR-SUPERSEDE: Splitting out functions: bar{{$}} diff --git a/test/lit/wasm-split/jspi-secondary-export.wast b/test/lit/wasm-split/jspi-secondary-export.wast index 6e89bab28b0..43586343849 100644 --- a/test/lit/wasm-split/jspi-secondary-export.wast +++ b/test/lit/wasm-split/jspi-secondary-export.wast @@ -1,5 +1,5 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: wasm-split %s --export-prefix='%' -g -o1 %t.1.wasm -o2 %t.2.wasm --jspi +;; RUN: wasm-split %s --export-prefix='%' -g -o1 %t.1.wasm -o2 %t.2.wasm --jspi --split-funcs=foo,bar ;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY ;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY diff --git a/test/lit/wasm-split/profile-guided.wast b/test/lit/wasm-split/profile-guided.wast index 709ff4c8f4e..9ee25975496 100644 --- a/test/lit/wasm-split/profile-guided.wast +++ b/test/lit/wasm-split/profile-guided.wast @@ -83,7 +83,6 @@ ;; PROFILE_KEEP: Keeping functions: bar, bar_callee, shared_callee, uncalled ;; PROFILE_KEEP: Splitting out functions: deep_foo_callee, foo, foo_callee -;; PROFILE_SPLIT: warning: function shared_callee was to be kept in primary module. However it will now be split out into secondary module. ;; PROFILE_SPLIT: Keeping functions: bar, bar_callee, deep_foo_callee, foo, foo_callee ;; PROFILE_SPLIT: Splitting out functions: shared_callee, uncalled From da5646961c61f21dbb1d6218e370325ba43be9f0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Sep 2024 13:59:17 -0700 Subject: [PATCH 034/622] [wasm-split] Configure split functions rather than kept functions (#6949) The configuration for the module splitting utility previous took a set of functions to keep in the primary module. Change it to take a list of functions to split into the secondary module instead. This improves the code quality in multi-split mode because it keeps stub functions generated by previous splits from being moved into secondary modules during later splits. --- src/ir/module-splitting.cpp | 2 +- src/ir/module-splitting.h | 6 +- src/tools/wasm-split/wasm-split.cpp | 11 +-- test/example/module-splitting.cpp | 13 ++-- test/lit/wasm-split/multi-split.wast | 102 +++++++++++++-------------- 5 files changed, 65 insertions(+), 69 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 777818689bd..caa996b308d 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -442,7 +442,7 @@ ModuleSplitter::classifyFunctions(Module& primary, const Config& config) { // module since that would make them async when they may not have the JSPI // wrapper. Exported JSPI functions can still benefit from splitting though // since only the JSPI wrapper stub will remain in the primary module. - if (func->imported() || config.primaryFuncs.count(func->name) || + if (func->imported() || !config.secondaryFuncs.count(func->name) || (config.jspi && ExportUtils::isExported(primary, *func)) || segmentReferrers.count(func->name)) { primaryFuncs.insert(func->name); diff --git a/src/ir/module-splitting.h b/src/ir/module-splitting.h index 620993d2d88..89e4dd2bb14 100644 --- a/src/ir/module-splitting.h +++ b/src/ir/module-splitting.h @@ -47,11 +47,11 @@ namespace wasm::ModuleSplitting { static const Name LOAD_SECONDARY_MODULE("__load_secondary_module"); struct Config { - // The set of functions to keep in the primary module. All others are split - // out into the new secondary module. Must include the start function if it + // The set of functions to split into the secondary module. All others are + // kept in the primary module. Must not include the start function if it // exists. May or may not include imported functions, which are always kept in // the primary module regardless. - std::set primaryFuncs; + std::set secondaryFuncs; // Whether to import placeholder functions into the primary module that will // be called when a secondary function is called before the secondary module // has been loaded. diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 1cfe5bad945..d26f6f1d63a 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -221,7 +221,7 @@ void splitModule(const WasmSplitOptions& options) { std::set splitFuncs; if (options.profileFile.size()) { - // Use the profile to set `keepFuncs`. + // Use the profile to set `keepFuncs` and `splitFuncs`. uint64_t hash = hashFile(options.inputFiles[0]); getFunctionsToKeepAndSplit( wasm, hash, options.profileFile, keepFuncs, splitFuncs); @@ -319,7 +319,7 @@ void splitModule(const WasmSplitOptions& options) { // Actually perform the splitting ModuleSplitting::Config config; - config.primaryFuncs = std::move(keepFuncs); + config.secondaryFuncs = std::move(splitFuncs); if (options.importNamespace.size()) { config.importNamespace = options.importNamespace; } @@ -418,9 +418,6 @@ void multiSplitModule(const WasmSplitOptions& options) { config.usePlaceholders = false; config.importNamespace = ""; config.minimizeNewExportNames = true; - for (auto& func : wasm.functions) { - config.primaryFuncs.insert(func->name); - } for (auto& [mod, funcs] : moduleFuncs) { if (options.verbose) { std::cerr << "Splitting module " << mod << '\n'; @@ -428,9 +425,7 @@ void multiSplitModule(const WasmSplitOptions& options) { if (!options.quiet && funcs.empty()) { std::cerr << "warning: Module " << mod << " will be empty\n"; } - for (auto& func : funcs) { - config.primaryFuncs.erase(Name(func)); - } + config.secondaryFuncs = std::set(funcs.begin(), funcs.end()); auto splitResults = ModuleSplitting::splitFunctions(wasm, config); // TODO: symbolMap, placeholderMap, emitModuleNames // TODO: Support --emit-text and use .wast in that case. diff --git a/test/example/module-splitting.cpp b/test/example/module-splitting.cpp index 81f095f48d2..b55beb1d3fa 100644 --- a/test/example/module-splitting.cpp +++ b/test/example/module-splitting.cpp @@ -28,14 +28,21 @@ void do_test(const std::set& keptFuncs, std::string&& module) { valid = validator.validate(*primary); assert(valid && "before invalid!"); + std::set splitFuncs; + for (auto& func : primary->functions) { + splitFuncs.insert(func->name); + } + std::cout << "Before:\n"; std::cout << *primary.get(); std::cout << "Keeping: "; if (keptFuncs.size()) { auto it = keptFuncs.begin(); + splitFuncs.erase(*it); std::cout << *it++; while (it != keptFuncs.end()) { + splitFuncs.erase(*it); std::cout << ", " << *it++; } } else { @@ -44,7 +51,7 @@ void do_test(const std::set& keptFuncs, std::string&& module) { std::cout << "\n"; ModuleSplitting::Config config; - config.primaryFuncs = keptFuncs; + config.secondaryFuncs = std::move(splitFuncs); config.newExportPrefix = "%"; auto secondary = splitFunctions(*primary, config).secondary; @@ -443,7 +450,6 @@ void test_minimized_exports() { Module primary; primary.features = FeatureSet::All; - std::set keep; Expression* callBody = nullptr; Builder builder(primary); @@ -453,7 +459,6 @@ void test_minimized_exports() { Name name = std::to_string(i); primary.addFunction( Builder::makeFunction(name, funcType, {}, builder.makeNop())); - keep.insert(name); callBody = builder.blockify(callBody, builder.makeCall(name, {}, Type::none)); @@ -470,7 +475,7 @@ void test_minimized_exports() { primary.addFunction(Builder::makeFunction("call", funcType, {}, callBody)); ModuleSplitting::Config config; - config.primaryFuncs = std::move(keep); + config.secondaryFuncs = {"call"}; config.newExportPrefix = "%"; config.minimizeNewExportNames = true; diff --git a/test/lit/wasm-split/multi-split.wast b/test/lit/wasm-split/multi-split.wast index 9206e60eee5..d7fb56c0792 100644 --- a/test/lit/wasm-split/multi-split.wast +++ b/test/lit/wasm-split/multi-split.wast @@ -7,8 +7,10 @@ ;; RUN: wasm-dis %t3.wasm | filecheck %s --check-prefix=CHECK-C (module - (type $ret-i32 (func (result i32))) ;; PRIMARY: (type $ret-i64 (func (result i64))) + + ;; PRIMARY: (type $ret-i32 (func (result i32))) + (type $ret-i32 (func (result i32))) (type $ret-i64 (func (result i64))) ;; PRIMARY: (type $ret-f32 (func (result f32))) (type $ret-f32 (func (result f32))) @@ -62,24 +64,24 @@ ) (i32.const 0) ) - ;; CHECK-B: (type $0 (func (result i32))) + ;; CHECK-B: (type $0 (func (result f32))) - ;; CHECK-B: (type $1 (func (result f32))) + ;; CHECK-B: (type $1 (func (result i32))) ;; CHECK-B: (type $2 (func (result i64))) - ;; CHECK-B: (import "" "table_3" (table $timport$0 2 funcref)) - - ;; CHECK-B: (import "" "table" (table $timport$1 1 funcref)) + ;; CHECK-B: (import "" "table_4" (table $timport$0 1 funcref)) ;; CHECK-B: (import "" "b" (func $C (result f32))) - ;; CHECK-B: (elem $0 (table $timport$0) (i32.const 0) func $B $1) + ;; CHECK-B: (import "" "c" (func $fimport$1 (result i32))) + + ;; CHECK-B: (elem $0 (i32.const 0) $B) ;; CHECK-B: (func $B (result i64) ;; CHECK-B-NEXT: (drop - ;; CHECK-B-NEXT: (call_ref $0 - ;; CHECK-B-NEXT: (ref.func $1) + ;; CHECK-B-NEXT: (call_ref $1 + ;; CHECK-B-NEXT: (ref.func $fimport$1) ;; CHECK-B-NEXT: ) ;; CHECK-B-NEXT: ) ;; CHECK-B-NEXT: (drop @@ -88,7 +90,7 @@ ;; CHECK-B-NEXT: ) ;; CHECK-B-NEXT: ) ;; CHECK-B-NEXT: (drop - ;; CHECK-B-NEXT: (call_ref $1 + ;; CHECK-B-NEXT: (call_ref $0 ;; CHECK-B-NEXT: (ref.func $C) ;; CHECK-B-NEXT: ) ;; CHECK-B-NEXT: ) @@ -112,33 +114,29 @@ ) (i64.const 0) ) - ;; CHECK-C: (type $0 (func (result i64))) + ;; CHECK-C: (type $0 (func (result i32))) - ;; CHECK-C: (type $1 (func (result i32))) + ;; CHECK-C: (type $1 (func (result i64))) ;; CHECK-C: (type $2 (func (result f32))) - ;; CHECK-C: (import "" "table_4" (table $timport$0 2 funcref)) + ;; CHECK-C: (import "" "table_6" (table $timport$0 1 funcref)) - ;; CHECK-C: (import "" "table_3" (table $timport$1 2 funcref)) + ;; CHECK-C: (import "" "c" (func $fimport$0 (result i32))) - ;; CHECK-C: (elem $0 (table $timport$0) (i32.const 0) func $0 $C) + ;; CHECK-C: (import "" "d" (func $fimport$1 (result i64))) - ;; CHECK-C: (func $0 (result i64) - ;; CHECK-C-NEXT: (call_indirect (type $0) - ;; CHECK-C-NEXT: (i32.const 0) - ;; CHECK-C-NEXT: ) - ;; CHECK-C-NEXT: ) + ;; CHECK-C: (elem $0 (i32.const 0) $C) ;; CHECK-C: (func $C (result f32) ;; CHECK-C-NEXT: (drop - ;; CHECK-C-NEXT: (call_ref $1 - ;; CHECK-C-NEXT: (ref.func $3) + ;; CHECK-C-NEXT: (call_ref $0 + ;; CHECK-C-NEXT: (ref.func $fimport$0) ;; CHECK-C-NEXT: ) ;; CHECK-C-NEXT: ) ;; CHECK-C-NEXT: (drop - ;; CHECK-C-NEXT: (call_ref $0 - ;; CHECK-C-NEXT: (ref.func $2) + ;; CHECK-C-NEXT: (call_ref $1 + ;; CHECK-C-NEXT: (ref.func $fimport$1) ;; CHECK-C-NEXT: ) ;; CHECK-C-NEXT: ) ;; CHECK-C-NEXT: (drop @@ -169,52 +167,50 @@ ) ;; PRIMARY: (table $0 1 funcref) -;; PRIMARY: (table $1 2 funcref) +;; PRIMARY: (table $1 1 funcref) -;; PRIMARY: (table $2 2 funcref) +;; PRIMARY: (table $2 1 funcref) ;; PRIMARY: (elem $0 (table $0) (i32.const 0) funcref (item (ref.null nofunc))) -;; PRIMARY: (elem $1 (table $1) (i32.const 0) funcref (item (ref.null nofunc)) (item (ref.null nofunc))) +;; PRIMARY: (elem $1 (table $1) (i32.const 0) funcref (item (ref.null nofunc))) -;; PRIMARY: (elem $2 (table $2) (i32.const 0) funcref (item (ref.null nofunc)) (item (ref.null nofunc))) +;; PRIMARY: (elem $2 (table $2) (i32.const 0) funcref (item (ref.null nofunc))) -;; PRIMARY: (export "a" (func $0)) +;; PRIMARY: (export "a" (func $1)) -;; PRIMARY: (export "b" (func $1)) +;; PRIMARY: (export "b" (func $3)) ;; PRIMARY: (export "table" (table $0)) -;; PRIMARY: (export "table_3" (table $1)) +;; PRIMARY: (export "c" (func $0)) + +;; PRIMARY: (export "table_4" (table $1)) + +;; PRIMARY: (export "d" (func $2)) + +;; PRIMARY: (export "table_6" (table $2)) + +;; PRIMARY: (func $0 (result i32) +;; PRIMARY-NEXT: (call_indirect (type $ret-i32) +;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) -;; PRIMARY: (export "table_4" (table $2)) +;; PRIMARY: (func $1 (result i64) +;; PRIMARY-NEXT: (call_indirect (type $ret-i64) +;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) -;; PRIMARY: (func $0 (result i64) +;; PRIMARY: (func $2 (result i64) ;; PRIMARY-NEXT: (call_indirect (type $ret-i64) ;; PRIMARY-NEXT: (i32.const 0) ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) -;; PRIMARY: (func $1 (result f32) +;; PRIMARY: (func $3 (result f32) ;; PRIMARY-NEXT: (call_indirect (type $ret-f32) -;; PRIMARY-NEXT: (i32.const 1) +;; PRIMARY-NEXT: (i32.const 0) ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) - -;; CHECK-B: (func $1 (result i32) -;; CHECK-B-NEXT: (call_indirect (type $0) -;; CHECK-B-NEXT: (i32.const 0) -;; CHECK-B-NEXT: ) -;; CHECK-B-NEXT: ) - -;; CHECK-C: (func $2 (result i64) -;; CHECK-C-NEXT: (call_indirect (type $0) -;; CHECK-C-NEXT: (i32.const 0) -;; CHECK-C-NEXT: ) -;; CHECK-C-NEXT: ) - -;; CHECK-C: (func $3 (result i32) -;; CHECK-C-NEXT: (call_indirect (type $1) -;; CHECK-C-NEXT: (i32.const 1) -;; CHECK-C-NEXT: ) -;; CHECK-C-NEXT: ) From bdc7ce0fbb2373d0ce666782fdce88f3bd8d6f17 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Sep 2024 14:42:56 -0700 Subject: [PATCH 035/622] [wasm-split] Minimize non-function export names (#6951) The module splitting utility has a configuration option for minimizing new export names, but it was previously applied only to newly exported functions. Using the new multi-split mode can produce lots of exported tables and splitting WasmGC programs can produce lots of exported globals, so minimizing these export names can have a big impact on code size. --- src/ir/module-splitting.cpp | 7 +++++-- test/lit/wasm-split/minimized-exports.wast | 4 ++-- test/lit/wasm-split/multi-split.wast | 22 +++++++++++----------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index caa996b308d..f299d7a3554 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -826,8 +826,11 @@ void ModuleSplitter::shareImportableItems() { if (exportIt != exports.end()) { secondaryItem.base = exportIt->second; } else { - Name exportName = Names::getValidExportName( - primary, config.newExportPrefix + genericExportName); + std::string baseName = + config.newExportPrefix + (config.minimizeNewExportNames + ? minified.getName() + : genericExportName); + Name exportName = Names::getValidExportName(primary, baseName); primary.addExport(new Export{exportName, primaryItem.name, kind}); secondaryItem.base = exportName; } diff --git a/test/lit/wasm-split/minimized-exports.wast b/test/lit/wasm-split/minimized-exports.wast index e938f4ab69e..b7d28afab60 100644 --- a/test/lit/wasm-split/minimized-exports.wast +++ b/test/lit/wasm-split/minimized-exports.wast @@ -9,7 +9,7 @@ ;; PRIMARY-NEXT: (export "baz" (func $2) ;; PRIMARY-NEXT: (export "%a" (func $1)) ;; PRIMARY-NEXT: (export "%b" (func $0)) -;; PRIMARY-NEXT: (export "%table" (table $0)) +;; PRIMARY-NEXT: (export "%c" (table $0)) ;; PRIMARY-NEXT: (func $0 ;; PRIMARY-NEXT: (nop) ;; PRIMARY-NEXT: ) @@ -25,7 +25,7 @@ ;; SECONDARY: (module ;; SECONDARY-NEXT: (type $0 (func)) -;; SECONDARY-NEXT: (import "primary" "%table" (table $timport$0 1 funcref)) +;; SECONDARY-NEXT: (import "primary" "%c" (table $timport$0 1 funcref)) ;; SECONDARY-NEXT: (import "primary" "%a" (func $fimport$0)) ;; SECONDARY-NEXT: (import "primary" "%b" (func $fimport$1)) ;; SECONDARY-NEXT: (elem $0 (i32.const 0) $0) diff --git a/test/lit/wasm-split/multi-split.wast b/test/lit/wasm-split/multi-split.wast index d7fb56c0792..f3baf201406 100644 --- a/test/lit/wasm-split/multi-split.wast +++ b/test/lit/wasm-split/multi-split.wast @@ -20,7 +20,7 @@ ;; CHECK-A: (type $2 (func (result i32))) - ;; CHECK-A: (import "" "table" (table $timport$0 1 funcref)) + ;; CHECK-A: (import "" "c" (table $timport$0 1 funcref)) ;; CHECK-A: (import "" "a" (func $B (result i64))) @@ -70,11 +70,11 @@ ;; CHECK-B: (type $2 (func (result i64))) - ;; CHECK-B: (import "" "table_4" (table $timport$0 1 funcref)) + ;; CHECK-B: (import "" "e" (table $timport$0 1 funcref)) ;; CHECK-B: (import "" "b" (func $C (result f32))) - ;; CHECK-B: (import "" "c" (func $fimport$1 (result i32))) + ;; CHECK-B: (import "" "d" (func $fimport$1 (result i32))) ;; CHECK-B: (elem $0 (i32.const 0) $B) @@ -120,11 +120,11 @@ ;; CHECK-C: (type $2 (func (result f32))) - ;; CHECK-C: (import "" "table_6" (table $timport$0 1 funcref)) + ;; CHECK-C: (import "" "g" (table $timport$0 1 funcref)) - ;; CHECK-C: (import "" "c" (func $fimport$0 (result i32))) + ;; CHECK-C: (import "" "d" (func $fimport$0 (result i32))) - ;; CHECK-C: (import "" "d" (func $fimport$1 (result i64))) + ;; CHECK-C: (import "" "f" (func $fimport$1 (result i64))) ;; CHECK-C: (elem $0 (i32.const 0) $C) @@ -181,15 +181,15 @@ ;; PRIMARY: (export "b" (func $3)) -;; PRIMARY: (export "table" (table $0)) +;; PRIMARY: (export "c" (table $0)) -;; PRIMARY: (export "c" (func $0)) +;; PRIMARY: (export "d" (func $0)) -;; PRIMARY: (export "table_4" (table $1)) +;; PRIMARY: (export "e" (table $1)) -;; PRIMARY: (export "d" (func $2)) +;; PRIMARY: (export "f" (func $2)) -;; PRIMARY: (export "table_6" (table $2)) +;; PRIMARY: (export "g" (table $2)) ;; PRIMARY: (func $0 (result i32) ;; PRIMARY-NEXT: (call_indirect (type $ret-i32) From a99b48a4502628d46683fc85697251db3a8069b0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 17 Sep 2024 16:18:14 -0700 Subject: [PATCH 036/622] [NFC] Make the GCData constructor a move constructor (#6946) This avoids creating a large Literals (SmallVector of Literal) and then copying it. All the places that construct GCData do not need the Literals afterwards. This gives a 7% speedup on the --precompute benchmark from #6931 --- src/literal.h | 3 ++- src/support/small_vector.h | 20 ++++++++++++++++++++ src/wasm-interpreter.h | 23 +++++++++++++---------- src/wasm/literal.cpp | 2 +- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/literal.h b/src/literal.h index 424121f5a10..6aa348084aa 100644 --- a/src/literal.h +++ b/src/literal.h @@ -763,7 +763,8 @@ struct GCData { // The element or field values. Literals values; - GCData(HeapType type, Literals values) : type(type), values(values) {} + GCData(HeapType type, Literals&& values) + : type(type), values(std::move(values)) {} }; // The data of a (ref exn) literal. diff --git a/src/support/small_vector.h b/src/support/small_vector.h index 503dfbd5c21..dd037055459 100644 --- a/src/support/small_vector.h +++ b/src/support/small_vector.h @@ -65,6 +65,12 @@ template class SmallVector { using value_type = T; SmallVector() {} + SmallVector(const SmallVector& other) + : usedFixed(other.usedFixed), fixed(other.fixed), flexible(other.flexible) { + } + SmallVector(SmallVector&& other) + : usedFixed(other.usedFixed), fixed(std::move(other.fixed)), + flexible(std::move(other.flexible)) {} SmallVector(std::initializer_list init) { for (T item : init) { push_back(item); @@ -72,6 +78,20 @@ template class SmallVector { } SmallVector(size_t initialSize) { resize(initialSize); } + SmallVector& operator=(const SmallVector& other) { + usedFixed = other.usedFixed; + fixed = other.fixed; + flexible = other.flexible; + return *this; + } + + SmallVector& operator=(SmallVector&& other) { + usedFixed = other.usedFixed; + fixed = std::move(other.fixed); + flexible = std::move(other.flexible); + return *this; + } + T& operator[](size_t i) { if (i < N) { return fixed[i]; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 86ba3b3a2e2..25303abfe45 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -191,8 +191,11 @@ class ExpressionRunner : public OverriddenVisitor { // data, as we don't have a cycle collector. Those leaks are not a serious // problem as Binaryen is not really used in long-running tasks, so we ignore // this function in LSan. - Literal makeGCData(const Literals& data, Type type) { - auto allocation = std::make_shared(type.getHeapType(), data); + // + // This consumes the input |data| entirely. + Literal makeGCData(Literals&& data, Type type) { + auto allocation = + std::make_shared(type.getHeapType(), std::move(data)); #if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer) // GC data with cycles will leak, since shared_ptrs do not handle cycles. // Binaryen is generally not used in long-running programs so we just ignore @@ -1655,7 +1658,7 @@ class ExpressionRunner : public OverriddenVisitor { data[i] = truncateForPacking(value.getSingleValue(), field); } } - return makeGCData(data, curr->type); + return makeGCData(std::move(data), curr->type); } Flow visitStructGet(StructGet* curr) { NOTE_ENTER("StructGet"); @@ -1735,7 +1738,7 @@ class ExpressionRunner : public OverriddenVisitor { data[i] = value; } } - return makeGCData(data, curr->type); + return makeGCData(std::move(data), curr->type); } Flow visitArrayNewData(ArrayNewData* curr) { WASM_UNREACHABLE("unimp"); } Flow visitArrayNewElem(ArrayNewElem* curr) { WASM_UNREACHABLE("unimp"); } @@ -1766,7 +1769,7 @@ class ExpressionRunner : public OverriddenVisitor { } data[i] = truncateForPacking(value.getSingleValue(), field); } - return makeGCData(data, curr->type); + return makeGCData(std::move(data), curr->type); } Flow visitArrayGet(ArrayGet* curr) { NOTE_ENTER("ArrayGet"); @@ -1971,7 +1974,7 @@ class ExpressionRunner : public OverriddenVisitor { contents.push_back(ptrDataValues[i]); } } - return makeGCData(contents, curr->type); + return makeGCData(std::move(contents), curr->type); } case StringNewFromCodePoint: { uint32_t codePoint = ptr.getSingleValue().getUnsigned(); @@ -2041,7 +2044,7 @@ class ExpressionRunner : public OverriddenVisitor { contents.push_back(l); } - return makeGCData(contents, curr->type); + return makeGCData(std::move(contents), curr->type); } Flow visitStringEncode(StringEncode* curr) { // For now we only support JS-style strings into arrays. @@ -2203,7 +2206,7 @@ class ExpressionRunner : public OverriddenVisitor { } } } - return makeGCData(contents, curr->type); + return makeGCData(std::move(contents), curr->type); } virtual void trap(const char* why) { WASM_UNREACHABLE("unimp"); } @@ -4010,7 +4013,7 @@ class ModuleRunnerBase : public ExpressionRunner { auto addr = (void*)&seg.data[i]; contents.push_back(this->makeFromMemory(addr, element)); } - return self()->makeGCData(contents, curr->type); + return self()->makeGCData(std::move(contents), curr->type); } Flow visitArrayNewElem(ArrayNewElem* curr) { NOTE_ENTER("ArrayNewElem"); @@ -4041,7 +4044,7 @@ class ModuleRunnerBase : public ExpressionRunner { auto val = self()->visit(seg.data[i]).getSingleValue(); contents.push_back(val); } - return self()->makeGCData(contents, curr->type); + return self()->makeGCData(std::move(contents), curr->type); } Flow visitArrayInitData(ArrayInitData* curr) { NOTE_ENTER("ArrayInit"); diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index e8641cbd7f2..6aaba729ae2 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -96,7 +96,7 @@ Literal::Literal(std::string_view string) int32_t u = uint8_t(string[i]) | (uint8_t(string[i + 1]) << 8); contents.push_back(Literal(u)); } - gcData = std::make_shared(HeapType::string, contents); + gcData = std::make_shared(HeapType::string, std::move(contents)); } Literal::Literal(const Literal& other) : type(other.type) { From b381a8c82030c2be3d1605d6f2854098f039f617 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Sep 2024 19:59:26 -0700 Subject: [PATCH 037/622] Improve types for null accesses and remove hacks (#6954) When a struct.get or array.get is optimized to have a null reference operand, its return type loses meaning since the operation will always trap. Previously when refinalizing such expressions, we just left their return type unchanged since there was no longer an associated struct or array type to calculate it from. However, this could lead to a strange setup where the stale return type was the last remaining use of some heap type in the module. That heap type would never be emitted in the binary, but it was still used in the IR, so type optimizations would have to keep updating it. Our type collecting logic went out of its way to include the return types of struct.get and array.get expressions to account for this strange possibility, even though it otherwise collected only types that would appear in binaries. In principle, all of this should have applied to `call_ref` as well, but the type collection logic did not have the necessary special case, so there was probably a latent bug there. Get rid of these special cases in the type collection logic and make it impossible for the IR to use a stale type that no longer appears in the binary by updating such stale types during finalization. One possibility would have been to make the return types of null accessors unreachable, but this violates the usual invariant that unreachable instructions must either have unreachable children or be branches or `(unreachable)`. Instead, refine the return types to be uninhabitable non-nullable references to bottom, which is nearly as good as refining them directly to unreachable. We can consider refining them to `unreachable` in the future, but another problem with that is that it would currently allow the parsers to admit more invalid modules with arbitrary junk after null accessor instructions. --- src/ir/module-utils.cpp | 19 ---------------- src/wasm/wasm.cpp | 34 +++++++++++++++++++++++++--- test/lit/passes/local-subtyping.wast | 2 +- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index a16592d15a7..1efbe490a07 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -399,15 +399,10 @@ struct CodeScanner } } else if (auto* get = curr->dynCast()) { info.note(get->ref->type); - // TODO: Just include curr->type for AllTypes and UsedIRTypes to avoid - // this special case and to avoid emitting unnecessary types in binaries. - info.include(get->type); } else if (auto* set = curr->dynCast()) { info.note(set->ref->type); } else if (auto* get = curr->dynCast()) { info.note(get->ref->type); - // See above. - info.include(get->type); } else if (auto* set = curr->dynCast()) { info.note(set->ref->type); } else if (auto* contBind = curr->dynCast()) { @@ -468,20 +463,6 @@ InsertOrderedMap collectHeapTypeInfo( } } - // TODO: Remove this once we remove the hack for StructGet and StructSet in - // CodeScanner. - if (inclusion == TypeInclusion::UsedIRTypes) { - auto it = info.info.begin(); - while (it != info.info.end()) { - if (it->second.useCount == 0) { - auto deleted = it++; - info.info.erase(deleted); - } else { - ++it; - } - } - } - // Recursively traverse each reference type, which may have a child type that // is itself a reference type. This reflects an appearance in the binary // format that is in the type section itself. As we do this we may find more diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 98146dfbcc3..87ae6ac5a38 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1008,7 +1008,25 @@ void CallRef::finalize() { return; } assert(target->type.isRef()); - if (target->type.getHeapType().isBottom()) { + if (target->type.isNull()) { + // If this call_ref has been optimized to have a null reference, then it + // will definitely trap. We could update the type to be unreachable, but + // that would violate the invariant that non-branch instructions other than + // `unreachable` can only be unreachable if they have unreachable children. + // Make the result type as close to `unreachable` as possible without + // actually making it unreachable. TODO: consider just making this + // unreachable instead (and similar in other GC accessors), although this + // would currently cause the parser to admit more invalid modules. + if (type.isRef()) { + type = Type(type.getHeapType().getBottom(), NonNullable); + } else if (type.isTuple()) { + Tuple elems; + for (auto t : type) { + elems.push_back( + t.isRef() ? Type(t.getHeapType().getBottom(), NonNullable) : t); + } + type = Type(elems); + } return; } assert(target->type.getHeapType().isSignature()); @@ -1136,7 +1154,12 @@ void StructNew::finalize() { void StructGet::finalize() { if (ref->type == Type::unreachable) { type = Type::unreachable; - } else if (!ref->type.isNull()) { + } else if (ref->type.isNull()) { + // See comment on CallRef for explanation. + if (type.isRef()) { + type = Type(type.getHeapType().getBottom(), NonNullable); + } + } else { type = ref->type.getHeapType().getStruct().fields[index].type; } } @@ -1180,7 +1203,12 @@ void ArrayNewFixed::finalize() { void ArrayGet::finalize() { if (ref->type == Type::unreachable || index->type == Type::unreachable) { type = Type::unreachable; - } else if (!ref->type.isNull()) { + } else if (ref->type.isNull()) { + // See comment on CallRef for explanation. + if (type.isRef()) { + type = Type(type.getHeapType().getBottom(), NonNullable); + } + } else { type = ref->type.getHeapType().getArray().element.type; } } diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index d5fc3d63d71..04890521dea 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -274,7 +274,7 @@ ;; CHECK: (func $multiple-iterations-refinalize-call-ref-bottom (type $0) ;; CHECK-NEXT: (local $f nullfuncref) - ;; CHECK-NEXT: (local $x anyref) + ;; CHECK-NEXT: (local $x (ref none)) ;; CHECK-NEXT: (local.set $f ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) From 5e4a4bae6544eda7d6e5be923bd1086921ffcb1b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 18 Sep 2024 15:55:43 -0700 Subject: [PATCH 038/622] [NFC + bugfix] Remove BreakTargetLocation from GUFA (#6956) Before, a br would send its value to a BreakTargetLocation. That would then be linked to the target: { br's value } => BreakTargetLocation(target name) => { location of target } This PR skips the middle: { br's value } => { location of target } It just connects breaks directly to the targets. We can do that if we keep a map of the targets as we go. This is 2% faster as well as simplifies the code, as an NFC refactoring. But it also fixes a bug: we have handling on ExpressionLocation that filters values as they come in (they must accord with the expression's type). We were not doing that on BreakTargetLocation, leading to an assert. Removing BreakTargetLocation entirely is easier and better than adding filtering logic for it. Fixes #6955 --- src/ir/possible-contents.cpp | 63 +++++++++++++++++++----------------- src/ir/possible-contents.h | 23 ------------- 2 files changed, 34 insertions(+), 52 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index aa656465307..17e40f1d805 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -503,12 +503,30 @@ struct CollectedFuncInfo { std::unordered_map childParents; }; +// Does a walk while maintaining a map of names of branch targets to those +// expressions, so they can be found by their name. +// TODO: can this replace ControlFlowWalker in other places? +template> +struct BreakTargetWalker : public PostWalker { + std::unordered_map breakTargets; + + Expression* findBreakTarget(Name name) { return breakTargets[name]; } + + static void scan(SubType* self, Expression** currp) { + auto* curr = *currp; + BranchUtils::operateOnScopeNameDefs( + curr, [&](Name name) { self->breakTargets[name] = curr; }); + + PostWalker::scan(self, currp); + } +}; + // Walk the wasm and find all the links we need to care about, and the locations // and roots related to them. This builds up a CollectedFuncInfo data structure. // After all InfoCollectors run, those data structures will be merged and the // main flow will begin. struct InfoCollector - : public PostWalker> { + : public BreakTargetWalker> { CollectedFuncInfo& info; InfoCollector(CollectedFuncInfo& info) : info(info) {} @@ -553,9 +571,6 @@ struct InfoCollector return; } - // Values sent to breaks to this block must be received here. - handleBreakTarget(curr); - // The final item in the block can flow a value to here as well. receiveChildValue(curr->list.back(), curr); } @@ -1151,8 +1166,7 @@ struct InfoCollector for (Index i = 0; i < params.size(); i++) { if (isRelevant(params[i])) { info.links.push_back( - {TagLocation{tag, i}, - BreakTargetLocation{getFunction(), target, i}}); + {TagLocation{tag, i}, getBreakTargetLocation(target, i)}); } } @@ -1164,7 +1178,7 @@ struct InfoCollector addRoot(location, PossibleContents::fromType(Type(HeapType::exn, NonNullable))); info.links.push_back( - {location, BreakTargetLocation{getFunction(), target, exnrefIndex}}); + {location, getBreakTargetLocation(target, exnrefIndex)}); } } } @@ -1279,6 +1293,13 @@ struct InfoCollector // Helpers + // Returns the location of a break target by the name (e.g. returns the + // location of a block, if the name is the name of a block). Also receives the + // index in a tuple, if this is part of a tuple value. + Location getBreakTargetLocation(Name target, Index i) { + return ExpressionLocation{findBreakTarget(target), i}; + } + // Handles the value sent in a break instruction. Does not handle anything // else like the condition etc. void handleBreakValue(Expression* curr) { @@ -1288,26 +1309,13 @@ struct InfoCollector for (Index i = 0; i < value->type.size(); i++) { // Breaks send the contents of the break value to the branch target // that the break goes to. - info.links.push_back( - {ExpressionLocation{value, i}, - BreakTargetLocation{getFunction(), target, i}}); + info.links.push_back({ExpressionLocation{value, i}, + getBreakTargetLocation(target, i)}); } } }); } - // Handles receiving values from breaks at the target (as in a block). - void handleBreakTarget(Expression* curr) { - if (isRelevant(curr->type)) { - BranchUtils::operateOnScopeNameDefs(curr, [&](Name target) { - for (Index i = 0; i < curr->type.size(); i++) { - info.links.push_back({BreakTargetLocation{getFunction(), target, i}, - ExpressionLocation{curr, i}}); - } - }); - } - } - // Connect a child's value to the parent, that is, all content in the child is // now considered possible in the parent as well. void receiveChildValue(Expression* child, Expression* parent) { @@ -2400,16 +2408,16 @@ bool Flower::updateContents(LocationIndex locationIndex, } } - // After filtering we should always have more precise information than "many" - // - in the worst case, we can have the type declared in the wasm. - assert(!contents.isMany()); - #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 std::cout << " updateContents has something new\n"; contents.dump(std::cout, &wasm); std::cout << '\n'; #endif + // After filtering we should always have more precise information than "many" + // - in the worst case, we can have the type declared in the wasm. + assert(!contents.isMany()); + // Add a work item if there isn't already. workQueue.insert(locationIndex); @@ -2896,9 +2904,6 @@ void Flower::dump(Location location) { << '\n'; } else if (auto* loc = std::get_if(&location)) { std::cout << " globalloc " << loc->name << '\n'; - } else if (auto* loc = std::get_if(&location)) { - std::cout << " branchloc " << loc->func->name << " : " << loc->target - << " tupleIndex " << loc->tupleIndex << '\n'; } else if (std::get_if(&location)) { std::cout << " sigparamloc " << '\n'; } else if (std::get_if(&location)) { diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 945d59d2623..21f5ecb4535 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -402,21 +402,6 @@ struct ResultLocation { } }; -// The location of a break target in a function, identified by its name. -struct BreakTargetLocation { - Function* func; - Name target; - // As in ExpressionLocation, the index inside the tuple, or 0 if not a tuple. - // That is, if the branch target has a tuple type, then each branch to that - // location sends a tuple, and we'll have a separate BreakTargetLocation for - // each, indexed by the index in the tuple that the branch sends. - Index tupleIndex; - bool operator==(const BreakTargetLocation& other) const { - return func == other.func && target == other.target && - tupleIndex == other.tupleIndex; - } -}; - // The location of a global in the module. struct GlobalLocation { Name name; @@ -523,7 +508,6 @@ using Location = std::variant struct hash { } }; -template<> struct hash { - size_t operator()(const wasm::BreakTargetLocation& loc) const { - return std::hash>{}( - {size_t(loc.func), loc.target, loc.tupleIndex}); - } -}; - template<> struct hash { size_t operator()(const wasm::GlobalLocation& loc) const { return std::hash{}(loc.name); From e2ce099f7e85b506cb45d306ec7c89a237d2e7c6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 18 Sep 2024 16:03:50 -0700 Subject: [PATCH 039/622] [NFC] Avoid collecting unnecessary parents in OptimizeAddedConstants (#6953) This makes the pass 20% faster. --- src/passes/OptimizeAddedConstants.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/passes/OptimizeAddedConstants.cpp b/src/passes/OptimizeAddedConstants.cpp index f03109223e2..142f1002491 100644 --- a/src/passes/OptimizeAddedConstants.cpp +++ b/src/passes/OptimizeAddedConstants.cpp @@ -32,13 +32,35 @@ #include #include -#include #include #include #include namespace wasm { +namespace { + +// Similar to Parents from parents.h, but we only care about gets, so it is much +// more efficient to just collect their parents. +struct GetParents { + GetParents(Expression* expr) { inner.walk(expr); } + + Expression* getParent(LocalGet* curr) const { + auto iter = inner.parentMap.find(curr); + assert(iter != inner.parentMap.end()); + return iter->second; + } + +private: + struct Inner : public ExpressionStackWalker { + void visitLocalGet(LocalGet* curr) { parentMap[curr] = getParent(); } + + std::unordered_map parentMap; + } inner; +}; + +} // anonymous namespace + template class MemoryAccessOptimizer { public: MemoryAccessOptimizer(P* parent, @@ -356,7 +378,7 @@ struct OptimizeAddedConstants // g(a, offset=10) // but if x has other uses, then avoid doing so - we'll be doing that add // anyhow, so the load/store offset trick won't actually help. - Parents parents(getFunction()->body); + GetParents parents(getFunction()->body); for (auto& [location, _] : localGraph->locations) { if (auto* set = location->dynCast()) { if (auto* add = set->value->dynCast()) { From b285448a3f8f06123b5b6048efa4ff3d2a13e0a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 18 Sep 2024 16:58:13 -0700 Subject: [PATCH 040/622] [NFC] Add isSSA to LazyLocalGraph, and use it in OptimizeAddedConstants (#6952) This makes the pass 15% faster. --- src/ir/LocalGraph.cpp | 32 +++++++++++++++++++++++++++ src/ir/local-graph.h | 15 +++++++++++++ src/passes/OptimizeAddedConstants.cpp | 12 +++++----- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/ir/LocalGraph.cpp b/src/ir/LocalGraph.cpp index a5495b5b33f..7e877e65e18 100644 --- a/src/ir/LocalGraph.cpp +++ b/src/ir/LocalGraph.cpp @@ -624,6 +624,38 @@ void LazyLocalGraph::computeGetInfluences() const { doComputeGetInfluences(*locations, *getInfluences); } +bool LazyLocalGraph::computeSSA(Index index) const { + // We must never repeat work. + assert(!SSAIndexes.count(index)); + + if (!flower) { + makeFlower(); + } + + // Similar logic to LocalGraph::computeSSAIndexes(), but optimized for the + // case of a single index. + + // All the sets for this index that we've seen. We'll add all relevant ones, + // and exit if we see more than one. + SmallUnorderedSet sets; + for (auto* set : flower->setsByIndex[index]) { + sets.insert(set); + if (sets.size() > 1) { + return SSAIndexes[index] = false; + } + } + for (auto* get : flower->getsByIndex[index]) { + for (auto* set : getSets(get)) { + sets.insert(set); + if (sets.size() > 1) { + return SSAIndexes[index] = false; + } + } + } + // Finally, check that we have 1 and not 0 sets. + return SSAIndexes[index] = (sets.size() == 1); +} + void LazyLocalGraph::computeLocations() const { // We must never repeat work. assert(!locations); diff --git a/src/ir/local-graph.h b/src/ir/local-graph.h index 6f1e621cfdf..7b8001be698 100644 --- a/src/ir/local-graph.h +++ b/src/ir/local-graph.h @@ -209,6 +209,16 @@ struct LazyLocalGraph : public LocalGraphBase { } return (*getInfluences)[get]; } + bool isSSA(Index index) const { + auto iter = SSAIndexes.find(index); + if (iter == SSAIndexes.end()) { + auto ret = computeSSA(index); + // The result must have been memoized. + assert(SSAIndexes.count(index)); + return ret; + } + return iter->second; + } const Locations& getLocations() const { if (!locations) { @@ -229,6 +239,9 @@ struct LazyLocalGraph : public LocalGraphBase { // include sets of other indexes, so there is no simple way to lazify that // computation. mutable std::optional getInfluences; + // A map if indexes to a bool indicating if the index is SSA. If there is no + // entry, it has not yet been computed. + mutable std::unordered_map SSAIndexes; mutable std::optional locations; // Compute the sets for a get and store them on getSetsMap. @@ -237,6 +250,8 @@ struct LazyLocalGraph : public LocalGraphBase { void computeSetInfluences(LocalSet* set) const; // Compute influences for all gets and store them on getInfluences. void computeGetInfluences() const; + // Compute whether an index is SSA and store that on SSAIndexes. + bool computeSSA(Index index) const; // Compute locations and store them on getInfluences. void computeLocations() const; diff --git a/src/passes/OptimizeAddedConstants.cpp b/src/passes/OptimizeAddedConstants.cpp index 142f1002491..fd584083641 100644 --- a/src/passes/OptimizeAddedConstants.cpp +++ b/src/passes/OptimizeAddedConstants.cpp @@ -66,7 +66,7 @@ template class MemoryAccessOptimizer { MemoryAccessOptimizer(P* parent, T* curr, Module* module, - LocalGraph* localGraph) + LazyLocalGraph* localGraph) : parent(parent), curr(curr), module(module), localGraph(localGraph) { memory64 = module->getMemory(curr->memory)->is64(); } @@ -133,7 +133,7 @@ template class MemoryAccessOptimizer { P* parent; T* curr; Module* module; - LocalGraph* localGraph; + LazyLocalGraph* localGraph; bool memory64; void optimizeConstantPointer() { @@ -326,9 +326,7 @@ struct OptimizeAddedConstants helperIndexes.clear(); propagatable.clear(); if (propagate) { - localGraph = std::make_unique(func, getModule()); - localGraph->computeSetInfluences(); - localGraph->computeSSAIndexes(); + localGraph = std::make_unique(func, getModule()); findPropagatable(); } Super::doWalkFunction(func); @@ -362,7 +360,7 @@ struct OptimizeAddedConstants private: bool propagated; - std::unique_ptr localGraph; + std::unique_ptr localGraph; // Whether a set is propagatable. std::set propagatable; @@ -379,7 +377,7 @@ struct OptimizeAddedConstants // but if x has other uses, then avoid doing so - we'll be doing that add // anyhow, so the load/store offset trick won't actually help. GetParents parents(getFunction()->body); - for (auto& [location, _] : localGraph->locations) { + for (auto& [location, _] : localGraph->getLocations()) { if (auto* set = location->dynCast()) { if (auto* add = set->value->dynCast()) { if (add->op == AddInt32) { From 2711d4fe4b4514ea146e8810959a8f170c932591 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 19 Sep 2024 17:07:51 -0700 Subject: [PATCH 041/622] [NFC] Eagerly create Functions in binary parser (#6957) In preparation for using IRBuilder in the binary parser, eagerly create Functions when parsing the function section so that they are already created once we parse the code section. IRBuilder will require the functions to exist when parsing calls so it can figure out what type each call should have, even when there is a call to a function whose body has not been parsed yet. --- src/ir/module-utils.cpp | 5 ++++- src/passes/Print.cpp | 3 +++ src/wasm-binary.h | 5 +++++ src/wasm/wasm-binary.cpp | 22 +++++++++++----------- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 1efbe490a07..490955866b0 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -448,7 +448,10 @@ InsertOrderedMap collectHeapTypeInfo( for (auto type : func->vars) { info.note(type); } - if (!func->imported()) { + // Don't just use `func->imported()` here because we also might be + // printing an error message on a partially parsed module whose declared + // function bodies have not all been parsed yet. + if (func->body) { CodeScanner(wasm, info, inclusion).walk(func->body); } }); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 46d519e9eda..d49bc297472 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2980,6 +2980,9 @@ void PrintSExpression::visitDefinedGlobal(Global* curr) { void PrintSExpression::visitFunction(Function* curr) { if (curr->imported()) { visitImportedFunction(curr); + } else if (curr->body == nullptr) { + // We are in the middle of parsing the module and have not parsed this + // function's code yet. Skip it. } else { visitDefinedFunction(curr); } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index fe740d56a76..5021b6a297f 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1532,6 +1532,11 @@ class WasmBinaryReader { // reconstructing the HeapTypes from the Signatures is expensive. std::vector functionTypes; + // Used to make sure the number of imported functions, signatures, and + // declared functions all match up. + Index numFuncImports = 0; + Index numFuncBodies = 0; + void readFunctionSignatures(); HeapType getTypeByIndex(Index index); HeapType getTypeByFunctionIndex(Index index); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index d8a310103da..d999141fd4a 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2578,6 +2578,7 @@ void WasmBinaryReader::readImports() { } } } + numFuncImports = wasm.functions.size(); } Name WasmBinaryReader::getNextLabel() { @@ -2595,9 +2596,12 @@ void WasmBinaryReader::readFunctionSignatures() { size_t num = getU32LEB(); for (size_t i = 0; i < num; i++) { auto index = getU32LEB(); - functionTypes.push_back(getTypeByIndex(index)); + HeapType type = getTypeByIndex(index); + functionTypes.push_back(type); // Check that the type is a signature. getSignatureByTypeIndex(index); + wasm.addFunction( + Builder(wasm).makeFunction(makeName("", i), type, {}, nullptr)); } } @@ -2633,12 +2637,11 @@ Signature WasmBinaryReader::getSignatureByFunctionIndex(Index index) { } void WasmBinaryReader::readFunctions() { - auto numImports = wasm.functions.size(); - size_t total = getU32LEB(); - if (total != functionTypes.size() - numImports) { + numFuncBodies = getU32LEB(); + if (numFuncBodies + numFuncImports != wasm.functions.size()) { throwError("invalid function section size, must equal types"); } - for (size_t i = 0; i < total; i++) { + for (size_t i = 0; i < numFuncBodies; i++) { auto sizePos = pos; size_t size = getU32LEB(); if (size == 0) { @@ -2646,9 +2649,7 @@ void WasmBinaryReader::readFunctions() { } endOfFunction = pos + size; - auto func = std::make_unique(); - func->name = makeName("", i); - func->type = getTypeByFunctionIndex(numImports + i); + auto& func = wasm.functions[numFuncImports + i]; currFunction = func.get(); if (DWARF) { @@ -2715,7 +2716,6 @@ void WasmBinaryReader::readFunctions() { std::swap(func->epilogLocation, debugLocation); currFunction = nullptr; debugLocation.clear(); - wasm.addFunction(std::move(func)); } } @@ -3192,8 +3192,8 @@ void WasmBinaryReader::validateBinary() { throwError("Number of segments does not agree with DataCount section"); } - if (functionTypes.size() != wasm.functions.size()) { - throwError("function section without code section"); + if (functionTypes.size() != numFuncImports + numFuncBodies) { + throwError("function and code sections have inconsistent lengths"); } } From 480f5ba352a9f89afe72779c81f8a16fd3c8ba4a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 19 Sep 2024 17:08:17 -0700 Subject: [PATCH 042/622] [NFC] Eagerly create segments when parsing datacount (#6958) The purpose of the datacount section is to pre-declare how many data segments there will be so that engines can allocate space for them and not have to back patch subsequent instructions in the code section that refer to them. Once we use IRBuilder in the binary parser, we will have to have the data segments available by the time we parse instructions that use them, so eagerly construct the data segments when parsing the datacount section. --- src/passes/Print.cpp | 5 ++++ src/wasm/wasm-binary.cpp | 24 +++++++++++++++--- test/lit/binary/bad-datacount.test | 9 +++++++ .../binary/bad-datacount.test.wasm} | Bin test/unit/test_datacount.py | 15 ----------- 5 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 test/lit/binary/bad-datacount.test rename test/{unit/input/bulkmem_bad_datacount.wasm => lit/binary/bad-datacount.test.wasm} (100%) delete mode 100644 test/unit/test_datacount.py diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index d49bc297472..427bff32905 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3238,6 +3238,11 @@ void PrintSExpression::visitMemory(Memory* curr) { } void PrintSExpression::visitDataSegment(DataSegment* curr) { + if (!curr->isPassive && !curr->offset) { + // This data segment must have been created from the datacount section but + // not parsed yet. Skip it. + return; + } doIndent(o, indent); o << '('; printMajor(o, "data "); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index d999141fd4a..cb9ea3731ff 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3273,18 +3273,37 @@ void WasmBinaryReader::processNames() { void WasmBinaryReader::readDataSegmentCount() { hasDataCount = true; dataCount = getU32LEB(); + // Eagerly create the data segments so they are available during parsing of + // the code section. + for (size_t i = 0; i < dataCount; ++i) { + auto curr = Builder::makeDataSegment(); + curr->setName(Name::fromInt(i), false); + wasm.addDataSegment(std::move(curr)); + } } void WasmBinaryReader::readDataSegments() { auto num = getU32LEB(); + if (hasDataCount) { + if (num != dataCount) { + throwError("data count and data sections disagree on size"); + } + } else { + // We haven't already created the data segments, so create them now. + for (size_t i = 0; i < num; ++i) { + auto curr = Builder::makeDataSegment(); + curr->setName(Name::fromInt(i), false); + wasm.addDataSegment(std::move(curr)); + } + } + assert(wasm.dataSegments.size() == num); for (size_t i = 0; i < num; i++) { - auto curr = Builder::makeDataSegment(); + auto& curr = wasm.dataSegments[i]; uint32_t flags = getU32LEB(); if (flags > 2) { throwError("bad segment flags, must be 0, 1, or 2, not " + std::to_string(flags)); } - curr->setName(Name::fromInt(i), false); curr->isPassive = flags & BinaryConsts::IsPassive; if (curr->isPassive) { curr->memory = Name(); @@ -3300,7 +3319,6 @@ void WasmBinaryReader::readDataSegments() { auto size = getU32LEB(); auto data = getByteView(size); curr->data = {data.begin(), data.end()}; - wasm.addDataSegment(std::move(curr)); } } diff --git a/test/lit/binary/bad-datacount.test b/test/lit/binary/bad-datacount.test new file mode 100644 index 00000000000..27e0c72a398 --- /dev/null +++ b/test/lit/binary/bad-datacount.test @@ -0,0 +1,9 @@ +RUN: not wasm-opt --debug %s.wasm 2>&1 | filecheck %s + +;; Check that we get the expected error. + +;; CHECK: data count and data sections disagree on size + +;; Check that we print out the module rather than hitting an assertion error. + +;; CHECK: (module diff --git a/test/unit/input/bulkmem_bad_datacount.wasm b/test/lit/binary/bad-datacount.test.wasm similarity index 100% rename from test/unit/input/bulkmem_bad_datacount.wasm rename to test/lit/binary/bad-datacount.test.wasm diff --git a/test/unit/test_datacount.py b/test/unit/test_datacount.py deleted file mode 100644 index b996865a53c..00000000000 --- a/test/unit/test_datacount.py +++ /dev/null @@ -1,15 +0,0 @@ -from scripts.test import shared -from . import utils - - -class DataCountTest(utils.BinaryenTestCase): - def test_datacount(self): - self.roundtrip('bulkmem_data.wasm') - - def test_bad_datacount(self): - path = self.input_path('bulkmem_bad_datacount.wasm') - p = shared.run_process(shared.WASM_OPT + ['-g', '-o', '-', path], - check=False, capture_output=True) - self.assertNotEqual(p.returncode, 0) - self.assertIn('Number of segments does not agree with DataCount section', - p.stderr) From 4d906204ebd3ad88a48350f008c7b72a0844e1da Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 24 Sep 2024 14:28:26 -0700 Subject: [PATCH 043/622] [NFC] Parallelize the actual inlining part of the Inlining pass (#6966) We already parallelized collecting info about functions and finding inlining opportunities, but the actual inlining - copying the code into the target function - was done sequentially. It turns out that a lot of work happens there: this PR makes the pass over 2x faster. Details: * Move nameHint to InliningAction, so it is there when we apply the actions. * Add a DoInlining internal pass which calls doInlining(). * Refactor OptUtils a little to make it easy to run DoInlining before opts. * Also remove the return value of doInlining which was not used. --- src/passes/Inlining.cpp | 113 ++++++++++++++++++++++++++++++---------- src/passes/opt-utils.h | 14 +++-- 2 files changed, 97 insertions(+), 30 deletions(-) diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index e67a0f35bf7..30bb5c23ff0 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -244,8 +244,18 @@ struct InliningAction { Function* contents; bool insideATry; - InliningAction(Expression** callSite, Function* contents, bool insideATry) - : callSite(callSite), contents(contents), insideATry(insideATry) {} + // An optional name hint can be provided, which will then be used in the name + // of the block we put the inlined code in. Using a unique name hint in each + // inlining can reduce the risk of name overlaps (which cause fixup work in + // UniqueNameMapper::uniquify). + Index nameHint = 0; + + InliningAction(Expression** callSite, + Function* contents, + bool insideATry, + Index nameHint = 0) + : callSite(callSite), contents(contents), insideATry(insideATry), + nameHint(nameHint) {} }; struct InliningState { @@ -436,17 +446,11 @@ struct Updater : public TryDepthWalker { }; // Core inlining logic. Modifies the outside function (adding locals as -// needed), and returns the inlined code. -// -// An optional name hint can be provided, which will then be used in the name of -// the block we put the inlined code in. Using a unique name hint in each call -// of this function can reduce the risk of name overlaps (which cause fixup work -// in UniqueNameMapper::uniquify). -static Expression* doInlining(Module* module, - Function* into, - const InliningAction& action, - PassOptions& options, - Index nameHint = 0) { +// needed). +static void doInlining(Module* module, + Function* into, + const InliningAction& action, + PassOptions& options) { Function* from = action.contents; auto* call = (*action.callSite)->cast(); @@ -457,8 +461,8 @@ static Expression* doInlining(Module* module, Builder builder(*module); auto* block = builder.makeBlock(); auto name = std::string("__inlined_func$") + from->name.toString(); - if (nameHint) { - name += '$' + std::to_string(nameHint); + if (action.nameHint) { + name += '$' + std::to_string(action.nameHint); } block->name = Name(name); @@ -629,9 +633,39 @@ static Expression* doInlining(Module* module, // New locals we added may require fixups for nondefaultability. // FIXME Is this not done automatically? TypeUpdating::handleNonDefaultableLocals(into, *module); - return block; } +// A map of function names to the inlining actions we've decided to actually +// perform in them. +using ChosenActions = std::unordered_map>; + +// A pass that calls doInlining() on a bunch of actions that were chosen to +// perform. +struct DoInlining : public Pass { + bool isFunctionParallel() override { return true; } + + std::unique_ptr create() override { + return std::make_unique(chosenActions); + } + + DoInlining(const ChosenActions& chosenActions) + : chosenActions(chosenActions) {} + + void runOnFunction(Module* module, Function* func) override { + auto iter = chosenActions.find(func->name); + // We must be called on a function that we actually want to inline into. + assert(iter != chosenActions.end()); + const auto& actions = iter->second; + assert(!actions.empty()); + for (auto action : actions) { + doInlining(module, func, action, getPassOptions()); + } + } + +private: + const ChosenActions& chosenActions; +}; + // // Function splitting / partial inlining / inlining of conditions. // @@ -1260,11 +1294,20 @@ struct Inlining : public Pass { state.actionsForFunction[func->name]; funcNames.push_back(func->name); } - // find and plan inlinings + + // Find and plan inlinings in parallel. This discovers inlining + // opportunities, by themselves, but does not yet take into account + // interactions between them (e.g. we don't want to both inline into a + // function and then inline it as well). Planner(&state).run(getPassRunner(), module); - // perform inlinings TODO: parallelize - std::unordered_map inlinedUses; // how many uses we inlined - // which functions were inlined into + + // Choose which inlinings to perform. We do this sequentially so that we + // can consider interactions between them, and avoid nondeterminism. + ChosenActions chosenActions; + + // How many uses (calls of the function) we inlined. + std::unordered_map inlinedUses; + for (auto name : funcNames) { auto* func = module->getFunction(name); // if we've inlined a function, don't inline into it in this iteration, @@ -1293,24 +1336,40 @@ struct Inlining : public Pass { std::cout << "inline " << inlinedName << " into " << func->name << '\n'; #endif - // Update the action for the actual inlining we are about to perform + // Update the action for the actual inlining we have chosen to perform // (when splitting, we will actually inline one of the split pieces and // not the original function itself; note how even if we do that then // we are still removing a call to the original function here, and so // we do not need to change anything else lower down - we still want to // note that we got rid of one use of the original function). action.contents = getActuallyInlinedFunction(action.contents); - - // Perform the inlining and update counts. - doInlining(module, func, action, getPassOptions(), inlinedNameHint++); + action.nameHint = inlinedNameHint++; + chosenActions[func->name].push_back(action); inlinedUses[inlinedName]++; inlinedInto.insert(func); assert(inlinedUses[inlinedName] <= infos[inlinedName].refs); } } - if (optimize && inlinedInto.size() > 0) { - OptUtils::optimizeAfterInlining(inlinedInto, module, getPassRunner()); + + if (chosenActions.empty()) { + // We found nothing to do. + return; + } + + // Perform the inlinings in parallel (sequentially inside each function we + // inline into, but in parallel between them). If we are optimizing, do so + // as well. + { + PassUtils::FilteredPassRunner runner( + module, inlinedInto, getPassRunner()->options); + runner.setIsNested(true); + runner.add(std::make_unique(chosenActions)); + if (optimize) { + OptUtils::addUsefulPassesAfterInlining(runner); + } + runner.run(); } + // remove functions that we no longer need after inlining module->removeFunctions([&](Function* func) { auto name = func->name; @@ -1320,7 +1379,7 @@ struct Inlining : public Pass { }); } - // See explanation in doInlining() for the parameter nameHint. + // See explanation in InliningAction. Index inlinedNameHint = 0; // Decide for a given function whether to inline, and if so in what mode. diff --git a/src/passes/opt-utils.h b/src/passes/opt-utils.h index 7bb733b0323..69552f7178a 100644 --- a/src/passes/opt-utils.h +++ b/src/passes/opt-utils.h @@ -28,15 +28,23 @@ namespace wasm::OptUtils { +// Given a PassRunner, applies a set of useful passes that make sense to run +// after inlining. +inline void addUsefulPassesAfterInlining(PassRunner& runner) { + // Propagating constants makes a lot of sense after inlining, as new constants + // may have arrived. + runner.add("precompute-propagate"); + // Do all the usual stuff. + runner.addDefaultFunctionOptimizationPasses(); +} + // Run useful optimizations after inlining new code into a set of functions. inline void optimizeAfterInlining(const PassUtils::FuncSet& funcs, Module* module, PassRunner* parentRunner) { PassUtils::FilteredPassRunner runner(module, funcs, parentRunner->options); runner.setIsNested(true); - // this is especially useful after inlining - runner.add("precompute-propagate"); - runner.addDefaultFunctionOptimizationPasses(); // do all the usual stuff + addUsefulPassesAfterInlining(runner); runner.run(); } From ccef354cc8c0379e91b4dffb365fc69f616d6e25 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 24 Sep 2024 16:15:06 -0700 Subject: [PATCH 044/622] [NFC-ish] Avoid repeated ReFinalize etc. when inlining (#6967) We may inline multiple times into a single function. Previously, if we did so, we did the "fixups" such as ReFinalize and non-nullable local fixes once per such inlining. But that is wasteful as each ReFinalize etc. scans the whole function, and could be done after we copy all the code from all the inlinings, which is what this PR does: it splits doInlining() into one function that inlines code and one that does the updates after, and the update is done after all inlinings. This turns out to be very important, a 5x speedup on two large real-world wasm files I am looking at. The reason is that we actually inline more than once in half the cases, and sometimes far more - in one case we inline over 1,000 times into a function! (and ReFinalized 1,000 times too many) This is practically NFC, but it turns out that there are some tiny noticeable differences between running ReFinalize once at the end vs. once after each inlining. These differences are not really functional or observable in the behavior of the code, and optimizations would remove them anyhow, but they are noticeable in two tests here. The changes to tests are, in order: * Different block names, just because the counter we use sees more things. * In a testcase with unreachable code, we inline twice into a function, and the first inlining brings in an unreachable, and ReFinalizing early will lead to it propagating differently than if we wait to ReFinalize. (It actually leads to another cycle of inlining in that case, as a fluke.) --- src/passes/Inlining.cpp | 30 ++++++++++--- .../inlining-optimizing_optimize-level=3.wast | 6 +-- .../lit/passes/inlining_optimize-level=3.wast | 43 +++++++++---------- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 30bb5c23ff0..cbee7e77fc1 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -446,11 +446,11 @@ struct Updater : public TryDepthWalker { }; // Core inlining logic. Modifies the outside function (adding locals as -// needed). -static void doInlining(Module* module, - Function* into, - const InliningAction& action, - PassOptions& options) { +// needed) by copying the inlined code into it. +static void doCodeInlining(Module* module, + Function* into, + const InliningAction& action, + PassOptions& options) { Function* from = action.contents; auto* call = (*action.callSite)->cast(); @@ -622,6 +622,12 @@ static void doInlining(Module* module, } *action.callSite = builder.makeSequence(old, builder.makeUnreachable()); } +} + +// Updates the outer function after we inline into it. This is a general +// operation that does not depend on what we inlined, it just makes sure that we +// refinalize everything, have no duplicate break labels, etc. +static void updateAfterInlining(Module* module, Function* into) { // Anything we inlined into may now have non-unique label names, fix it up. // Note that we must do this before refinalization, as otherwise duplicate // block labels can lead to errors (the IR must be valid before we @@ -635,6 +641,14 @@ static void doInlining(Module* module, TypeUpdating::handleNonDefaultableLocals(into, *module); } +static void doInlining(Module* module, + Function* into, + const InliningAction& action, + PassOptions& options) { + doCodeInlining(module, into, action, options); + updateAfterInlining(module, into); +} + // A map of function names to the inlining actions we've decided to actually // perform in them. using ChosenActions = std::unordered_map>; @@ -657,9 +671,13 @@ struct DoInlining : public Pass { assert(iter != chosenActions.end()); const auto& actions = iter->second; assert(!actions.empty()); + + // Inline all the code first, then update func once at the end (which saves + // e.g. running ReFinalize after each action, of which there might be many). for (auto action : actions) { - doInlining(module, func, action, getPassOptions()); + doCodeInlining(module, func, action, getPassOptions()); } + updateAfterInlining(module, func); } private: diff --git a/test/lit/passes/inlining-optimizing_optimize-level=3.wast b/test/lit/passes/inlining-optimizing_optimize-level=3.wast index fdfbf7b35d6..f6df7305620 100644 --- a/test/lit/passes/inlining-optimizing_optimize-level=3.wast +++ b/test/lit/passes/inlining-optimizing_optimize-level=3.wast @@ -8820,7 +8820,7 @@ ;; CHECK-NEXT: (br $__rjti$8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block $label$break$L8 - ;; CHECK-NEXT: (block $__rjti$20 + ;; CHECK-NEXT: (block $__rjti$23 ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (local.tee $9 @@ -8845,7 +8845,7 @@ ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (loop $while-in5 - ;; CHECK-NEXT: (br_if $__rjti$20 + ;; CHECK-NEXT: (br_if $__rjti$23 ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (i32.load8_u ;; CHECK-NEXT: (local.get $8) @@ -8890,7 +8890,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br_if $__rjti$20 + ;; CHECK-NEXT: (br_if $__rjti$23 ;; CHECK-NEXT: (local.get $9) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $9 diff --git a/test/lit/passes/inlining_optimize-level=3.wast b/test/lit/passes/inlining_optimize-level=3.wast index 1064ba04d11..ab93865330e 100644 --- a/test/lit/passes/inlining_optimize-level=3.wast +++ b/test/lit/passes/inlining_optimize-level=3.wast @@ -503,23 +503,6 @@ ;; (That avoids possible validation problems, and maximizes DCE.) To keep it ;; unreachable we'll add an unreachable instruction after the inlined code. (module - ;; CHECK: (type $0 (func (param f32))) - - ;; CHECK: (type $1 (func)) - - ;; CHECK: (func $A (param $0 f32) - ;; CHECK-NEXT: (local $1 f32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result f32) - ;; CHECK-NEXT: (block $__inlined_func$C (result f32) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) (func $A (param $0 f32) (drop (call $C @@ -527,12 +510,16 @@ ) ) ) + ;; CHECK: (type $0 (func)) + ;; CHECK: (func $B ;; CHECK-NEXT: (local $0 f32) - ;; CHECK-NEXT: (call $A - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local $1 f32) + ;; CHECK-NEXT: (local $2 f32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $__inlined_func$A$3 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block (result f32) ;; CHECK-NEXT: (block $__inlined_func$C$2 (result f32) ;; CHECK-NEXT: (local.tee $0 ;; CHECK-NEXT: (block @@ -544,7 +531,19 @@ ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f32) + ;; CHECK-NEXT: (block $__inlined_func$C (result f32) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From 86258e52beff652ed44f888287300f369fea2a87 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Sep 2024 13:41:14 -0700 Subject: [PATCH 045/622] [NFC] Use an unordered map in Parents (#6970) This makes it slightly faster. Followup to https://github.com/WebAssembly/binaryen/pull/6953#discussion_r1765313401 --- src/ir/parents.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/parents.h b/src/ir/parents.h index 5ab0bc2828c..10652a8a1f1 100644 --- a/src/ir/parents.h +++ b/src/ir/parents.h @@ -37,7 +37,7 @@ struct Parents { : public ExpressionStackWalker> { void visitExpression(Expression* curr) { parentMap[curr] = getParent(); } - std::map parentMap; + std::unordered_map parentMap; } inner; }; From 2e0a7b53446e55b5b048c2c7559612d19059f5c3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Sep 2024 13:41:26 -0700 Subject: [PATCH 046/622] [NFC] Early-exit PickLoadSigns if there are no memories (#6971) In WasmGC modules there is often no memory at all, and we can skip walking the code in this pass in such cases. --- src/passes/PickLoadSigns.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/passes/PickLoadSigns.cpp b/src/passes/PickLoadSigns.cpp index d30e0a381c5..587314209e6 100644 --- a/src/passes/PickLoadSigns.cpp +++ b/src/passes/PickLoadSigns.cpp @@ -45,6 +45,11 @@ struct PickLoadSigns : public WalkerPass> { std::unordered_map loads; void doWalkFunction(Function* func) { + if (getModule()->memories.empty()) { + // There can be no loads without a memory. + return; + } + // prepare usages.resize(func->getNumLocals()); // walk From 3856a2dc909b3c713497ef311fe4051078ee74b9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Sep 2024 13:42:35 -0700 Subject: [PATCH 047/622] [NFC-ish] Stop creating unneeded blocks around calls when inlining (#6969) Inlining was careful about nested calls like this: (call $a (call $b) ) If we inlined the outer call first, we'd have (block $inlined-code-from-a ..code.. (call $b) ) After that, the inner call is a child of a block, not of a call. That is, we've moved the inner call to another parent. To replace that inner call when we inline, we'd need to update the new parent, which would require work. To avoid that work, the pass simply created a block in the middle: (call $a (block (call $b) ) ) Now the inner call's immediate parent will not change when we inline the outer call. However, it turns out that this was entirely unnecessary. We find the calls using a post-order traversal, and we store the actions in a vector that we traverse in order, so we only ever process things in the optimal order of children before parents. And in that order there is no problem: inlining the inner call first leads to (call $a (block $inlined-code-from-b (..code..) ) ) That does not affect the outer call's parent. This PR removes the creation of the unnecessary blocks. This doesn't improve the final output as optimizations remove the unneeded blocks later anyhow, but it does make the code simpler and a little faster. It also makes debugging less confusing. But this is not truly NFC because --inlining (but not --inlining-optimizing) will actually emit fewer blocks now (but only --inlining-optimizing is used by default in production). The diff on tests here is very small when ignoring whitespace. The remaining differences are just emitting fewer obviously-unneeded blocks. There is also one test that needed manual changes, inlining-eh-legacy, because it tested that we do Pop fixups, but after emitting one fewer block, those fixups were not needed. I added a new test there with two nested calls, which does end up needing those fixups. I also added such a test in inlining_all-features so that we have coverage for such nested calls (we might remove the eh-legacy file some day, and other existing tests with nested calls that I found were more complex). --- src/passes/Inlining.cpp | 9 +- test/lit/passes/inlining-eh-legacy.wast | 85 +- test/lit/passes/inlining-unreachable.wast | 40 +- test/lit/passes/inlining_all-features.wast | 41 +- .../lit/passes/inlining_enable-tail-call.wast | 574 +++----- .../lit/passes/inlining_optimize-level=3.wast | 404 +++--- test/lit/passes/inlining_splitting.wast | 1270 ++++++++--------- .../lit/passes/inlining_splitting_basics.wast | 100 +- .../no-inline-monomorphize-inlining.wast | 68 +- test/lit/passes/no-inline.wast | 608 ++++---- 10 files changed, 1433 insertions(+), 1766 deletions(-) diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index cbee7e77fc1..998e355c91f 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -290,15 +290,12 @@ struct Planner : public WalkerPass> { } if (state->inlinableFunctions.count(curr->target) && !isUnreachable && curr->target != getFunction()->name) { - // nest the call in a block. that way the location of the pointer to the - // call will not change even if we inline multiple times into the same - // function, otherwise call1(call2()) might be a problem - auto* block = Builder(*getModule()).makeBlock(curr); - replaceCurrent(block); // can't add a new element in parallel assert(state->actionsForFunction.count(getFunction()->name) > 0); state->actionsForFunction[getFunction()->name].emplace_back( - &block->list[0], getModule()->getFunction(curr->target), tryDepth > 0); + getCurrentPointer(), + getModule()->getFunction(curr->target), + tryDepth > 0); } } diff --git a/test/lit/passes/inlining-eh-legacy.wast b/test/lit/passes/inlining-eh-legacy.wast index b6e3caec2cf..9135f786ed0 100644 --- a/test/lit/passes/inlining-eh-legacy.wast +++ b/test/lit/passes/inlining-eh-legacy.wast @@ -22,16 +22,14 @@ ;; CHECK: (func $caller-with-label (type $1) (param $x i32) ;; CHECK-NEXT: (loop $label - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$callee-with-label - ;; CHECK-NEXT: (try $label0 - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $tag$0 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$callee-with-label + ;; CHECK-NEXT: (try $label0 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag$0 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -89,10 +87,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$callee-a$1 (result i32) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$callee-a$1 (result i32) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller-with-try-delegate (result i32) @@ -109,36 +105,77 @@ ;; CHECK: (func $caller-with-pop (type $0) ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag$0 + ;; CHECK-NEXT: (block $__inlined_func$callee-b$2 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller-with-pop + (try + (do) + (catch $tag$0 + ;; After this $callee-b is inlined, there will be an additional block + ;; surrouding this 'pop'. However, that block has no breaks to it, and + ;; will be considered the implicit block of the catch scope, so no + ;; fixups are done. + (call $callee-b + (pop i32) + ) + ) + ) + ) + + (func $callee-c (param $x i32) (result i32) + (local.get $x) + ) + + ;; CHECK: (func $caller-with-pop-twice (type $0) + ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag$0 - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$callee-b$2 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (block $__inlined_func$callee-b$4 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block $__inlined_func$callee-c$3 (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $caller-with-pop + (func $caller-with-pop-twice (try (do) (catch $tag$0 - ;; After this $callee-b is inlined, there will be additional 'block's - ;; surrouding this 'pop', which makes its location invalid. We fix it by + ;; As above, but now with two calls here, both of whom will be inlined. + ;; As a result we have two blocks that nest the pops. We fix that by ;; creating a new local to set the result of 'pop' and later use ;; local.get to get the value within the inlined function body. (call $callee-b - (pop i32) + (call $callee-c + (pop i32) + ) ) ) ) diff --git a/test/lit/passes/inlining-unreachable.wast b/test/lit/passes/inlining-unreachable.wast index 044de4848c9..d723212fc4b 100644 --- a/test/lit/passes/inlining-unreachable.wast +++ b/test/lit/passes/inlining-unreachable.wast @@ -75,11 +75,9 @@ ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$callee - ;; CHECK-NEXT: (call $imported - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$callee + ;; CHECK-NEXT: (call $imported + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -100,22 +98,20 @@ ;; CHECK: (func $caller-2 (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$callee-2$1 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__return_call - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (br $__return_call) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$callee-2$1 + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $__return_call + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (br $__return_call) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $imported - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $imported + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -179,11 +175,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$1 - ;; CHECK-NEXT: (block $block0 - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$1 + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/inlining_all-features.wast b/test/lit/passes/inlining_all-features.wast index c274e9abb28..38d24ab72bb 100644 --- a/test/lit/passes/inlining_all-features.wast +++ b/test/lit/passes/inlining_all-features.wast @@ -20,10 +20,8 @@ (func $foo) ;; CHECK: (func $ref_func_test (type $1) (result funcref) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$foo - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$foo + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: ) @@ -139,3 +137,38 @@ ) ) ) + +;; Test handling of nested inlined calls. +(module + (func $callee-a (param i32)) + + (func $callee-b (param $x i32) (result i32) + (local.get $x) + ) + + ;; CHECK: (type $0 (func)) + + ;; CHECK: (func $caller-with-pop-twice (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (block $__inlined_func$callee-a$1 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block $__inlined_func$callee-b (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller-with-pop-twice + ;; Both these calls should be inlined. + (call $callee-a + (call $callee-b + (i32.const 42) + ) + ) + ) +) diff --git a/test/lit/passes/inlining_enable-tail-call.wast b/test/lit/passes/inlining_enable-tail-call.wast index a5e797ca74f..dae243dcf5b 100644 --- a/test/lit/passes/inlining_enable-tail-call.wast +++ b/test/lit/passes/inlining_enable-tail-call.wast @@ -24,124 +24,92 @@ ;; CHECK-NEXT: (local $4 f32) ;; CHECK-NEXT: (local $5 i64) ;; CHECK-NEXT: (local $6 f32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$exported - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$exported + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$tabled$1 - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$tabled$1 + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$multi$2 - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$multi$2 + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$multi$3 - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$multi$3 + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$ok$4 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$ok$4 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$int$5 (result i32) - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$int$5 (result i32) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result f64) - ;; CHECK-NEXT: (block $__inlined_func$double$6 (result f64) - ;; CHECK-NEXT: (f64.const 3.14159) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$double$6 (result f64) + ;; CHECK-NEXT: (f64.const 3.14159) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$int2$7 (result i32) - ;; CHECK-NEXT: (i32.const 112) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$int2$7 (result i32) + ;; CHECK-NEXT: (i32.const 112) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $y - ;; CHECK-NEXT: (block (result f64) - ;; CHECK-NEXT: (block $__inlined_func$double2$8 (result f64) - ;; CHECK-NEXT: (f64.const 113.14159) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$double2$8 (result f64) + ;; CHECK-NEXT: (f64.const 113.14159) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$with-local$9 - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (f32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (f32.const 2.1418280601501465) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$with-local$9 + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f32.const 2.1418280601501465) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$with-local2$10 - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (i64.const 4) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$with-local2$10 + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i64.const 4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$return$11 (result i32) - ;; CHECK-NEXT: (br $__inlined_func$return$11 - ;; CHECK-NEXT: (i32.const 5) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$return$11 (result i32) + ;; CHECK-NEXT: (br $__inlined_func$return$11 + ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$multipass$12 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$multipass2$15 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 6) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$multipass$12 + ;; CHECK-NEXT: (block $__inlined_func$multipass2$15 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$param$13 - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (f32.const 12.34000015258789) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (i64.const 890005350012) + ;; CHECK-NEXT: (block $__inlined_func$param$13 + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (f32.const 12.34000015258789) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i64.const 890005350012) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $5) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $6) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -184,9 +152,7 @@ ) ;; CHECK: (func $cycle1 ;; CHECK-NEXT: (block $__inlined_func$cycle2$14 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (call $cycle1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $cycle1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cycle1 @@ -313,18 +279,16 @@ ;; CHECK-NEXT: (then ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$func_3 (result i32) - ;; CHECK-NEXT: (local.set $8 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (select - ;; CHECK-NEXT: (local.get $8) - ;; CHECK-NEXT: (local.tee $8 - ;; CHECK-NEXT: (i32.const -1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (block $__inlined_func$func_3 (result i32) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (local.tee $8 + ;; CHECK-NEXT: (i32.const -1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -474,14 +438,12 @@ ;; CHECK: (type $0 (func)) ;; CHECK: (func $caller - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$callee - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$callee + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) (func $caller (return_call $callee) @@ -561,17 +523,15 @@ ;; CHECK: (func $caller ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$callee - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$callee + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) (func $caller (return_call $callee @@ -583,12 +543,10 @@ ;; CHECK-NEXT: (block $__original_body ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__original_body) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__original_body) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return) @@ -641,12 +599,10 @@ ;; CHECK-NEXT: (return ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__original_body) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__original_body) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -678,25 +634,23 @@ ;; CHECK-NEXT: (local $y i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$callee - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (block $__inlined_func$callee + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) (func $caller (local $x i32) @@ -727,15 +681,13 @@ ;; CHECK-NEXT: (block $__original_body ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__original_body) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__original_body) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return) @@ -778,23 +730,17 @@ ;; CHECK: (type $0 (func)) ;; CHECK: (func $first - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$second - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$third$2 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$second + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $__inlined_func$third$2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) (func $first (return_call $second) @@ -813,9 +759,7 @@ ;; CHECK-NEXT: (block $__inlined_func$second-2$1 ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (br $__original_body_0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__original_body_0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -871,26 +815,22 @@ ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (return - ;; CHECK-NEXT: (block $__inlined_func$third$1 (result i32) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $5) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (block $__inlined_func$third$1 (result i32) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -940,15 +880,13 @@ ;; CHECK-NEXT: (return ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__original_body) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__original_body) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -956,17 +894,13 @@ ;; CHECK-NEXT: (block $__inlined_func$second ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__original_body_0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__original_body_0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1023,16 +957,10 @@ ;; CHECK: (func $first ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$second (result i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (br $__inlined_func$second - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$third$2 (result i32) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$second (result i32) + ;; CHECK-NEXT: (br $__inlined_func$second + ;; CHECK-NEXT: (block $__inlined_func$third$2 (result i32) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1045,25 +973,19 @@ ) ;; CHECK: (func $first-2 ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$second-2$1 (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__return_call - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (br $__return_call) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$second-2$1 (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block $__return_call + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (br $__return_call) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$third$3 (result i32) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$third$3 (result i32) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1096,19 +1018,15 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (block $__inlined_func$second ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$third$2 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$third$2 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$second) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__inlined_func$second) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1122,21 +1040,17 @@ ;; CHECK-NEXT: (block $__return_call ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (br $__return_call) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__return_call) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $__inlined_func$second-2$1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$third$3 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$third$3 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1241,14 +1155,12 @@ ;; CHECK: (func $first ;; CHECK-NEXT: (block $__inlined_func$second ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (call $third - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (br $__inlined_func$second) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $third + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (br $__inlined_func$second) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$second) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__inlined_func$second) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1263,26 +1175,22 @@ ;; CHECK-NEXT: (block $__return_call ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.tee $0 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (br $__inlined_func$second-2$1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (br $__inlined_func$second-2$1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__return_call) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__return_call) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $__inlined_func$second-2$1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$third$2 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$third$2 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1331,13 +1239,11 @@ ;; CHECK: (func $caller ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$callee (result i32) - ;; CHECK-NEXT: (br $__inlined_func$callee - ;; CHECK-NEXT: (call_indirect (type $T) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$callee (result i32) + ;; CHECK-NEXT: (br $__inlined_func$callee + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1356,22 +1262,20 @@ ) ;; CHECK: (func $caller-2 ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$callee-2$1 (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__return_call - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (br $__return_call) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$callee-2$1 (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block $__return_call + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (br $__return_call) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call_indirect (type $T) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1484,35 +1388,29 @@ (return_call $2) ) ;; CHECK: (func $19 - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $__inlined_func$13$1 ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$13$1 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (global.get $global$0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (global.get $global$0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $__inlined_func$2 ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$2 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (global.get $global$0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (global.set $global$0 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (global.get $global$0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$13$1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $global$0 + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__inlined_func$13$1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1558,39 +1456,35 @@ ) ) ;; CHECK: (func $19 - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $__inlined_func$13$1 ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$13$1 + ;; CHECK-NEXT: (block $__original_body ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__original_body - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (global.get $global$0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (br $__original_body) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (global.get $global$0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$13$1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block $__inlined_func$2 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (global.get $global$0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (global.set $global$0 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (br $__original_body) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $__inlined_func$13$1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$2 + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (global.get $global$0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $global$0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/inlining_optimize-level=3.wast b/test/lit/passes/inlining_optimize-level=3.wast index ab93865330e..654786d5e0d 100644 --- a/test/lit/passes/inlining_optimize-level=3.wast +++ b/test/lit/passes/inlining_optimize-level=3.wast @@ -82,138 +82,106 @@ ) ;; CHECK: (func $intoHere ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$yes$2 (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$yes$2 (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$yes-big-but-single-use$3 (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$yes-big-but-single-use$3 (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$no-calls$21 (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$yes (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$no-calls$21 (result i32) + ;; CHECK-NEXT: (block $__inlined_func$yes (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$no-calls$22 (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$yes0 (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$no-calls$22 (result i32) + ;; CHECK-NEXT: (block $__inlined_func$yes0 (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$yes-calls-but-one-use$23 (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$yes$1 (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$yes-calls-but-one-use$23 (result i32) + ;; CHECK-NEXT: (block $__inlined_func$yes$1 (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$no-loops$4 (result i32) - ;; CHECK-NEXT: (loop (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$no-loops$4 (result i32) + ;; CHECK-NEXT: (loop (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$no-loops$5 (result i32) - ;; CHECK-NEXT: (loop (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$no-loops$5 (result i32) + ;; CHECK-NEXT: (loop (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$yes-loops-but-one-use$6 (result i32) - ;; CHECK-NEXT: (loop (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$yes-loops-but-one-use$6 (result i32) + ;; CHECK-NEXT: (loop (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$no-loops-but-one-use-but-exported$7 (result i32) - ;; CHECK-NEXT: (loop (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$no-loops-but-one-use-but-exported$7 (result i32) + ;; CHECK-NEXT: (loop (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$no-loops-but-one-use-but-tabled$8 (result i32) - ;; CHECK-NEXT: (loop (result i32) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$no-loops-but-one-use-but-tabled$8 (result i32) + ;; CHECK-NEXT: (loop (result i32) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -243,10 +211,8 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (call $recursive-inlining-1 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $recursive-inlining-1 + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -280,32 +246,24 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$b-recursive-inlining-2$24 (result i32) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (block $__inlined_func$b-recursive-inlining-2$24 (result i32) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$b-recursive-inlining-2$25 (result i32) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$b-recursive-inlining-2$25 (result i32) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (block $__inlined_func$b-recursive-inlining-2$26 (result i32) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$b-recursive-inlining-2$27 (result i32) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$b-recursive-inlining-2$26 (result i32) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$b-recursive-inlining-2$27 (result i32) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $b-recursive-inlining-2 - ;; CHECK-NEXT: (local.get $5) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $b-recursive-inlining-2 + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -335,74 +293,54 @@ ;; a single iteration (which is the usual case, and the case here). ;; CHECK: (func $call-many-getters - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$11 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$11 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$12 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$12 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$13 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$13 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$14 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$14 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$15 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$15 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$16 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$16 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$17 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$17 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$18 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$18 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$19 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$19 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$getter$20 - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$getter$20 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -461,18 +399,16 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (block $__inlined_func$0_0 ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$0_0_0 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (br_if $__inlined_func$0_0 - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (block $__inlined_func$0_0_0 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (br_if $__inlined_func$0_0 + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -516,34 +452,26 @@ ;; CHECK-NEXT: (local $0 f32) ;; CHECK-NEXT: (local $1 f32) ;; CHECK-NEXT: (local $2 f32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$A$3 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (block (result f32) - ;; CHECK-NEXT: (block $__inlined_func$C$2 (result f32) - ;; CHECK-NEXT: (local.tee $0 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$D$1 - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block $__inlined_func$A$3 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block $__inlined_func$C$2 (result f32) + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (block $__inlined_func$D$1 + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (f32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result f32) - ;; CHECK-NEXT: (block $__inlined_func$C (result f32) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$C (result f32) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -656,40 +584,36 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$middle1 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $top - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$middle1 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $top + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$middle2$1 - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $top - ;; CHECK-NEXT: (local.get $5) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$middle2$1 + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $top + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $middle3 diff --git a/test/lit/passes/inlining_splitting.wast b/test/lit/passes/inlining_splitting.wast index 4d944d454f1..84c0b9832a3 100644 --- a/test/lit/passes/inlining_splitting.wast +++ b/test/lit/passes/inlining_splitting.wast @@ -52,54 +52,48 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-work-hard - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-work-hard + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$maybe-work-hard ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$maybe-work-hard - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-work-hard$1 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-work-hard$1 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$maybe-work-hard ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$maybe-work-hard - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-work-hard$2 - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-work-hard$2 + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$maybe-work-hard ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$maybe-work-hard - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -135,32 +129,28 @@ ;; CHECK: (func $call-just-if (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$just-if$3 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-B$just-if - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$just-if$3 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$just-if + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$just-if$4 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-B$just-if - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$just-if$4 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$just-if + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -290,52 +280,48 @@ ;; CHECK-NEXT: (local $3 i64) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$many-params$6 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$many-params$6 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$many-params + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$many-params - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$many-params$7 - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$many-params$7 + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$many-params + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$many-params - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: (local.get $5) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -370,40 +356,36 @@ ;; CHECK: (func $call-condition-eqz (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-eqz$8 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-eqz$8 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-eqz - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-eqz + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-eqz$9 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-eqz$9 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-eqz - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-eqz + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -429,27 +411,23 @@ ) ;; CHECK: (func $call-condition-global (type $0) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-global$10 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (global.get $glob) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-global) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-global$10 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (global.get $glob) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-global) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-global$11 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (global.get $glob) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-global) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-global$11 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (global.get $glob) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-global) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -478,40 +456,36 @@ ;; CHECK: (func $call-condition-ref.is (type $0) ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-ref.is$12 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-ref.is$12 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-ref.is - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-ref.is + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-ref.is$13 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$condition-ref.is$13 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-ref.is - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$condition-ref.is + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -640,27 +614,23 @@ ) ;; CHECK: (func $call-start-used-globally (type $0) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$start-used-globally$14 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (global.get $glob) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$start-used-globally) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$start-used-globally$14 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (global.get $glob) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$start-used-globally) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$start-used-globally$15 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (global.get $glob) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$start-used-globally) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$start-used-globally$15 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (global.get $glob) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$start-used-globally) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -683,23 +653,19 @@ ) ;; CHECK: (func $call-inlineable (type $0) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$inlineable$16 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (global.get $glob) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$inlineable$16) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$inlineable$16 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (global.get $glob) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$inlineable$16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$inlineable$17 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (global.get $glob) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$inlineable$17) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$inlineable$17 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (global.get $glob) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$inlineable$17) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -853,37 +819,33 @@ ;; CHECK: (func $call-colliding-name (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$colliding-name$18 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$colliding-name$18 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$colliding-name_67 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$colliding-name_67 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$colliding-name$19 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$colliding-name$19 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$colliding-name_67 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$colliding-name_67 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -929,50 +891,46 @@ ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$error-if-null$20 (result anyref) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$byn-split-inlineable-B$error-if-null$20 - ;; CHECK-NEXT: (call $byn-split-outlined-B$error-if-null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$error-if-null$20 (result anyref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$byn-split-inlineable-B$error-if-null$20 + ;; CHECK-NEXT: (call $byn-split-outlined-B$error-if-null + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$error-if-null$21 (result anyref) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$byn-split-inlineable-B$error-if-null$21 - ;; CHECK-NEXT: (call $byn-split-outlined-B$error-if-null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$error-if-null$21 (result anyref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$byn-split-inlineable-B$error-if-null$21 + ;; CHECK-NEXT: (call $byn-split-outlined-B$error-if-null + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1097,46 +1055,42 @@ ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$reachable-if-body$22 (result anyref) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$reachable-if-body$22 (result anyref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$reachable-if-body ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-B$reachable-if-body - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$reachable-if-body$23 (result anyref) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$reachable-if-body$23 (result anyref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$reachable-if-body ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-B$reachable-if-body - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1163,42 +1117,38 @@ ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (block $__inlined_func$reachable-if-body-noloop$24 (result anyref) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $import) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$reachable-if-body-noloop$24 (result anyref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (block $__inlined_func$reachable-if-body-noloop$25 (result anyref) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $import) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$reachable-if-body-noloop$25 (result anyref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1291,37 +1241,33 @@ ;; CHECK: (func $call-unreachable-if-body-no-result (type $0) ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$unreachable-if-body-no-result$26 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$unreachable-if-body-no-result$26 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$unreachable-if-body-no-result ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-B$unreachable-if-body-no-result - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$unreachable-if-body-no-result$27 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$unreachable-if-body-no-result$27 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$unreachable-if-body-no-result ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-B$unreachable-if-body-no-result - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1363,72 +1309,68 @@ ;; CHECK-NEXT: (local $2 anyref) ;; CHECK-NEXT: (local $3 anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$multi-if$28 (result anyref) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (block $__inlined_func$byn-split-outlined-B$multi-if$30 - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$multi-if$28 (result anyref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $__inlined_func$byn-split-outlined-B$multi-if$30 + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$multi-if_76 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-B$multi-if_76 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$multi-if$29 (result anyref) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (block $__inlined_func$byn-split-outlined-B$multi-if$31 - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$multi-if$29 (result anyref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $__inlined_func$byn-split-outlined-B$multi-if$31 + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$multi-if_76 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-B$multi-if_76 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1642,19 +1584,11 @@ ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$1 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (call $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$1 + ;; CHECK-NEXT: (call $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$1$1 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (call $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$1$1 + ;; CHECK-NEXT: (call $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $0 @@ -1669,52 +1603,34 @@ (call $1) ) ;; CHECK: (func $1 (type $none_=>_none) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$2 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (global.get $global$0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (block $__inlined_func$byn-split-outlined-A$0$3 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$1 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$4 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (global.get $global$0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$2 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (global.get $global$0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $__inlined_func$byn-split-outlined-A$0$3 + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $__inlined_func$1 + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$4 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (global.get $global$0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$1$1 - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$5 - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz - ;; CHECK-NEXT: (global.get $global$0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$1$1 + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$5 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (global.get $global$0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1839,38 +1755,26 @@ ) ;; CHECK: (func $byn-split-outlined-A$0 (type $none_=>_none) -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$1 -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$6 -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.eqz -;; CHECK-NEXT: (global.get $global$0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (call $byn-split-outlined-A$0_21) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $__inlined_func$1 +;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$6 +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (global.get $global$0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (call $byn-split-outlined-A$0_21) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$1$1 -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$7 -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.eqz -;; CHECK-NEXT: (global.get $global$0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (call $byn-split-outlined-A$0_21) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $__inlined_func$1$1 +;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$7 +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (global.get $global$0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (call $byn-split-outlined-A$0_21) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1878,38 +1782,26 @@ ;; CHECK-NEXT: ) ;; CHECK: (func $byn-split-outlined-A$0_21 (type $none_=>_none) -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$1 -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$8 -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.eqz -;; CHECK-NEXT: (global.get $global$0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (call $byn-split-outlined-A$0_22) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $__inlined_func$1 +;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$8 +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (global.get $global$0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (call $byn-split-outlined-A$0_22) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$1$1 -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$9 -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.eqz -;; CHECK-NEXT: (global.get $global$0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (call $byn-split-outlined-A$0_22) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $__inlined_func$1$1 +;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$0$9 +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (global.get $global$0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (call $byn-split-outlined-A$0_22) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1917,19 +1809,11 @@ ;; CHECK-NEXT: ) ;; CHECK: (func $byn-split-outlined-A$0_22 (type $none_=>_none) -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$1 -;; CHECK-NEXT: (block -;; CHECK-NEXT: (call $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $__inlined_func$1 +;; CHECK-NEXT: (call $0) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (block -;; CHECK-NEXT: (block $__inlined_func$1$1 -;; CHECK-NEXT: (block -;; CHECK-NEXT: (call $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $__inlined_func$1$1 +;; CHECK-NEXT: (call $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (module @@ -1961,88 +1845,84 @@ ;; CHECK: (func $call-$middle-size-A (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$middle-size-A - ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (block $__inlined_func$middle-size-A + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$middle-size-A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$middle-size-A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$middle-size-A$1 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$middle-size-A$1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (block $__inlined_func$middle-size-A$1 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$middle-size-A$1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2077,37 +1957,33 @@ ;; CHECK: (func $call-$big-size-A (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$big-size-A$2 - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$big-size-A$2 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$big-size-A ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$big-size-A - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$big-size-A$3 - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$big-size-A$3 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$big-size-A ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $byn-split-outlined-A$big-size-A - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2142,92 +2018,88 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$middle-size-B$4 (result i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block $__inlined_func$middle-size-B$4 (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$middle-size-B$5 (result i32) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block $__inlined_func$middle-size-B$5 (result i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2268,46 +2140,42 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$big-size-B$6 (result i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$byn-split-inlineable-B$big-size-B$6 - ;; CHECK-NEXT: (call $byn-split-outlined-B$big-size-B - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$big-size-B$6 (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$byn-split-inlineable-B$big-size-B$6 + ;; CHECK-NEXT: (call $byn-split-outlined-B$big-size-B + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$big-size-B$7 (result i32) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $__inlined_func$byn-split-inlineable-B$big-size-B$7 - ;; CHECK-NEXT: (call $byn-split-outlined-B$big-size-B - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$big-size-B$7 (result i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__inlined_func$byn-split-inlineable-B$big-size-B$7 + ;; CHECK-NEXT: (call $byn-split-outlined-B$big-size-B + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/inlining_splitting_basics.wast b/test/lit/passes/inlining_splitting_basics.wast index fe07a3926cb..2f3a3290870 100644 --- a/test/lit/passes/inlining_splitting_basics.wast +++ b/test/lit/passes/inlining_splitting_basics.wast @@ -66,37 +66,33 @@ ;; PARTIAL: (func $call-pattern-A (type $0) ;; PARTIAL-NEXT: (local $0 i32) ;; PARTIAL-NEXT: (local $1 i32) - ;; PARTIAL-NEXT: (block - ;; PARTIAL-NEXT: (block $__inlined_func$byn-split-inlineable-A$pattern-A - ;; PARTIAL-NEXT: (local.set $0 - ;; PARTIAL-NEXT: (i32.const 1) + ;; PARTIAL-NEXT: (block $__inlined_func$byn-split-inlineable-A$pattern-A + ;; PARTIAL-NEXT: (local.set $0 + ;; PARTIAL-NEXT: (i32.const 1) + ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (if + ;; PARTIAL-NEXT: (i32.eqz + ;; PARTIAL-NEXT: (local.get $0) ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (if - ;; PARTIAL-NEXT: (i32.eqz + ;; PARTIAL-NEXT: (then + ;; PARTIAL-NEXT: (call $byn-split-outlined-A$pattern-A ;; PARTIAL-NEXT: (local.get $0) ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (then - ;; PARTIAL-NEXT: (call $byn-split-outlined-A$pattern-A - ;; PARTIAL-NEXT: (local.get $0) - ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (block - ;; PARTIAL-NEXT: (block $__inlined_func$byn-split-inlineable-A$pattern-A$1 - ;; PARTIAL-NEXT: (local.set $1 - ;; PARTIAL-NEXT: (i32.const 2) + ;; PARTIAL-NEXT: (block $__inlined_func$byn-split-inlineable-A$pattern-A$1 + ;; PARTIAL-NEXT: (local.set $1 + ;; PARTIAL-NEXT: (i32.const 2) + ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (if + ;; PARTIAL-NEXT: (i32.eqz + ;; PARTIAL-NEXT: (local.get $1) ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (if - ;; PARTIAL-NEXT: (i32.eqz + ;; PARTIAL-NEXT: (then + ;; PARTIAL-NEXT: (call $byn-split-outlined-A$pattern-A ;; PARTIAL-NEXT: (local.get $1) ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (then - ;; PARTIAL-NEXT: (call $byn-split-outlined-A$pattern-A - ;; PARTIAL-NEXT: (local.get $1) - ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) @@ -155,50 +151,46 @@ ;; PARTIAL-NEXT: (local $0 i32) ;; PARTIAL-NEXT: (local $1 i32) ;; PARTIAL-NEXT: (drop - ;; PARTIAL-NEXT: (block (result i32) - ;; PARTIAL-NEXT: (block $__inlined_func$byn-split-inlineable-B$pattern-B$2 (result i32) - ;; PARTIAL-NEXT: (local.set $0 - ;; PARTIAL-NEXT: (i32.const 1) - ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (block (result i32) - ;; PARTIAL-NEXT: (if - ;; PARTIAL-NEXT: (i32.eqz - ;; PARTIAL-NEXT: (local.get $0) - ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (then - ;; PARTIAL-NEXT: (br $__inlined_func$byn-split-inlineable-B$pattern-B$2 - ;; PARTIAL-NEXT: (call $byn-split-outlined-B$pattern-B - ;; PARTIAL-NEXT: (local.get $0) - ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (block $__inlined_func$byn-split-inlineable-B$pattern-B$2 (result i32) + ;; PARTIAL-NEXT: (local.set $0 + ;; PARTIAL-NEXT: (i32.const 1) + ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (block (result i32) + ;; PARTIAL-NEXT: (if + ;; PARTIAL-NEXT: (i32.eqz + ;; PARTIAL-NEXT: (local.get $0) + ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (then + ;; PARTIAL-NEXT: (br $__inlined_func$byn-split-inlineable-B$pattern-B$2 + ;; PARTIAL-NEXT: (call $byn-split-outlined-B$pattern-B + ;; PARTIAL-NEXT: (local.get $0) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (local.get $0) ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (local.get $0) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: (drop - ;; PARTIAL-NEXT: (block (result i32) - ;; PARTIAL-NEXT: (block $__inlined_func$byn-split-inlineable-B$pattern-B$3 (result i32) - ;; PARTIAL-NEXT: (local.set $1 - ;; PARTIAL-NEXT: (i32.const 2) - ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (block (result i32) - ;; PARTIAL-NEXT: (if - ;; PARTIAL-NEXT: (i32.eqz - ;; PARTIAL-NEXT: (local.get $1) - ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (then - ;; PARTIAL-NEXT: (br $__inlined_func$byn-split-inlineable-B$pattern-B$3 - ;; PARTIAL-NEXT: (call $byn-split-outlined-B$pattern-B - ;; PARTIAL-NEXT: (local.get $1) - ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (block $__inlined_func$byn-split-inlineable-B$pattern-B$3 (result i32) + ;; PARTIAL-NEXT: (local.set $1 + ;; PARTIAL-NEXT: (i32.const 2) + ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (block (result i32) + ;; PARTIAL-NEXT: (if + ;; PARTIAL-NEXT: (i32.eqz + ;; PARTIAL-NEXT: (local.get $1) + ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (then + ;; PARTIAL-NEXT: (br $__inlined_func$byn-split-inlineable-B$pattern-B$3 + ;; PARTIAL-NEXT: (call $byn-split-outlined-B$pattern-B + ;; PARTIAL-NEXT: (local.get $1) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) - ;; PARTIAL-NEXT: (local.get $1) ;; PARTIAL-NEXT: ) + ;; PARTIAL-NEXT: (local.get $1) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) ;; PARTIAL-NEXT: ) diff --git a/test/lit/passes/no-inline-monomorphize-inlining.wast b/test/lit/passes/no-inline-monomorphize-inlining.wast index 2479b46f4ec..5065e6e0581 100644 --- a/test/lit/passes/no-inline-monomorphize-inlining.wast +++ b/test/lit/passes/no-inline-monomorphize-inlining.wast @@ -48,53 +48,45 @@ ;; YESINLINE-NEXT: (local $5 (ref $A)) ;; YESINLINE-NEXT: (local $6 (ref $B)) ;; YESINLINE-NEXT: (local $7 (ref $A)) - ;; YESINLINE-NEXT: (block - ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline - ;; YESINLINE-NEXT: (local.set $2 - ;; YESINLINE-NEXT: (local.get $A) - ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $2) - ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline + ;; YESINLINE-NEXT: (local.set $2 + ;; YESINLINE-NEXT: (local.get $A) + ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (drop + ;; YESINLINE-NEXT: (local.get $2) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (block - ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline$1 - ;; YESINLINE-NEXT: (local.set $3 - ;; YESINLINE-NEXT: (local.get $A) - ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $3) - ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline$1 + ;; YESINLINE-NEXT: (local.set $3 + ;; YESINLINE-NEXT: (local.get $A) + ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (drop + ;; YESINLINE-NEXT: (local.get $3) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (block - ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline_2$2 - ;; YESINLINE-NEXT: (local.set $4 - ;; YESINLINE-NEXT: (local.get $B) + ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline_2$2 + ;; YESINLINE-NEXT: (local.set $4 + ;; YESINLINE-NEXT: (local.get $B) + ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (block + ;; YESINLINE-NEXT: (local.set $5 + ;; YESINLINE-NEXT: (local.get $4) ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (block - ;; YESINLINE-NEXT: (local.set $5 - ;; YESINLINE-NEXT: (local.get $4) - ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $5) - ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (drop + ;; YESINLINE-NEXT: (local.get $5) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (block - ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline_2$3 - ;; YESINLINE-NEXT: (local.set $6 - ;; YESINLINE-NEXT: (local.get $B) + ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline_2$3 + ;; YESINLINE-NEXT: (local.set $6 + ;; YESINLINE-NEXT: (local.get $B) + ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (block + ;; YESINLINE-NEXT: (local.set $7 + ;; YESINLINE-NEXT: (local.get $6) ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (block - ;; YESINLINE-NEXT: (local.set $7 - ;; YESINLINE-NEXT: (local.get $6) - ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $7) - ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (drop + ;; YESINLINE-NEXT: (local.get $7) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) diff --git a/test/lit/passes/no-inline.wast b/test/lit/passes/no-inline.wast index 9fd95d0b769..2e21105cb41 100644 --- a/test/lit/passes/no-inline.wast +++ b/test/lit/passes/no-inline.wast @@ -101,53 +101,45 @@ ;; YES_ALL-NEXT: (local $1 i32) ;; YES_ALL-NEXT: (local $2 i32) ;; YES_ALL-NEXT: (local $3 i32) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$full-yes-inline - ;; YES_ALL-NEXT: (local.set $0 - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (call $import) + ;; YES_ALL-NEXT: (block $__inlined_func$full-yes-inline + ;; YES_ALL-NEXT: (local.set $0 + ;; YES_ALL-NEXT: (i32.const 0) ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (call $import) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$full-maybe-inline$1 - ;; YES_ALL-NEXT: (local.set $1 - ;; YES_ALL-NEXT: (i32.const 1) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (call $import) + ;; YES_ALL-NEXT: (block $__inlined_func$full-maybe-inline$1 + ;; YES_ALL-NEXT: (local.set $1 + ;; YES_ALL-NEXT: (i32.const 1) ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (call $import) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$2 - ;; YES_ALL-NEXT: (local.set $2 - ;; YES_ALL-NEXT: (i32.const 2) + ;; YES_ALL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$2 + ;; YES_ALL-NEXT: (local.set $2 + ;; YES_ALL-NEXT: (i32.const 2) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (if + ;; YES_ALL-NEXT: (i32.eqz + ;; YES_ALL-NEXT: (local.get $2) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (if - ;; YES_ALL-NEXT: (i32.eqz + ;; YES_ALL-NEXT: (then + ;; YES_ALL-NEXT: (call $byn-split-outlined-A$partial-yes-inline ;; YES_ALL-NEXT: (local.get $2) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (then - ;; YES_ALL-NEXT: (call $byn-split-outlined-A$partial-yes-inline - ;; YES_ALL-NEXT: (local.get $2) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-maybe-inline$3 - ;; YES_ALL-NEXT: (local.set $3 - ;; YES_ALL-NEXT: (i32.const 3) + ;; YES_ALL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-maybe-inline$3 + ;; YES_ALL-NEXT: (local.set $3 + ;; YES_ALL-NEXT: (i32.const 3) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (if + ;; YES_ALL-NEXT: (i32.eqz + ;; YES_ALL-NEXT: (local.get $3) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (if - ;; YES_ALL-NEXT: (i32.eqz + ;; YES_ALL-NEXT: (then + ;; YES_ALL-NEXT: (call $byn-split-outlined-A$partial-maybe-inline ;; YES_ALL-NEXT: (local.get $3) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (then - ;; YES_ALL-NEXT: (call $byn-split-outlined-A$partial-maybe-inline - ;; YES_ALL-NEXT: (local.get $3) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) @@ -156,36 +148,30 @@ ;; NO_PART-NEXT: (local $0 i32) ;; NO_PART-NEXT: (local $1 i32) ;; NO_PART-NEXT: (local $2 i32) - ;; NO_PART-NEXT: (block - ;; NO_PART-NEXT: (block $__inlined_func$full-yes-inline - ;; NO_PART-NEXT: (local.set $0 - ;; NO_PART-NEXT: (i32.const 0) - ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (call $import) + ;; NO_PART-NEXT: (block $__inlined_func$full-yes-inline + ;; NO_PART-NEXT: (local.set $0 + ;; NO_PART-NEXT: (i32.const 0) ;; NO_PART-NEXT: ) + ;; NO_PART-NEXT: (call $import) ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (block - ;; NO_PART-NEXT: (block $__inlined_func$full-maybe-inline$1 - ;; NO_PART-NEXT: (local.set $1 - ;; NO_PART-NEXT: (i32.const 1) - ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (call $import) + ;; NO_PART-NEXT: (block $__inlined_func$full-maybe-inline$1 + ;; NO_PART-NEXT: (local.set $1 + ;; NO_PART-NEXT: (i32.const 1) ;; NO_PART-NEXT: ) + ;; NO_PART-NEXT: (call $import) ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (block - ;; NO_PART-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$2 - ;; NO_PART-NEXT: (local.set $2 - ;; NO_PART-NEXT: (i32.const 2) + ;; NO_PART-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$2 + ;; NO_PART-NEXT: (local.set $2 + ;; NO_PART-NEXT: (i32.const 2) + ;; NO_PART-NEXT: ) + ;; NO_PART-NEXT: (if + ;; NO_PART-NEXT: (i32.eqz + ;; NO_PART-NEXT: (local.get $2) ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (if - ;; NO_PART-NEXT: (i32.eqz + ;; NO_PART-NEXT: (then + ;; NO_PART-NEXT: (call $byn-split-outlined-A$partial-yes-inline ;; NO_PART-NEXT: (local.get $2) ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (then - ;; NO_PART-NEXT: (call $byn-split-outlined-A$partial-yes-inline - ;; NO_PART-NEXT: (local.get $2) - ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: ) ;; NO_PART-NEXT: ) ;; NO_PART-NEXT: ) ;; NO_PART-NEXT: ) @@ -197,48 +183,42 @@ ;; NO_FULL-NEXT: (local $0 i32) ;; NO_FULL-NEXT: (local $1 i32) ;; NO_FULL-NEXT: (local $2 i32) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$full-yes-inline - ;; NO_FULL-NEXT: (local.set $0 - ;; NO_FULL-NEXT: (i32.const 0) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (call $import) + ;; NO_FULL-NEXT: (block $__inlined_func$full-yes-inline + ;; NO_FULL-NEXT: (local.set $0 + ;; NO_FULL-NEXT: (i32.const 0) ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (call $import) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: (call $full-maybe-inline ;; NO_FULL-NEXT: (i32.const 1) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$1 - ;; NO_FULL-NEXT: (local.set $1 - ;; NO_FULL-NEXT: (i32.const 2) + ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$1 + ;; NO_FULL-NEXT: (local.set $1 + ;; NO_FULL-NEXT: (i32.const 2) + ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (if + ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (local.get $1) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (if - ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (then + ;; NO_FULL-NEXT: (call $byn-split-outlined-A$partial-yes-inline ;; NO_FULL-NEXT: (local.get $1) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (then - ;; NO_FULL-NEXT: (call $byn-split-outlined-A$partial-yes-inline - ;; NO_FULL-NEXT: (local.get $1) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-maybe-inline$2 - ;; NO_FULL-NEXT: (local.set $2 - ;; NO_FULL-NEXT: (i32.const 3) + ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-maybe-inline$2 + ;; NO_FULL-NEXT: (local.set $2 + ;; NO_FULL-NEXT: (i32.const 3) + ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (if + ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (local.get $2) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (if - ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (then + ;; NO_FULL-NEXT: (call $byn-split-outlined-A$partial-maybe-inline ;; NO_FULL-NEXT: (local.get $2) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (then - ;; NO_FULL-NEXT: (call $byn-split-outlined-A$partial-maybe-inline - ;; NO_FULL-NEXT: (local.get $2) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) @@ -246,31 +226,27 @@ ;; NO_BOTH: (func $caller ;; NO_BOTH-NEXT: (local $0 i32) ;; NO_BOTH-NEXT: (local $1 i32) - ;; NO_BOTH-NEXT: (block - ;; NO_BOTH-NEXT: (block $__inlined_func$full-yes-inline - ;; NO_BOTH-NEXT: (local.set $0 - ;; NO_BOTH-NEXT: (i32.const 0) - ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: (call $import) + ;; NO_BOTH-NEXT: (block $__inlined_func$full-yes-inline + ;; NO_BOTH-NEXT: (local.set $0 + ;; NO_BOTH-NEXT: (i32.const 0) ;; NO_BOTH-NEXT: ) + ;; NO_BOTH-NEXT: (call $import) ;; NO_BOTH-NEXT: ) ;; NO_BOTH-NEXT: (call $full-maybe-inline ;; NO_BOTH-NEXT: (i32.const 1) ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: (block - ;; NO_BOTH-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$1 - ;; NO_BOTH-NEXT: (local.set $1 - ;; NO_BOTH-NEXT: (i32.const 2) + ;; NO_BOTH-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$1 + ;; NO_BOTH-NEXT: (local.set $1 + ;; NO_BOTH-NEXT: (i32.const 2) + ;; NO_BOTH-NEXT: ) + ;; NO_BOTH-NEXT: (if + ;; NO_BOTH-NEXT: (i32.eqz + ;; NO_BOTH-NEXT: (local.get $1) ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: (if - ;; NO_BOTH-NEXT: (i32.eqz + ;; NO_BOTH-NEXT: (then + ;; NO_BOTH-NEXT: (call $byn-split-outlined-A$partial-yes-inline ;; NO_BOTH-NEXT: (local.get $1) ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: (then - ;; NO_BOTH-NEXT: (call $byn-split-outlined-A$partial-yes-inline - ;; NO_BOTH-NEXT: (local.get $1) - ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: ) ;; NO_BOTH-NEXT: ) ;; NO_BOTH-NEXT: ) ;; NO_BOTH-NEXT: ) @@ -298,53 +274,45 @@ ;; YES_ALL-NEXT: (local $1 i32) ;; YES_ALL-NEXT: (local $2 i32) ;; YES_ALL-NEXT: (local $3 i32) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$full-yes-inline$4 - ;; YES_ALL-NEXT: (local.set $0 - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (call $import) + ;; YES_ALL-NEXT: (block $__inlined_func$full-yes-inline$4 + ;; YES_ALL-NEXT: (local.set $0 + ;; YES_ALL-NEXT: (i32.const 0) ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (call $import) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$full-maybe-inline$5 - ;; YES_ALL-NEXT: (local.set $1 - ;; YES_ALL-NEXT: (i32.const 1) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (call $import) + ;; YES_ALL-NEXT: (block $__inlined_func$full-maybe-inline$5 + ;; YES_ALL-NEXT: (local.set $1 + ;; YES_ALL-NEXT: (i32.const 1) ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (call $import) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$6 - ;; YES_ALL-NEXT: (local.set $2 - ;; YES_ALL-NEXT: (i32.const 2) + ;; YES_ALL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$6 + ;; YES_ALL-NEXT: (local.set $2 + ;; YES_ALL-NEXT: (i32.const 2) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (if + ;; YES_ALL-NEXT: (i32.eqz + ;; YES_ALL-NEXT: (local.get $2) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (if - ;; YES_ALL-NEXT: (i32.eqz + ;; YES_ALL-NEXT: (then + ;; YES_ALL-NEXT: (call $byn-split-outlined-A$partial-yes-inline ;; YES_ALL-NEXT: (local.get $2) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (then - ;; YES_ALL-NEXT: (call $byn-split-outlined-A$partial-yes-inline - ;; YES_ALL-NEXT: (local.get $2) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-maybe-inline$7 - ;; YES_ALL-NEXT: (local.set $3 - ;; YES_ALL-NEXT: (i32.const 3) + ;; YES_ALL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-maybe-inline$7 + ;; YES_ALL-NEXT: (local.set $3 + ;; YES_ALL-NEXT: (i32.const 3) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (if + ;; YES_ALL-NEXT: (i32.eqz + ;; YES_ALL-NEXT: (local.get $3) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (if - ;; YES_ALL-NEXT: (i32.eqz + ;; YES_ALL-NEXT: (then + ;; YES_ALL-NEXT: (call $byn-split-outlined-A$partial-maybe-inline ;; YES_ALL-NEXT: (local.get $3) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (then - ;; YES_ALL-NEXT: (call $byn-split-outlined-A$partial-maybe-inline - ;; YES_ALL-NEXT: (local.get $3) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) @@ -353,36 +321,30 @@ ;; NO_PART-NEXT: (local $0 i32) ;; NO_PART-NEXT: (local $1 i32) ;; NO_PART-NEXT: (local $2 i32) - ;; NO_PART-NEXT: (block - ;; NO_PART-NEXT: (block $__inlined_func$full-yes-inline$3 - ;; NO_PART-NEXT: (local.set $0 - ;; NO_PART-NEXT: (i32.const 0) - ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (call $import) + ;; NO_PART-NEXT: (block $__inlined_func$full-yes-inline$3 + ;; NO_PART-NEXT: (local.set $0 + ;; NO_PART-NEXT: (i32.const 0) ;; NO_PART-NEXT: ) + ;; NO_PART-NEXT: (call $import) ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (block - ;; NO_PART-NEXT: (block $__inlined_func$full-maybe-inline$4 - ;; NO_PART-NEXT: (local.set $1 - ;; NO_PART-NEXT: (i32.const 1) - ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (call $import) + ;; NO_PART-NEXT: (block $__inlined_func$full-maybe-inline$4 + ;; NO_PART-NEXT: (local.set $1 + ;; NO_PART-NEXT: (i32.const 1) ;; NO_PART-NEXT: ) + ;; NO_PART-NEXT: (call $import) ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (block - ;; NO_PART-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$5 - ;; NO_PART-NEXT: (local.set $2 - ;; NO_PART-NEXT: (i32.const 2) + ;; NO_PART-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$5 + ;; NO_PART-NEXT: (local.set $2 + ;; NO_PART-NEXT: (i32.const 2) + ;; NO_PART-NEXT: ) + ;; NO_PART-NEXT: (if + ;; NO_PART-NEXT: (i32.eqz + ;; NO_PART-NEXT: (local.get $2) ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (if - ;; NO_PART-NEXT: (i32.eqz + ;; NO_PART-NEXT: (then + ;; NO_PART-NEXT: (call $byn-split-outlined-A$partial-yes-inline ;; NO_PART-NEXT: (local.get $2) ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: (then - ;; NO_PART-NEXT: (call $byn-split-outlined-A$partial-yes-inline - ;; NO_PART-NEXT: (local.get $2) - ;; NO_PART-NEXT: ) - ;; NO_PART-NEXT: ) ;; NO_PART-NEXT: ) ;; NO_PART-NEXT: ) ;; NO_PART-NEXT: ) @@ -394,48 +356,42 @@ ;; NO_FULL-NEXT: (local $0 i32) ;; NO_FULL-NEXT: (local $1 i32) ;; NO_FULL-NEXT: (local $2 i32) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$full-yes-inline$3 - ;; NO_FULL-NEXT: (local.set $0 - ;; NO_FULL-NEXT: (i32.const 0) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (call $import) + ;; NO_FULL-NEXT: (block $__inlined_func$full-yes-inline$3 + ;; NO_FULL-NEXT: (local.set $0 + ;; NO_FULL-NEXT: (i32.const 0) ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (call $import) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: (call $full-maybe-inline ;; NO_FULL-NEXT: (i32.const 1) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$4 - ;; NO_FULL-NEXT: (local.set $1 - ;; NO_FULL-NEXT: (i32.const 2) + ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$4 + ;; NO_FULL-NEXT: (local.set $1 + ;; NO_FULL-NEXT: (i32.const 2) + ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (if + ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (local.get $1) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (if - ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (then + ;; NO_FULL-NEXT: (call $byn-split-outlined-A$partial-yes-inline ;; NO_FULL-NEXT: (local.get $1) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (then - ;; NO_FULL-NEXT: (call $byn-split-outlined-A$partial-yes-inline - ;; NO_FULL-NEXT: (local.get $1) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-maybe-inline$5 - ;; NO_FULL-NEXT: (local.set $2 - ;; NO_FULL-NEXT: (i32.const 3) + ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-maybe-inline$5 + ;; NO_FULL-NEXT: (local.set $2 + ;; NO_FULL-NEXT: (i32.const 3) + ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (if + ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (local.get $2) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (if - ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (then + ;; NO_FULL-NEXT: (call $byn-split-outlined-A$partial-maybe-inline ;; NO_FULL-NEXT: (local.get $2) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (then - ;; NO_FULL-NEXT: (call $byn-split-outlined-A$partial-maybe-inline - ;; NO_FULL-NEXT: (local.get $2) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) @@ -443,31 +399,27 @@ ;; NO_BOTH: (func $caller-2 ;; NO_BOTH-NEXT: (local $0 i32) ;; NO_BOTH-NEXT: (local $1 i32) - ;; NO_BOTH-NEXT: (block - ;; NO_BOTH-NEXT: (block $__inlined_func$full-yes-inline$2 - ;; NO_BOTH-NEXT: (local.set $0 - ;; NO_BOTH-NEXT: (i32.const 0) - ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: (call $import) + ;; NO_BOTH-NEXT: (block $__inlined_func$full-yes-inline$2 + ;; NO_BOTH-NEXT: (local.set $0 + ;; NO_BOTH-NEXT: (i32.const 0) ;; NO_BOTH-NEXT: ) + ;; NO_BOTH-NEXT: (call $import) ;; NO_BOTH-NEXT: ) ;; NO_BOTH-NEXT: (call $full-maybe-inline ;; NO_BOTH-NEXT: (i32.const 1) ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: (block - ;; NO_BOTH-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$3 - ;; NO_BOTH-NEXT: (local.set $1 - ;; NO_BOTH-NEXT: (i32.const 2) + ;; NO_BOTH-NEXT: (block $__inlined_func$byn-split-inlineable-A$partial-yes-inline$3 + ;; NO_BOTH-NEXT: (local.set $1 + ;; NO_BOTH-NEXT: (i32.const 2) + ;; NO_BOTH-NEXT: ) + ;; NO_BOTH-NEXT: (if + ;; NO_BOTH-NEXT: (i32.eqz + ;; NO_BOTH-NEXT: (local.get $1) ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: (if - ;; NO_BOTH-NEXT: (i32.eqz + ;; NO_BOTH-NEXT: (then + ;; NO_BOTH-NEXT: (call $byn-split-outlined-A$partial-yes-inline ;; NO_BOTH-NEXT: (local.get $1) ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: (then - ;; NO_BOTH-NEXT: (call $byn-split-outlined-A$partial-yes-inline - ;; NO_BOTH-NEXT: (local.get $1) - ;; NO_BOTH-NEXT: ) - ;; NO_BOTH-NEXT: ) ;; NO_BOTH-NEXT: ) ;; NO_BOTH-NEXT: ) ;; NO_BOTH-NEXT: ) @@ -691,109 +643,101 @@ ;; YES_ALL-NEXT: (local $1 i32) ;; YES_ALL-NEXT: (local $2 i32) ;; YES_ALL-NEXT: (local $3 i32) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$maybe-partial-or-full-1 - ;; YES_ALL-NEXT: (local.set $0 - ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: (block $__inlined_func$maybe-partial-or-full-1 + ;; YES_ALL-NEXT: (local.set $0 + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (if + ;; YES_ALL-NEXT: (local.get $0) + ;; YES_ALL-NEXT: (then + ;; YES_ALL-NEXT: (call $import) ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (block $__inlined_func$maybe-partial-or-full-1$1 + ;; YES_ALL-NEXT: (local.set $1 + ;; YES_ALL-NEXT: (i32.const 1) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (if + ;; YES_ALL-NEXT: (local.get $1) + ;; YES_ALL-NEXT: (then + ;; YES_ALL-NEXT: (call $import) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (block $__inlined_func$maybe-partial-or-full-2$2 + ;; YES_ALL-NEXT: (local.set $2 + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (block ;; YES_ALL-NEXT: (if - ;; YES_ALL-NEXT: (local.get $0) + ;; YES_ALL-NEXT: (local.get $2) ;; YES_ALL-NEXT: (then - ;; YES_ALL-NEXT: (call $import) + ;; YES_ALL-NEXT: (br $__inlined_func$maybe-partial-or-full-2$2) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (nop) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$maybe-partial-or-full-1$1 - ;; YES_ALL-NEXT: (local.set $1 - ;; YES_ALL-NEXT: (i32.const 1) - ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (block $__inlined_func$maybe-partial-or-full-2$3 + ;; YES_ALL-NEXT: (local.set $3 + ;; YES_ALL-NEXT: (i32.const 1) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (block ;; YES_ALL-NEXT: (if - ;; YES_ALL-NEXT: (local.get $1) + ;; YES_ALL-NEXT: (local.get $3) ;; YES_ALL-NEXT: (then - ;; YES_ALL-NEXT: (call $import) + ;; YES_ALL-NEXT: (br $__inlined_func$maybe-partial-or-full-2$3) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$maybe-partial-or-full-2$2 - ;; YES_ALL-NEXT: (local.set $2 + ;; YES_ALL-NEXT: (nop) + ;; YES_ALL-NEXT: (drop ;; YES_ALL-NEXT: (i32.const 0) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (if - ;; YES_ALL-NEXT: (local.get $2) - ;; YES_ALL-NEXT: (then - ;; YES_ALL-NEXT: (br $__inlined_func$maybe-partial-or-full-2$2) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (nop) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (block $__inlined_func$maybe-partial-or-full-2$3 - ;; YES_ALL-NEXT: (local.set $3 - ;; YES_ALL-NEXT: (i32.const 1) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (block - ;; YES_ALL-NEXT: (if - ;; YES_ALL-NEXT: (local.get $3) - ;; YES_ALL-NEXT: (then - ;; YES_ALL-NEXT: (br $__inlined_func$maybe-partial-or-full-2$3) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (nop) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) - ;; YES_ALL-NEXT: (drop - ;; YES_ALL-NEXT: (i32.const 0) - ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) + ;; YES_ALL-NEXT: ) + ;; YES_ALL-NEXT: (drop + ;; YES_ALL-NEXT: (i32.const 0) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) ;; YES_ALL-NEXT: ) @@ -817,67 +761,59 @@ ;; NO_FULL-NEXT: (local $1 i32) ;; NO_FULL-NEXT: (local $2 i32) ;; NO_FULL-NEXT: (local $3 i32) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-B$maybe-partial-or-full-1 - ;; NO_FULL-NEXT: (local.set $0 - ;; NO_FULL-NEXT: (i32.const 0) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (if - ;; NO_FULL-NEXT: (local.get $0) - ;; NO_FULL-NEXT: (then - ;; NO_FULL-NEXT: (call $byn-split-outlined-B$maybe-partial-or-full-1 - ;; NO_FULL-NEXT: (local.get $0) - ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-B$maybe-partial-or-full-1 + ;; NO_FULL-NEXT: (local.set $0 + ;; NO_FULL-NEXT: (i32.const 0) + ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (if + ;; NO_FULL-NEXT: (local.get $0) + ;; NO_FULL-NEXT: (then + ;; NO_FULL-NEXT: (call $byn-split-outlined-B$maybe-partial-or-full-1 + ;; NO_FULL-NEXT: (local.get $0) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-B$maybe-partial-or-full-1$1 - ;; NO_FULL-NEXT: (local.set $1 - ;; NO_FULL-NEXT: (i32.const 1) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (if - ;; NO_FULL-NEXT: (local.get $1) - ;; NO_FULL-NEXT: (then - ;; NO_FULL-NEXT: (call $byn-split-outlined-B$maybe-partial-or-full-1 - ;; NO_FULL-NEXT: (local.get $1) - ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-B$maybe-partial-or-full-1$1 + ;; NO_FULL-NEXT: (local.set $1 + ;; NO_FULL-NEXT: (i32.const 1) + ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (if + ;; NO_FULL-NEXT: (local.get $1) + ;; NO_FULL-NEXT: (then + ;; NO_FULL-NEXT: (call $byn-split-outlined-B$maybe-partial-or-full-1 + ;; NO_FULL-NEXT: (local.get $1) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-partial-or-full-2$2 - ;; NO_FULL-NEXT: (local.set $2 - ;; NO_FULL-NEXT: (i32.const 0) + ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-partial-or-full-2$2 + ;; NO_FULL-NEXT: (local.set $2 + ;; NO_FULL-NEXT: (i32.const 0) + ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (if + ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (local.get $2) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (if - ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (then + ;; NO_FULL-NEXT: (call $byn-split-outlined-A$maybe-partial-or-full-2 ;; NO_FULL-NEXT: (local.get $2) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (then - ;; NO_FULL-NEXT: (call $byn-split-outlined-A$maybe-partial-or-full-2 - ;; NO_FULL-NEXT: (local.get $2) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (block - ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-partial-or-full-2$3 - ;; NO_FULL-NEXT: (local.set $3 - ;; NO_FULL-NEXT: (i32.const 1) + ;; NO_FULL-NEXT: (block $__inlined_func$byn-split-inlineable-A$maybe-partial-or-full-2$3 + ;; NO_FULL-NEXT: (local.set $3 + ;; NO_FULL-NEXT: (i32.const 1) + ;; NO_FULL-NEXT: ) + ;; NO_FULL-NEXT: (if + ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (local.get $3) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (if - ;; NO_FULL-NEXT: (i32.eqz + ;; NO_FULL-NEXT: (then + ;; NO_FULL-NEXT: (call $byn-split-outlined-A$maybe-partial-or-full-2 ;; NO_FULL-NEXT: (local.get $3) ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: (then - ;; NO_FULL-NEXT: (call $byn-split-outlined-A$maybe-partial-or-full-2 - ;; NO_FULL-NEXT: (local.get $3) - ;; NO_FULL-NEXT: ) - ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) ;; NO_FULL-NEXT: ) From c3a71ff46d8f38e29896c321d89b6d0c3b90fbc1 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 26 Sep 2024 15:35:47 -0700 Subject: [PATCH 048/622] [FP16] Implement conversion operations. (#6974) Note: FP16 is a little different from F32/F64 since it can't represent the full 2^16 integer range. 65504 is the max whole integer. This leads to some slightly strange behavior when converting integers greater than 65504 since they become infinity. Specified at https://github.com/WebAssembly/half-precision/blob/main/proposals/half-precision/Overview.md --- scripts/gen-s-parser.py | 4 ++ src/gen-s-parser.inc | 49 ++++++++++++++++++-- src/ir/child-typer.h | 4 ++ src/ir/cost.h | 4 ++ src/literal.h | 8 ++++ src/passes/Print.cpp | 12 +++++ src/support/safe_integer.cpp | 10 ++++ src/support/safe_integer.h | 2 + src/tools/fuzzing/fuzzing.cpp | 6 ++- src/wasm-binary.h | 4 ++ src/wasm-interpreter.h | 8 ++++ src/wasm.h | 4 ++ src/wasm/literal.cpp | 43 +++++++++++++++++ src/wasm/wasm-binary.cpp | 16 +++++++ src/wasm/wasm-stack.cpp | 16 +++++++ src/wasm/wasm-validator.cpp | 4 ++ src/wasm/wasm.cpp | 4 ++ test/lit/basic/f16.wast | 87 +++++++++++++++++++++++++++++++++++ test/spec/f16.wast | 24 ++++++++++ 19 files changed, 303 insertions(+), 6 deletions(-) diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 9c39acb48cf..d15c07e8eca 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -540,6 +540,10 @@ ("i32x4.trunc_sat_f64x2_u_zero", "makeUnary(UnaryOp::TruncSatZeroUVecF64x2ToVecI32x4)"), ("f32x4.demote_f64x2_zero", "makeUnary(UnaryOp::DemoteZeroVecF64x2ToVecF32x4)"), ("f64x2.promote_low_f32x4", "makeUnary(UnaryOp::PromoteLowVecF32x4ToVecF64x2)"), + ("i16x8.trunc_sat_f16x8_s", "makeUnary(UnaryOp::TruncSatSVecF16x8ToVecI16x8)"), + ("i16x8.trunc_sat_f16x8_u", "makeUnary(UnaryOp::TruncSatUVecF16x8ToVecI16x8)"), + ("f16x8.convert_i16x8_s", "makeUnary(UnaryOp::ConvertSVecI16x8ToVecF16x8)"), + ("f16x8.convert_i16x8_u", "makeUnary(UnaryOp::ConvertUVecI16x8ToVecF16x8)"), # relaxed SIMD ops ("i8x16.relaxed_swizzle", "makeBinary(BinaryOp::RelaxedSwizzleVecI8x16)"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index f7d12a1beb8..75fda4f7a6a 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -326,12 +326,34 @@ switch (buf[0]) { default: goto parse_error; } } - case 'c': - if (op == "f16x8.ceil"sv) { - CHECK_ERR(makeUnary(ctx, pos, annotations, UnaryOp::CeilVecF16x8)); - return Ok{}; + case 'c': { + switch (buf[7]) { + case 'e': + if (op == "f16x8.ceil"sv) { + CHECK_ERR(makeUnary(ctx, pos, annotations, UnaryOp::CeilVecF16x8)); + return Ok{}; + } + goto parse_error; + case 'o': { + switch (buf[20]) { + case 's': + if (op == "f16x8.convert_i16x8_s"sv) { + CHECK_ERR(makeUnary(ctx, pos, annotations, UnaryOp::ConvertSVecI16x8ToVecF16x8)); + return Ok{}; + } + goto parse_error; + case 'u': + if (op == "f16x8.convert_i16x8_u"sv) { + CHECK_ERR(makeUnary(ctx, pos, annotations, UnaryOp::ConvertUVecI16x8ToVecF16x8)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + default: goto parse_error; } - goto parse_error; + } case 'd': if (op == "f16x8.div"sv) { CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::DivVecF16x8)); @@ -2038,6 +2060,23 @@ switch (buf[0]) { default: goto parse_error; } } + case 't': { + switch (buf[22]) { + case 's': + if (op == "i16x8.trunc_sat_f16x8_s"sv) { + CHECK_ERR(makeUnary(ctx, pos, annotations, UnaryOp::TruncSatSVecF16x8ToVecI16x8)); + return Ok{}; + } + goto parse_error; + case 'u': + if (op == "i16x8.trunc_sat_f16x8_u"sv) { + CHECK_ERR(makeUnary(ctx, pos, annotations, UnaryOp::TruncSatUVecF16x8ToVecI16x8)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } default: goto parse_error; } } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 638bb9c33ae..499a7e4ddfc 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -423,6 +423,10 @@ template struct ChildTyper : OverriddenVisitor { case RelaxedTruncUVecF32x4ToVecI32x4: case RelaxedTruncZeroSVecF64x2ToVecI32x4: case RelaxedTruncZeroUVecF64x2ToVecI32x4: + case TruncSatSVecF16x8ToVecI16x8: + case TruncSatUVecF16x8ToVecI16x8: + case ConvertSVecI16x8ToVecF16x8: + case ConvertUVecI16x8ToVecF16x8: case AnyTrueVec128: case AllTrueVecI8x16: case AllTrueVecI16x8: diff --git a/src/ir/cost.h b/src/ir/cost.h index d11a9bfac07..fcee6c18edb 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -257,6 +257,10 @@ struct CostAnalyzer : public OverriddenVisitor { case RelaxedTruncUVecF32x4ToVecI32x4: case RelaxedTruncZeroSVecF64x2ToVecI32x4: case RelaxedTruncZeroUVecF64x2ToVecI32x4: + case TruncSatSVecF16x8ToVecI16x8: + case TruncSatUVecF16x8ToVecI16x8: + case ConvertSVecI16x8ToVecF16x8: + case ConvertUVecI16x8ToVecF16x8: ret = 1; break; case InvalidUnary: diff --git a/src/literal.h b/src/literal.h index 6aa348084aa..50666083eb4 100644 --- a/src/literal.h +++ b/src/literal.h @@ -377,14 +377,18 @@ class Literal { Literal extendS32() const; Literal wrapToI32() const; + Literal convertSIToF16() const; + Literal convertUIToF16() const; Literal convertSIToF32() const; Literal convertUIToF32() const; Literal convertSIToF64() const; Literal convertUIToF64() const; Literal convertF32ToF16() const; + Literal truncSatToSI16() const; Literal truncSatToSI32() const; Literal truncSatToSI64() const; + Literal truncSatToUI16() const; Literal truncSatToUI32() const; Literal truncSatToUI64() const; @@ -693,6 +697,10 @@ class Literal { Literal truncSatZeroUToI32x4() const; Literal demoteZeroToF32x4() const; Literal promoteLowToF64x2() const; + Literal truncSatToSI16x8() const; + Literal truncSatToUI16x8() const; + Literal convertSToF16x8() const; + Literal convertUToF16x8() const; Literal swizzleI8x16(const Literal& other) const; Literal relaxedMaddF16x8(const Literal& left, const Literal& right) const; Literal relaxedNmaddF16x8(const Literal& left, const Literal& right) const; diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 427bff32905..8549342042a 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -1368,6 +1368,18 @@ struct PrintExpressionContents case RelaxedTruncZeroUVecF64x2ToVecI32x4: o << "i32x4.relaxed_trunc_f64x2_u_zero"; break; + case TruncSatSVecF16x8ToVecI16x8: + o << "i16x8.trunc_sat_f16x8_s"; + break; + case TruncSatUVecF16x8ToVecI16x8: + o << "i16x8.trunc_sat_f16x8_u"; + break; + case ConvertSVecI16x8ToVecF16x8: + o << "f16x8.convert_i16x8_s"; + break; + case ConvertUVecI16x8ToVecF16x8: + o << "f16x8.convert_i16x8_u"; + break; case InvalidUnary: WASM_UNREACHABLE("unvalid unary operator"); } diff --git a/src/support/safe_integer.cpp b/src/support/safe_integer.cpp index 3a50b50eaa2..86ba2547a53 100644 --- a/src/support/safe_integer.cpp +++ b/src/support/safe_integer.cpp @@ -98,6 +98,11 @@ int64_t wasm::toSInteger64(double x) { * 1 11111111 111...11 => 0xffffffff => -nan(0x7fffff) */ +bool wasm::isInRangeI16TruncS(int32_t i) { + uint32_t u = i; + return (u < 0x47000000U) || (u >= 0x80000000U && u <= 0xc7000000U); +} + bool wasm::isInRangeI32TruncS(int32_t i) { uint32_t u = i; return (u < 0x4f000000U) || (u >= 0x80000000U && u <= 0xcf000000U); @@ -108,6 +113,11 @@ bool wasm::isInRangeI64TruncS(int32_t i) { return (u < 0x5f000000U) || (u >= 0x80000000U && u <= 0xdf000000U); } +bool wasm::isInRangeI16TruncU(int32_t i) { + uint32_t u = i; + return (u < 0x47800000) || (u >= 0x80000000U && u < 0xbf800000U); +} + bool wasm::isInRangeI32TruncU(int32_t i) { uint32_t u = i; return (u < 0x4f800000U) || (u >= 0x80000000U && u < 0xbf800000U); diff --git a/src/support/safe_integer.h b/src/support/safe_integer.h index 031c6c32365..6888ab25f3f 100644 --- a/src/support/safe_integer.h +++ b/src/support/safe_integer.h @@ -32,8 +32,10 @@ uint64_t toUInteger64(double x); int64_t toSInteger64(double x); // The isInRange* functions all expect to be passed the binary representation // of a float or double. +bool isInRangeI16TruncS(int32_t i); bool isInRangeI32TruncS(int32_t i); bool isInRangeI64TruncS(int32_t i); +bool isInRangeI16TruncU(int32_t i); bool isInRangeI32TruncU(int32_t i); bool isInRangeI64TruncU(int32_t i); bool isInRangeI32TruncS(int64_t i); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index a66fa6772d0..a8b8f7855ef 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3143,7 +3143,11 @@ Expression* TranslateToFuzzReader::makeUnary(Type type) { CeilVecF16x8, FloorVecF16x8, TruncVecF16x8, - NearestVecF16x8)), + NearestVecF16x8, + TruncSatSVecF16x8ToVecI16x8, + TruncSatUVecF16x8ToVecI16x8, + ConvertSVecI16x8ToVecF16x8, + ConvertUVecI16x8ToVecF16x8)), make(Type::v128)}); } WASM_UNREACHABLE("invalid value"); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 5021b6a297f..35f13195260 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1039,6 +1039,10 @@ enum ASTNodes { F16x8Max = 0x142, F16x8Pmin = 0x143, F16x8Pmax = 0x144, + I16x8TruncSatF16x8S = 0x145, + I16x8TruncSatF16x8U = 0x146, + F16x8ConvertI16x8S = 0x147, + F16x8ConvertI16x8U = 0x148, // bulk memory opcodes diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 25303abfe45..9659a5c3495 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -632,6 +632,14 @@ class ExpressionRunner : public OverriddenVisitor { return value.demoteZeroToF32x4(); case PromoteLowVecF32x4ToVecF64x2: return value.promoteLowToF64x2(); + case TruncSatSVecF16x8ToVecI16x8: + return value.truncSatToSI16x8(); + case TruncSatUVecF16x8ToVecI16x8: + return value.truncSatToUI16x8(); + case ConvertSVecI16x8ToVecF16x8: + return value.convertSToF16x8(); + case ConvertUVecI16x8ToVecF16x8: + return value.convertUToF16x8(); case InvalidUnary: WASM_UNREACHABLE("invalid unary op"); } diff --git a/src/wasm.h b/src/wasm.h index a86f7701371..e54d628bd4c 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -238,6 +238,10 @@ enum UnaryOp { // Half precision SIMD SplatVecF16x8, + TruncSatSVecF16x8ToVecI16x8, + TruncSatUVecF16x8ToVecI16x8, + ConvertSVecI16x8ToVecF16x8, + ConvertUVecI16x8ToVecF16x8, InvalidUnary }; diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 6aaba729ae2..b53378cfa44 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -798,6 +798,20 @@ Literal Literal::wrapToI32() const { return Literal((int32_t)i64); } +Literal Literal::convertSIToF16() const { + if (type == Type::i32) { + return Literal(fp16_ieee_from_fp32_value(float(i32))); + } + WASM_UNREACHABLE("invalid type"); +} + +Literal Literal::convertUIToF16() const { + if (type == Type::i32) { + return Literal(fp16_ieee_from_fp32_value(float(uint16_t(i32)))); + } + WASM_UNREACHABLE("invalid type"); +} + Literal Literal::convertSIToF32() const { if (type == Type::i32) { return Literal(float(i32)); @@ -861,6 +875,14 @@ static Literal saturating_trunc(typename AsInt::type val) { return Literal(I(std::trunc(bit_cast(val)))); } +Literal Literal::truncSatToSI16() const { + if (type == Type::f32) { + return saturating_trunc( + Literal(*this).castToI32().geti32()); + } + WASM_UNREACHABLE("invalid type"); +} + Literal Literal::truncSatToSI32() const { if (type == Type::f32) { return saturating_trunc( @@ -885,6 +907,14 @@ Literal Literal::truncSatToSI64() const { WASM_UNREACHABLE("invalid type"); } +Literal Literal::truncSatToUI16() const { + if (type == Type::f32) { + return saturating_trunc( + Literal(*this).castToI32().geti32()); + } + WASM_UNREACHABLE("invalid type"); +} + Literal Literal::truncSatToUI32() const { if (type == Type::f32) { return saturating_trunc( @@ -1997,6 +2027,19 @@ Literal Literal::convertUToF32x4() const { return unary<4, &Literal::getLanesI32x4, &Literal::convertUIToF32>(*this); } +Literal Literal::truncSatToSI16x8() const { + return unary<8, &Literal::getLanesF16x8, &Literal::truncSatToSI16>(*this); +} +Literal Literal::truncSatToUI16x8() const { + return unary<8, &Literal::getLanesF16x8, &Literal::truncSatToUI16>(*this); +} +Literal Literal::convertSToF16x8() const { + return unary<8, &Literal::getLanesSI16x8, &Literal::convertSIToF16>(*this); +} +Literal Literal::convertUToF16x8() const { + return unary<8, &Literal::getLanesSI16x8, &Literal::convertUIToF16>(*this); +} + Literal Literal::anyTrueV128() const { auto lanes = getLanesI32x4(); for (size_t i = 0; i < 4; ++i) { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index cb9ea3731ff..3bb33529bdd 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -6522,6 +6522,22 @@ bool WasmBinaryReader::maybeVisitSIMDUnary(Expression*& out, uint32_t code) { curr = allocator.alloc(); curr->op = RelaxedTruncZeroUVecF64x2ToVecI32x4; break; + case BinaryConsts::I16x8TruncSatF16x8S: + curr = allocator.alloc(); + curr->op = TruncSatSVecF16x8ToVecI16x8; + break; + case BinaryConsts::I16x8TruncSatF16x8U: + curr = allocator.alloc(); + curr->op = TruncSatUVecF16x8ToVecI16x8; + break; + case BinaryConsts::F16x8ConvertI16x8S: + curr = allocator.alloc(); + curr->op = ConvertSVecI16x8ToVecF16x8; + break; + case BinaryConsts::F16x8ConvertI16x8U: + curr = allocator.alloc(); + curr->op = ConvertUVecI16x8ToVecF16x8; + break; default: return false; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index a1446c2de9e..7194229fea0 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -1324,6 +1324,22 @@ void BinaryInstWriter::visitUnary(Unary* curr) { o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4RelaxedTruncF64x2UZero); break; + case TruncSatSVecF16x8ToVecI16x8: + o << int8_t(BinaryConsts::SIMDPrefix) + << U32LEB(BinaryConsts::I16x8TruncSatF16x8S); + break; + case TruncSatUVecF16x8ToVecI16x8: + o << int8_t(BinaryConsts::SIMDPrefix) + << U32LEB(BinaryConsts::I16x8TruncSatF16x8U); + break; + case ConvertSVecI16x8ToVecF16x8: + o << int8_t(BinaryConsts::SIMDPrefix) + << U32LEB(BinaryConsts::F16x8ConvertI16x8S); + break; + case ConvertUVecI16x8ToVecF16x8: + o << int8_t(BinaryConsts::SIMDPrefix) + << U32LEB(BinaryConsts::F16x8ConvertI16x8U); + break; case InvalidUnary: WASM_UNREACHABLE("invalid unary op"); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 40726d7cd12..a86187fa788 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2160,6 +2160,10 @@ void FunctionValidator::visitUnary(Unary* curr) { case RelaxedTruncUVecF32x4ToVecI32x4: case RelaxedTruncZeroSVecF64x2ToVecI32x4: case RelaxedTruncZeroUVecF64x2ToVecI32x4: + case TruncSatSVecF16x8ToVecI16x8: + case TruncSatUVecF16x8ToVecI16x8: + case ConvertSVecI16x8ToVecF16x8: + case ConvertUVecI16x8ToVecF16x8: shouldBeEqual(curr->type, Type(Type::v128), curr, "expected v128 type"); shouldBeEqual( curr->value->type, Type(Type::v128), curr, "expected v128 operand"); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 87ae6ac5a38..84fd9a06ffc 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -703,6 +703,10 @@ void Unary::finalize() { case RelaxedTruncUVecF32x4ToVecI32x4: case RelaxedTruncZeroSVecF64x2ToVecI32x4: case RelaxedTruncZeroUVecF64x2ToVecI32x4: + case TruncSatSVecF16x8ToVecI16x8: + case TruncSatUVecF16x8ToVecI16x8: + case ConvertSVecI16x8ToVecF16x8: + case ConvertUVecI16x8ToVecF16x8: type = Type::v128; break; case AnyTrueVec128: diff --git a/test/lit/basic/f16.wast b/test/lit/basic/f16.wast index ba806bb5733..faf7006d4df 100644 --- a/test/lit/basic/f16.wast +++ b/test/lit/basic/f16.wast @@ -534,6 +534,69 @@ (local.get $2) ) ) + ;; CHECK-TEXT: (func $i16x8.trunc_sat_f16x8_s (type $1) (param $0 v128) (result v128) + ;; CHECK-TEXT-NEXT: (i16x8.trunc_sat_f16x8_s + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $i16x8.trunc_sat_f16x8_s (type $1) (param $0 v128) (result v128) + ;; CHECK-BIN-NEXT: (i16x8.trunc_sat_f16x8_s + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $i16x8.trunc_sat_f16x8_s (param $0 v128) (result v128) + (i16x8.trunc_sat_f16x8_s + (local.get $0) + ) + ) + + ;; CHECK-TEXT: (func $i16x8.trunc_sat_f16x8_u (type $1) (param $0 v128) (result v128) + ;; CHECK-TEXT-NEXT: (i16x8.trunc_sat_f16x8_u + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $i16x8.trunc_sat_f16x8_u (type $1) (param $0 v128) (result v128) + ;; CHECK-BIN-NEXT: (i16x8.trunc_sat_f16x8_u + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $i16x8.trunc_sat_f16x8_u (param $0 v128) (result v128) + (i16x8.trunc_sat_f16x8_u + (local.get $0) + ) + ) + + ;; CHECK-TEXT: (func $f16x8.convert_i16x8_s (type $1) (param $0 v128) (result v128) + ;; CHECK-TEXT-NEXT: (f16x8.convert_i16x8_s + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $f16x8.convert_i16x8_s (type $1) (param $0 v128) (result v128) + ;; CHECK-BIN-NEXT: (f16x8.convert_i16x8_s + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $f16x8.convert_i16x8_s (param $0 v128) (result v128) + (f16x8.convert_i16x8_s + (local.get $0) + ) + ) + + ;; CHECK-TEXT: (func $f16x8.convert_i16x8_u (type $1) (param $0 v128) (result v128) + ;; CHECK-TEXT-NEXT: (f16x8.convert_i16x8_u + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $f16x8.convert_i16x8_u (type $1) (param $0 v128) (result v128) + ;; CHECK-BIN-NEXT: (f16x8.convert_i16x8_u + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $f16x8.convert_i16x8_u (param $0 v128) (result v128) + (f16x8.convert_i16x8_u + (local.get $0) + ) + ) ) ;; CHECK-BIN-NODEBUG: (type $0 (func (param v128 v128) (result v128))) @@ -740,3 +803,27 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $28 (type $1) (param $0 v128) (result v128) +;; CHECK-BIN-NODEBUG-NEXT: (i16x8.trunc_sat_f16x8_s +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $29 (type $1) (param $0 v128) (result v128) +;; CHECK-BIN-NODEBUG-NEXT: (i16x8.trunc_sat_f16x8_u +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $30 (type $1) (param $0 v128) (result v128) +;; CHECK-BIN-NODEBUG-NEXT: (f16x8.convert_i16x8_s +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $31 (type $1) (param $0 v128) (result v128) +;; CHECK-BIN-NODEBUG-NEXT: (f16x8.convert_i16x8_u +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/spec/f16.wast b/test/spec/f16.wast index f0d2b067827..b495fe2b64e 100644 --- a/test/spec/f16.wast +++ b/test/spec/f16.wast @@ -34,6 +34,10 @@ (func (export "f16x8.nearest") (param $0 v128) (result v128) (f16x8.nearest (local.get $0))) (func (export "f16x8.relaxed_madd") (param $0 v128) (param $1 v128) (param $2 v128) (result v128) (f16x8.relaxed_madd (local.get $0) (local.get $1) (local.get $2))) (func (export "f16x8.relaxed_nmadd") (param $0 v128) (param $1 v128) (param $2 v128) (result v128) (f16x8.relaxed_nmadd (local.get $0) (local.get $1) (local.get $2))) + (func (export "i16x8.trunc_sat_f16x8_s") (param $0 v128) (result v128) (i16x8.trunc_sat_f16x8_s (local.get $0))) + (func (export "i16x8.trunc_sat_f16x8_u") (param $0 v128) (result v128) (i16x8.trunc_sat_f16x8_u (local.get $0))) + (func (export "f16x8.convert_i16x8_s") (param $0 v128) (result v128) (f16x8.convert_i16x8_s (local.get $0))) + (func (export "f16x8.convert_i16x8_u") (param $0 v128) (result v128) (f16x8.convert_i16x8_u (local.get $0))) ;; Multiple operation tests: (func (export "splat_replace") (result v128) (f16x8.replace_lane 0 (f16x8.splat (f32.const 1)) (f32.const 99)) ) @@ -223,3 +227,23 @@ (assert_return (invoke "splat_replace") (v128.const i16x8 0x5630 0x3c00 0x3c00 0x3c00 0x3c00 0x3c00 0x3c00 0x3c00) ) + +;; conversions +(assert_return (invoke "i16x8.trunc_sat_f16x8_s" + ;; 42 nan inf -inf 65504 -65504 0 0 + (v128.const i16x8 0x5140 0x7e00 0x7c00 0xfc00 0x7bff 0xfbff 0 0)) + (v128.const i16x8 42 0 32767 -32768 32767 -32768 0 0)) +(assert_return (invoke "i16x8.trunc_sat_f16x8_u" + ;; 42 nan inf -inf 65504 -65504 0 0 + (v128.const i16x8 0x5140 0x7e00 0x7c00 0xfc00 0x7bff 0xfbff 0 0)) + (v128.const i16x8 42 0 65535 0 65504 0 0 0)) +(assert_return (invoke "f16x8.convert_i16x8_s" + ;; + (v128.const i16x8 0 1 -1 32767 -32768 0 0 0)) + ;; 0 1 -1 32767 -32768 0 0 0 + (v128.const i16x8 0 0x3c00 0xbc00 0x7800 0xf800 0 0 0)) +(assert_return (invoke "f16x8.convert_i16x8_u" + ;; Unlike f32/64, f16 can't represent the full 2^16 integer range so 2^16 becomes infinity. + (v128.const i16x8 0 1 -1 -32 0 0 0 0)) + ;; 1 inf 65504 + (v128.const i16x8 0 0x3c00 0x7c00 0x7bff 0 0 0 0)) From d8c1b0c0ceb4cc4eb59f3f3ab4840636c78e2a44 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 27 Sep 2024 11:04:15 -0700 Subject: [PATCH 049/622] [NFC] Add changelog items since the last release (#6977) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d415051425..ddfc1730490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,13 @@ full changeset diff at the end of each section. Current Trunk ------------- + - Many compile time speedups were implemented (2x overall improvement), see + https://github.com/WebAssembly/binaryen/issues/4165#issuecomment-2372548271 + - [wasm-split] Add a multi-split mode. (#6943) + - Add a `--preserve-type-order` option that minimizes text format changes in + type ordering. (#6916) + - Add a J2CL specific pass that moves itable entries to vtables. (#6888) + v119 ---- From 2da69a84b69a316409d5af65f66443a27422a353 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 30 Sep 2024 12:33:30 -0700 Subject: [PATCH 050/622] [NFC] Print type names in more places when logging (#6975) --- src/passes/Print.cpp | 5 +++++ src/wasm-interpreter.h | 6 +++--- src/wasm.h | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 8549342042a..0533b97865c 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3714,4 +3714,9 @@ std::ostream& operator<<(std::ostream& o, wasm::StackInst& inst) { return wasm::printStackInst(&inst, o); } +std::ostream& operator<<(std::ostream& o, wasm::ModuleType pair) { + wasm::printTypeOrName(pair.second, o, &pair.first); + return o; +} + } // namespace std diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 9659a5c3495..c5019b2f3bc 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -237,9 +237,9 @@ class ExpressionRunner : public OverriddenVisitor { if (type.isConcrete() || curr->type.isConcrete()) { #if 1 // def WASM_INTERPRETER_DEBUG if (!Type::isSubType(type, curr->type)) { - std::cerr << "expected " << curr->type << ", seeing " << type - << " from\n" - << *curr << '\n'; + std::cerr << "expected " << ModuleType(*module, curr->type) + << ", seeing " << ModuleType(*module, type) << " from\n" + << ModuleExpression(*module, curr) << '\n'; } #endif assert(Type::isSubType(type, curr->type)); diff --git a/src/wasm.h b/src/wasm.h index e54d628bd4c..ede3e50c1de 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2401,6 +2401,9 @@ class Module { // Utility for printing an expression with named types. using ModuleExpression = std::pair; +// Utility for printing an type with a name, if the module defines a name. +using ModuleType = std::pair; + // Utility for printing only the top level of an expression. Named types will be // used if `module` is non-null. struct ShallowExpression { @@ -2422,6 +2425,7 @@ std::ostream& operator<<(std::ostream& o, wasm::Function& func); std::ostream& operator<<(std::ostream& o, wasm::Expression& expression); std::ostream& operator<<(std::ostream& o, wasm::ModuleExpression pair); std::ostream& operator<<(std::ostream& o, wasm::ShallowExpression expression); +std::ostream& operator<<(std::ostream& o, wasm::ModuleType pair); } // namespace std From b5e995f774ebd72c8b6c100ee94b1e03c36d22cc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 30 Sep 2024 12:35:09 -0700 Subject: [PATCH 051/622] Fix the type of reused RefFunc in Precompute (#6976) When we precompute something, we try to avoid allocating a new copy. That's important to avoid many allocations each time we run Precompute - otherwise, each time we see a br we'd allocate a fresh one, and for its values. But we had a bug where we reused a RefFunc as the value of a br without updating the type. It's actually tricky to reach a situation where we find a RefFunc to reuse and it is different from the actual one we want, but the fuzzer found one. Fixes the fuzz bug reported on #6845 (but unrelated to that PR). --- src/passes/Precompute.cpp | 3 +- test/lit/passes/precompute-ref-func.wast | 35 ++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 709fd7d3d85..0fc0753ae7c 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -298,7 +298,8 @@ struct Precompute singleValue.type.getHeapType().isSignature()) { if (auto* r = curr->value->template dynCast()) { r->func = singleValue.getFunc(); - r->finalize(); + auto heapType = getModule()->getFunction(r->func)->type; + r->finalize(Type(heapType, NonNullable)); curr->finalize(); return; } diff --git a/test/lit/passes/precompute-ref-func.wast b/test/lit/passes/precompute-ref-func.wast index df4415c7b5a..495563271e9 100644 --- a/test/lit/passes/precompute-ref-func.wast +++ b/test/lit/passes/precompute-ref-func.wast @@ -2,18 +2,23 @@ ;; RUN: wasm-opt %s -all --precompute -S -o - | filecheck %s (module - ;; CHECK: (type $0 (func (result funcref))) ;; CHECK: (type $shared-func (shared (func (result (ref null (shared func)))))) (type $shared-func (shared (func (result (ref null (shared func)))))) + + ;; CHECK: (type $func (func (result funcref))) + (type $func (func (result funcref))) + + ;; CHECK: (type $2 (func (result (ref $shared-func)))) + ;; CHECK: (elem declare func $test $test-shared) - ;; CHECK: (func $test (type $0) (result funcref) + ;; CHECK: (func $test (type $func) (result funcref) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (ref.func $test) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $test (result funcref) + (func $test (type $func) (result funcref) (block (return (ref.func $test) @@ -33,4 +38,28 @@ ) ) ) + + ;; CHECK: (func $precompute-nested-brs (type $2) (result (ref $shared-func)) + ;; CHECK-NEXT: (ref.func $test-shared) + ;; CHECK-NEXT: ) + (func $precompute-nested-brs (result (ref $shared-func)) + ;; We have two nested brs here, and we can precompute it all. While doing so + ;; we must not get confused between the targets and values: one sends a + ;; shared func, the other a normal func, so the types are different, and any + ;; mistake there will fail validation (say, if we reused the ref.func from + ;; the inner br when generating the outer one). + (block $shared (result (ref $shared-func)) + (drop + (block $func (result (ref $func)) + (br_if $func + (ref.func $test) + (br $shared + (ref.func $test-shared) + ) + ) + ) + ) + (ref.func $test-shared) + ) + ) ) From fcd8dfe7ddd1a4a3ce1be40c674fdd60bbec73d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Mon, 30 Sep 2024 21:37:09 +0200 Subject: [PATCH 052/622] Binary parser: Lift the limit on the number of locals (#6973) This raises the number of locals accepted by the binary parser to the absolute limit in the spec. A warning is now printed when writing a binary file if the Web limit of 50,000 locals is exceeded. Fixes #6968. --- src/wasm/wasm-binary.cpp | 20 ++++++++++++++------ test/unit/test_web_limitations.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 3bb33529bdd..4667718034c 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -472,6 +472,10 @@ void WasmBinaryWriter::writeFunctions() { std::cerr << "Some VMs may not accept this binary because it has a large " << "number of parameters in function " << func->name << ".\n"; } + if (func->getNumLocals() > WebLimitations::MaxFunctionLocals) { + std::cerr << "Some VMs may not accept this binary because it has a large " + << "number of locals in function " << func->name << ".\n"; + } }); finishSection(sectionStart); } @@ -2722,16 +2726,20 @@ void WasmBinaryReader::readFunctions() { void WasmBinaryReader::readVars() { uint32_t totalVars = 0; size_t numLocalTypes = getU32LEB(); + // Use a SmallVector as in the common (MVP) case there are only 4 possible + // types. + SmallVector, 4> decodedVars; + decodedVars.reserve(numLocalTypes); for (size_t t = 0; t < numLocalTypes; t++) { auto num = getU32LEB(); - // The core spec allows up to 2^32 locals, but to avoid allocation failures, - // we additionally impose a much smaller limit, matching the JS embedding. - if (std::ckd_add(&totalVars, totalVars, num) || - totalVars > WebLimitations::MaxFunctionLocals) { - throwError("too many locals"); + if (std::ckd_add(&totalVars, totalVars, num)) { + throwError("unaddressable number of locals"); } auto type = getConcreteType(); - + decodedVars.emplace_back(num, type); + } + currFunction->vars.reserve(totalVars); + for (auto [num, type] : decodedVars) { while (num > 0) { currFunction->vars.push_back(type); num--; diff --git a/test/unit/test_web_limitations.py b/test/unit/test_web_limitations.py index 6359390f9a0..921e02e95bd 100644 --- a/test/unit/test_web_limitations.py +++ b/test/unit/test_web_limitations.py @@ -20,3 +20,19 @@ def test_many_params(self): input=module, capture_output=True) self.assertIn('Some VMs may not accept this binary because it has a large number of parameters in function foo.', p.stderr) + + def test_many_locals(self): + """Test that we warn on large numbers of locals, which Web VMs + disallow.""" + + params = '(local i32) ' * 50_001 + module = ''' + (module + (func $foo %s + ) + ) + ''' % params + p = shared.run_process(shared.WASM_OPT + ['-o', os.devnull], + input=module, capture_output=True) + self.assertIn('Some VMs may not accept this binary because it has a large number of locals in function foo.', + p.stderr) From cb53f0c6966fe7c8e5cc1a975eab9653b5914bde Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 1 Oct 2024 11:34:39 -0700 Subject: [PATCH 053/622] [NFC] Move a TypeInfo constructor out of a header (#6979) Some versions of libcxx or clang error without this, apparently due to Type being a forward declaration. --- src/wasm-type.h | 2 +- src/wasm/wasm-type.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 5b2074e1c19..d26ba324f04 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -280,7 +280,7 @@ struct TypeInfo { Ref ref; }; - TypeInfo(const Tuple& tuple) : kind(TupleKind), tuple(tuple) {} + TypeInfo(const Tuple& tuple); TypeInfo(Tuple&& tuple) : kind(TupleKind), tuple(std::move(tuple)) {} TypeInfo(HeapType heapType, Nullability nullable) : kind(RefKind), ref{heapType, nullable} {} diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index dad8bb669bc..9bb4f425907 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -479,6 +479,8 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, } // anonymous namespace +TypeInfo::TypeInfo(const Tuple& tuple) : kind(TupleKind), tuple(tuple) {} + TypeInfo::TypeInfo(const TypeInfo& other) { kind = other.kind; switch (kind) { From 347fc8a57dcfd9361b05f271a7f2badc929500cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 1 Oct 2024 23:39:34 +0200 Subject: [PATCH 054/622] Source Maps: Support 5 segment mappings (#6795) Support 5-segment source mappings, which add a name. Reference: https://github.com/tc39/source-map/blob/main/source-map-rev3.md#proposed-format --- src/ir/module-utils.cpp | 61 +++++++++++++++++-- src/ir/module-utils.h | 21 ++++--- src/parser/contexts.h | 27 ++++++++- src/passes/Print.cpp | 10 ++- src/wasm-binary.h | 1 + src/wasm.h | 18 +++--- src/wasm/wasm-binary.cpp | 74 +++++++++++++++++++---- src/wasm/wasm.cpp | 5 +- test/lit/merge/sourcemap.wat | 22 +++---- test/lit/merge/sourcemap.wat.second | 6 +- test/lit/metadce/sourcemap.wat | 14 ++--- test/lit/passes/inlining_source-maps.wast | 36 +++++++++++ test/lit/source-map.wast | 17 ++++++ 13 files changed, 253 insertions(+), 59 deletions(-) create mode 100644 test/lit/passes/inlining_source-maps.wast diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 490955866b0..2f26cfa779c 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -38,15 +38,35 @@ static void updateLocationSet(std::set& locations, std::swap(locations, updatedLocations); } +// Update the symbol name indices when moving a set of debug locations from one +// module to another. +static void updateSymbolSet(std::set& locations, + std::vector& symbolIndexMap) { + std::set updatedLocations; + + for (auto iter : locations) { + if (iter.symbolNameIndex) { + iter.symbolNameIndex = symbolIndexMap[*iter.symbolNameIndex]; + } + updatedLocations.insert(iter); + } + locations.clear(); + std::swap(locations, updatedLocations); +} + // Copies a function into a module. If newName is provided it is used as the // name of the function (otherwise the original name is copied). If fileIndexMap // is specified, it is used to rename source map filename indices when copying +// the function from one module to another one. If symbolNameIndexMap is +// specified, it is used to rename source map symbol name indices when copying // the function from one module to another one. Function* copyFunction(Function* func, Module& out, Name newName, - std::optional> fileIndexMap) { - auto ret = copyFunctionWithoutAdd(func, out, newName, fileIndexMap); + std::optional> fileIndexMap, + std::optional> symbolNameIndexMap) { + auto ret = copyFunctionWithoutAdd( + func, out, newName, fileIndexMap, symbolNameIndexMap); return out.addFunction(std::move(ret)); } @@ -54,7 +74,8 @@ std::unique_ptr copyFunctionWithoutAdd(Function* func, Module& out, Name newName, - std::optional> fileIndexMap) { + std::optional> fileIndexMap, + std::optional> symbolNameIndexMap) { auto ret = std::make_unique(); ret->name = newName.is() ? newName : func->name; ret->hasExplicitName = func->hasExplicitName; @@ -76,6 +97,18 @@ copyFunctionWithoutAdd(Function* func, updateLocationSet(ret->prologLocation, *fileIndexMap); updateLocationSet(ret->epilogLocation, *fileIndexMap); } + if (symbolNameIndexMap) { + for (auto& iter : ret->debugLocations) { + if (iter.second) { + if (iter.second->symbolNameIndex.has_value()) { + iter.second->symbolNameIndex = + (*symbolNameIndexMap)[*(iter.second->symbolNameIndex)]; + } + } + updateSymbolSet(ret->prologLocation, *symbolNameIndexMap); + updateSymbolSet(ret->epilogLocation, *symbolNameIndexMap); + } + } ret->module = func->module; ret->base = func->base; ret->noFullInline = func->noFullInline; @@ -199,8 +232,27 @@ void copyModuleItems(const Module& in, Module& out) { } } + std::optional> symbolNameIndexMap; + if (!in.debugInfoSymbolNames.empty()) { + std::unordered_map debugInfoSymbolNameIndices; + for (Index i = 0; i < out.debugInfoSymbolNames.size(); i++) { + debugInfoSymbolNameIndices[out.debugInfoSymbolNames[i]] = i; + } + symbolNameIndexMap.emplace(); + for (Index i = 0; i < in.debugInfoSymbolNames.size(); i++) { + std::string file = in.debugInfoSymbolNames[i]; + auto iter = debugInfoSymbolNameIndices.find(file); + if (iter == debugInfoSymbolNameIndices.end()) { + Index index = out.debugInfoSymbolNames.size(); + out.debugInfoSymbolNames.push_back(file); + debugInfoSymbolNameIndices[file] = index; + } + symbolNameIndexMap->push_back(debugInfoSymbolNameIndices[file]); + } + } + for (auto& curr : in.functions) { - copyFunction(curr.get(), out, Name(), fileIndexMap); + copyFunction(curr.get(), out, Name(), fileIndexMap, symbolNameIndexMap); } for (auto& curr : in.globals) { copyGlobal(curr.get(), out); @@ -241,6 +293,7 @@ void copyModule(const Module& in, Module& out) { out.start = in.start; out.customSections = in.customSections; out.debugInfoFileNames = in.debugInfoFileNames; + out.debugInfoSymbolNames = in.debugInfoSymbolNames; out.features = in.features; } diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 46e52416538..bb8b6ae439d 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -25,21 +25,24 @@ namespace wasm::ModuleUtils { // Copies a function into a module. If newName is provided it is used as the -// name of the function (otherwise the original name is copied). If fileIndexMap -// is specified, it is used to rename source map filename indices when copying -// the function from one module to another one. -Function* -copyFunction(Function* func, - Module& out, - Name newName = Name(), - std::optional> fileIndexMap = std::nullopt); +// name of the function (otherwise the original name is copied). When specified, +// fileIndexMap and symbolNameIndexMap are used to rename source map filename +// and symbol name indices when copying the function from one module to another +// one. +Function* copyFunction( + Function* func, + Module& out, + Name newName = Name(), + std::optional> fileIndexMap = std::nullopt, + std::optional> symbolNameIndexMap = std::nullopt); // As above, but does not add the copy to the module. std::unique_ptr copyFunctionWithoutAdd( Function* func, Module& out, Name newName = Name(), - std::optional> fileIndexMap = std::nullopt); + std::optional> fileIndexMap = std::nullopt, + std::optional> symbolNameIndexMap = std::nullopt); Global* copyGlobal(Global* global, Module& out); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 387cb146eef..b0cd1458bb6 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1398,6 +1398,7 @@ struct ParseDefsCtx : TypeParserCtx { typeNames; const std::unordered_map& implicitElemIndices; + std::unordered_map debugSymbolNameIndices; std::unordered_map debugFileIndices; // The index of the current module element. @@ -1777,12 +1778,32 @@ struct ParseDefsCtx : TypeParserCtx { } contents = contents.substr(lineSize + 1); - lexer = Lexer(contents); + auto colSize = contents.find(':'); + if (colSize == contents.npos) { + colSize = contents.size(); + if (colSize == 0) { + return; + } + } + lexer = Lexer(contents.substr(0, colSize)); auto col = lexer.takeU32(); - if (!col || !lexer.empty()) { + if (!col) { return; } + std::optional symbolNameIndex; + if (colSize != contents.size()) { + contents = contents.substr(colSize + 1); + auto symbolName = contents; + auto [it, inserted] = debugSymbolNameIndices.insert( + {symbolName, debugSymbolNameIndices.size()}); + if (inserted) { + assert(wasm.debugInfoSymbolNames.size() == it->second); + wasm.debugInfoSymbolNames.push_back(std::string(symbolName)); + } + symbolNameIndex = it->second; + } + // TODO: If we ever parallelize the parse, access to // `wasm.debugInfoFileNames` will have to be protected by a lock. auto [it, inserted] = @@ -1792,7 +1813,7 @@ struct ParseDefsCtx : TypeParserCtx { wasm.debugInfoFileNames.push_back(std::string(file)); } irBuilder.setDebugLocation( - Function::DebugLocation({it->second, *line, *col})); + Function::DebugLocation({it->second, *line, *col, symbolNameIndex})); } Result<> makeBlock(Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 0533b97865c..106beac39be 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2516,7 +2516,15 @@ void PrintSExpression::printDebugLocation( } else { auto fileName = currModule->debugInfoFileNames[location->fileIndex]; o << ";;@ " << fileName << ":" << location->lineNumber << ":" - << location->columnNumber << '\n'; + << location->columnNumber; + + if (location->symbolNameIndex) { + auto symbolName = + currModule->debugInfoSymbolNames[*(location->symbolNameIndex)]; + o << ":" << symbolName; + } + + o << '\n'; } doIndent(o, indent); } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 35f13195260..3c59ed3aacb 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1674,6 +1674,7 @@ class WasmBinaryReader { // Debug information reading helpers void setDebugLocations(std::istream* sourceMap_) { sourceMap = sourceMap_; } std::unordered_map debugInfoFileIndices; + std::unordered_map debugInfoSymbolNameIndices; void readNextDebugLocation(); void readSourceMapHeader(); diff --git a/src/wasm.h b/src/wasm.h index ede3e50c1de..4901723fd59 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2073,22 +2073,25 @@ class Function : public Importable { std::unordered_map localNames; std::unordered_map localIndices; - // Source maps debugging info: map expression nodes to their file, line, col. + // Source maps debugging info: map expression nodes to their file, line, col, + // symbol name. struct DebugLocation { BinaryLocation fileIndex, lineNumber, columnNumber; + std::optional symbolNameIndex; bool operator==(const DebugLocation& other) const { return fileIndex == other.fileIndex && lineNumber == other.lineNumber && - columnNumber == other.columnNumber; + columnNumber == other.columnNumber && + symbolNameIndex == other.symbolNameIndex; } bool operator!=(const DebugLocation& other) const { return !(*this == other); } bool operator<(const DebugLocation& other) const { - return fileIndex != other.fileIndex - ? fileIndex < other.fileIndex - : lineNumber != other.lineNumber - ? lineNumber < other.lineNumber - : columnNumber < other.columnNumber; + return fileIndex != other.fileIndex ? fileIndex < other.fileIndex + : lineNumber != other.lineNumber ? lineNumber < other.lineNumber + : columnNumber != other.columnNumber + ? columnNumber < other.columnNumber + : symbolNameIndex < other.symbolNameIndex; } }; // One can explicitly set the debug location of an expression to @@ -2300,6 +2303,7 @@ class Module { // Source maps debug info. std::vector debugInfoFileNames; + std::vector debugInfoSymbolNames; // `features` are the features allowed to be used in this module and should be // respected regardless of the value of`hasFeaturesSection`. diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 4667718034c..920baf9440f 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1189,7 +1189,7 @@ void WasmBinaryWriter::writeSymbolMap() { } void WasmBinaryWriter::initializeDebugInfo() { - lastDebugLocation = {0, /* lineNumber = */ 1, 0}; + lastDebugLocation = {0, /* lineNumber = */ 1, 0, std::nullopt}; } void WasmBinaryWriter::writeSourceMapProlog() { @@ -1225,7 +1225,17 @@ void WasmBinaryWriter::writeSourceMapProlog() { // TODO respect JSON string encoding, e.g. quotes and control chars. *sourceMap << "\"" << wasm->debugInfoFileNames[i] << "\""; } - *sourceMap << "],\"names\":[],\"mappings\":\""; + *sourceMap << "],\"names\":["; + + for (size_t i = 0; i < wasm->debugInfoSymbolNames.size(); i++) { + if (i > 0) { + *sourceMap << ","; + } + // TODO respect JSON string encoding, e.g. quotes and control chars. + *sourceMap << "\"" << wasm->debugInfoSymbolNames[i] << "\""; + } + + *sourceMap << "],\"mappings\":\""; } static void writeBase64VLQ(std::ostream& out, int32_t n) { @@ -1249,7 +1259,10 @@ static void writeBase64VLQ(std::ostream& out, int32_t n) { void WasmBinaryWriter::writeSourceMapEpilog() { // write source map entries size_t lastOffset = 0; - Function::DebugLocation lastLoc = {0, /* lineNumber = */ 1, 0}; + BinaryLocation lastFileIndex = 0; + BinaryLocation lastLineNumber = 1; + BinaryLocation lastColumnNumber = 0; + BinaryLocation lastSymbolNameIndex = 0; for (const auto& [offset, loc] : sourceMapLocations) { if (lastOffset > 0) { *sourceMap << ","; @@ -1257,13 +1270,20 @@ void WasmBinaryWriter::writeSourceMapEpilog() { writeBase64VLQ(*sourceMap, int32_t(offset - lastOffset)); lastOffset = offset; if (loc) { - // There is debug information for this location, so emit the next 3 - // fields and update lastLoc. - writeBase64VLQ(*sourceMap, int32_t(loc->fileIndex - lastLoc.fileIndex)); - writeBase64VLQ(*sourceMap, int32_t(loc->lineNumber - lastLoc.lineNumber)); - writeBase64VLQ(*sourceMap, - int32_t(loc->columnNumber - lastLoc.columnNumber)); - lastLoc = *loc; + writeBase64VLQ(*sourceMap, int32_t(loc->fileIndex - lastFileIndex)); + lastFileIndex = loc->fileIndex; + + writeBase64VLQ(*sourceMap, int32_t(loc->lineNumber - lastLineNumber)); + lastLineNumber = loc->lineNumber; + + writeBase64VLQ(*sourceMap, int32_t(loc->columnNumber - lastColumnNumber)); + lastColumnNumber = loc->columnNumber; + + if (loc->symbolNameIndex) { + writeBase64VLQ(*sourceMap, + int32_t(*loc->symbolNameIndex - lastSymbolNameIndex)); + lastSymbolNameIndex = *loc->symbolNameIndex; + } } } *sourceMap << "\"}"; @@ -1716,7 +1736,7 @@ WasmBinaryReader::WasmBinaryReader(Module& wasm, FeatureSet features, const std::vector& input) : wasm(wasm), allocator(wasm.allocator), input(input), sourceMap(nullptr), - nextDebugPos(0), nextDebugLocation{0, 0, 0}, + nextDebugPos(0), nextDebugLocation{0, 0, 0, std::nullopt}, nextDebugLocationHasDebugInfo(false), debugLocation() { wasm.features = features; } @@ -2885,6 +2905,21 @@ void WasmBinaryReader::readSourceMapHeader() { mustReadChar(']'); } + if (findField("names")) { + skipWhitespace(); + mustReadChar('['); + if (!maybeReadChar(']')) { + do { + std::string symbol; + readString(symbol); + Index index = wasm.debugInfoSymbolNames.size(); + wasm.debugInfoSymbolNames.push_back(symbol); + debugInfoSymbolNameIndices[symbol] = index; + } while (maybeReadChar(',')); + mustReadChar(']'); + } + } + if (!findField("mappings")) { throw MapParseException("cannot find the 'mappings' field in map"); } @@ -2911,7 +2946,12 @@ void WasmBinaryReader::readSourceMapHeader() { uint32_t lineNumber = readBase64VLQ(*sourceMap) + 1; // adjust zero-based line number uint32_t columnNumber = readBase64VLQ(*sourceMap); - nextDebugLocation = {fileIndex, lineNumber, columnNumber}; + std::optional symbolNameIndex; + peek = sourceMap->peek(); + if (!(peek == ',' || peek == '\"')) { + symbolNameIndex = readBase64VLQ(*sourceMap); + } + nextDebugLocation = {fileIndex, lineNumber, columnNumber, symbolNameIndex}; nextDebugLocationHasDebugInfo = true; } } @@ -2966,7 +3006,15 @@ void WasmBinaryReader::readNextDebugLocation() { int32_t columnNumberDelta = readBase64VLQ(*sourceMap); uint32_t columnNumber = nextDebugLocation.columnNumber + columnNumberDelta; - nextDebugLocation = {fileIndex, lineNumber, columnNumber}; + std::optional symbolNameIndex; + peek = sourceMap->peek(); + if (!(peek == ',' || peek == '\"')) { + int32_t symbolNameIndexDelta = readBase64VLQ(*sourceMap); + symbolNameIndex = + nextDebugLocation.symbolNameIndex.value_or(0) + symbolNameIndexDelta; + } + + nextDebugLocation = {fileIndex, lineNumber, columnNumber, symbolNameIndex}; nextDebugLocationHasDebugInfo = true; } } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 84fd9a06ffc..e8de4572bd7 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1863,6 +1863,9 @@ void Module::updateMaps() { assert(tagsMap.size() == tags.size()); } -void Module::clearDebugInfo() { debugInfoFileNames.clear(); } +void Module::clearDebugInfo() { + debugInfoFileNames.clear(); + debugInfoSymbolNames.clear(); +} } // namespace wasm diff --git a/test/lit/merge/sourcemap.wat b/test/lit/merge/sourcemap.wat index 8ccb373998d..1eec0bcbe8c 100644 --- a/test/lit/merge/sourcemap.wat +++ b/test/lit/merge/sourcemap.wat @@ -9,11 +9,11 @@ ;; Test that sourcemap information is preserved (module - ;;@ a:1:1 + ;;@ a:1:2 (func (export "f") - ;;@ a:2:1 + ;;@ a:3:4:myFunction (nop) - ;;@ a:3:1 + ;;@ a:5:6 ) ) ;; CHECK-TEXT: (type $0 (func)) @@ -23,15 +23,15 @@ ;; CHECK-TEXT: (export "g" (func $0_1)) ;; CHECK-TEXT: (func $0 -;; CHECK-TEXT-NEXT: ;;@ a:2:1 +;; CHECK-TEXT-NEXT: ;;@ a:3:4:myFunction ;; CHECK-TEXT-NEXT: (nop) -;; CHECK-TEXT-NEXT: ;;@ a:3:1 +;; CHECK-TEXT-NEXT: ;;@ a:5:6 ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT: (func $0_1 -;; CHECK-TEXT-NEXT: ;;@ b:2:2 +;; CHECK-TEXT-NEXT: ;;@ b:9:10:MyClass::g ;; CHECK-TEXT-NEXT: (nop) -;; CHECK-TEXT-NEXT: ;;@ b:3:2 +;; CHECK-TEXT-NEXT: ;;@ b:11:12 ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (type $0 (func)) @@ -41,13 +41,13 @@ ;; CHECK-BIN: (export "g" (func $1)) ;; CHECK-BIN: (func $0 -;; CHECK-BIN-NEXT: ;;@ a:2:1 +;; CHECK-BIN-NEXT: ;;@ a:3:4:myFunction ;; CHECK-BIN-NEXT: (nop) -;; CHECK-BIN-NEXT: ;;@ a:3:1 +;; CHECK-BIN-NEXT: ;;@ a:5:6 ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN: (func $1 -;; CHECK-BIN-NEXT: ;;@ b:2:2 +;; CHECK-BIN-NEXT: ;;@ b:9:10:MyClass::g ;; CHECK-BIN-NEXT: (nop) -;; CHECK-BIN-NEXT: ;;@ b:3:2 +;; CHECK-BIN-NEXT: ;;@ b:11:12 ;; CHECK-BIN-NEXT: ) diff --git a/test/lit/merge/sourcemap.wat.second b/test/lit/merge/sourcemap.wat.second index 0ea7c75fa03..3f1c110da8f 100644 --- a/test/lit/merge/sourcemap.wat.second +++ b/test/lit/merge/sourcemap.wat.second @@ -1,8 +1,8 @@ (module - ;;@ b:1:2 + ;;@ b:7:8 (func (export "g") - ;;@ b:2:2 + ;;@ b:9:10:MyClass::g (nop) - ;;@ b:3:2 + ;;@ b:11:12 ) ) diff --git a/test/lit/metadce/sourcemap.wat b/test/lit/metadce/sourcemap.wat index 8a73a01da57..0190fe557da 100644 --- a/test/lit/metadce/sourcemap.wat +++ b/test/lit/metadce/sourcemap.wat @@ -7,20 +7,20 @@ ;; Test that sourcemap information is preserved (module - ;;@ a:1:1 + ;;@ a:1:2 ;; TXT: (type $0 (func)) ;; TXT: (export "f" (func $f)) ;; TXT: (func $f - ;; TXT-NEXT: ;;@ a:2:1 + ;; TXT-NEXT: ;;@ a:7:8:someSymbol ;; TXT-NEXT: (nop) - ;; TXT-NEXT: ;;@ a:3:1 + ;; TXT-NEXT: ;;@ a:9:10 ;; TXT-NEXT: ) (func $f (export "f") - ;;@ a:2:1 + ;;@ a:7:8:someSymbol (nop) - ;;@ a:3:1 + ;;@ a:9:10 ) ) ;; BIN: (type $0 (func)) @@ -28,7 +28,7 @@ ;; BIN: (export "f" (func $0)) ;; BIN: (func $0 -;; BIN-NEXT: ;;@ a:2:1 +;; BIN-NEXT: ;;@ a:7:8:someSymbol ;; BIN-NEXT: (nop) -;; BIN-NEXT: ;;@ a:3:1 +;; BIN-NEXT: ;;@ a:9:10 ;; BIN-NEXT: ) diff --git a/test/lit/passes/inlining_source-maps.wast b/test/lit/passes/inlining_source-maps.wast new file mode 100644 index 00000000000..5b5549986b9 --- /dev/null +++ b/test/lit/passes/inlining_source-maps.wast @@ -0,0 +1,36 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --inlining --all-features -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (start $2) + (start $2) + + (func $0 + ;;@ foo.cpp:1:2 + (nop) + ) + + (func $1 + ;;@ bar.cpp:3:4:MyFunction + (nop) + ) + + ;; CHECK: (func $2 (type $0) + ;; CHECK-NEXT: (block $__inlined_func$0 + ;; CHECK-NEXT: ;;@ foo.cpp:1:2 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ;;@ + ;; CHECK-NEXT: (block $__inlined_func$1$1 + ;; CHECK-NEXT: ;;@ bar.cpp:3:4:MyFunction + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $2 + (call $0) + (call $1) + ) +) diff --git a/test/lit/source-map.wast b/test/lit/source-map.wast index f8ef07c65cc..b4fc55374a5 100644 --- a/test/lit/source-map.wast +++ b/test/lit/source-map.wast @@ -110,4 +110,21 @@ ;;@ src.cpp:3 (nop) ) + + ;; CHECK: (func $symbolNames + ;; CHECK-NEXT: ;;@ /tmp/src.cpp:1:2:MyClass.myMethod + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ;;@ ../src.cpp:3:4:MyClass::myMethod + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ;;@ café.cpp:5:6:topLevelFunction + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $symbolNames + ;;@ /tmp/src.cpp:1:2:MyClass.myMethod + (nop) + ;;@ ../src.cpp:3:4:MyClass::myMethod + (nop) + ;;@ café.cpp:5:6:topLevelFunction + (nop) + ) ) From 7e1413902d74111898419b9ed5add45429fa62f8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Oct 2024 16:07:56 -0700 Subject: [PATCH 055/622] Fuzz Wasm Exceptions in V8 (#6981) The blocking bug https://issues.chromium.org/issues/332931390 has been fixed. --- scripts/fuzz_opt.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 2bf4086dbef..1e29a84226a 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -838,9 +838,7 @@ def can_run(self, wasm): # V8 does not support shared memories when running with # shared-everything enabled, so do not fuzz shared-everything # for now. - # Due to the V8 bug https://issues.chromium.org/issues/332931390 - # we do not fuzz exception-handling either. - return all_disallowed(['shared-everything', 'exception-handling']) + return all_disallowed(['shared-everything']) def can_compare_to_self(self): # With nans, VM differences can confuse us, so only very simple VMs From ce1a13c06aca0690b296f34401011b5a0bc07636 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Oct 2024 08:15:52 -0700 Subject: [PATCH 056/622] [Wasm EH] Optimize throws caught by TryTable into breaks (#6980) E.g. (try_table (catch_all $catch) (throw $e) ) => (try_table (catch_all $catch) (br $catch) ) This can then allow other passes to remove the TryTable, if no throwing things remain. --- src/passes/RemoveUnusedBrs.cpp | 95 +++++-- test/lit/passes/remove-unused-brs-eh.wast | 324 ++++++++++++++++++++++ test/lit/passes/vacuum-eh.wast | 6 +- 3 files changed, 407 insertions(+), 18 deletions(-) create mode 100644 test/lit/passes/remove-unused-brs-eh.wast diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 7fe169bdbb7..0c316e7e799 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -18,16 +18,17 @@ // Removes branches for which we go to where they go anyhow // -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "ir/branch-utils.h" +#include "ir/cost.h" +#include "ir/drop.h" +#include "ir/effects.h" +#include "ir/gc-type-utils.h" +#include "ir/literal-utils.h" +#include "ir/utils.h" +#include "parsing.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" namespace wasm { @@ -260,7 +261,7 @@ struct RemoveUnusedBrs : public WalkerPass> { } else if (curr->is()) { // ignore (could be result of a previous cycle) self->stopValueFlow(); - } else if (curr->is()) { + } else if (curr->is()) { // TODO: eh // do nothing - it's ok for values to flow out } else if (auto* sw = curr->dynCast()) { self->stopFlow(); @@ -462,13 +463,69 @@ struct RemoveUnusedBrs : public WalkerPass> { // later down, see visitLocalSet. } + // A stack of try_tables that are parents of the current expression. + std::vector tryTables; + + static void popTryTable(RemoveUnusedBrs* self, Expression** currp) { + assert(!self->tryTables.empty() && self->tryTables.back() == *currp); + self->tryTables.pop_back(); + } + + void visitThrow(Throw* curr) { + // If a throw will definitely be caught, and it is not a catch with a + // reference, then it is just a branch (i.e. the code is using exceptions as + // control flow). Turn it into a branch here so that the rest of the pass + // can optimize it with all other branches. + // + // To do so, look at the closest try and see if it will catch us, and + // proceed outwards if not. + auto thrownTag = curr->tag; + for (int i = tryTables.size() - 1; i >= 0; i--) { + auto* tryy = tryTables[i]; + for (Index j = 0; j < tryy->catchTags.size(); j++) { + auto tag = tryy->catchTags[j]; + // The tag must match, or be a catch_all. + if (tag == thrownTag || tag.isNull()) { + // This must not be a catch with exnref. + if (!tryy->catchRefs[j]) { + // Success! Create a break to replace the throw. + auto dest = tryy->catchDests[j]; + auto& wasm = *getModule(); + Builder builder(wasm); + if (!tag.isNull()) { + // We are catching a specific tag, so values might be sent. + Expression* value = nullptr; + if (curr->operands.size() == 1) { + value = curr->operands[0]; + } else if (curr->operands.size() > 1) { + value = builder.makeTupleMake(curr->operands); + } + auto* br = builder.makeBreak(dest, value); + replaceCurrent(br); + return; + } + + // catch_all: no values are sent. Drop the throw's children (while + // ignoring parent effects: the parent is a throw, but we have + // proven we can remove that effect). + auto* br = builder.makeBreak(dest); + auto* rep = getDroppedChildrenAndAppend( + curr, wasm, getPassOptions(), br, DropMode::IgnoreParentEffects); + replaceCurrent(rep); + } + + // Return even if we did not optimize: we found our tag was caught. + return; + } + } + } + } + // override scan to add a pre and a post check task to all nodes static void scan(RemoveUnusedBrs* self, Expression** currp) { self->pushTask(visitAny, currp); - auto* iff = (*currp)->dynCast(); - - if (iff) { + if (auto* iff = (*currp)->dynCast()) { if (iff->condition->type == Type::unreachable) { // avoid trying to optimize this, we never reach it anyhow return; @@ -484,9 +541,15 @@ struct RemoveUnusedBrs : public WalkerPass> { self->pushTask(scan, &iff->ifTrue); self->pushTask(clear, currp); // clear all flow after the condition self->pushTask(scan, &iff->condition); - } else { - Super::scan(self, currp); + return; + } + if (auto* tryy = (*currp)->dynCast()) { + // Push the try we are reaching, and add a task to pop it, after all the + // tasks that Super::scan will push for its children. + self->tryTables.push_back(tryy); + self->pushTask(popTryTable, currp); } + Super::scan(self, currp); } // optimizes a loop. returns true if we made changes diff --git a/test/lit/passes/remove-unused-brs-eh.wast b/test/lit/passes/remove-unused-brs-eh.wast new file mode 100644 index 00000000000..a4c6591ab15 --- /dev/null +++ b/test/lit/passes/remove-unused-brs-eh.wast @@ -0,0 +1,324 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --remove-unused-brs -S -o - | filecheck %s + +(module + ;; CHECK: (tag $e) + (tag $e) + ;; CHECK: (tag $f) + (tag $f) + ;; CHECK: (tag $g) + (tag $g) + + ;; CHECK: (func $throw-caught-all (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-all + (block $catch + (try_table (catch_all $catch) + ;; This throw can be a br. + (throw $e) + ) + ) + ) + + ;; CHECK: (func $throw-caught-all-more (type $2) (param $x i32) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (br_if $catch + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-all-more (param $x i32) + (block $catch + (try_table (catch_all $catch) + ;; Look into nested children. After we turn the throw into a br, it also + ;; fuses with the if into a br_if. + (if + (local.get $x) + (then + (throw $e) + ) + ) + ) + ) + ) + + ;; CHECK: (func $throw-caught-precise (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $e $catch) + ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-precise + (block $catch + ;; We can still optimize here, even though we replaced the catch_all with + ;; a precise tag, because the tag matches. + (try_table (catch $e $catch) + (throw $e) + ) + ) + ) + + ;; CHECK: (func $throw-caught-precise-later (type $0) + ;; CHECK-NEXT: (block $fail + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $f $fail) (catch $e $catch) + ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $throw-caught-precise-later) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-precise-later + (block $fail + (block $catch + ;; We can still optimize here, by looking through the tags til we find + ;; ours. + (try_table (catch $f $fail) (catch $e $catch) + (throw $e) + ) + ) + ;; Add an effect here, so the two blocks are not mergeable. + (call $throw-caught-precise-later) + ) + ) + + ;; CHECK: (func $throw-caught-all-later (type $0) + ;; CHECK-NEXT: (block $fail + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $f $fail) (catch_all $catch) + ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $throw-caught-precise-later) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-all-later + (block $fail + (block $catch + ;; We can still optimize here, by looking through the tags til we find + ;; the catch_all + (try_table (catch $f $fail) (catch_all $catch) + (throw $e) + ) + ) + ;; Add an effect here, so the two blocks are not mergeable. + (call $throw-caught-precise-later) + ) + ) + + ;; CHECK: (func $throw-caught-fail (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $f $catch) + ;; CHECK-NEXT: (throw $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-fail + (block $catch + ;; The tag does *not* match. + (try_table (catch $f $catch) + (throw $e) + ) + ) + ) + + ;; CHECK: (func $throw-caught-outer (type $0) + ;; CHECK-NEXT: (block $fail + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $e $catch) + ;; CHECK-NEXT: (try_table (catch $f $fail) + ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $throw-caught-precise-later) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-outer + (block $fail + (block $catch + (try_table (catch $e $catch) + (try_table (catch $f $fail) + ;; This throw can be a br, thanks to the outer try. + (throw $e) + ) + ) + ) + ;; Add an effect here, so the two blocks are not mergeable. + (call $throw-caught-precise-later) + ) + ) + + ;; CHECK: (func $throw-catch-all-ref (type $1) (result exnref) + ;; CHECK-NEXT: (block $catch (result exnref) + ;; CHECK-NEXT: (try_table (catch_all_ref $catch) + ;; CHECK-NEXT: (throw $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-catch-all-ref (result exnref) + (block $catch (result exnref) + (try_table (catch_all_ref $catch) + ;; This is caught with a ref, so we do not optimize. + (throw $e) + ) + ) + ) + + ;; CHECK: (func $throw-caught-ref (type $1) (result exnref) + ;; CHECK-NEXT: (block $catch (result exnref) + ;; CHECK-NEXT: (try_table (catch_ref $e $catch) + ;; CHECK-NEXT: (throw $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-ref (result exnref) + (block $catch (result exnref) + (try_table (catch_ref $e $catch) + ;; As above, but without catch_all. + (throw $e) + ) + ) + ) + + ;; CHECK: (func $throw-caught-ref-later-all (type $1) (result exnref) + ;; CHECK-NEXT: (block $outer (result exnref) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_ref $e $outer) (catch_all $catch) + ;; CHECK-NEXT: (throw $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-ref-later-all (result exnref) + (block $outer (result exnref) + (block $catch + (try_table (catch_ref $e $outer) (catch_all $catch) + ;; This is caught with a ref, before we reach the catch all, so we do + ;; not optimize. + (throw $e) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $throw-multi (type $0) + ;; CHECK-NEXT: (block $outer + ;; CHECK-NEXT: (block $middle + ;; CHECK-NEXT: (block $inner + ;; CHECK-NEXT: (try_table (catch $e $outer) (catch $f $middle) (catch_all $inner) + ;; CHECK-NEXT: (br $outer) + ;; CHECK-NEXT: (br $middle) + ;; CHECK-NEXT: (br $inner) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $throw-caught-precise-later) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $throw-caught-precise-later) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-multi + (block $outer + (block $middle + (block $inner + (try_table (catch $e $outer) (catch $f $middle) (catch_all $inner) + ;; Multiple throws, optimizable in different ways. + (throw $e) + (throw $f) + (throw $g) + ) + ) + ;; Add an effect here, so the two blocks are not mergeable. + (call $throw-caught-precise-later) + ) + (call $throw-caught-precise-later) + ) + ) +) + +(module + ;; CHECK: (import "a" "b" (func $effect (type $1) (result i32))) + (import "a" "b" (func $effect (result i32))) + + ;; CHECK: (tag $e (param i32)) + (tag $e (param i32)) + + ;; CHECK: (tag $multi (param i32 f64)) + (tag $multi (param i32 f64)) + + ;; CHECK: (func $throw-caught-all (type $0) (param $x i32) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-all (param $x i32) + (block $catch + (try_table (catch_all $catch) + ;; This throw can be a br. The call must be kept in a drop. + (throw $e + (call $effect) + ) + ) + ) + ) + + ;; CHECK: (func $throw-br-contents (type $1) (result i32) + ;; CHECK-NEXT: (block $catch (result i32) + ;; CHECK-NEXT: (try_table (catch $e $catch) + ;; CHECK-NEXT: (br $catch + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-br-contents (result i32) + (block $catch (result i32) + (try_table (catch $e $catch) + ;; This throw is not caught by catch_all as above, so the value must be + ;; sent as a value on the br we optimize it to. + (throw $e + (i32.const 42) + ) + ) + ) + ) + + ;; CHECK: (func $throw-br-contents-multi (type $2) (result i32 f64) + ;; CHECK-NEXT: (block $catch (type $2) (result i32 f64) + ;; CHECK-NEXT: (try_table (catch $multi $catch) + ;; CHECK-NEXT: (br $catch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-br-contents-multi (result i32 f64) + ;; As above, but now with a multivalue tag. + (block $catch (result i32 f64) + (try_table (catch $multi $catch) + (throw $multi + (i32.const 42) + (f64.const 3.14159) + ) + ) + ) + ) +) diff --git a/test/lit/passes/vacuum-eh.wast b/test/lit/passes/vacuum-eh.wast index ec62f3c58df..d42fed56ba3 100644 --- a/test/lit/passes/vacuum-eh.wast +++ b/test/lit/passes/vacuum-eh.wast @@ -151,8 +151,10 @@ ;; CHECK-NEXT: ) (func $trivial-catch-all-of-throw (local $0 i32) ;; This try_table's body throws, but the catch_all catches it, so the entire - ;; try_table could be optimized out. We do this for `try` but not yet for - ;; `try_table`. + ;; try_table could be optimized out. We do this for `try` but not for + ;; `try_table` - we leave such optimizations to --remove-unused-brs (that + ;; pass can see that the throw can be converted to a br). + (block $catch (try_table (catch_all $catch) (throw $e (i32.const 0)) From 36181c881c67f2b11386fdf885dbe33567db0d67 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Oct 2024 09:06:46 -0700 Subject: [PATCH 057/622] [NFC] Refactor out the dropped-block optimization code in MergeBlocks (#6982) This just moves the code out into a function. A later PR will use it in another place. Add a test that shows the motivation for that later PR: we fail to optimize away a block return value at the top level of a function. Fixing that will involve calling the new function here from another place. --- src/passes/MergeBlocks.cpp | 76 +++++++++++++++++++------------ test/lit/passes/merge-blocks.wast | 27 +++++++++-- 2 files changed, 70 insertions(+), 33 deletions(-) diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 9a0b5d27be5..401d2e6291b 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -197,7 +197,46 @@ static bool hasDeadCode(Block* block) { return false; } -// core block optimizer routine +// Given a dropped block, see if we can simplify it by optimizing the drop into +// the block, removing the return value while doin so. Returns whether we +// succeeded. +static bool optimizeDroppedBlock(Drop* drop, + Block* block, + Module& wasm, + PassOptions& options, + BranchUtils::BranchSeekerCache& branchInfo) { + assert(drop->value == block); + if (block->name.is()) { + // There may be breaks: see if we can remove their values. + Expression* expression = block; + ProblemFinder finder(options); + finder.setModule(&wasm); + finder.origin = block->name; + finder.walk(expression); + if (finder.found()) { + // We found a problem that blocks us. + return false; + } + + // Success. Fix up breaks before continuing to handle the drop itself. + BreakValueDropper fixer(options, branchInfo); + fixer.origin = block->name; + fixer.setModule(&wasm); + fixer.walk(expression); + } + + // Optimize by removing the block. Reuse the drop, if we still need it. + auto* last = block->list.back(); + if (last->type.isConcrete()) { + drop->value = last; + drop->finalize(); + block->list.back() = drop; + } + block->finalize(); + return true; +} + +// Core block optimizer routine. static void optimizeBlock(Block* curr, Module* module, PassOptions& passOptions, @@ -218,8 +257,8 @@ static void optimizeBlock(Block* curr, Loop* loop = nullptr; // To to handle a non-block child. if (!childBlock) { - // if we have a child that is (drop (block ..)) then we can move the - // drop into the block, and remove br values. this allows more merging, + // If we have a child that is (drop (block ..)) then we can move the + // drop into the block, and remove br values. This allows more merging. if (auto* drop = list[i]->dynCast()) { childBlock = drop->value->dynCast(); if (childBlock) { @@ -228,36 +267,13 @@ static void optimizeBlock(Block* curr, // dce should have been run anyhow continue; } - if (childBlock->name.is()) { - Expression* expression = childBlock; - // check if it's ok to remove the value from all breaks to us - ProblemFinder finder(passOptions); - finder.setModule(module); - finder.origin = childBlock->name; - finder.walk(expression); - if (finder.found()) { - childBlock = nullptr; - } else { - // fix up breaks - BreakValueDropper fixer(passOptions, branchInfo); - fixer.origin = childBlock->name; - fixer.setModule(module); - fixer.walk(expression); - } - } - if (childBlock) { - // we can do it! - // reuse the drop, if we still need it - auto* last = childBlock->list.back(); - if (last->type.isConcrete()) { - drop->value = last; - drop->finalize(); - childBlock->list.back() = drop; - } - childBlock->finalize(); + if (optimizeDroppedBlock( + drop, childBlock, *module, passOptions, branchInfo)) { child = list[i] = childBlock; more = true; changed = true; + } else { + childBlock = nullptr; } } } else if ((loop = list[i]->dynCast())) { diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 66b0e8b71f5..e1ac88dd83e 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -15,7 +15,7 @@ (type $array (array (mut i32))) - ;; CHECK: (func $br_on_to_drop (type $5) + ;; CHECK: (func $br_on_to_drop (type $4) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $label$1 (result i31ref) @@ -43,7 +43,7 @@ ) ) - ;; CHECK: (func $struct.set (type $4) (param $struct (ref null $struct)) + ;; CHECK: (func $struct.set (type $5) (param $struct (ref null $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1234) @@ -68,7 +68,7 @@ ) ) - ;; CHECK: (func $struct.get (type $4) (param $struct (ref null $struct)) + ;; CHECK: (func $struct.get (type $5) (param $struct (ref null $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1234) @@ -404,6 +404,27 @@ ) ) + ;; CHECK: (func $toplevel (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $toplevel + ;; Test we can remove a block value even when the drop is at the toplevel of + ;; a function. This is not done yet TODO + (drop + (block $label (result i32) + (br $label + (i32.const 42) + ) + ) + ) + ) + ;; CHECK: (func $helper (type $7) (param $x i32) (result i32) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) From 7f30b6c4c0af98642a8f22cbd4e61ff688f9e9fe Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 3 Oct 2024 16:34:29 -0700 Subject: [PATCH 058/622] [FP16] Fix comment on f16x8.convert_i16x8_s test. (#6985) 0x7800 as FP16 is 32,768 not 32,767 as I had in the comment. I also added another comment to explain the somewhat confusing behavior. --- test/spec/f16.wast | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/f16.wast b/test/spec/f16.wast index b495fe2b64e..60bcc74bac0 100644 --- a/test/spec/f16.wast +++ b/test/spec/f16.wast @@ -238,9 +238,9 @@ (v128.const i16x8 0x5140 0x7e00 0x7c00 0xfc00 0x7bff 0xfbff 0 0)) (v128.const i16x8 42 0 65535 0 65504 0 0 0)) (assert_return (invoke "f16x8.convert_i16x8_s" - ;; + ;; 32767 is not representable as a whole integer in FP16, so it becomes 32768. (v128.const i16x8 0 1 -1 32767 -32768 0 0 0)) - ;; 0 1 -1 32767 -32768 0 0 0 + ;; 0 1 -1 32768 -32768 0 0 0 (v128.const i16x8 0 0x3c00 0xbc00 0x7800 0xf800 0 0 0)) (assert_return (invoke "f16x8.convert_i16x8_u" ;; Unlike f32/64, f16 can't represent the full 2^16 integer range so 2^16 becomes infinity. From cbad8ec02680710001ebdc7157e487c54e9b4c66 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Oct 2024 08:43:17 -0700 Subject: [PATCH 059/622] RemoveUnusedBrs: Generalize jump threading optimizations to all branches (#6983) This change is NFC on all things we previously optimized, but also makes us optimize TryTable, BrOn, etc., by replacing hard-coded logic for Break with generic code. Also simplify the code there a little - we didn't really need ControlFlowWalker. --- src/passes/RemoveUnusedBrs.cpp | 51 ++++++++++---------- test/lit/passes/remove-unused-brs-eh.wast | 57 +++++++++++++++++++++++ test/lit/passes/remove-unused-brs-gc.wast | 24 ++++++++++ test/lit/passes/vacuum-eh.wast | 1 - 4 files changed, 108 insertions(+), 25 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 0c316e7e799..c166d3c0741 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -27,6 +27,7 @@ #include "ir/utils.h" #include "parsing.h" #include "pass.h" +#include "support/small_set.h" #include "wasm-builder.h" #include "wasm.h" @@ -1020,31 +1021,35 @@ struct RemoveUnusedBrs : public WalkerPass> { } } while (anotherCycle); - // thread trivial jumps - struct JumpThreader : public ControlFlowWalker { - // map of all value-less breaks and switches going to a block (and not a - // loop) - std::map> branchesToBlock; + // Thread trivial jumps. + struct JumpThreader + : public PostWalker> { + // Map of all labels (branch targets) to the branches going to them. (We + // only care about blocks here, and not loops, but for simplicitly we + // store all branch targets since blocks are 99% of that set anyhow. Any + // loops are ignored later.) + std::unordered_map> labelToBranches; bool worked = false; - void visitBreak(Break* curr) { - if (!curr->value) { - if (auto* target = findBreakTarget(curr->name)->dynCast()) { - branchesToBlock[target].push_back(curr); - } - } - } - void visitSwitch(Switch* curr) { - if (!curr->value) { - auto names = BranchUtils::getUniqueTargets(curr); - for (auto name : names) { - if (auto* target = findBreakTarget(name)->dynCast()) { - branchesToBlock[target].push_back(curr); + void visitExpression(Expression* curr) { + // Find the relevant targets: targets that (as mentioned above) have no + // value sent to them. + SmallSet relevantTargets; + BranchUtils::operateOnScopeNameUsesAndSentTypes( + curr, [&](Name name, Type sent) { + if (sent == Type::none) { + relevantTargets.insert(name); } - } + }); + + // Note ourselves on all relevant targets. + for (auto target : relevantTargets) { + labelToBranches[target].push_back(curr); } } + void visitBlock(Block* curr) { auto& list = curr->list; if (list.size() == 1 && curr->name.is()) { @@ -1073,7 +1078,7 @@ struct RemoveUnusedBrs : public WalkerPass> { } void redirectBranches(Block* from, Name to) { - auto& branches = branchesToBlock[from]; + auto& branches = labelToBranches[from->name]; for (auto* branch : branches) { if (BranchUtils::replacePossibleTarget(branch, from->name, to)) { worked = true; @@ -1081,10 +1086,8 @@ struct RemoveUnusedBrs : public WalkerPass> { } // if the jump is to another block then we can update the list, and // maybe push it even more later - if (auto* newTarget = findBreakTarget(to)->dynCast()) { - for (auto* branch : branches) { - branchesToBlock[newTarget].push_back(branch); - } + for (auto* branch : branches) { + labelToBranches[to].push_back(branch); } } diff --git a/test/lit/passes/remove-unused-brs-eh.wast b/test/lit/passes/remove-unused-brs-eh.wast index a4c6591ab15..544ff81b06f 100644 --- a/test/lit/passes/remove-unused-brs-eh.wast +++ b/test/lit/passes/remove-unused-brs-eh.wast @@ -244,6 +244,63 @@ (call $throw-caught-precise-later) ) ) + + ;; CHECK: (func $threading (type $0) + ;; CHECK-NEXT: (block $outer + ;; CHECK-NEXT: (block $middle + ;; CHECK-NEXT: (block $inner + ;; CHECK-NEXT: (try_table (catch $e $outer) (catch $f $outer) (catch_all $outer) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $threading + (block $outer + (block $middle + (block $inner + ;; All the branch targets here will turn into "outer", see below. + (try_table (catch $e $outer) (catch $f $middle) (catch_all $inner) + ) + ) + ;; Jumping to inner is the same as middle, as there is nothing + ;; between them. + ) + ;; Jumping to middle is the same as outer, as we jump there anyhow. + (br $outer) + ) + ) + + ;; CHECK: (func $threading-2 (type $0) + ;; CHECK-NEXT: (block $outer + ;; CHECK-NEXT: (block $middle + ;; CHECK-NEXT: (block $inner + ;; CHECK-NEXT: (try_table (catch $e $outer) (catch $f $middle) (catch_all $outer) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $outer) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $threading-2 + (block $outer + (block $middle + (block $inner + (try_table (catch $e $outer) (catch $f $middle) (catch_all $inner) + ;; Only inner will turn into outer. + ) + ) + ;; Skip over middle, so jumps to inner go to outer. + (br $outer) + ) + ;; Stop execution between middle and outer. We should still optimize + ;; inner to outer. + (unreachable) + ) + ) ) (module diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 115002e8903..42c2f4b1c18 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -782,4 +782,28 @@ ) ) ) + + ;; CHECK: (func $threading (type $10) (param $x anyref) + ;; CHECK-NEXT: (block $outer + ;; CHECK-NEXT: (block $inner + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_null $outer + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $threading (param $x anyref) + (block $outer + (block $inner + ;; This jump can go to $outer. + (drop + (br_on_null $inner + (local.get $x) + ) + ) + ) + ) + ) ) diff --git a/test/lit/passes/vacuum-eh.wast b/test/lit/passes/vacuum-eh.wast index d42fed56ba3..4df41f854f1 100644 --- a/test/lit/passes/vacuum-eh.wast +++ b/test/lit/passes/vacuum-eh.wast @@ -154,7 +154,6 @@ ;; try_table could be optimized out. We do this for `try` but not for ;; `try_table` - we leave such optimizations to --remove-unused-brs (that ;; pass can see that the throw can be converted to a br). - (block $catch (try_table (catch_all $catch) (throw $e (i32.const 0)) From 0be8d5e07e5f893705bc36c5d09676ee65d3466f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Oct 2024 11:27:59 -0700 Subject: [PATCH 060/622] MergeBlocks: Optimize all dropped blocks (#6984) Just call optimizeDroppedBlock from visitDrop to handle that. Followup to #6982. This optimizes the new testcase added there. Some older tests also improve. --- src/passes/MergeBlocks.cpp | 9 +++ test/lit/passes/merge-blocks.wast | 11 ++-- test/lit/passes/merge-blocks_names.wast | 28 ++++++++ test/lit/passes/monomorphize-drop.wast | 7 +- test/passes/Oz_fuzz-exec_all-features.txt | 10 +-- test/passes/merge-blocks.txt | 29 +++------ ...unused-names_merge-blocks_all-features.txt | 64 ++++++------------- 7 files changed, 76 insertions(+), 82 deletions(-) create mode 100644 test/lit/passes/merge-blocks_names.wast diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 401d2e6291b..12f67329d4b 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -434,6 +434,15 @@ struct MergeBlocks optimizeBlock(curr, getModule(), getPassOptions(), branchInfo); } + void visitDrop(Drop* curr) { + if (auto* block = curr->value->dynCast()) { + if (optimizeDroppedBlock( + curr, block, *getModule(), getPassOptions(), branchInfo)) { + replaceCurrent(block); + } + } + } + // given // (curr // (block=child diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index e1ac88dd83e..86181f7a488 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -405,17 +405,16 @@ ) ;; CHECK: (func $toplevel (type $4) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label (result i32) - ;; CHECK-NEXT: (br $label - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $label + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $toplevel ;; Test we can remove a block value even when the drop is at the toplevel of - ;; a function. This is not done yet TODO + ;; a function. (drop (block $label (result i32) (br $label diff --git a/test/lit/passes/merge-blocks_names.wast b/test/lit/passes/merge-blocks_names.wast new file mode 100644 index 00000000000..c6e08b83ab3 --- /dev/null +++ b/test/lit/passes/merge-blocks_names.wast @@ -0,0 +1,28 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --merge-blocks -all -S -o - | filecheck %s +;; +;; Similar to merge-blocks.wast, but without --remove-unused-names. This tests +;; the pass entirely by itself. + +(module + ;; CHECK: (func $nested-dropped-blocks (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-dropped-blocks + ;; Fully removing unneeded blocks here requires multiple operations to happen. + ;; Specifically, we remove all the outer blocks first, and only then get to + ;; the inner named block, which we can then infer is not needed either. + (block + (drop + (block (result i32) + (block $named (result i32) + (i32.const 42) + ) + ) + ) + ) + ) +) + diff --git a/test/lit/passes/monomorphize-drop.wast b/test/lit/passes/monomorphize-drop.wast index a9f0e1f06df..2c683420408 100644 --- a/test/lit/passes/monomorphize-drop.wast +++ b/test/lit/passes/monomorphize-drop.wast @@ -672,12 +672,7 @@ ;; CAREFUL: (func $return-normal_4 (type $1) ;; CAREFUL-NEXT: (drop -;; CAREFUL-NEXT: (block -;; CAREFUL-NEXT: (drop -;; CAREFUL-NEXT: (call $import) -;; CAREFUL-NEXT: ) -;; CAREFUL-NEXT: (return) -;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: (call $import) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index b69b9728cca..d1a80c3182b 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -183,14 +183,10 @@ (nop) ) (func $br-on_non_null-2 (type $void_func) - (drop - (block - (call $log - (i32.const 1) - ) - (unreachable) - ) + (call $log + (i32.const 1) ) + (unreachable) ) (func $cast-on-func (type $void_func) (call $log diff --git a/test/passes/merge-blocks.txt b/test/passes/merge-blocks.txt index 86d97af4b01..96e62170443 100644 --- a/test/passes/merge-blocks.txt +++ b/test/passes/merge-blocks.txt @@ -10,11 +10,12 @@ ) ) (func $drop-block-br - (drop - (block $x (result i32) - (br $x - (i32.const 1) - ) + (block $x + (drop + (i32.const 1) + ) + (br $x) + (drop (i32.const 0) ) ) @@ -77,15 +78,9 @@ ) ) (func $drop-block-squared-iloop - (drop - (block $label$0 (result i32) - (drop - (block $label$1 - (loop $label$2 - (br $label$2) - ) - ) - ) + (block $label$0 + (loop $label$2 + (br $label$2) ) ) ) @@ -109,11 +104,7 @@ (func $loop-block-drop-block-return (loop $label$4 (block $label$5 - (drop - (block $label$6 (result i32) - (return) - ) - ) + (return) ) ) ) diff --git a/test/passes/remove-unused-names_merge-blocks_all-features.txt b/test/passes/remove-unused-names_merge-blocks_all-features.txt index c1c803af5dd..17ba2bd4d12 100644 --- a/test/passes/remove-unused-names_merge-blocks_all-features.txt +++ b/test/passes/remove-unused-names_merge-blocks_all-features.txt @@ -286,15 +286,11 @@ (i32.const 30) ) (drop - (block - (drop - (i32.const 10) - ) - (i32.add - (unreachable) - (i32.const 20) - ) - ) + (i32.const 10) + ) + (i32.add + (unreachable) + (i32.const 20) ) (drop (i32.const 20) @@ -822,17 +818,13 @@ ) (func $drop-unreachable (type $2) (result i32) (local $0 i32) - (drop - (block (result i32) - (unreachable) - ) - ) + (unreachable) (unreachable) ) (func $concrete_finale_in_unreachable (type $5) (result f64) - (drop - (block (result f64) - (unreachable) + (block + (unreachable) + (drop (f64.const 6.322092475576799e-96) ) ) @@ -840,22 +832,16 @@ ) (func $dont-move-unreachable (type $3) (loop $label$0 + (br $label$0) (drop - (block (result i32) - (br $label$0) - (i32.const 1) - ) + (i32.const 1) ) ) ) (func $dont-move-unreachable-last (type $3) (loop $label$0 - (drop - (block (result i32) - (call $dont-move-unreachable-last) - (br $label$0) - ) - ) + (call $dont-move-unreachable-last) + (br $label$0) ) ) (func $move-around-unreachable-in-middle (type $3) @@ -876,16 +862,10 @@ ) (func $drop-unreachable-block-with-concrete-final (type $3) (drop - (block (result i32) - (drop - (block - (drop - (return) - ) - ) - ) - (i32.const -452) - ) + (return) + ) + (drop + (i32.const -452) ) ) (func $merging-with-unreachable-in-middle (type $2) (result i32) @@ -901,13 +881,9 @@ ) (func $remove-br-after-unreachable (type $3) (block $label$9 - (drop - (block - (block - (return) - (br $label$9) - ) - ) + (block + (return) + (br $label$9) ) ) ) From bcdedab9a97dbea1c2ee151ca1013e3896f16c8a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Oct 2024 13:43:08 -0700 Subject: [PATCH 061/622] Fix a fuzz issue with #6984 (#6988) When I refactored the optimizeDroppedBlock logic in #6982, I didn't move the unreachability check with that code, which was wrong. When that function was called from another place in #6984, the fuzzer found an issue. Diff without whitespace is smaller. This reverts almost all the test updates from #6984 - those changes were on blocks with unreachable children. The change was safe on them, but in general removing a block value in the presence of unreachable code is tricky, so it's best to avoid it. The testcase is a little bizarre, but it's the one the fuzzer found and I can't find a way to generate a better one (other than to reduce it, which I did). --- src/passes/MergeBlocks.cpp | 27 ++++---- test/lit/passes/merge-blocks.wast | 9 +-- test/lit/passes/monomorphize-drop.wast | 7 +- test/passes/Oz_fuzz-exec_all-features.txt | 10 ++- test/passes/merge-blocks.txt | 29 ++++++--- ...unused-names_merge-blocks_all-features.txt | 64 +++++++++++++------ 6 files changed, 94 insertions(+), 52 deletions(-) diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 12f67329d4b..7518c73ca32 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -206,6 +206,11 @@ static bool optimizeDroppedBlock(Drop* drop, PassOptions& options, BranchUtils::BranchSeekerCache& branchInfo) { assert(drop->value == block); + if (hasUnreachableChild(block)) { + // Don't move around unreachable code, as it can change types (leave it for + // DCE). + return false; + } if (block->name.is()) { // There may be breaks: see if we can remove their values. Expression* expression = block; @@ -261,20 +266,14 @@ static void optimizeBlock(Block* curr, // drop into the block, and remove br values. This allows more merging. if (auto* drop = list[i]->dynCast()) { childBlock = drop->value->dynCast(); - if (childBlock) { - if (hasUnreachableChild(childBlock)) { - // don't move around unreachable code, as it can change types - // dce should have been run anyhow - continue; - } - if (optimizeDroppedBlock( - drop, childBlock, *module, passOptions, branchInfo)) { - child = list[i] = childBlock; - more = true; - changed = true; - } else { - childBlock = nullptr; - } + if (childBlock && + optimizeDroppedBlock( + drop, childBlock, *module, passOptions, branchInfo)) { + child = list[i] = childBlock; + more = true; + changed = true; + } else { + childBlock = nullptr; } } else if ((loop = list[i]->dynCast())) { // We can merge a loop's "tail" - if the body is a block and has diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 86181f7a488..6fa1687b861 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -405,11 +405,12 @@ ) ;; CHECK: (func $toplevel (type $4) - ;; CHECK-NEXT: (block $label - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $toplevel diff --git a/test/lit/passes/monomorphize-drop.wast b/test/lit/passes/monomorphize-drop.wast index 2c683420408..a9f0e1f06df 100644 --- a/test/lit/passes/monomorphize-drop.wast +++ b/test/lit/passes/monomorphize-drop.wast @@ -672,7 +672,12 @@ ;; CAREFUL: (func $return-normal_4 (type $1) ;; CAREFUL-NEXT: (drop -;; CAREFUL-NEXT: (call $import) +;; CAREFUL-NEXT: (block +;; CAREFUL-NEXT: (drop +;; CAREFUL-NEXT: (call $import) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: (return) +;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index d1a80c3182b..b69b9728cca 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -183,10 +183,14 @@ (nop) ) (func $br-on_non_null-2 (type $void_func) - (call $log - (i32.const 1) + (drop + (block + (call $log + (i32.const 1) + ) + (unreachable) + ) ) - (unreachable) ) (func $cast-on-func (type $void_func) (call $log diff --git a/test/passes/merge-blocks.txt b/test/passes/merge-blocks.txt index 96e62170443..86d97af4b01 100644 --- a/test/passes/merge-blocks.txt +++ b/test/passes/merge-blocks.txt @@ -10,12 +10,11 @@ ) ) (func $drop-block-br - (block $x - (drop - (i32.const 1) - ) - (br $x) - (drop + (drop + (block $x (result i32) + (br $x + (i32.const 1) + ) (i32.const 0) ) ) @@ -78,9 +77,15 @@ ) ) (func $drop-block-squared-iloop - (block $label$0 - (loop $label$2 - (br $label$2) + (drop + (block $label$0 (result i32) + (drop + (block $label$1 + (loop $label$2 + (br $label$2) + ) + ) + ) ) ) ) @@ -104,7 +109,11 @@ (func $loop-block-drop-block-return (loop $label$4 (block $label$5 - (return) + (drop + (block $label$6 (result i32) + (return) + ) + ) ) ) ) diff --git a/test/passes/remove-unused-names_merge-blocks_all-features.txt b/test/passes/remove-unused-names_merge-blocks_all-features.txt index 17ba2bd4d12..c1c803af5dd 100644 --- a/test/passes/remove-unused-names_merge-blocks_all-features.txt +++ b/test/passes/remove-unused-names_merge-blocks_all-features.txt @@ -286,11 +286,15 @@ (i32.const 30) ) (drop - (i32.const 10) - ) - (i32.add - (unreachable) - (i32.const 20) + (block + (drop + (i32.const 10) + ) + (i32.add + (unreachable) + (i32.const 20) + ) + ) ) (drop (i32.const 20) @@ -818,13 +822,17 @@ ) (func $drop-unreachable (type $2) (result i32) (local $0 i32) - (unreachable) + (drop + (block (result i32) + (unreachable) + ) + ) (unreachable) ) (func $concrete_finale_in_unreachable (type $5) (result f64) - (block - (unreachable) - (drop + (drop + (block (result f64) + (unreachable) (f64.const 6.322092475576799e-96) ) ) @@ -832,16 +840,22 @@ ) (func $dont-move-unreachable (type $3) (loop $label$0 - (br $label$0) (drop - (i32.const 1) + (block (result i32) + (br $label$0) + (i32.const 1) + ) ) ) ) (func $dont-move-unreachable-last (type $3) (loop $label$0 - (call $dont-move-unreachable-last) - (br $label$0) + (drop + (block (result i32) + (call $dont-move-unreachable-last) + (br $label$0) + ) + ) ) ) (func $move-around-unreachable-in-middle (type $3) @@ -862,10 +876,16 @@ ) (func $drop-unreachable-block-with-concrete-final (type $3) (drop - (return) - ) - (drop - (i32.const -452) + (block (result i32) + (drop + (block + (drop + (return) + ) + ) + ) + (i32.const -452) + ) ) ) (func $merging-with-unreachable-in-middle (type $2) (result i32) @@ -881,9 +901,13 @@ ) (func $remove-br-after-unreachable (type $3) (block $label$9 - (block - (return) - (br $label$9) + (drop + (block + (block + (return) + (br $label$9) + ) + ) ) ) ) From cc40ed0684153a9954d32d5b6b2cf5856e0c15cb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Oct 2024 15:37:26 -0700 Subject: [PATCH 062/622] Fix a misoptimization with mixed Try/TryTable in RemoveUnusedBrs (#6991) We ignored legacy Trys in #6980, but they can also catch. --- src/passes/RemoveUnusedBrs.cpp | 26 ++++++++++------- test/lit/passes/remove-unused-brs-eh.wast | 34 +++++++++++++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index c166d3c0741..c2370aca630 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -464,12 +464,13 @@ struct RemoveUnusedBrs : public WalkerPass> { // later down, see visitLocalSet. } - // A stack of try_tables that are parents of the current expression. - std::vector tryTables; + // A stack of catching expressions that are parents of the current expression, + // that is, Try and TryTable. + std::vector catchers; - static void popTryTable(RemoveUnusedBrs* self, Expression** currp) { - assert(!self->tryTables.empty() && self->tryTables.back() == *currp); - self->tryTables.pop_back(); + static void popCatcher(RemoveUnusedBrs* self, Expression** currp) { + assert(!self->catchers.empty() && self->catchers.back() == *currp); + self->catchers.pop_back(); } void visitThrow(Throw* curr) { @@ -481,8 +482,12 @@ struct RemoveUnusedBrs : public WalkerPass> { // To do so, look at the closest try and see if it will catch us, and // proceed outwards if not. auto thrownTag = curr->tag; - for (int i = tryTables.size() - 1; i >= 0; i--) { - auto* tryy = tryTables[i]; + for (int i = catchers.size() - 1; i >= 0; i--) { + auto* tryy = catchers[i]->dynCast(); + if (!tryy) { + // We do not handle mixtures of Try and TryTable. + return; + } for (Index j = 0; j < tryy->catchTags.size(); j++) { auto tag = tryy->catchTags[j]; // The tag must match, or be a catch_all. @@ -544,12 +549,13 @@ struct RemoveUnusedBrs : public WalkerPass> { self->pushTask(scan, &iff->condition); return; } - if (auto* tryy = (*currp)->dynCast()) { + if ((*currp)->is() || (*currp)->is()) { // Push the try we are reaching, and add a task to pop it, after all the // tasks that Super::scan will push for its children. - self->tryTables.push_back(tryy); - self->pushTask(popTryTable, currp); + self->catchers.push_back(*currp); + self->pushTask(popCatcher, currp); } + Super::scan(self, currp); } diff --git a/test/lit/passes/remove-unused-brs-eh.wast b/test/lit/passes/remove-unused-brs-eh.wast index 544ff81b06f..b3b89df632b 100644 --- a/test/lit/passes/remove-unused-brs-eh.wast +++ b/test/lit/passes/remove-unused-brs-eh.wast @@ -245,6 +245,40 @@ ) ) + ;; CHECK: (func $throw-mixed (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (throw $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-mixed + ;; When we see mixed Trys and TryTables, we do not optimize (we would need + ;; to analyze if the Trys catch the exceptions and not the TryTables, but + ;; we don't bother to handle this odd case of mixing the old and new + ;; styles of code). + (block $catch + (try_table (catch_all $catch) + (try + (do + ;; This throw is caught by the Try, not the TryTable. + (throw $e) + ) + (catch_all + (unreachable) + ) + ) + ) + ) + ) + ;; CHECK: (func $threading (type $0) ;; CHECK-NEXT: (block $outer ;; CHECK-NEXT: (block $middle From a1b88267bb977cb5fdba614b5f61fa7c84f51bf6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Oct 2024 15:37:53 -0700 Subject: [PATCH 063/622] Add explicit errors on unhandled instructions in Flatten (#6992) This error makes #6989 less confusing. --- src/passes/Flatten.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/passes/Flatten.cpp b/src/passes/Flatten.cpp index 8ddd8632a1f..37fa15b1185 100644 --- a/src/passes/Flatten.cpp +++ b/src/passes/Flatten.cpp @@ -329,6 +329,11 @@ struct Flatten } } + if (curr->is() || curr->is()) { + Fatal() << "Unsupported instruction for Flatten: " + << getExpressionName(curr); + } + // continue for general handling of everything, control flow or otherwise curr = getCurrent(); // we may have replaced it // we have changed children From 1eb01260efdcb65828c81cf5f72fb358b03d2328 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Oct 2024 16:57:56 -0700 Subject: [PATCH 064/622] Fuzzer: Generate TryTables (#6987) Also make Try/TryTables with type none, and not just concrete types as before. --- src/tools/fuzzing.h | 1 + src/tools/fuzzing/fuzzing.cpp | 68 +++++++++++++++ ...e-to-fuzz_all-features_metrics_noprint.txt | 82 +++++++++---------- 3 files changed, 110 insertions(+), 41 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 75e3a2a9a4e..f4400e39ec9 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -293,6 +293,7 @@ class TranslateToFuzzReader { Expression* buildIf(const struct ThreeArgs& args, Type type); Expression* makeIf(Type type); Expression* makeTry(Type type); + Expression* makeTryTable(Type type); Expression* makeBreak(Type type); Expression* makeCall(Type type); Expression* makeCallIndirect(Type type); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index a8b8f7855ef..abc6a63d530 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1366,6 +1366,7 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { &Self::makeCall, &Self::makeCallIndirect) .add(FeatureSet::ExceptionHandling, &Self::makeTry) + .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef); } if (type.isSingle()) { @@ -1451,6 +1452,8 @@ Expression* TranslateToFuzzReader::_makenone() { &Self::makeGlobalSet) .add(FeatureSet::BulkMemory, &Self::makeBulkMemory) .add(FeatureSet::Atomics, &Self::makeAtomic) + .add(FeatureSet::ExceptionHandling, &Self::makeTry) + .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef) .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeStructSet) .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeArraySet) @@ -1688,6 +1691,71 @@ Expression* TranslateToFuzzReader::makeTry(Type type) { return builder.makeTry(body, catchTags, catchBodies); } +Expression* TranslateToFuzzReader::makeTryTable(Type type) { + auto* body = make(type); + + if (funcContext->breakableStack.empty()) { + // Nothing to break to, emit a trivial TryTable. + // TODO: Perhaps generate a block wrapping us? + return builder.makeTryTable(body, {}, {}, {}); + } + + if (wasm.tags.empty()) { + addTag(); + } + + // Add catches of specific tags, and possibly a catch_all at the end. We use + // the last iteration of the loop for that. + std::vector catchTags; + std::vector catchDests; + std::vector catchRefs; + auto numCatches = upTo(MAX_TRY_CATCHES); + for (Index i = 0; i <= numCatches; i++) { + Name tagName; + Type tagType; + if (i < numCatches) { + // Look for a specific tag. + auto& tag = pick(wasm.tags); + tagName = tag->name; + tagType = tag->sig.params; + } else { + // Add a catch_all at the end, some of the time (but all of the time if we + // have nothing else). + if (!catchTags.empty() && oneIn(2)) { + break; + } + tagType = Type::none; + } + + // We need to find a proper target to break to, which means a target that + // has the type of the tag, or the tag + an exnref at the end. + std::vector vec(tagType.begin(), tagType.end()); + // Use a non-nullable exnref here, and then the subtyping check below will + // also accept a target that is nullable. + vec.push_back(Type(HeapType::exn, NonNullable)); + auto tagTypeWithExn = Type(vec); + int tries = TRIES; + while (tries-- > 0) { + auto* target = pick(funcContext->breakableStack); + auto dest = getTargetName(target); + auto valueType = getTargetType(target); + auto subOfTagType = Type::isSubType(tagType, valueType); + auto subOfTagTypeWithExn = Type::isSubType(tagTypeWithExn, valueType); + if (subOfTagType || subOfTagTypeWithExn) { + catchTags.push_back(tagName); + catchDests.push_back(dest); + catchRefs.push_back(subOfTagTypeWithExn); + break; + } + } + // TODO: Perhaps generate a block wrapping us, if we fail to find a target? + // TODO: It takes a bit of luck to find a target with an exnref - perhaps + // generate those? + } + + return builder.makeTryTable(body, catchTags, catchDests, catchRefs); +} + Expression* TranslateToFuzzReader::makeBreak(Type type) { if (funcContext->breakableStack.empty()) { return makeTrivial(type); diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 07afaa7ebf4..e58a508309c 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,53 +1,53 @@ Metrics total - [exports] : 5 - [funcs] : 9 + [exports] : 3 + [funcs] : 5 [globals] : 26 [imports] : 5 [memories] : 1 [memory-data] : 20 - [table-data] : 3 + [table-data] : 0 [tables] : 1 [tags] : 2 - [total] : 669 - [vars] : 27 - ArrayNew : 16 - ArrayNewFixed : 3 + [total] : 499 + [vars] : 20 + ArrayNew : 14 + ArrayNewFixed : 2 AtomicCmpxchg : 1 - AtomicFence : 1 - Binary : 75 - Block : 70 - Break : 7 - Call : 26 - CallRef : 1 - Const : 143 - Drop : 3 - GlobalGet : 37 - GlobalSet : 27 + AtomicNotify : 1 + AtomicRMW : 1 + Binary : 69 + Block : 42 + Break : 8 + Call : 6 + Const : 126 + Drop : 2 + GlobalGet : 27 + GlobalSet : 16 I31Get : 1 - If : 20 - Load : 21 - LocalGet : 55 - LocalSet : 40 - Loop : 6 - Nop : 5 - Pop : 5 + If : 10 + Load : 18 + LocalGet : 43 + LocalSet : 22 + Loop : 5 + Nop : 3 + Pop : 3 RefAs : 2 - RefEq : 2 - RefFunc : 5 - RefI31 : 2 - RefNull : 11 - RefTest : 2 - Return : 6 - Select : 2 - StringConst : 6 - StringEq : 1 - StringMeasure : 1 - StringWTF16Get : 1 - StructNew : 17 + RefFunc : 2 + RefI31 : 1 + RefNull : 8 + RefTest : 1 + Return : 1 + Select : 1 + Store : 3 + StringConst : 9 + StringEncode : 1 + StringEq : 3 + StructNew : 12 StructSet : 1 - Try : 4 - TupleExtract : 3 - TupleMake : 5 - Unary : 20 - Unreachable : 15 + Try : 3 + TryTable : 2 + TupleExtract : 1 + TupleMake : 4 + Unary : 13 + Unreachable : 11 From 930034ff1637f611a05582ac28fb734d6503b12e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Oct 2024 11:41:24 -0700 Subject: [PATCH 065/622] Fix flow reset during throw => break opts in RemoveUnusedBrs (#6993) #6980 was missing the logic to reset flows after replacing a throw. The process of replacing the throw introduces new code and in particular a drop, which blocks branches from flowing to their targets. In the testcase here, the br was turned into nop before this fix. --- src/passes/RemoveUnusedBrs.cpp | 4 +++ test/lit/passes/remove-unused-brs-eh.wast | 40 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index c2370aca630..c2f470d80f2 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -518,6 +518,10 @@ struct RemoveUnusedBrs : public WalkerPass> { auto* rep = getDroppedChildrenAndAppend( curr, wasm, getPassOptions(), br, DropMode::IgnoreParentEffects); replaceCurrent(rep); + // We modified the code here and may have added a drop, etc., so + // stop the flow (rather than re-scan it somehow). We leave + // optimizing anything that flows out for later iterations. + stopFlow(); } // Return even if we did not optimize: we found our tag was caught. diff --git a/test/lit/passes/remove-unused-brs-eh.wast b/test/lit/passes/remove-unused-brs-eh.wast index b3b89df632b..bb4278f86e3 100644 --- a/test/lit/passes/remove-unused-brs-eh.wast +++ b/test/lit/passes/remove-unused-brs-eh.wast @@ -412,4 +412,44 @@ ) ) ) + + ;; CHECK: (func $no-flow-through-throw (type $4) + ;; CHECK-NEXT: (block $label + ;; CHECK-NEXT: (try_table (catch_all $label) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $label) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $no-flow-through-throw + ;; The throw here can turn into a break. While doing so, we must clear all + ;; the currently-flowing things, namely the br in the if arm. If we do not + ;; do so then it will try to flow out through the drop that we add for the + ;; throw's value, which is impossible. + (block $label + (try_table (catch_all $label) + (throw $e + (if (result i32) + (i32.const 0) + (then + (br $label) + ) + (else + (i32.const 42) + ) + ) + ) + ) + ) + ) ) From debd24681cb4764e75936dd74bc33c41899b8a23 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Oct 2024 08:39:53 -0700 Subject: [PATCH 066/622] Fix BranchUtils::operateOnScopeNameUsesAndSentValues() on BrOn (#6995) BrOn does not always send a value. This is an odd asymmetry in the wasm spec, where br_on_null does not send the null on the branch (which makes sense, but the asymmetry does mean we need to special-case it). --- src/ir/branch-utils.h | 3 +- test/lit/passes/gufa-refs.wast | 63 +++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index be3f7f7a811..369365e728c 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -108,7 +108,8 @@ void operateOnScopeNameUsesAndSentValues(Expression* expr, T func) { } else if (auto* sw = expr->dynCast()) { func(name, sw->value); } else if (auto* br = expr->dynCast()) { - func(name, br->ref); + // A value may not be sent (e.g. BrOnNull does *not* send a null). + func(name, br->getSentType() != Type::none ? br->ref : nullptr); } else if (expr->is()) { // The values are supplied by throwing instructions, so we are unable to // know what they will be here. diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index d70de6a7a62..6722d0ed452 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -6058,15 +6058,17 @@ ) (module + ;; CHECK: (type $0 (func (result i64 nullref i32))) + ;; CHECK: (type $array (sub (array (mut i8)))) (type $array (sub (array (mut i8)))) - ;; CHECK: (type $1 (func)) + ;; CHECK: (type $2 (func)) ;; CHECK: (global $global (ref null $array) (array.new_fixed $array 0)) (global $global (ref null $array) (array.new_fixed $array 0)) - ;; CHECK: (func $test (type $1) + ;; CHECK: (func $test-set-bottom (type $2) ;; CHECK-NEXT: (block ;; (replaces unreachable ArraySet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast nullref @@ -6082,15 +6084,58 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $test + (func $test-set-bottom ;; We should not error on sets to bottom types, even if they are cast from ;; valid values. (array.set $array - (ref.cast nullref - (global.get $global) - ) - (i32.const 0) - (i32.const 0) - ) + (ref.cast nullref + (global.get $global) + ) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $loop-tuple-br_on (type $2) + ;; CHECK-NEXT: (tuple.drop 3 + ;; CHECK-NEXT: (loop $loop (type $0) (result i64 nullref i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_null $loop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-tuple-br_on + ;; The br_on here does not send any values when it branches. This is a test + ;; for a bug where it did send the null along, which then caused an + ;; assertion when we tried to combine the null with the tuple that flows + ;; out. + (tuple.drop 3 + (loop $loop (result i64 anyref i32) + (drop + ;; As this br always happens, we can add an unreachable after it. + (br_on_null $loop + (ref.null any) + ) + ) + (tuple.make 3 + (i64.const 1) + (ref.null any) + (i32.const 2) + ) + ) + ) ) ) From a8aa6602cdbedd04e69d362c01bbf378a44b395d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Oct 2024 08:40:24 -0700 Subject: [PATCH 067/622] ReFinalize in MergeBlocks so we can optimize unreachable instructions too (#6994) In #6984 we optimized dropped blocks even if they had unreachable code. In #6988 that part was reverted, and blocks with unreachable code were ignored once more. However, I realized that the check was not actually for unreachable code, but for having an unreachable child, so it would miss things like this: (block (block .. (br $somewhere) ;; unreachable type, but no unreachable code ) ) But it is useful to merge such blocks: we don't need the inner block here. To fix this, just run ReFinalize if we change anything, which will propagate unreachability as needed. I think MergeBlocks was written before we had that utility, so it didn't use it... This is not only useful for itself but will unblock an EH optimization in a later PR, that has code in this form. It also simplifies the code by removing the hasUnreachableChild checks. --- src/passes/MergeBlocks.cpp | 40 ++-- test/lit/passes/merge-blocks.wast | 9 +- test/lit/passes/monomorphize-drop.wast | 7 +- test/passes/Oz_fuzz-exec_all-features.txt | 10 +- test/passes/merge-blocks.txt | 29 +-- ...unused-names_merge-blocks_all-features.txt | 206 ++++++++---------- 6 files changed, 123 insertions(+), 178 deletions(-) diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 7518c73ca32..147ba9f4579 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -176,15 +176,6 @@ struct BreakValueDropper : public ControlFlowWalker { } }; -static bool hasUnreachableChild(Block* block) { - for (auto* test : block->list) { - if (test->type == Type::unreachable) { - return true; - } - } - return false; -} - // Checks for code after an unreachable element. static bool hasDeadCode(Block* block) { auto& list = block->list; @@ -206,11 +197,6 @@ static bool optimizeDroppedBlock(Drop* drop, PassOptions& options, BranchUtils::BranchSeekerCache& branchInfo) { assert(drop->value == block); - if (hasUnreachableChild(block)) { - // Don't move around unreachable code, as it can change types (leave it for - // DCE). - return false; - } if (block->name.is()) { // There may be breaks: see if we can remove their values. Expression* expression = block; @@ -241,8 +227,8 @@ static bool optimizeDroppedBlock(Drop* drop, return true; } -// Core block optimizer routine. -static void optimizeBlock(Block* curr, +// Core block optimizer routine. Returns true when we optimize. +static bool optimizeBlock(Block* curr, Module* module, PassOptions& passOptions, BranchUtils::BranchSeekerCache& branchInfo) { @@ -412,6 +398,7 @@ static void optimizeBlock(Block* curr, if (changed) { curr->finalize(curr->type); } + return changed; } void BreakValueDropper::visitBlock(Block* curr) { @@ -427,6 +414,8 @@ struct MergeBlocks return std::make_unique(); } + bool refinalize = false; + BranchUtils::BranchSeekerCache branchInfo; void visitBlock(Block* curr) { @@ -438,6 +427,7 @@ struct MergeBlocks if (optimizeDroppedBlock( curr, block, *getModule(), getPassOptions(), branchInfo)) { replaceCurrent(block); + refinalize = true; } } } @@ -485,13 +475,6 @@ struct MergeBlocks } if (auto* block = child->dynCast()) { if (!block->name.is() && block->list.size() >= 2) { - // if we move around unreachable code, type changes could occur. avoid - // that, as anyhow it means we should have run dce before getting here - if (curr->type == Type::none && hasUnreachableChild(block)) { - // moving the block to the outside would replace a none with an - // unreachable - return outer; - } auto* back = block->list.back(); if (back->type == Type::unreachable) { // curr is not reachable, dce could remove it; don't try anything @@ -510,6 +493,7 @@ struct MergeBlocks return outer; } child = back; + refinalize = true; if (outer == nullptr) { // reuse the block, move it out block->list.back() = curr; @@ -600,8 +584,7 @@ struct MergeBlocks // too small for us to remove anything from (we cannot remove the last // element), or if it has unreachable code (leave that for dce), then give // up. - if (!block || block->name.is() || block->list.size() <= 1 || - hasUnreachableChild(block)) { + if (!block || block->name.is() || block->list.size() <= 1) { continueEarly(); continue; } @@ -682,6 +665,7 @@ struct MergeBlocks outerBlock->list.push_back(curr); outerBlock->finalize(curr->type); replaceCurrent(outerBlock); + refinalize = true; } } @@ -700,6 +684,12 @@ struct MergeBlocks outer = optimize(curr, curr->operands[i], outer); } } + + void visitFunction(Function* curr) { + if (refinalize) { + ReFinalize().walkFunctionInModule(curr, getModule()); + } + } }; Pass* createMergeBlocksPass() { return new MergeBlocks(); } diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 6fa1687b861..86181f7a488 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -405,12 +405,11 @@ ) ;; CHECK: (func $toplevel (type $4) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label (result i32) - ;; CHECK-NEXT: (br $label - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $label + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $toplevel diff --git a/test/lit/passes/monomorphize-drop.wast b/test/lit/passes/monomorphize-drop.wast index a9f0e1f06df..2c683420408 100644 --- a/test/lit/passes/monomorphize-drop.wast +++ b/test/lit/passes/monomorphize-drop.wast @@ -672,12 +672,7 @@ ;; CAREFUL: (func $return-normal_4 (type $1) ;; CAREFUL-NEXT: (drop -;; CAREFUL-NEXT: (block -;; CAREFUL-NEXT: (drop -;; CAREFUL-NEXT: (call $import) -;; CAREFUL-NEXT: ) -;; CAREFUL-NEXT: (return) -;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: (call $import) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index b69b9728cca..d1a80c3182b 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -183,14 +183,10 @@ (nop) ) (func $br-on_non_null-2 (type $void_func) - (drop - (block - (call $log - (i32.const 1) - ) - (unreachable) - ) + (call $log + (i32.const 1) ) + (unreachable) ) (func $cast-on-func (type $void_func) (call $log diff --git a/test/passes/merge-blocks.txt b/test/passes/merge-blocks.txt index 86d97af4b01..96e62170443 100644 --- a/test/passes/merge-blocks.txt +++ b/test/passes/merge-blocks.txt @@ -10,11 +10,12 @@ ) ) (func $drop-block-br - (drop - (block $x (result i32) - (br $x - (i32.const 1) - ) + (block $x + (drop + (i32.const 1) + ) + (br $x) + (drop (i32.const 0) ) ) @@ -77,15 +78,9 @@ ) ) (func $drop-block-squared-iloop - (drop - (block $label$0 (result i32) - (drop - (block $label$1 - (loop $label$2 - (br $label$2) - ) - ) - ) + (block $label$0 + (loop $label$2 + (br $label$2) ) ) ) @@ -109,11 +104,7 @@ (func $loop-block-drop-block-return (loop $label$4 (block $label$5 - (drop - (block $label$6 (result i32) - (return) - ) - ) + (return) ) ) ) diff --git a/test/passes/remove-unused-names_merge-blocks_all-features.txt b/test/passes/remove-unused-names_merge-blocks_all-features.txt index c1c803af5dd..ec63edd5e01 100644 --- a/test/passes/remove-unused-names_merge-blocks_all-features.txt +++ b/test/passes/remove-unused-names_merge-blocks_all-features.txt @@ -286,26 +286,22 @@ (i32.const 30) ) (drop - (block - (drop - (i32.const 10) - ) - (i32.add - (unreachable) - (i32.const 20) - ) - ) + (i32.const 10) ) - (drop + (i32.add + (unreachable) (i32.const 20) ) - (drop - (i32.add - (block (result i32) - (unreachable) + (block + (unreachable) + (drop + (i32.const 20) + ) + (drop + (i32.add (i32.const 10) + (i32.const 30) ) - (i32.const 30) ) ) ) @@ -413,20 +409,20 @@ ) ) ) - (drop - (i32.const 30) - ) - (drop - (i32.const 50) - ) - (drop - (select - (block (result i32) - (unreachable) + (block + (unreachable) + (drop + (i32.const 30) + ) + (drop + (i32.const 50) + ) + (drop + (select (i32.const 20) + (i32.const 40) + (i32.const 60) ) - (i32.const 40) - (i32.const 60) ) ) (drop @@ -437,7 +433,7 @@ ) (drop (select - (block (result i32) + (block (drop (i32.const 10) ) @@ -447,20 +443,20 @@ (i32.const 60) ) ) - (drop - (i32.const 10) - ) - (drop - (i32.const 50) - ) - (drop - (select - (i32.const 20) - (block (result i32) - (unreachable) + (block + (drop + (i32.const 10) + ) + (unreachable) + (drop + (i32.const 50) + ) + (drop + (select + (i32.const 20) (i32.const 40) + (i32.const 60) ) - (i32.const 60) ) ) (drop @@ -472,7 +468,7 @@ (drop (select (i32.const 20) - (block (result i32) + (block (drop (i32.const 30) ) @@ -481,18 +477,18 @@ (i32.const 60) ) ) - (drop - (i32.const 10) - ) - (drop - (i32.const 30) - ) - (drop - (select - (i32.const 20) - (i32.const 40) - (block (result i32) - (unreachable) + (block + (drop + (i32.const 10) + ) + (drop + (i32.const 30) + ) + (unreachable) + (drop + (select + (i32.const 20) + (i32.const 40) (i32.const 60) ) ) @@ -507,7 +503,7 @@ (select (i32.const 20) (i32.const 40) - (block (result i32) + (block (drop (i32.const 50) ) @@ -597,21 +593,21 @@ (i32.const 20) (i32.const 40) ) - (drop - (i32.const 20) - ) - (call $call-ii - (block (result i32) - (unreachable) + (block + (unreachable) + (drop + (i32.const 20) + ) + (call $call-ii (i32.const 10) + (i32.const 30) ) - (i32.const 30) ) (drop (i32.const 20) ) (call $call-ii - (block (result i32) + (block (drop (i32.const 10) ) @@ -619,13 +615,13 @@ ) (i32.const 30) ) - (drop - (i32.const 10) - ) - (call $call-ii - (i32.const 20) - (block (result i32) - (unreachable) + (block + (drop + (i32.const 10) + ) + (unreachable) + (call $call-ii + (i32.const 20) (i32.const 30) ) ) @@ -634,7 +630,7 @@ ) (call $call-ii (i32.const 20) - (block (result i32) + (block (drop (i32.const 30) ) @@ -807,32 +803,24 @@ ) (func $return-different-type (type $2) (result i32) (drop - (f64.abs - (block - (drop - (i32.const 2) - ) - (return - (i32.const 1) - ) - ) + (i32.const 2) + ) + (f64.abs + (return + (i32.const 1) ) ) (unreachable) ) (func $drop-unreachable (type $2) (result i32) (local $0 i32) - (drop - (block (result i32) - (unreachable) - ) - ) + (unreachable) (unreachable) ) (func $concrete_finale_in_unreachable (type $5) (result f64) - (drop - (block (result f64) - (unreachable) + (block + (unreachable) + (drop (f64.const 6.322092475576799e-96) ) ) @@ -840,22 +828,16 @@ ) (func $dont-move-unreachable (type $3) (loop $label$0 + (br $label$0) (drop - (block (result i32) - (br $label$0) - (i32.const 1) - ) + (i32.const 1) ) ) ) (func $dont-move-unreachable-last (type $3) (loop $label$0 - (drop - (block (result i32) - (call $dont-move-unreachable-last) - (br $label$0) - ) - ) + (call $dont-move-unreachable-last) + (br $label$0) ) ) (func $move-around-unreachable-in-middle (type $3) @@ -864,9 +846,11 @@ (drop (block $label$3 (result i32) (drop - (br_if $label$3 + (block (br $label$0) - (i32.const 0) + (drop + (i32.const 0) + ) ) ) (i32.const 1) @@ -876,16 +860,10 @@ ) (func $drop-unreachable-block-with-concrete-final (type $3) (drop - (block (result i32) - (drop - (block - (drop - (return) - ) - ) - ) - (i32.const -452) - ) + (return) + ) + (drop + (i32.const -452) ) ) (func $merging-with-unreachable-in-middle (type $2) (result i32) @@ -901,13 +879,9 @@ ) (func $remove-br-after-unreachable (type $3) (block $label$9 - (drop - (block - (block - (return) - (br $label$9) - ) - ) + (block + (return) + (br $label$9) ) ) ) From f0a5e488ae68074837706edafb54050e56cf9937 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Oct 2024 16:06:11 -0700 Subject: [PATCH 068/622] [Wasm EH] Optimize values flowing out of TryTable (#6997) This allows (block $out (result i32) (try_table (catch..) .. (br $out (i32.const 42) ) ) ) => (block $out (result i32) (try_table (result i32) (catch..) ;; add a result .. (i32.const 42) ;; remove the br around the value ) ) --- src/passes/RemoveUnusedBrs.cpp | 9 +-- test/lit/passes/remove-unused-brs-eh.wast | 68 ++++++++++++++++------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index c2f470d80f2..2452130cc87 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -260,15 +260,16 @@ struct RemoveUnusedBrs : public WalkerPass> { } } } else if (curr->is()) { - // ignore (could be result of a previous cycle) + // Ignore (could be result of a previous cycle). self->stopValueFlow(); - } else if (curr->is()) { // TODO: eh - // do nothing - it's ok for values to flow out + } else if (curr->is() || curr->is()) { + // Do nothing - it's ok for values to flow out. + // TODO: Legacy Try as well? } else if (auto* sw = curr->dynCast()) { self->stopFlow(); self->optimizeSwitch(sw); } else { - // anything else stops the flow + // Anything else stops the flow. self->stopFlow(); } } diff --git a/test/lit/passes/remove-unused-brs-eh.wast b/test/lit/passes/remove-unused-brs-eh.wast index bb4278f86e3..8ce578101c3 100644 --- a/test/lit/passes/remove-unused-brs-eh.wast +++ b/test/lit/passes/remove-unused-brs-eh.wast @@ -12,16 +12,36 @@ ;; CHECK: (func $throw-caught-all (type $0) ;; CHECK-NEXT: (block $catch ;; CHECK-NEXT: (try_table (catch_all $catch) - ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $throw-caught-all (block $catch (try_table (catch_all $catch) - ;; This throw can be a br. + ;; This throw can be a br. After that, it can also be removed, as we + ;; flow to that block anyhow. + (throw $e) + ) + ) + ) + + ;; CHECK: (func $throw-caught-all-no-flow (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-caught-all-no-flow + (block $catch + (try_table (catch_all $catch) (throw $e) ) + ;; Block the flow, so that after the throw is optimized to a br, the br + ;; remains. + (unreachable) ) ) @@ -52,7 +72,7 @@ ;; CHECK: (func $throw-caught-precise (type $0) ;; CHECK-NEXT: (block $catch ;; CHECK-NEXT: (try_table (catch $e $catch) - ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -70,7 +90,7 @@ ;; CHECK-NEXT: (block $fail ;; CHECK-NEXT: (block $catch ;; CHECK-NEXT: (try_table (catch $f $fail) (catch $e $catch) - ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $throw-caught-precise-later) @@ -94,7 +114,7 @@ ;; CHECK-NEXT: (block $fail ;; CHECK-NEXT: (block $catch ;; CHECK-NEXT: (try_table (catch $f $fail) (catch_all $catch) - ;; CHECK-NEXT: (br $catch) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $throw-caught-precise-later) @@ -138,6 +158,7 @@ ;; CHECK-NEXT: (br $catch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $throw-caught-precise-later) ;; CHECK-NEXT: ) @@ -151,6 +172,8 @@ (throw $e) ) ) + ;; Block the flow, so that the br above remains. + (unreachable) ) ;; Add an effect here, so the two blocks are not mergeable. (call $throw-caught-precise-later) @@ -195,6 +218,7 @@ ;; CHECK-NEXT: (try_table (catch_ref $e $outer) (catch_all $catch) ;; CHECK-NEXT: (throw $e) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -207,6 +231,7 @@ ;; not optimize. (throw $e) ) + (unreachable) ) (unreachable) ) @@ -220,6 +245,7 @@ ;; CHECK-NEXT: (br $outer) ;; CHECK-NEXT: (br $middle) ;; CHECK-NEXT: (br $inner) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $throw-caught-precise-later) @@ -236,6 +262,9 @@ (throw $e) (throw $f) (throw $g) + ;; Prevent the br we optimize to at the end from getting optimized + ;; out. + (unreachable) ) ) ;; Add an effect here, so the two blocks are not mergeable. @@ -338,7 +367,7 @@ ) (module - ;; CHECK: (import "a" "b" (func $effect (type $1) (result i32))) + ;; CHECK: (import "a" "b" (func $effect (type $2) (result i32))) (import "a" "b" (func $effect (result i32))) ;; CHECK: (tag $e (param i32)) @@ -347,7 +376,7 @@ ;; CHECK: (tag $multi (param i32 f64)) (tag $multi (param i32 f64)) - ;; CHECK: (func $throw-caught-all (type $0) (param $x i32) + ;; CHECK: (func $throw-caught-all (type $1) (param $x i32) ;; CHECK-NEXT: (block $catch ;; CHECK-NEXT: (try_table (catch_all $catch) ;; CHECK-NEXT: (drop @@ -368,12 +397,10 @@ ) ) - ;; CHECK: (func $throw-br-contents (type $1) (result i32) + ;; CHECK: (func $throw-br-contents (type $2) (result i32) ;; CHECK-NEXT: (block $catch (result i32) - ;; CHECK-NEXT: (try_table (catch $e $catch) - ;; CHECK-NEXT: (br $catch - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try_table (result i32) (catch $e $catch) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -381,7 +408,8 @@ (block $catch (result i32) (try_table (catch $e $catch) ;; This throw is not caught by catch_all as above, so the value must be - ;; sent as a value on the br we optimize it to. + ;; sent as a value on the br we optimize it to. That br can also be + ;; optimized away by letting the value flow out. (throw $e (i32.const 42) ) @@ -389,14 +417,12 @@ ) ) - ;; CHECK: (func $throw-br-contents-multi (type $2) (result i32 f64) - ;; CHECK-NEXT: (block $catch (type $2) (result i32 f64) - ;; CHECK-NEXT: (try_table (catch $multi $catch) - ;; CHECK-NEXT: (br $catch - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (f64.const 3.14159) - ;; CHECK-NEXT: ) + ;; CHECK: (func $throw-br-contents-multi (type $0) (result i32 f64) + ;; CHECK-NEXT: (block $catch (type $0) (result i32 f64) + ;; CHECK-NEXT: (try_table (type $0) (result i32 f64) (catch $multi $catch) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (f64.const 3.14159) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From 34906bfbc99d31b3e62d2d006af2247d8418ba2f Mon Sep 17 00:00:00 2001 From: Petr Makhnev <51853996+i582@users.noreply.github.com> Date: Fri, 11 Oct 2024 03:15:28 +0400 Subject: [PATCH 069/622] Optimize Module::get_* family of functions with std::string_view in getModuleElement (#6998) Passing a constant string to functions requires memory allocation, and allocation is inherently slow. Since we are using C++17, we can use string_view and remove this unnecessary allocation. Although the code seems simple enough for the optimizer to remove this allocation after inlining, tests on Clang 18 show that this is not the case (on Apple Silicon at least). --- src/wasm/wasm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index e8de4572bd7..0245fc01ebe 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1523,7 +1523,7 @@ void Function::clearDebugInfo() { template typename Map::mapped_type& -getModuleElement(Map& m, Name name, const std::string& funcName) { +getModuleElement(Map& m, Name name, std::string_view funcName) { auto iter = m.find(name); if (iter == m.end()) { Fatal() << "Module::" << funcName << ": " << name << " does not exist"; From 31b4558f3decc49c5d780083d995d7c094132b77 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 14 Oct 2024 13:57:03 -0700 Subject: [PATCH 070/622] [WasmGC] OptimizeInstructions: Reorder externalize/internalize operations with ref.as_non_null (#7004) (any.convert_extern/extern.convert_any (ref.as_non_null ..)) => (ref.as_non_null (any.convert_extern/extern.convert_any ..)) This then allows the RefAsNonNull to be combined with parents in some cases (whereas the reverse allows nothing). --- src/passes/OptimizeInstructions.cpp | 23 ++++++++++- .../optimize-instructions-gc-extern.wast | 39 ++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index fff18b925c4..64d59ca1d2b 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2248,8 +2248,27 @@ struct OptimizeInstructions } if (curr->op == ExternConvertAny || curr->op == AnyConvertExtern) { - // We can't optimize these. Even removing a non-null cast is not valid as - // they allow nulls to filter through, unlike other RefAs*. + // These pass nulls through, and we can reorder them with null traps: + // + // (any.convert_extern/extern.convert_any (ref.as_non_null.. )) + // => + // (ref.as_non_null (any.convert_extern/extern.convert_any ..)) + // + // By moving the RefAsNonNull outside, it may reach a position where it + // can be optimized (e.g. if the parent traps anyhow). And, + // ExternConvertAny/AnyConvertExtern cannot be folded with anything, so + // there is no harm to moving them inside. + if (auto* refAsChild = curr->value->dynCast()) { + if (refAsChild->op == RefAsNonNull) { + // Reorder and fix up the types. + curr->value = refAsChild->value; + curr->finalize(); + refAsChild->value = curr; + refAsChild->finalize(); + replaceCurrent(refAsChild); + } + } + // TODO: optimize away ExternConvertAny of AnyConvertExtern, etc. return; } diff --git a/test/lit/passes/optimize-instructions-gc-extern.wast b/test/lit/passes/optimize-instructions-gc-extern.wast index 658b2b1153d..067efb0ec96 100644 --- a/test/lit/passes/optimize-instructions-gc-extern.wast +++ b/test/lit/passes/optimize-instructions-gc-extern.wast @@ -3,6 +3,9 @@ ;; RUN: | filecheck %s (module + ;; CHECK: (type $array (array (mut i8))) + (type $array (array (mut i8))) + ;; CHECK: (func $extern.convert_any (type $0) (param $x anyref) (param $y externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (extern.convert_any @@ -10,8 +13,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (extern.convert_any - ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (extern.convert_any ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -22,20 +25,22 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (any.convert_extern - ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (any.convert_extern ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $extern.convert_any (export "ext") (param $x (ref null any)) (param $y (ref null extern)) + (func $extern.convert_any (param $x (ref null any)) (param $y (ref null extern)) ;; We should not change anything here, and also not hit an internal error. (drop (extern.convert_any (local.get $x) ) ) + ;; We can reorder the externalize with the ref.as_non_null, which sometimes + ;; helps later optimizations, see below. (drop (extern.convert_any (ref.as_non_null @@ -43,6 +48,7 @@ ) ) ) + ;; As the above two cases, but for internalize. (drop (any.convert_extern (local.get $y) @@ -56,4 +62,27 @@ ) ) ) + + ;; CHECK: (func $convert.optimize.parent (type $1) (param $ext externref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $array) + ;; CHECK-NEXT: (any.convert_extern + ;; CHECK-NEXT: (local.get $ext) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $convert.optimize.parent (param $ext externref) + ;; The ref.cast can fold in the ref.as_non_null, after it is moved + ;; outside of the any.convert_extern. + (drop + (ref.cast (ref null $array) + (any.convert_extern + (ref.as_non_null + (local.get $ext) + ) + ) + ) + ) + ) ) From e201819761bd8ae21bd03b2656a15544f9e44c32 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 14 Oct 2024 14:38:12 -0700 Subject: [PATCH 071/622] [Wasm EH] Optimize away _ref from try_table catches when unused (#6996) If we have (drop (block $b (result exnref) (try_table (catch_all_ref $b) then we don't really need to send the ref: it is dropped, so we can just replace catch_all_ref with catch_all and then remove the drop and the block value. MergeBlocks already had logic to remove block values, so it is the natural place to add this. --- src/passes/MergeBlocks.cpp | 56 ++++++- test/lit/passes/merge-blocks-eh.wast | 215 +++++++++++++++++++++++++++ 2 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 test/lit/passes/merge-blocks-eh.wast diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 147ba9f4579..e0c5e575a37 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -83,9 +83,11 @@ namespace wasm { -// Looks for reasons we can't remove the values from breaks to an origin -// For example, if there is a switch targeting us, we can't do it - we can't -// remove the value from other targets +// Looks for reasons we can't remove the values from breaks to an origin. This +// is run when we know the value sent to that block is dropped, so the value is +// not needed, but some corner cases stop us (for example, if there is a switch +// targeting us, we can't do it - we can't remove the value from the switch's +// other targets). struct ProblemFinder : public ControlFlowWalker> { @@ -123,6 +125,39 @@ struct ProblemFinder return; } + if (auto* tryy = curr->dynCast()) { + auto num = tryy->catchTags.size(); + for (Index i = 0; i < num; i++) { + if (tryy->catchDests[i] == origin) { + // This try_table branches to the origin we care about. We know the + // value being sent to the block is dropped, so we'd like to stop + // anything from being sent to it. We can stop a ref from being sent, + // so if that is enough to remove all the values, then we can + // optimize here. In other words, if this is a catch_all_ref (which + // can only send a ref) or this is a catch_ref of a specific tag that + // has no contents (so if we remove the ref, nothing remains), then we + // can optimize, but if this is is a catch of a tag *with* contents + // then those contents stop us. + // + // TODO: We could also support cases where the target block has + // multiple values, and remove just ref at the end. That might + // make more sense in TupleOptimization though as it would need + // to track uses of parts of a tuple. + if (!tryy->catchTags[i] || + getModule()->getTag(tryy->catchTags[i])->sig.params.size() == 0) { + // There must be a ref here, otherwise there is no value being sent + // at all, and we should not be running ProblemFinder at all. + assert(tryy->catchRefs[i]); + } else { + // Anything else is a problem. + foundProblem = true; + return; + } + } + } + return; + } + // Any other branch type - switch, br_on, etc. - is not handled yet. BranchUtils::operateOnScopeNameUses(curr, [&](Name& name) { if (name == origin) { @@ -174,6 +209,18 @@ struct BreakValueDropper : public ControlFlowWalker { replaceCurrent(curr->value); } } + + void visitTryTable(TryTable* curr) { + auto num = curr->catchTags.size(); + for (Index i = 0; i < num; i++) { + if (curr->catchDests[i] == origin) { + // Remove the existing ref being sent. + assert(curr->catchRefs[i]); + curr->catchRefs[i] = false; + curr->sentTypes[i] = Type::none; + } + } + } }; // Checks for code after an unreachable element. @@ -223,7 +270,8 @@ static bool optimizeDroppedBlock(Drop* drop, drop->finalize(); block->list.back() = drop; } - block->finalize(); + // Remove the old type, which was from the value we just removed. + block->finalize(Type::none); return true; } diff --git a/test/lit/passes/merge-blocks-eh.wast b/test/lit/passes/merge-blocks-eh.wast new file mode 100644 index 00000000000..e53f601fd97 --- /dev/null +++ b/test/lit/passes/merge-blocks-eh.wast @@ -0,0 +1,215 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s -all --merge-blocks -all -S -o - | filecheck %s + +(module + ;; CHECK: (import "a" "b" (func $import (type $0))) + (import "a" "b" (func $import)) + + ;; CHECK: (tag $empty) + (tag $empty) + + ;; CHECK: (tag $i32 (param i32)) + (tag $i32 (param i32)) + + ;; CHECK: (tag $exnref (param exnref)) + (tag $exnref (param exnref)) + + ;; CHECK: (func $drop-block-try_catch_all_ref (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_all_ref + ;; This block is dropped, so the try_table's exnref value can be removed + ;; by replacing catch_all_ref with catch_all. + (drop + (block $catch (result exnref) + (try_table (catch_all_ref $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_ref (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $empty $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_ref + ;; As above, but with catch_ref instead of catch_all_ref. We can still + ;; optimize. + (drop + (block $catch (result exnref) + (try_table (catch_ref $empty $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_multi (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $empty $catch) (catch_all $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_multi + ;; As above, but with two catches, both of whom can be optimized. + (drop + (block $catch (result exnref) + (try_table (catch_ref $empty $catch) (catch_all_ref $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_all_i32 (type $0) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $catch (type $1) (result i32 exnref) + ;; CHECK-NEXT: (try_table (catch_ref $i32 $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_all_i32 + ;; Send a tag value + exnref. We don't handle this yet TODO + (tuple.drop 2 + (block $catch (result i32 exnref) + (try_table (catch_ref $i32 $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_multi_partial (type $0) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $outer (type $1) (result i32 exnref) + ;; CHECK-NEXT: (block $inner + ;; CHECK-NEXT: (try_table (catch_ref $i32 $outer) (catch_all $inner) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_multi_partial + ;; Two catches, one of which we can optimize, but not both. + (tuple.drop 2 + (block $outer (result i32 exnref) + (drop + (block $inner (result exnref) + (try_table (catch_ref $i32 $outer) (catch_all_ref $inner) + (call $import) + (unreachable) + ) + ) + ) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $drop-block_try_catch_multi-fail (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $catch (result exnref) + ;; CHECK-NEXT: (try_table (catch $exnref $catch) (catch_all_ref $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block_try_catch_multi-fail + (drop + ;; This block is sent an exnref in two ways: once as the contents of a tag + ;; and once as the ref of a catch_all_ref. We can remove the latter but + ;; not the former, and they go to the same block, so we cannot optimize. + (block $catch (result exnref) + (try_table (catch $exnref $catch) (catch_all_ref $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_all (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_all + ;; Without _ref, there is nothing to optimize (and we should not error). + ;; Also since there is no ref, there is no drop anyhow. + (block $catch + (try_table (catch_all $catch) + (call $import) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $empty $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch + ;; As above, but with a catch instead of catch_all. + (block $catch + (try_table (catch $empty $catch) + (call $import) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_i32 (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $catch (result i32) + ;; CHECK-NEXT: (try_table (catch $i32 $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_i32 + ;; Send an i32 without a ref. We can't optimize here, as we can't prevent + ;; the i32 from being sent. + (drop + (block $catch (result i32) + (try_table (catch $i32 $catch) + (call $import) + (unreachable) + ) + ) + ) + ) +) + From 93883fde36ac158fd415dcd6dbd387dcfd928d3c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 14 Oct 2024 16:07:57 -0700 Subject: [PATCH 072/622] [WasmGC] OptimizeInstructions: Cancel out internalize+externalize pairs (#7005) --- src/passes/OptimizeInstructions.cpp | 12 ++++++- test/lit/extern-conversions.wast | 36 +++++++++++++------ .../optimize-instructions-gc-extern.wast | 26 ++++++++++++++ 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 64d59ca1d2b..e1478b54f78 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2266,9 +2266,19 @@ struct OptimizeInstructions refAsChild->value = curr; refAsChild->finalize(); replaceCurrent(refAsChild); + return; + } + + // We can optimize away externalizations of internalizations and vice + // versa. + if ((curr->op == ExternConvertAny && + refAsChild->op == AnyConvertExtern) || + (curr->op == AnyConvertExtern && + refAsChild->op == ExternConvertAny)) { + replaceCurrent(refAsChild->value); + return; } } - // TODO: optimize away ExternConvertAny of AnyConvertExtern, etc. return; } diff --git a/test/lit/extern-conversions.wast b/test/lit/extern-conversions.wast index f8409755749..6702ba17da9 100644 --- a/test/lit/extern-conversions.wast +++ b/test/lit/extern-conversions.wast @@ -7,19 +7,21 @@ (module - ;; CHECK: (type $0 (func (param (ref any)) (result (ref extern)))) + ;; CHECK: (type $0 (func (param externref) (result anyref))) - ;; CHECK: (type $1 (func (param externref) (result anyref))) + ;; CHECK: (type $1 (func (param (ref any)) (result (ref extern)))) - ;; CHECK: (type $2 (func (param externref) (result externref))) + ;; CHECK: (type $2 (func (param anyref) (result externref))) ;; CHECK: (export "ext" (func $extern.convert_any)) ;; CHECK: (export "int" (func $any.convert_extern)) - ;; CHECK: (export "legacy" (func $legacy_notation)) + ;; CHECK: (export "legacy.1" (func $legacy_notation.1)) - ;; CHECK: (func $extern.convert_any (type $0) (param $0 (ref any)) (result (ref extern)) + ;; CHECK: (export "legacy.2" (func $legacy_notation.2)) + + ;; CHECK: (func $extern.convert_any (type $1) (param $0 (ref any)) (result (ref extern)) ;; CHECK-NEXT: (extern.convert_any ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -30,7 +32,7 @@ ) ) - ;; CHECK: (func $any.convert_extern (type $1) (param $0 externref) (result anyref) + ;; CHECK: (func $any.convert_extern (type $0) (param $0 externref) (result anyref) ;; CHECK-NEXT: (any.convert_extern ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -41,18 +43,30 @@ ) ) - ;; CHECK: (func $legacy_notation (type $2) (param $0 externref) (result externref) - ;; CHECK-NEXT: (extern.convert_any + ;; CHECK: (func $legacy_notation.1 (type $0) (param $0 externref) (result anyref) + ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (any.convert_extern ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $legacy_notation (export "legacy") (param $x (ref null extern)) (result (ref null extern)) - (extern.externalize - (extern.internalize + (func $legacy_notation.1 (export "legacy.1") (param $x (ref null extern)) (result (ref null any)) + (extern.internalize + (ref.as_non_null ;; Add this to avoid the entire function being merged with + ;; another. (local.get $x) ) ) ) + + ;; CHECK: (func $legacy_notation.2 (type $2) (param $0 anyref) (result externref) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $legacy_notation.2 (export "legacy.2") (param $x (ref null any)) (result (ref null extern)) + (extern.externalize + (local.get $x) + ) + ) ) diff --git a/test/lit/passes/optimize-instructions-gc-extern.wast b/test/lit/passes/optimize-instructions-gc-extern.wast index 067efb0ec96..633a18a3325 100644 --- a/test/lit/passes/optimize-instructions-gc-extern.wast +++ b/test/lit/passes/optimize-instructions-gc-extern.wast @@ -85,4 +85,30 @@ ) ) ) + + ;; CHECK: (func $extern.intern (type $3) (param $ext externref) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ext) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $extern.intern (param $ext externref) (param $any anyref) + ;; Internalize/externalize operations cancel out. + (drop + (any.convert_extern + (extern.convert_any + (local.get $any) + ) + ) + ) + (drop + (extern.convert_any + (any.convert_extern + (local.get $ext) + ) + ) + ) + ) ) From 686c76bf8f694505e549c8e402bcfa8a38dc5050 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 15 Oct 2024 12:26:36 -0700 Subject: [PATCH 073/622] [Strings] StringGathering: Handle uses of strings before their definitions (#7008) When we gather strings, we create new globals for each one, that is then the canonical defining global for it, which will then be used everywhere else. We create such a global if we lack one, but if we happen to have such a global - a global that simply defines a string - then we reuse it. But we didn't handle the case where there was a use before the definition, and failed to sort the definition before the use. --- src/passes/StringLowering.cpp | 18 +++--- test/lit/passes/string-gathering.wast | 87 +++++++++++++++++++++++---- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 081db606876..66841f29995 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -111,18 +111,15 @@ struct StringGathering : public Pass { // then we can just use that as the global for that string. This avoids // repeated executions of the pass adding more and more globals. // - // Note that we don't note these in newNames: They are already in the right - // sorted position, before any uses, as we use the first of them for each - // string. Only actually new names need sorting. - // // Any time we reuse a global, we must not modify its body (or else we'd // replace the global that all others read from); we note them here and // avoid them in replaceStrings later to avoid such trampling. std::unordered_set stringPtrsToPreserve; void addGlobals(Module* module) { - // Note all the new names we create for the sorting later. - std::unordered_set newNames; + // The names of the globals that define a string. Such globals may be + // referred to by others, and so we will need to sort them, later. + std::unordered_set definingNames; // Find globals to reuse (see comment on stringPtrsToPreserve for context). for (auto& global : module->globals) { @@ -143,7 +140,8 @@ struct StringGathering : public Pass { for (Index i = 0; i < strings.size(); i++) { auto& globalName = stringToGlobalName[strings[i]]; if (globalName.is()) { - // We are reusing a global for this one. + // We are reusing a global for this one, with its existing name. + definingNames.insert(globalName); continue; } @@ -160,14 +158,14 @@ struct StringGathering : public Pass { auto name = Names::getValidGlobalName( *module, std::string("string.const_") + std::string(escaped.str())); globalName = name; - newNames.insert(name); + definingNames.insert(name); auto* stringConst = builder.makeStringConst(string); auto global = builder.makeGlobal(name, nnstringref, stringConst, Builder::Immutable); module->addGlobal(std::move(global)); } - // Sort our new globals to the start, as other global initializers may use + // Sort defining globals to the start, as other global initializers may use // them (and it would be invalid for us to appear after a use). This sort is // a simple way to ensure that we validate, but it may be unoptimal (we // leave that for reorder-globals). @@ -175,7 +173,7 @@ struct StringGathering : public Pass { module->globals.begin(), module->globals.end(), [&](const std::unique_ptr& a, const std::unique_ptr& b) { - return newNames.count(a->name) && !newNames.count(b->name); + return definingNames.count(a->name) && !definingNames.count(b->name); }); } diff --git a/test/lit/passes/string-gathering.wast b/test/lit/passes/string-gathering.wast index a6243c2386b..cf29c787f6d 100644 --- a/test/lit/passes/string-gathering.wast +++ b/test/lit/passes/string-gathering.wast @@ -17,13 +17,13 @@ ;; CHECK: (type $0 (func)) + ;; CHECK: (global $global (ref string) (string.const "foo")) + (global $global (ref string) (string.const "foo")) + ;; CHECK: (global $"string.const_\"bar\"" (ref string) (string.const "bar")) ;; CHECK: (global $"string.const_\"other\"" (ref string) (string.const "other")) - ;; CHECK: (global $global (ref string) (string.const "foo")) - (global $global (ref string) (string.const "foo")) - ;; CHECK: (global $global2 stringref (global.get $"string.const_\"bar\"")) ;; LOWER: (type $0 (array (mut i16))) @@ -45,11 +45,11 @@ ;; LOWER: (type $9 (func (param externref i32 i32) (result (ref extern)))) - ;; LOWER: (import "string.const" "0" (global $"string.const_\"bar\"" (ref extern))) + ;; LOWER: (import "string.const" "0" (global $global (ref extern))) - ;; LOWER: (import "string.const" "1" (global $"string.const_\"other\"" (ref extern))) + ;; LOWER: (import "string.const" "1" (global $"string.const_\"bar\"" (ref extern))) - ;; LOWER: (import "string.const" "2" (global $global (ref extern))) + ;; LOWER: (import "string.const" "2" (global $"string.const_\"other\"" (ref extern))) ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) @@ -165,17 +165,19 @@ ;; LOWER: (type $8 (func (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (import "string.const" "0" (global $global1 (ref extern))) + + ;; LOWER: (import "string.const" "1" (global $global4 (ref extern))) + ;; LOWER: (import "a" "b" (global $import (ref extern))) (import "a" "b" (global $import (ref string))) ;; CHECK: (global $global1 (ref string) (string.const "foo")) (global $global1 (ref string) (string.const "foo")) - ;; CHECK: (global $global2 (ref string) (global.get $global1)) - ;; LOWER: (import "string.const" "0" (global $global1 (ref extern))) - - ;; LOWER: (import "string.const" "1" (global $global4 (ref extern))) + ;; CHECK: (global $global4 (ref string) (string.const "bar")) + ;; CHECK: (global $global2 (ref string) (global.get $global1)) ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $2) (param (ref null $0) i32 i32) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $3) (param i32) (result (ref extern)))) @@ -201,7 +203,6 @@ ;; LOWER: (global $global3 (ref extern) (global.get $global1)) (global $global3 (ref string) (string.const "foo")) - ;; CHECK: (global $global4 (ref string) (string.const "bar")) (global $global4 (ref string) (string.const "bar")) ;; CHECK: (global $global5 (ref string) (global.get $global4)) @@ -279,3 +280,67 @@ ) ) ) + +;; A module where a string (in this case the empty string) appears twice, so we +;; will use a single global for both. The first use of the string appears in a +;; nested position, inside a struct constructor, so we cannot use that one as +;; our defining global, but there is an appropriate global after it. We must be +;; careful to then sort the globals, as $string must then appear before $struct. +(module + ;; CHECK: (type $struct (struct (field stringref))) + ;; LOWER: (type $0 (array (mut i16))) + + ;; LOWER: (type $struct (struct (field externref))) + (type $struct (struct (field stringref))) + + ;; CHECK: (global $string (ref string) (string.const "")) + + ;; CHECK: (global $struct (ref $struct) (struct.new $struct + ;; CHECK-NEXT: (global.get $string) + ;; CHECK-NEXT: )) + ;; LOWER: (type $2 (func (param externref externref) (result i32))) + + ;; LOWER: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + + ;; LOWER: (type $4 (func (param i32) (result (ref extern)))) + + ;; LOWER: (type $5 (func (param externref externref) (result (ref extern)))) + + ;; LOWER: (type $6 (func (param externref (ref null $0) i32) (result i32))) + + ;; LOWER: (type $7 (func (param externref) (result i32))) + + ;; LOWER: (type $8 (func (param externref i32) (result i32))) + + ;; LOWER: (type $9 (func (param externref i32 i32) (result (ref extern)))) + + ;; LOWER: (import "string.const" "0" (global $string (ref extern))) + + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + + ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + + ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + + ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) + + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) + + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + + ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) + + ;; LOWER: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + + ;; LOWER: (global $struct (ref $struct) (struct.new $struct + ;; LOWER-NEXT: (global.get $string) + ;; LOWER-NEXT: )) + (global $struct (ref $struct) (struct.new $struct + (string.const "") + )) + + (global $string (ref string) (string.const "")) +) + From 525870cb6e8b650dc1ac46b314eed049455ced8a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 15 Oct 2024 15:04:48 -0700 Subject: [PATCH 074/622] GlobalRefining: Do not refine mutable exported globals (#7007) A mutable exported global might be shared with another module which writes to it using the current type, which is unsafe and the type system does not allow, so do not refine there. --- src/passes/GlobalRefining.cpp | 11 +++++- test/lit/passes/global-refining.wast | 56 +++++++++++++++------------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/passes/GlobalRefining.cpp b/src/passes/GlobalRefining.cpp index 1313421d5c2..4ef6252b5ac 100644 --- a/src/passes/GlobalRefining.cpp +++ b/src/passes/GlobalRefining.cpp @@ -67,9 +67,16 @@ struct GlobalRefining : public Pass { // In closed world we cannot change the types of exports, as we might change // from a public type to a private that would cause a validation error. // TODO We could refine to a type that is still public, however. + // + // We are also limited in open world: in that mode we must assume that + // another module might import our exported globals with the current type + // (that type is a contract between them), and in such a case the type of + // mutable globals must match precisely (the same rule as for mutable struct + // fields in subtypes - the types must match exactly, or else a write in + // one place could store a type considered in valid in another place). std::unordered_set unoptimizable; - if (getPassOptions().closedWorld) { - for (auto* global : ExportUtils::getExportedGlobals(*module)) { + for (auto* global : ExportUtils::getExportedGlobals(*module)) { + if (getPassOptions().closedWorld || global->mutable_) { unoptimizable.insert(global->name); } } diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index f1f6048f765..1ede2009eb9 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -169,31 +169,6 @@ ) ) -;; We can refine here, but as it is an export we only do so in open world. -(module - ;; CHECK: (type $0 (func)) - - ;; CHECK: (global $func-init (mut (ref $0)) (ref.func $foo)) - ;; CLOSD: (type $0 (func)) - - ;; CLOSD: (global $func-init (mut funcref) (ref.func $foo)) - (global $func-init (mut funcref) (ref.func $foo)) - - ;; CHECK: (export "global" (global $func-init)) - ;; CLOSD: (export "global" (global $func-init)) - (export "global" (global $func-init)) - - ;; CHECK: (func $foo (type $0) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CLOSD: (func $foo (type $0) - ;; CLOSD-NEXT: (nop) - ;; CLOSD-NEXT: ) - (func $foo - (nop) - ) -) - ;; We can refine $a, after which we should update the global.get in the other ;; global, or else we'd error on validation. ;; TODO: we could optimize further here and refine the type of the global $b. @@ -221,3 +196,34 @@ (func $func (type $sub) ) ) + +;; Test all combinations of being exported and being mutable. +;; +;; Mutability limits our ability to optimize in open world: mutable globals that +;; are exported cannot be refined, as they might be modified in another module +;; using the old type. In closed world, however, we can optimize both globals +;; here, as mutability is not a concern. As a result, we can refine the +;; (ref null func) to nullfuncref only when not exported, and if exported, then +;; only when immutable in open world. +(module + ;; CHECK: (global $mut (mut nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $mut (mut nullfuncref) (ref.null nofunc)) + (global $mut (mut (ref null func)) (ref.null nofunc)) + ;; CHECK: (global $imm nullfuncref (ref.null nofunc)) + ;; CLOSD: (global $imm nullfuncref (ref.null nofunc)) + (global $imm (ref null func) (ref.null nofunc)) + ;; CHECK: (global $mut-exp (mut funcref) (ref.null nofunc)) + ;; CLOSD: (global $mut-exp (mut funcref) (ref.null nofunc)) + (global $mut-exp (mut (ref null func)) (ref.null nofunc)) + ;; CHECK: (global $imm-exp nullfuncref (ref.null nofunc)) + ;; CLOSD: (global $imm-exp funcref (ref.null nofunc)) + (global $imm-exp (ref null func) (ref.null nofunc)) + + ;; CHECK: (export "mut-exp" (global $mut-exp)) + ;; CLOSD: (export "mut-exp" (global $mut-exp)) + (export "mut-exp" (global $mut-exp)) + ;; CHECK: (export "imm-exp" (global $imm-exp)) + ;; CLOSD: (export "imm-exp" (global $imm-exp)) + (export "imm-exp" (global $imm-exp)) +) + From c7e42ae04ffd45b0da3e281be03d753516083803 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 16 Oct 2024 08:24:30 -0700 Subject: [PATCH 075/622] [NFC] Remove unused, ancient file wasm-module-building.h (#7010) This was used in asm2wasm (the asm.js to wasm compiler, used in fastcomp, before the LLVM wasm backend replaced it). --- src/wasm-module-building.h | 316 ------------------------------------- 1 file changed, 316 deletions(-) delete mode 100644 src/wasm-module-building.h diff --git a/src/wasm-module-building.h b/src/wasm-module-building.h deleted file mode 100644 index fe58cd4b8a7..00000000000 --- a/src/wasm-module-building.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2016 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef wasm_wasm_module_building_h -#define wasm_wasm_module_building_h - -#include "pass.h" -#include -#include - -namespace wasm { - -#ifdef BINARYEN_THREAD_DEBUG -static std::mutex debug; -#define DEBUG_THREAD(x) \ - { \ - std::lock_guard lock(debug); \ - std::cerr << "[OptimizingIncrementalModuleBuilder Threading (thread: " \ - << std::this_thread::get_id() << ")] " << x; \ - std::cerr << '\n'; \ - } -#else -#define DEBUG_THREAD(x) -#endif - -// -// OptimizingIncrementalModuleBuilder -// -// Helps build wasm modules efficiently. If you build a module by -// adding function by function, and you want to optimize them, this class -// starts optimizing using worker threads *while you are still adding*. -// It runs function optimization passes at that time. This does not -// run global optimization after that by default, but you can do that -// to by calling optimizeGlobally(), which runs the the global post-passes -// (we can't run the pre-passes, as they must be run before function -// passes, and no such time is possible here given that we receive -// functions one by one and optimize them). -// -// This might also be faster than normal module optimization since it -// runs all passes on each function, then goes on to the next function -// which is better for data locality. -// -// Usage: Create an instance, passing it the module and the total -// number of functions. Then call addFunction as you have -// new functions to add (this also adds it to the module). Finally, -// call finish() when all functions have been added. -// -// This avoids locking by using atomics. We allocate an array of nullptrs -// that represent all the functions, and as we add a function, we place it -// at the next index. Each worker will read from the start to the end, -// and when a non-nullptr is found, the worker optimizes that function and -// nulls it. There is also an end marker that is not nullptr nor the address of -// a valid function, which represents that beyond this point we have not -// yet filled in. In other words, -// * the main thread fills everything with the end marker -// * the main thread transforms a marker entry into a function -// * workers pause when they see the marker -// * workers skip over nullptrs -// * workers transform functions into nullptrs, and optimize them -// * we keep an atomic count of the number of active workers and -// the number of optimized functions. -// * after adding a function, the main thread notifys up workers if -// it calculates there is work for them. -// * a lock is used for going to sleep and waking up. -// Locking should be rare, as optimization is -// generally slower than generation; in the optimal case, we never -// lock beyond the first step, and all further work is lock-free. -// -// N.B.: Optimizing functions in parallel with adding functions is possible, -// but the rest of the global state of the module should be fixed, -// such as globals, imports, etc. Function-parallel optimization passes -// may read (but not modify) those fields. - -class OptimizingIncrementalModuleBuilder { - Module* wasm; - uint32_t numFunctions; - PassOptions passOptions; - std::function addPrePasses; - Function* endMarker; - std::atomic* list; - uint32_t nextFunction; // only used on main thread - uint32_t numWorkers; - std::vector> threads; - std::atomic liveWorkers, activeWorkers, availableFuncs, - finishedFuncs; - std::mutex mutex; - std::condition_variable condition; - bool finishing; - bool debug; - bool validateGlobally; - -public: - // numFunctions must be equal to the number of functions allocated, or higher. - // Knowing this bounds helps avoid locking. - OptimizingIncrementalModuleBuilder( - Module* wasm, - Index numFunctions, - PassOptions passOptions, - std::function addPrePasses, - bool debug, - bool validateGlobally) - : 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; - } - - // prepare work list - endMarker = new Function(); - list = new std::atomic[numFunctions]; - for (uint32_t i = 0; i < numFunctions; i++) { - list[i].store(endMarker); - } - // create workers - DEBUG_THREAD("creating workers"); - numWorkers = ThreadPool::getNumCores(); - assert(numWorkers >= 1); - // worth it to use threads - liveWorkers.store(0); - activeWorkers.store(0); - // TODO: one less, and add it at the very end, to not compete with main - // thread? - for (uint32_t i = 0; i < numWorkers; i++) { - createWorker(); - } - waitUntilAllReady(); - DEBUG_THREAD("workers are ready"); - // prepare the rest of the initial state - availableFuncs.store(0); - finishedFuncs.store(0); - } - - ~OptimizingIncrementalModuleBuilder() { - delete[] list; - delete endMarker; - } - - bool useWorkers() { - return numFunctions > 0 && !debug && ThreadPool::getNumCores() > 1 && - !PassRunner::getPassDebug(); - } - - // Add a function to the module, and to be optimized - void addFunction(Function* func) { - wasm->addFunction(func); - if (!useWorkers()) { - return; // we optimize at the end in that case - } - queueFunction(func); - // notify workers if needed - auto notify = availableFuncs.load(); - for (uint32_t i = 0; i < notify; i++) { - notifyWorker(); - } - } - - // All functions have been added, block until all are optimized, and then do - // global optimizations. When this returns, the module is ready and optimized. - void finish() { - if (!useWorkers()) { - // optimize each function now that we are done adding functions, - // then optimize globally - PassRunner passRunner(wasm, passOptions); - if (debug) { - passRunner.setDebug(true); - passRunner.setValidateGlobally(validateGlobally); - } - addPrePasses(passRunner); - passRunner.addDefaultFunctionOptimizationPasses(); - passRunner.run(); - } else { - DEBUG_THREAD("finish()ing"); - assert(nextFunction == numFunctions); - notifyAllWorkers(); - waitUntilAllFinished(); - } - // TODO: clear side thread allocators from module allocator, as these - // threads were transient - } - -private: - void createWorker() { - DEBUG_THREAD("create a worker"); - threads.emplace_back(std::make_unique(workerMain, this)); - } - - void notifyWorker() { - DEBUG_THREAD("notify a worker"); - std::lock_guard lock(mutex); - condition.notify_one(); - } - - void notifyAllWorkers() { - DEBUG_THREAD("notify all workers"); - std::lock_guard lock(mutex); - condition.notify_all(); - } - - void waitUntilAllReady() { - DEBUG_THREAD("wait until all workers are ready"); - std::unique_lock lock(mutex); - if (liveWorkers.load() < numWorkers) { - condition.wait(lock, - [this]() { return liveWorkers.load() == numWorkers; }); - } - } - - void waitUntilAllFinished() { - DEBUG_THREAD("wait until all workers are finished"); - { - std::unique_lock lock(mutex); - finishing = true; - if (liveWorkers.load() > 0) { - condition.wait(lock, [this]() { return liveWorkers.load() == 0; }); - } - } - DEBUG_THREAD("joining"); - for (auto& thread : threads) { - thread->join(); - } - DEBUG_THREAD("joined"); - } - - void queueFunction(Function* func) { - DEBUG_THREAD("queue function"); - // TODO: if we are given more than we expected, use a slower work queue? - assert(nextFunction < numFunctions); - list[nextFunction++].store(func); - availableFuncs++; - } - - void optimizeGlobally() { - PassRunner passRunner(wasm, passOptions); - passRunner.addDefaultGlobalOptimizationPostPasses(); - passRunner.run(); - } - - // worker code - - void optimizeFunction(Function* func) { - PassRunner passRunner(wasm, passOptions); - addPrePasses(passRunner); - passRunner.addDefaultFunctionOptimizationPasses(); - passRunner.runOnFunction(func); - } - - static void workerMain(OptimizingIncrementalModuleBuilder* self) { - DEBUG_THREAD("workerMain"); - { - std::lock_guard lock(self->mutex); - self->liveWorkers++; - self->activeWorkers++; - self->condition.notify_all(); - } - for (uint32_t i = 0; i < self->numFunctions; i++) { - DEBUG_THREAD("workerMain iteration " << i); - if (self->list[i].load() == self->endMarker) { - // sleep, this entry isn't ready yet - DEBUG_THREAD("workerMain sleep"); - self->activeWorkers--; - { - std::unique_lock lock(self->mutex); - // while waiting for the lock, things may have ended - if (!self->finishing) { - self->condition.wait(lock); - } - } - // continue - DEBUG_THREAD("workerMain continue"); - self->activeWorkers++; - i--; - continue; - } - DEBUG_THREAD("workerMain exchange item"); - auto* func = self->list[i].exchange(nullptr); - if (func == nullptr) { - DEBUG_THREAD("workerMain sees was already taken"); - continue; // someone else has taken this one - } - // we have work to do! - DEBUG_THREAD("workerMain work on " << size_t(func)); - self->availableFuncs--; - self->optimizeFunction(func); - self->finishedFuncs++; - } - DEBUG_THREAD("workerMain ready to exit"); - { - std::lock_guard lock(self->mutex); - self->liveWorkers--; - self->condition.notify_all(); - } - DEBUG_THREAD("workerMain exiting"); - } -}; - -} // namespace wasm - -#endif // wasm_wasm_module_building_h From 33abf08976264700e6ab5815a73537a5fb6b7dcf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 16 Oct 2024 09:51:50 -0700 Subject: [PATCH 076/622] [Wasm GC] Fuzz BrOn (#7006) --- src/tools/fuzzing.h | 1 + src/tools/fuzzing/fuzzing.cpp | 124 +++++++++++++++++- ...e-to-fuzz_all-features_metrics_noprint.txt | 80 ++++++----- 3 files changed, 158 insertions(+), 47 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index f4400e39ec9..92e99791341 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -366,6 +366,7 @@ class TranslateToFuzzReader { Expression* makeRefEq(Type type); Expression* makeRefTest(Type type); Expression* makeRefCast(Type type); + Expression* makeBrOn(Type type); // Decide to emit a signed Struct/ArrayGet sometimes, when the field is // packed. diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index abc6a63d530..d3f52d3b0ec 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1367,7 +1367,8 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { &Self::makeCallIndirect) .add(FeatureSet::ExceptionHandling, &Self::makeTry) .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef); + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn); } if (type.isSingle()) { options @@ -1454,10 +1455,11 @@ Expression* TranslateToFuzzReader::_makenone() { .add(FeatureSet::Atomics, &Self::makeAtomic) .add(FeatureSet::ExceptionHandling, &Self::makeTry) .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeStructSet) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeArraySet) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeStructSet) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArraySet) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArrayBulkMemoryOp); return (this->*pick(options))(Type::none); } @@ -1484,7 +1486,7 @@ Expression* TranslateToFuzzReader::_makeunreachable() { &Self::makeDrop, &Self::makeReturn) .add(FeatureSet::ExceptionHandling, &Self::makeThrow) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef); + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef); return (this->*pick(options))(Type::unreachable); } @@ -3944,6 +3946,116 @@ Expression* TranslateToFuzzReader::makeRefCast(Type type) { return builder.makeRefCast(make(refType), type); } +Expression* TranslateToFuzzReader::makeBrOn(Type type) { + if (funcContext->breakableStack.empty()) { + return makeTrivial(type); + } + // We need to find a proper target to break to; try a few times. Finding the + // target is harder than flowing out the proper type, so focus on the target, + // and fix up the flowing type later. That is, once we find a target to break + // to, we can then either drop ourselves or wrap ourselves in a block + + // another value, so that we return the proper thing here (which is done below + // in fixFlowingType). + int tries = TRIES; + Name targetName; + Type targetType; + while (--tries >= 0) { + auto* target = pick(funcContext->breakableStack); + targetName = getTargetName(target); + targetType = getTargetType(target); + // We can send any reference type, or no value at all, but nothing else. + if (targetType.isRef() || targetType == Type::none) { + break; + } + } + if (tries < 0) { + return makeTrivial(type); + } + + auto fixFlowingType = [&](Expression* brOn) -> Expression* { + if (Type::isSubType(brOn->type, type)) { + // Already of the proper type. + return brOn; + } + if (type == Type::none) { + // We just need to drop whatever it is. + return builder.makeDrop(brOn); + } + // We need to replace the type with something else. Drop the BrOn if we need + // to, and append a value with the proper type. + if (brOn->type != Type::none) { + brOn = builder.makeDrop(brOn); + } + return builder.makeSequence(brOn, make(type)); + }; + + // We found something to break to. Figure out which BrOn variants we can + // send. + if (targetType == Type::none) { + // BrOnNull is the only variant that sends no value. + return fixFlowingType( + builder.makeBrOn(BrOnNull, targetName, make(getReferenceType()))); + } + + // We are sending a reference type to the target. All other BrOn variants can + // do that. + assert(targetType.isRef()); + auto op = pick(BrOnNonNull, BrOnCast, BrOnCastFail); + Type castType = Type::none; + Type refType; + switch (op) { + case BrOnNonNull: { + // The sent type is the non-nullable version of the reference, so any ref + // of that type is ok, nullable or not. + refType = Type(targetType.getHeapType(), getNullability()); + break; + } + case BrOnCast: { + // The sent type is the heap type we cast to, with the input type's + // nullability, so the combination of the two must be a subtype of + // targetType. + castType = getSubType(targetType); + // The ref's type must be castable to castType, or we'd not validate. But + // it can also be a subtype, which will trivially also succeed (so do that + // more rarely). Pick subtypes rarely, as they make the cast trivial. + refType = oneIn(5) ? getSubType(castType) : getSuperType(castType); + if (targetType.isNonNullable()) { + // And it must have the right nullability for the target, as mentioned + // above: if the target type is non-nullable then either the ref or the + // cast types must be. + if (!refType.isNonNullable() && !castType.isNonNullable()) { + // Pick one to make non-nullable. + if (oneIn(2)) { + refType = Type(refType.getHeapType(), NonNullable); + } else { + castType = Type(castType.getHeapType(), NonNullable); + } + } + } + break; + } + case BrOnCastFail: { + // The sent type is the ref's type, with adjusted nullability (if the cast + // allows nulls then no null can fail the cast, and what is sent is non- + // nullable). First, pick a ref type that we can send to the target. + refType = getSubType(targetType); + // See above on BrOnCast, but flipped. + castType = oneIn(5) ? getSuperType(refType) : getSubType(refType); + // There is no nullability to adjust: if targetType is non-nullable then + // both refType and castType are as well, as subtypes of it. But we can + // also allow castType to be nullable (it is not sent to the target). + if (castType.isNonNullable() && oneIn(2)) { + castType = Type(castType.getHeapType(), Nullable); + } + } break; + default: { + WASM_UNREACHABLE("bad br_on op"); + } + } + return fixFlowingType( + builder.makeBrOn(op, targetName, make(refType), castType)); +} + bool TranslateToFuzzReader::maybeSignedGet(const Field& field) { if (field.isPacked()) { return oneIn(2); diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index e58a508309c..3cc84161097 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,53 +1,51 @@ Metrics total - [exports] : 3 - [funcs] : 5 + [exports] : 4 + [funcs] : 7 [globals] : 26 [imports] : 5 [memories] : 1 [memory-data] : 20 - [table-data] : 0 + [table-data] : 2 [tables] : 1 [tags] : 2 - [total] : 499 - [vars] : 20 + [total] : 510 + [vars] : 15 ArrayNew : 14 ArrayNewFixed : 2 - AtomicCmpxchg : 1 - AtomicNotify : 1 - AtomicRMW : 1 - Binary : 69 - Block : 42 - Break : 8 - Call : 6 - Const : 126 - Drop : 2 - GlobalGet : 27 + AtomicFence : 1 + Binary : 64 + Block : 45 + Break : 2 + Call : 8 + CallRef : 2 + Const : 127 + Drop : 5 + GlobalGet : 28 GlobalSet : 16 - I31Get : 1 - If : 10 + If : 12 Load : 18 - LocalGet : 43 - LocalSet : 22 - Loop : 5 - Nop : 3 - Pop : 3 - RefAs : 2 - RefFunc : 2 - RefI31 : 1 - RefNull : 8 - RefTest : 1 - Return : 1 - Select : 1 - Store : 3 - StringConst : 9 - StringEncode : 1 - StringEq : 3 - StructNew : 12 - StructSet : 1 - Try : 3 - TryTable : 2 - TupleExtract : 1 - TupleMake : 4 - Unary : 13 - Unreachable : 11 + LocalGet : 46 + LocalSet : 29 + Loop : 3 + MemoryCopy : 1 + MemoryInit : 1 + Nop : 4 + Pop : 2 + RefCast : 1 + RefFunc : 7 + RefNull : 6 + Return : 9 + SIMDExtract : 1 + Store : 1 + StringConst : 7 + StringEq : 1 + StringMeasure : 1 + StringSliceWTF : 1 + StructNew : 14 + Try : 2 + TryTable : 1 + TupleExtract : 3 + TupleMake : 6 + Unary : 9 + Unreachable : 10 From d13c262dc8dc2239e6ed267eb3c0567eac378568 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 16 Oct 2024 11:08:53 -0700 Subject: [PATCH 077/622] [NFC] Add validation checks in OptUtils::optimizeAfterInlining (#7009) This can help find errors in the middle of passes like Inlining, that do multiple cycles and include optimizations in the middle. We do this in BINARYEN_PASS_DEBUG >= 2 to avoid slowing down the timing reports in 1. --- src/passes/opt-utils.h | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/passes/opt-utils.h b/src/passes/opt-utils.h index 69552f7178a..69f055fd37e 100644 --- a/src/passes/opt-utils.h +++ b/src/passes/opt-utils.h @@ -20,11 +20,12 @@ #include #include -#include -#include -#include -#include -#include +#include "ir/element-utils.h" +#include "ir/module-utils.h" +#include "pass.h" +#include "passes/pass-utils.h" +#include "wasm-validator.h" +#include "wasm.h" namespace wasm::OptUtils { @@ -42,10 +43,24 @@ inline void addUsefulPassesAfterInlining(PassRunner& runner) { inline void optimizeAfterInlining(const PassUtils::FuncSet& funcs, Module* module, PassRunner* parentRunner) { + // In pass-debug mode, validate before and after these optimizations. This + // helps catch bugs in the middle of passes like inlining and dae. We do this + // at level 2+ and not 1 so that this extra validation is not added to the + // timings that level 1 reports. + if (PassRunner::getPassDebug() >= 2) { + if (!WasmValidator().validate(*module, parentRunner->options)) { + Fatal() << "invalid wasm before optimizeAfterInlining"; + } + } PassUtils::FilteredPassRunner runner(module, funcs, parentRunner->options); runner.setIsNested(true); addUsefulPassesAfterInlining(runner); runner.run(); + if (PassRunner::getPassDebug() >= 2) { + if (!WasmValidator().validate(*module, parentRunner->options)) { + Fatal() << "invalid wasm after optimizeAfterInlining"; + } + } } struct FunctionRefReplacer From fd86eadb591576b82b0b564f95952b131979e91a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 16 Oct 2024 15:16:08 -0700 Subject: [PATCH 078/622] [EH][GC] Add missing subtyping constraints from TryTable (#7012) Similar to Break, BrOn, etc., we must apply subtyping constraints of the types we send to blocks, so that Unsubtyping will not remove subtypings that are actually needed. --- src/ir/subtype-exprs.h | 8 ++++++- test/lit/passes/unsubtyping.wast | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 640240df932..1895c856ae1 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -249,7 +249,13 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(body, curr); } } - void visitTryTable(TryTable* curr) { self()->noteSubtype(curr->body, curr); } + void visitTryTable(TryTable* curr) { + self()->noteSubtype(curr->body, curr); + for (Index i = 0; i < curr->catchTags.size(); i++) { + self()->noteSubtype(curr->sentTypes[i], + self()->findBreakTarget(curr->catchDests[i])); + } + } void visitThrow(Throw* curr) { Type params = self()->getModule()->getTag(curr->tag)->sig.params; assert(params.size() == curr->operands.size()); diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index aa4af720bf3..97a2dd59ab8 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -1779,3 +1779,39 @@ ) ) ) + +;; try_table +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (func))) + (type $super (sub (func))) + ;; CHECK: (type $sub (sub $super (func))) + (type $sub (sub $super (func))) + ) + + ;; CHECK: (type $2 (func (result (ref $super)))) + + ;; CHECK: (type $3 (func (param (ref $sub)))) + + ;; CHECK: (type $4 (func (param (ref $sub)))) + + ;; CHECK: (tag $tag (param (ref $sub))) + (tag $tag (param (ref $sub))) + + ;; CHECK: (func $test (type $2) (result (ref $super)) + ;; CHECK-NEXT: (block $label (result (ref $sub)) + ;; CHECK-NEXT: (try_table (catch $tag $label) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result (ref $super)) + (block $label (result (ref $super)) + ;; Sending the contents of $tag to $label cause us to require $sub <: $super + (try_table (catch $tag $label) + (unreachable) + ) + ) + ) +) From 6566329fecd36c8cd8e2ab29f89dafde0d2e9304 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 17 Oct 2024 12:49:43 -0700 Subject: [PATCH 079/622] [EH][GC] Send a non-nullable exnref from TryTable (#7013) When EH+GC are enabled then wasm has non-nullable types, and the sent exnref should be non-nullable. In BinaryenIR we use the non- nullable type all the time, which we also do for function references and other things; we lower it if GC is not enabled to a nullable type for the binary format (see `WasmBinaryWriter::writeType`, to which comments were added in this PR). That is, this PR makes us handle exnref the same as those other types. A new test verifies that behavior. Various existing tests are updated because ReFinalize will now use the more refined type, so this is an optimization. It is also a bugfix as in #6987 we started to emit the refined form in the fuzzer, and this PR makes us handle it properly in validation and ReFinalization. --- README.md | 10 ++ src/wasm/wasm-binary.cpp | 8 +- src/wasm/wasm-validator.cpp | 2 +- src/wasm/wasm.cpp | 6 +- test/lit/basic/exception-handling-no-gc.wast | 27 ++++ test/lit/passes/merge-blocks-eh.wast | 2 +- test/lit/passes/translate-to-exnref.wast | 140 +++++++++---------- 7 files changed, 119 insertions(+), 76 deletions(-) create mode 100644 test/lit/basic/exception-handling-no-gc.wast diff --git a/README.md b/README.md index 3eaafb7f72a..03f7cd13de7 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,16 @@ There are a few differences between Binaryen IR and the WebAssembly language: much about this when writing Binaryen passes. For more details see the `requiresNonNullableLocalFixups()` hook in `pass.h` and the `LocalStructuralDominance` class. + * Binaryen IR uses the most refined types possible for references, + specifically: + * The IR type of a `ref.func` is always a specific function type, and not + plain `funcref`. It is also non-nullable. + * Non-nullable types are also used for the type that `try_table` sends + on branches (if we branch, a null is never sent), that is, it sends + (ref exn) and not (ref null exn). + In both cases if GC is not enabled then we emit the less-refined type in the + binary. When reading a binary, the more refined types will be applied as we + build the IR. * `br_if` output types are more refined in Binaryen IR: they have the type of the value, when a value flows in. In the wasm spec the type is that of the branch target, which may be less refined. Using the more refined type here diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 920baf9440f..bc7ec8ac898 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1544,9 +1544,9 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) { void WasmBinaryWriter::writeType(Type type) { if (type.isRef()) { - // The only reference types allowed without GC are funcref and externref. We - // internally use more refined versions of those types, but we cannot emit - // those more refined types. + // The only reference types allowed without GC are funcref, externref, and + // exnref. We internally use more refined versions of those types, but we + // cannot emit those without GC. if (!wasm->features.hasGC()) { auto ht = type.getHeapType(); if (ht.isMaybeShared(HeapType::string)) { @@ -1555,6 +1555,8 @@ void WasmBinaryWriter::writeType(Type type) { // string, the stringref feature must be enabled. type = Type(HeapTypes::string.getBasic(ht.getShared()), Nullable); } else { + // Only the top type (func, extern, exn) is available, and only the + // nullable version. type = Type(type.getHeapType().getTop(), Nullable); } } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a86187fa788..0184e3284c8 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2658,7 +2658,7 @@ void FunctionValidator::visitTryTable(TryTable* curr) { "the number of catch tags and sent types do not match"); const char* invalidSentTypeMsg = "invalid catch sent type information"; - Type exnref = Type(HeapType::exn, Nullable); + Type exnref = Type(HeapType::exn, NonNullable); for (Index i = 0; i < curr->catchTags.size(); i++) { auto sentType = curr->sentTypes[i]; size_t tagTypeSize; diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 0245fc01ebe..4150af5b41a 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -933,7 +933,11 @@ static void populateTryTableSentTypes(TryTable* curr, Module* wasm) { return; } curr->sentTypes.clear(); - Type exnref = Type(HeapType::exn, Nullable); + // We always use the refined non-nullable type in our IR, which is what the + // wasm spec defines when GC is enabled (=== non-nullable types are allowed). + // If GC is not enabled then we emit a nullable type in the binary format in + // WasmBinaryWriter::writeType. + Type exnref = Type(HeapType::exn, NonNullable); for (Index i = 0; i < curr->catchTags.size(); i++) { auto tagName = curr->catchTags[i]; std::vector sentType; diff --git a/test/lit/basic/exception-handling-no-gc.wast b/test/lit/basic/exception-handling-no-gc.wast new file mode 100644 index 00000000000..4ab0708c4c2 --- /dev/null +++ b/test/lit/basic/exception-handling-no-gc.wast @@ -0,0 +1,27 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Test that we do not emit an invalid (ref exn) when exceptions are enabled +;; but not GC. GC is required for us to be non-nullable. + +;; RUN: wasm-opt %s --enable-reference-types --enable-exception-handling --disable-gc --roundtrip -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (result exnref) + ;; CHECK-NEXT: (block $label$1 (result exnref) + ;; CHECK-NEXT: (try_table (catch_all_ref $label$1) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result exnref) + ;; It is valid to write (ref exn) in Binaryen IR, and internally that is how + ;; we represent things, but when we emit the binary we emit a nullable type, + ;; so after the roundtrip we are less refined. + (block $label (result (ref exn)) + (try_table (catch_all_ref $label) + (unreachable) + ) + ) + ) +) + diff --git a/test/lit/passes/merge-blocks-eh.wast b/test/lit/passes/merge-blocks-eh.wast index e53f601fd97..e56f536f4b6 100644 --- a/test/lit/passes/merge-blocks-eh.wast +++ b/test/lit/passes/merge-blocks-eh.wast @@ -100,7 +100,7 @@ ;; CHECK: (func $drop-block-try_catch_multi_partial (type $0) ;; CHECK-NEXT: (tuple.drop 2 - ;; CHECK-NEXT: (block $outer (type $1) (result i32 exnref) + ;; CHECK-NEXT: (block $outer (type $2) (result i32 (ref exn)) ;; CHECK-NEXT: (block $inner ;; CHECK-NEXT: (try_table (catch_ref $i32 $outer) (catch_all $inner) ;; CHECK-NEXT: (call $import) diff --git a/test/lit/passes/translate-to-exnref.wast b/test/lit/passes/translate-to-exnref.wast index abba6b06ef0..8bac0dc39fa 100644 --- a/test/lit/passes/translate-to-exnref.wast +++ b/test/lit/passes/translate-to-exnref.wast @@ -10,9 +10,9 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (type $3 (func (result i32 exnref))) + ;; CHECK: (type $3 (func (result i32 (ref exn)))) - ;; CHECK: (type $4 (func (result i32 i64 exnref))) + ;; CHECK: (type $4 (func (result i32 i64 (ref exn)))) ;; CHECK: (type $5 (func (param i32))) @@ -25,9 +25,9 @@ ;; STACKIR-OPT: (type $2 (func (result i32))) - ;; STACKIR-OPT: (type $3 (func (result i32 exnref))) + ;; STACKIR-OPT: (type $3 (func (result i32 (ref exn)))) - ;; STACKIR-OPT: (type $4 (func (result i32 i64 exnref))) + ;; STACKIR-OPT: (type $4 (func (result i32 i64 (ref exn)))) ;; STACKIR-OPT: (type $5 (func (param i32))) @@ -115,9 +115,9 @@ ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (block $outer0 ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch1 (result exnref) + ;; CHECK-NEXT: (block $catch1 (result (ref exn)) ;; CHECK-NEXT: (try_table (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) @@ -137,8 +137,8 @@ ;; STACKIR-OPT: (func $try-none-tag-none-with-rethrow (type $1) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: block $outer0 - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (result exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (result (ref exn)) ;; STACKIR-OPT-NEXT: try_table (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: end @@ -220,12 +220,12 @@ ;; CHECK: (func $try-none-tag-single-with-rethrow (type $1) ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (local $2 (tuple i32 exnref)) + ;; CHECK-NEXT: (local $2 (tuple i32 (ref exn))) ;; CHECK-NEXT: (block $outer0 ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 exnref) + ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 (ref exn)) ;; CHECK-NEXT: (try_table (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) @@ -260,10 +260,10 @@ ;; STACKIR-OPT: (func $try-none-tag-single-with-rethrow (type $1) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: (local $1 i32) - ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 exnref)) + ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 (ref exn))) ;; STACKIR-OPT-NEXT: block $outer0 - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (type $3) (result i32 exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (type $3) (result i32 (ref exn)) ;; STACKIR-OPT-NEXT: try_table (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: end @@ -358,12 +358,12 @@ ;; CHECK: (func $try-none-tag-tuple-with-rethrow (type $1) ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (local $1 (tuple i32 i64)) - ;; CHECK-NEXT: (local $2 (tuple i32 i64 exnref)) + ;; CHECK-NEXT: (local $2 (tuple i32 i64 (ref exn))) ;; CHECK-NEXT: (block $outer0 ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 exnref) + ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 (ref exn)) ;; CHECK-NEXT: (try_table (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) @@ -403,10 +403,10 @@ ;; STACKIR-OPT: (func $try-none-tag-tuple-with-rethrow (type $1) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: (local $1 (tuple i32 i64)) - ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 i64 exnref)) + ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 i64 (ref exn))) ;; STACKIR-OPT-NEXT: block $outer0 - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (type $4) (result i32 i64 exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (type $4) (result i32 i64 (ref exn)) ;; STACKIR-OPT-NEXT: try_table (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: end @@ -500,9 +500,9 @@ ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (block $outer0 (result i32) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch1 (result exnref) + ;; CHECK-NEXT: (block $catch1 (result (ref exn)) ;; CHECK-NEXT: (br $outer0 ;; CHECK-NEXT: (try_table (result i32) (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) @@ -524,8 +524,8 @@ ;; STACKIR-OPT: (func $try-single-tag-none-with-rethrow (type $2) (result i32) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: block $outer0 (result i32) - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (result exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (result (ref exn)) ;; STACKIR-OPT-NEXT: try_table (result i32) (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: i32.const 0 @@ -609,12 +609,12 @@ ;; CHECK: (func $try-single-tag-single-with-rethrow (type $2) (result i32) ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (local $2 (tuple i32 exnref)) + ;; CHECK-NEXT: (local $2 (tuple i32 (ref exn))) ;; CHECK-NEXT: (block $outer0 (result i32) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 exnref) + ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 (ref exn)) ;; CHECK-NEXT: (br $outer0 ;; CHECK-NEXT: (try_table (result i32) (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) @@ -653,10 +653,10 @@ ;; STACKIR-OPT: (func $try-single-tag-single-with-rethrow (type $2) (result i32) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: (local $1 i32) - ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 exnref)) + ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 (ref exn))) ;; STACKIR-OPT-NEXT: block $outer0 (result i32) - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (type $3) (result i32 exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (type $3) (result i32 (ref exn)) ;; STACKIR-OPT-NEXT: try_table (result i32) (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: i32.const 0 @@ -764,12 +764,12 @@ ;; CHECK: (func $try-single-tag-tuple-with-rethrow (type $2) (result i32) ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (local $1 (tuple i32 i64)) - ;; CHECK-NEXT: (local $2 (tuple i32 i64 exnref)) + ;; CHECK-NEXT: (local $2 (tuple i32 i64 (ref exn))) ;; CHECK-NEXT: (block $outer0 (result i32) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 exnref) + ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 (ref exn)) ;; CHECK-NEXT: (br $outer0 ;; CHECK-NEXT: (try_table (result i32) (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) @@ -813,10 +813,10 @@ ;; STACKIR-OPT: (func $try-single-tag-tuple-with-rethrow (type $2) (result i32) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: (local $1 (tuple i32 i64)) - ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 i64 exnref)) + ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 i64 (ref exn))) ;; STACKIR-OPT-NEXT: block $outer0 (result i32) - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (type $4) (result i32 i64 exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (type $4) (result i32 i64 (ref exn)) ;; STACKIR-OPT-NEXT: try_table (result i32) (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: i32.const 0 @@ -937,9 +937,9 @@ ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch1 (result exnref) + ;; CHECK-NEXT: (block $catch1 (result (ref exn)) ;; CHECK-NEXT: (br $outer0 ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) @@ -964,8 +964,8 @@ ;; STACKIR-OPT: (func $try-tuple-tag-none-with-rethrow (type $0) (result i32 i64) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: block $outer0 (type $0) (result i32 i64) - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (result exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (result (ref exn)) ;; STACKIR-OPT-NEXT: try_table (type $0) (result i32 i64) (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: i32.const 0 @@ -1078,12 +1078,12 @@ ;; CHECK: (func $try-tuple-tag-single-with-rethrow (type $0) (result i32 i64) ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (local $2 (tuple i32 exnref)) + ;; CHECK-NEXT: (local $2 (tuple i32 (ref exn))) ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 exnref) + ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 (ref exn)) ;; CHECK-NEXT: (br $outer0 ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) @@ -1125,10 +1125,10 @@ ;; STACKIR-OPT: (func $try-tuple-tag-single-with-rethrow (type $0) (result i32 i64) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: (local $1 i32) - ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 exnref)) + ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 (ref exn))) ;; STACKIR-OPT-NEXT: block $outer0 (type $0) (result i32 i64) - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (type $3) (result i32 exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (type $3) (result i32 (ref exn)) ;; STACKIR-OPT-NEXT: try_table (type $0) (result i32 i64) (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: i32.const 0 @@ -1247,12 +1247,12 @@ ;; CHECK: (func $try-tuple-tag-tuple-with-rethrow (type $0) (result i32 i64) ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (local $1 (tuple i32 i64)) - ;; CHECK-NEXT: (local $2 (tuple i32 i64 exnref)) + ;; CHECK-NEXT: (local $2 (tuple i32 i64 (ref exn))) ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all2 (result exnref) + ;; CHECK-NEXT: (block $catch_all2 (result (ref exn)) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 exnref) + ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 (ref exn)) ;; CHECK-NEXT: (br $outer0 ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2) ;; CHECK-NEXT: (call $foo) @@ -1299,10 +1299,10 @@ ;; STACKIR-OPT: (func $try-tuple-tag-tuple-with-rethrow (type $0) (result i32 i64) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: (local $1 (tuple i32 i64)) - ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 i64 exnref)) + ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 i64 (ref exn))) ;; STACKIR-OPT-NEXT: block $outer0 (type $0) (result i32 i64) - ;; STACKIR-OPT-NEXT: block $catch_all2 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (type $4) (result i32 i64 exnref) + ;; STACKIR-OPT-NEXT: block $catch_all2 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (type $4) (result i32 i64 (ref exn)) ;; STACKIR-OPT-NEXT: try_table (type $0) (result i32 i64) (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: i32.const 0 @@ -1375,17 +1375,17 @@ ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 (tuple i32 i64)) - ;; CHECK-NEXT: (local $3 (tuple i32 exnref)) - ;; CHECK-NEXT: (local $4 (tuple i32 i64 exnref)) + ;; CHECK-NEXT: (local $3 (tuple i32 (ref exn))) + ;; CHECK-NEXT: (local $4 (tuple i32 i64 (ref exn))) ;; CHECK-NEXT: (block $outer0 ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all4 (result exnref) + ;; CHECK-NEXT: (block $catch_all4 (result (ref exn)) ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (block $catch3 (type $4) (result i32 i64 exnref) + ;; CHECK-NEXT: (block $catch3 (type $4) (result i32 i64 (ref exn)) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (block $catch2 (type $3) (result i32 exnref) + ;; CHECK-NEXT: (block $catch2 (type $3) (result i32 (ref exn)) ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch1 (result exnref) + ;; CHECK-NEXT: (block $catch1 (result (ref exn)) ;; CHECK-NEXT: (try_table (catch_ref $e-empty $catch1) (catch_ref $e-i32 $catch2) (catch_ref $e-i32-i64 $catch3) (catch_all_ref $catch_all4) ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) @@ -1451,13 +1451,13 @@ ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: (local $1 i32) ;; STACKIR-OPT-NEXT: (local $2 (tuple i32 i64)) - ;; STACKIR-OPT-NEXT: (local $3 (tuple i32 exnref)) - ;; STACKIR-OPT-NEXT: (local $4 (tuple i32 i64 exnref)) + ;; STACKIR-OPT-NEXT: (local $3 (tuple i32 (ref exn))) + ;; STACKIR-OPT-NEXT: (local $4 (tuple i32 i64 (ref exn))) ;; STACKIR-OPT-NEXT: block $outer0 - ;; STACKIR-OPT-NEXT: block $catch_all4 (result exnref) - ;; STACKIR-OPT-NEXT: block $catch3 (type $4) (result i32 i64 exnref) - ;; STACKIR-OPT-NEXT: block $catch2 (type $3) (result i32 exnref) - ;; STACKIR-OPT-NEXT: block $catch1 (result exnref) + ;; STACKIR-OPT-NEXT: block $catch_all4 (result (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch3 (type $4) (result i32 i64 (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch2 (type $3) (result i32 (ref exn)) + ;; STACKIR-OPT-NEXT: block $catch1 (result (ref exn)) ;; STACKIR-OPT-NEXT: try_table (catch_ref $e-empty $catch1) (catch_ref $e-i32 $catch2) (catch_ref $e-i32-i64 $catch3) (catch_all_ref $catch_all4) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: end @@ -1522,7 +1522,7 @@ ;; CHECK-NEXT: (local $1 exnref) ;; CHECK-NEXT: (block $outer3 ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch_all4 (result exnref) + ;; CHECK-NEXT: (block $catch_all4 (result (ref exn)) ;; CHECK-NEXT: (try_table (catch_all_ref $catch_all4) ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) @@ -1531,7 +1531,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block $outer0 ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (block $catch2 (result exnref) + ;; CHECK-NEXT: (block $catch2 (result (ref exn)) ;; CHECK-NEXT: (block $catch1 ;; CHECK-NEXT: (try_table (catch $e-empty $catch1) (catch_ref $e-empty $catch2) ;; CHECK-NEXT: (call $foo) @@ -1553,7 +1553,7 @@ ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: (local $1 exnref) ;; STACKIR-OPT-NEXT: block $outer3 - ;; STACKIR-OPT-NEXT: block $catch_all4 (result exnref) + ;; STACKIR-OPT-NEXT: block $catch_all4 (result (ref exn)) ;; STACKIR-OPT-NEXT: try_table (catch_all_ref $catch_all4) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: end @@ -1561,7 +1561,7 @@ ;; STACKIR-OPT-NEXT: end ;; STACKIR-OPT-NEXT: local.set $0 ;; STACKIR-OPT-NEXT: block $outer0 - ;; STACKIR-OPT-NEXT: block $catch2 (result exnref) + ;; STACKIR-OPT-NEXT: block $catch2 (result (ref exn)) ;; STACKIR-OPT-NEXT: block $catch1 ;; STACKIR-OPT-NEXT: try_table (catch $e-empty $catch1) (catch_ref $e-empty $catch2) ;; STACKIR-OPT-NEXT: call $foo @@ -2216,7 +2216,7 @@ ;; CHECK-NEXT: (local $0 exnref) ;; CHECK-NEXT: (block $outer2 ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $catch3 (result exnref) + ;; CHECK-NEXT: (block $catch3 (result (ref exn)) ;; CHECK-NEXT: (try_table (catch_ref $e-empty $catch3) ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) @@ -2245,7 +2245,7 @@ ;; STACKIR-OPT: (func $try-catch-rethrow-with-inner-delegate (type $1) ;; STACKIR-OPT-NEXT: (local $0 exnref) ;; STACKIR-OPT-NEXT: block $outer2 - ;; STACKIR-OPT-NEXT: block $catch3 (result exnref) + ;; STACKIR-OPT-NEXT: block $catch3 (result (ref exn)) ;; STACKIR-OPT-NEXT: try_table (catch_ref $e-empty $catch3) ;; STACKIR-OPT-NEXT: call $foo ;; STACKIR-OPT-NEXT: end From 245d9156d8bd52ce9bc50c554aa3a9cd42cd0e6b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 17 Oct 2024 14:27:55 -0700 Subject: [PATCH 080/622] [GC] Ignore public types in GlobalTypeOptimization (#7017) TypeUpdater which it uses internally already does so, but we must also ignore such types earlier, and make no other modifications to them. Helps #7015 --- src/passes/GlobalTypeOptimization.cpp | 19 +++++- test/lit/passes/gto-removals.wast | 90 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index dac6fd7b659..1f0da2595ec 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -23,6 +23,7 @@ // #include "ir/localize.h" +#include "ir/module-utils.h" #include "ir/ordering.h" #include "ir/struct-utils.h" #include "ir/subtypes.h" @@ -167,13 +168,18 @@ struct GlobalTypeOptimization : public Pass { auto dataFromSupersMap = std::move(combinedSetGetInfos); propagator.propagateToSubTypes(dataFromSupersMap); + // Find the public types, which we must not modify. + auto publicTypes = ModuleUtils::getPublicHeapTypes(*module); + std::unordered_set publicTypesSet(publicTypes.begin(), + publicTypes.end()); + // Process the propagated info. We look at supertypes first, as the order of // fields in a supertype is a constraint on what subtypes can do. That is, // we decide for each supertype what the optimal order is, and consider that // fixed, and then subtypes can decide how to sort fields that they append. for (auto type : HeapTypeOrdering::supertypesFirst(propagator.subTypes.types)) { - if (!type.isStruct()) { + if (!type.isStruct() || publicTypesSet.count(type)) { continue; } auto& fields = type.getStruct().fields; @@ -291,8 +297,15 @@ struct GlobalTypeOptimization : public Pass { keptFieldsNotInSuper.push_back(i); } } else { - // The super kept this field, so we must keep it as well. - assert(!removableIndexes.count(i)); + // The super kept this field, so we must keep it as well. The + // propagation analysis above ensures that we and the super are in + // agreement on keeping it (the reasons that prevent optimization + // propagate to both), except for the corner case of the parent + // being public but us being private (the propagation does not + // take into account visibility). + assert( + !removableIndexes.count(i) || + (publicTypesSet.count(*super) && !publicTypesSet.count(type))); // We need to keep it at the same index so we remain compatible. indexesAfterRemoval[i] = superIndex; // Update |next| to refer to the next available index. Due to diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index c2ac66a5793..61396cf8f7a 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -1405,3 +1405,93 @@ ) ) ) + +;; Public types cannot be optimized. The function type here is public as the +;; function is exported, and so the entire rec group is public, and cannot be +;; modified. We cannot even optimize $child3 which is outside of the rec group, +;; because its parent is inside. However, we can optimize $unrelated which is +;; unrelated to them (and so we can remove the field there). +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $parent (sub (struct (field (ref func))))) + (type $parent (sub (struct (field (ref func))))) + ;; CHECK: (type $child1 (sub $parent (struct (field (ref func))))) + (type $child1 (sub $parent (struct (field (ref func))))) + ;; CHECK: (type $child2 (sub $parent (struct (field (ref func))))) + (type $child2 (sub $parent (struct (field (ref func))))) + + ;; CHECK: (type $func (func (param (ref $child2)))) + (type $func (func (param $child2 (ref $child2)))) + ) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $unrelated (sub (struct))) + + ;; CHECK: (type $child3 (sub $parent (struct (field (ref func))))) + (type $child3 (sub $parent (struct (field (ref func))))) + + (type $unrelated (sub (struct (field (ref func))))) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (export "func" (func $func)) + + ;; CHECK: (func $func (type $func) (param $child2 (ref $child2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $parent + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $child1 + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $child2 + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $child3 + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $unrelated) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func (export "func") (type $func) (param $child2 (ref $child2)) + ;; Create all the types. Note that the value here is non-nullable, as is the + ;; field, so if we remove the field by mistake in GTO but leave it during + ;; TypeUpdater, we'd error (on providing a default value for a non-nullable + ;; field). + (drop + (struct.new $parent + (ref.func $func) + ) + ) + (drop + (struct.new $child1 + (ref.func $func) + ) + ) + (drop + (struct.new $child2 + (ref.func $func) + ) + ) + (drop + (struct.new $child3 + (ref.func $func) + ) + ) + ;; We can optimize this one, and no other. + (drop + (struct.new $unrelated + (ref.func $func) + ) + ) + ) +) From 795cf2895f713e8b05924e2a1e24a4d2de3ebcf5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 18 Oct 2024 09:17:50 -0700 Subject: [PATCH 081/622] [EH] Add TryTable to StripEH (#7020) --- src/passes/StripEH.cpp | 5 +++++ test/lit/passes/strip-eh.wast | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 test/lit/passes/strip-eh.wast diff --git a/src/passes/StripEH.cpp b/src/passes/StripEH.cpp index d2a68d6305d..95d86274542 100644 --- a/src/passes/StripEH.cpp +++ b/src/passes/StripEH.cpp @@ -51,6 +51,11 @@ struct StripEHImpl : public WalkerPass> { refinalize = true; } + void visitTryTable(TryTable* curr) { + replaceCurrent(curr->body); + refinalize = true; + } + void visitFunction(Function* curr) { if (refinalize) { ReFinalize().walkFunctionInModule(curr, getModule()); diff --git a/test/lit/passes/strip-eh.wast b/test/lit/passes/strip-eh.wast new file mode 100644 index 00000000000..41c432088b1 --- /dev/null +++ b/test/lit/passes/strip-eh.wast @@ -0,0 +1,35 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s -all --strip-eh -S -o - | filecheck %s + +;; Remove all EH instructions and tags. Converts 'throw's into 'unreachable's. + +(module + (tag $e-i32 (param i32)) + (tag $e-f32 (param f32)) + + ;; CHECK: (func $throw-i32 (type $0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $throw-i32 + (throw $e-i32 (i32.const 0)) + ) + ;; CHECK: (func $throw-f32 (type $0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $throw-f32 + (throw $e-f32 (f32.const 0.0)) + ) + + ;; CHECK: (func $try-catch (type $0) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (call $throw-f32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-catch + (block $out + (try_table (catch_all $out) + (call $throw-f32) + ) + ) + ) +) From 0b882b9e60f05db9e56f0c35cfbb38eb87619a76 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 18 Oct 2024 10:32:34 -0700 Subject: [PATCH 082/622] [GC] Ignore public types in SignatureRefining (#7022) Similar to #7017 and #7018 --- src/passes/SignatureRefining.cpp | 17 ++++------- test/lit/passes/signature-refining.wast | 40 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp index ae124dc79a0..db71c167a72 100644 --- a/src/passes/SignatureRefining.cpp +++ b/src/passes/SignatureRefining.cpp @@ -147,18 +147,11 @@ struct SignatureRefining : public Pass { } } - // We cannot alter the signature of an exported function, as the outside may - // notice us doing so. For example, if we turn a parameter from nullable - // into non-nullable then callers sending a null will break. Put another - // way, we need to see all callers to refine types, and for exports we - // cannot do so. - // TODO If a function type is passed we should also mark the types used - // there, etc., recursively. For now this code just handles the top- - // level type, which is enough to keep the fuzzer from erroring. More - // generally, we need to decide about adding a "closed-world" flag of - // some kind. - for (auto* exportedFunc : ExportUtils::getExportedFunctions(*module)) { - allInfo[exportedFunc->type].canModify = false; + // Find the public types, which we must not modify. + for (auto type : ModuleUtils::getPublicHeapTypes(*module)) { + if (type.isFunction()) { + allInfo[type].canModify = false; + } } // For now, do not optimize types that have subtypes. When we modify such a diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast index 7a5020e29e0..f6f88940847 100644 --- a/test/lit/passes/signature-refining.wast +++ b/test/lit/passes/signature-refining.wast @@ -1119,3 +1119,43 @@ ;; (see tests above for how we handle refining of return values). ) ) + +;; Visibility: The type we'd like to refine, $sig, is in a rec group with a +;; public type, so do not optimize. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $sig (sub (func (param anyref)))) + (type $sig (sub (func (param anyref)))) + + ;; CHECK: (type $struct (struct)) + (type $struct (struct)) + ) + + ;; Export a global with $struct to make it public. + ;; CHECK: (type $2 (func)) + + ;; CHECK: (global $struct (ref $struct) (struct.new_default $struct)) + (global $struct (ref $struct) (struct.new $struct)) + + ;; CHECK: (export "struct" (global $struct)) + (export "struct" (global $struct)) + + ;; CHECK: (func $func (type $sig) (param $x anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $2) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + ) +) + From ca3302b08c3f8e6b83e29b5582f6c3284e803df0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 18 Oct 2024 11:44:49 -0700 Subject: [PATCH 083/622] [GC] Ignore public types in SignaturePruning (#7018) Similar to #7017 . As with that PR, this reduces some optimizations that were valid, as we tried to do something complex here and refine types in a public rec group when it seemed safe to do so, but our analysis was incomplete. The testcase here shows how another operation can end up causing a dependency that breaks things, if another type that uses one that we modify is public. To be safe, ignore all public types. In the future perhaps we can find a good way to handle "almost-private" types in public rec groups, in closed world. --- src/passes/SignaturePruning.cpp | 21 ++----- test/lit/passes/signature-pruning.wast | 86 +++++++++++++++++++++----- 2 files changed, 78 insertions(+), 29 deletions(-) diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp index a9fb4d23aa4..d5a62e29816 100644 --- a/src/passes/SignaturePruning.cpp +++ b/src/passes/SignaturePruning.cpp @@ -162,11 +162,10 @@ struct SignaturePruning : public Pass { sigFuncs[func->type].push_back(func); } - // Exported functions cannot be modified. - for (auto& exp : module->exports) { - if (exp->kind == ExternalKind::Function) { - auto* func = module->getFunction(exp->value); - allInfo[func->type].optimizable = false; + // Find the public types, which cannot be modified. + for (auto type : ModuleUtils::getPublicHeapTypes(*module)) { + if (type.isFunction()) { + allInfo[type].optimizable = false; } } @@ -291,16 +290,8 @@ struct SignaturePruning : public Pass { } } - // Rewrite the types. We pass in all the types we intend to modify as being - // "additional private types" because we have proven above that they are - // safe to modify, even if they are technically public (e.g. they may be in - // a singleton big rec group that is public because one member is public). - std::vector additionalPrivateTypes; - for (auto& [type, sig] : newSignatures) { - additionalPrivateTypes.push_back(type); - } - GlobalTypeRewriter::updateSignatures( - newSignatures, *module, additionalPrivateTypes); + // Rewrite the types. + GlobalTypeRewriter::updateSignatures(newSignatures, *module); if (callTargetsToLocalize.empty()) { return false; diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast index 57dd783622c..b5c5b26e28d 100644 --- a/test/lit/passes/signature-pruning.wast +++ b/test/lit/passes/signature-pruning.wast @@ -1154,24 +1154,22 @@ ;; $exported is exported. The entire rec group becomes exported as well, which ;; causes $unused-param's type to be public, which means we cannot normally -;; modify it. However, in closed world we allow such changes, and we can remove -;; the unused param there. What happens is that we keep the original public rec -;; group as-is, and add a new rec group for private types, put the pruned type -;; there, and use that pruned type on $unused-param. +;; modify it. However, in closed world we could allow such changes, by keeping +;; the original public rec group as-is, and add a new rec group for private +;; types, put the pruned type there, and use that pruned type on $unused-param. +;; That can work here, but not in the testcase after us. For now, we also do not +;; optimize here, as figuring out when it is safe is very difficult, and may +;; need a new design TODO (module (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $0 (func)) - - ;; CHECK: (type $much (func)) - ;; CHECK: (rec ;; CHECK-NEXT: (type $none (func)) (type $none (func)) + ;; CHECK: (type $much (func (param i32))) (type $much (func (param i32))) ) - ;; CHECK: (type $much_0 (func (param i32))) + ;; CHECK: (type $2 (func)) ;; CHECK: (export "exported" (func $exported)) @@ -1181,23 +1179,83 @@ (func $exported (export "exported") (type $none) ) - ;; CHECK: (func $unused-param (type $much) - ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.set $0 + ;; CHECK: (func $unused-param (type $much) (param $param i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $unused-param (type $much) (param $param i32) + ) + + ;; CHECK: (func $caller (type $2) + ;; CHECK-NEXT: (call $unused-param ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $unused-param + (i32.const 0) + ) + ) +) + +;; As the previous testcase, but add another use of the type we want to prune, +;; in a struct.new. The struct type is public, so we cannot modify it and +;; replace the reference to the function type with the pruned version. +(module + (rec + ;; CHECK: (type $0 (func)) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $none (func)) + (type $none (func)) + ;; CHECK: (type $much (func (param i32))) + (type $much (func (param i32))) + + ;; CHECK: (type $struct (struct (field (ref $much)))) + (type $struct (struct (field (ref $much)))) + ) + + ;; CHECK: (elem declare func $unused-param) + + ;; CHECK: (export "exported" (func $exported)) + + ;; CHECK: (func $exported (type $none) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $exported (export "exported") (type $none) + ;; This makes the rec group public. + ) + + ;; CHECK: (func $unused-param (type $much) (param $param i32) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $unused-param (type $much) (param $param i32) ) ;; CHECK: (func $caller (type $0) - ;; CHECK-NEXT: (call $unused-param) + ;; CHECK-NEXT: (call $unused-param + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $unused-param (i32.const 0) ) ) + + ;; CHECK: (func $struct.new (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (ref.func $unused-param) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct.new + ;; This struct.new causes the problem mentioned above. + (drop + (struct.new $struct + (ref.func $unused-param) + ) + ) + ) ) From 679c26faec1a714eb220033254f7147ec12b8282 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 18 Oct 2024 12:36:08 -0700 Subject: [PATCH 084/622] wasm-split: Add fuzzer support (#7014) The support is added but not enabled as this is still finding bugs. The first part here is to add Split testcase handler to the fuzzer, which runs a wasm, then runs it again after splitting it and then linking it at runtime, and checking for different results. The second part is support for linking two modules at runtime in the fuzzer's JS code, that works in tandem with the first part. New options are added to load and link a second wasm, and to pick which exports to run. --- scripts/fuzz_opt.py | 111 +++++++++++++++++++++++++++++++++++++++- scripts/fuzz_shell.js | 115 +++++++++++++++++++++++++++++------------- 2 files changed, 189 insertions(+), 37 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 1e29a84226a..838ed54cc81 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -743,8 +743,8 @@ def run_d8_js(js, args=[], liftoff=True): FUZZ_SHELL_JS = in_binaryen('scripts', 'fuzz_shell.js') -def run_d8_wasm(wasm, liftoff=True): - return run_d8_js(FUZZ_SHELL_JS, [wasm], liftoff=liftoff) +def run_d8_wasm(wasm, liftoff=True, args=[]): + return run_d8_js(FUZZ_SHELL_JS, [wasm] + args, liftoff=liftoff) def all_disallowed(features): @@ -1391,6 +1391,111 @@ def handle(self, wasm): compare_between_vms(output, merged_output, 'Merge') +FUNC_NAMES_REGEX = re.compile(r'\n [(]func [$](\S+)') + + +# Tests wasm-split +class Split(TestCaseHandler): + frequency = 1 # TODO: adjust lower when we actually enable this + + def handle(self, wasm): + # get the list of function names, some of which we will decide to split + # out + wat = run([in_bin('wasm-dis'), wasm] + FEATURE_OPTS) + all_funcs = re.findall(FUNC_NAMES_REGEX, wat) + + # get the original output before splitting + output = run_d8_wasm(wasm) + output = fix_output(output) + + # find the names of the exports. we need this because when we split the + # module then new exports appear to connect the two halves of the + # original module. we do not want to call all the exports on the new + # primary module, but only the original ones. + exports = [] + for line in output.splitlines(): + if FUZZ_EXEC_CALL_PREFIX in line: + exports.append(get_export_from_call_line(line)) + + # pick which to split out, with a random rate of picking (biased towards + # 0.5). + rate = (random.random() + random.random()) / 2 + split_funcs = [] + for func in all_funcs: + if random.random() < rate: + split_funcs.append(func) + + if not split_funcs: + # nothing to split out + return + + # split the wasm into two + primary = wasm + '.primary.wasm' + secondary = wasm + '.secondary.wasm' + + # we require reference types, because that allows us to create our own + # table. without that we use the existing table, and that may interact + # with user code in odd ways (it really only works with the particular + # form of table+segments that LLVM emits, and not with random fuzzer + # content). + split_feature_opts = FEATURE_OPTS + ['--enable-reference-types'] + + run([in_bin('wasm-split'), wasm, '--split', + '--split-funcs', ','.join(split_funcs), + '--primary-output', primary, + '--secondary-output', secondary] + split_feature_opts) + + # sometimes also optimize the split modules + optimized = False + + def optimize(name): + # do not optimize if it would change the ABI + if CLOSED_WORLD: + return name + # TODO: use other optimizations here, but we'd need to be careful of + # anything that can alter the ABI, and also current + # limitations of open-world optimizations (see discussion in + # https://github.com/WebAssembly/binaryen/pull/6660) + opts = ['-O3'] + new_name = name + '.opt.wasm' + run([in_bin('wasm-opt'), name, '-o', new_name, '-all'] + opts + split_feature_opts) + nonlocal optimized + optimized = True + return new_name + + if random.random() < 0.5: + primary = optimize(primary) + if random.random() < 0.5: + secondary = optimize(secondary) + + # prepare the list of exports to call. the format is + # + # exports:A,B,C + # + exports_to_call = 'exports:' + ','.join(exports) + + # get the output from the split modules, linking them using JS + # TODO run liftoff/turboshaft/etc. + linked_output = run_d8_wasm(primary, args=[secondary, exports_to_call]) + linked_output = fix_output(linked_output) + + # see D8.can_compare_to_self: we cannot compare optimized outputs if + # NaNs are allowed, as the optimizer can modify NaNs differently than + # the JS engine. + if not (NANS and optimized): + compare_between_vms(output, linked_output, 'Split') + + def can_run_on_feature_opts(self, feature_opts): + # to run the split wasm we use JS, that is, JS links the exports of one + # to the imports of the other, etc. since we run in JS, the wasm must be + # valid for JS. + if not LEGALIZE: + return False + + # see D8.can_run + return all_disallowed(['shared-everything']) + + # Check that the text format round-trips without error. class RoundtripText(TestCaseHandler): frequency = 0.05 @@ -1413,6 +1518,8 @@ def handle(self, wasm): TrapsNeverHappen(), CtorEval(), Merge(), + # TODO: enable when stable enough, and adjust |frequency| (see above) + # Split(), RoundtripText() ] diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 1e4068dc8b4..3d29b197ce8 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -1,25 +1,47 @@ -// Shell integration. -if (typeof console === 'undefined') { - console = { log: print }; -} -var tempRet0; -var binary; -if (typeof process === 'object' && typeof require === 'function' /* node.js detection */) { - var args = process.argv.slice(2); - binary = require('fs').readFileSync(args[0]); - if (!binary.buffer) binary = new Uint8Array(binary); +// Shell integration: find argv and set up readBinary(). +var argv; +var readBinary; +if (typeof process === 'object' && typeof require === 'function') { + // Node.js. + argv = process.argv.slice(2); + readBinary = function(name) { + var data = require('fs').readFileSync(name); + if (!data.buffer) data = new Uint8Array(data); + return data; + }; } else { - var args; + // A shell like D8. if (typeof scriptArgs != 'undefined') { - args = scriptArgs; + argv = scriptArgs; } else if (typeof arguments != 'undefined') { - args = arguments; - } - if (typeof readbuffer === 'function') { - binary = new Uint8Array(readbuffer(args[0])); - } else { - binary = read(args[0], 'binary'); + argv = arguments; } + readBinary = function(name) { + if (typeof readbuffer === 'function') { + return new Uint8Array(readbuffer(name)); + } else { + return read(name, 'binary'); + } + }; +} + +// We are given the binary to run as a parameter. +var binary = readBinary(argv[0]); + +// Normally we call all the exports of the given wasm file. But, if we are +// passed a final parameter in the form of "exports:X,Y,Z" then we call +// specifically the exports X, Y, and Z. +var exportsToCall; +if (argv[argv.length - 1].startsWith('exports:')) { + exportsToCall = argv[argv.length - 1].substr('exports:'.length).split(','); + argv.pop(); +} + +// If a second parameter is given, it is a second binary that we will link in +// with it. +var secondBinary; +if (argv[1]) { + secondBinary = readBinary(argv[1]); } // Utilities. @@ -27,17 +49,6 @@ function assert(x, y) { if (!x) throw (y || 'assertion failed');// + new Error().stack; } -// Deterministic randomness. -var detrand = (function() { - var hash = 5381; // TODO DET_RAND_SEED; - var x = 0; - return function() { - hash = (((hash << 5) + hash) ^ (x & 0xff)) >>> 0; - x = (x + 1) % 256; - return (hash % 256) / 256; - }; -})(); - // Print out a value in a way that works well for fuzzing. function printed(x, y) { if (typeof y !== 'undefined') { @@ -124,6 +135,7 @@ function logValue(x, y) { } // Set up the imports. +var tempRet0; var imports = { 'fuzzing-support': { 'log-i32': logValue, @@ -151,6 +163,24 @@ if (typeof WebAssembly.Tag !== 'undefined') { }; } +// If a second binary will be linked in then set up the imports for +// placeholders. Any import like (import "placeholder" "0" (func .. will be +// provided by the secondary module, and must be called using an indirection. +if (secondBinary) { + imports['placeholder'] = new Proxy({}, { + get(target, prop, receiver) { + // Return a function that throws. We could do an indirect call using the + // exported table, but as we immediately link in the secondary module, + // these stubs will not be called (they are written to the table, and the + // secondary module overwrites them). We do need to return something so + // the primary module links without erroring, though. + return () => { + throw 'proxy stub should not be called'; + } + } + }); +} + // Create the wasm. var module = new WebAssembly.Module(binary); @@ -165,17 +195,32 @@ try { // Handle the exports. var exports = instance.exports; -var view; +// Link in a second module, if one was provided. +if (secondBinary) { + var secondModule = new WebAssembly.Module(secondBinary); -// Recreate the view. This is important both initially and after a growth. -function refreshView() { - if (exports.memory) { - view = new Int32Array(exports.memory.buffer); + // The secondary module just needs to import the primary one: all original + // imports it might have needed were exported from there. + var secondImports = {'primary': exports}; + var secondInstance; + try { + secondInstance = new WebAssembly.Instance(secondModule, secondImports); + } catch (e) { + console.log('exception thrown: failed to instantiate second module'); + quit(); } } // Run the wasm. -for (var e in exports) { +if (!exportsToCall) { + // We were not told specific exports, so call them all. + exportsToCall = []; + for (var e in exports) { + exportsToCall.push(e); + } +} + +for (var e of exportsToCall) { if (typeof exports[e] !== 'function') { continue; } From bc36c02d1a54c91d9fc4bdbffe00608929ec3169 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 18 Oct 2024 16:14:01 -0700 Subject: [PATCH 085/622] Remove closed world validation checks (#7019) These were added to avoid common problems with closed world mode, but in practice they are causing more harm than good, forcing users to work around them. In the meantime (until #6965), remove this validation to unblock current toolchain makers. Fix GlobalTypeOptimization and AbstractTypeRefining on issues that this uncovers: without this validation, it is possible to run them on more wasm files than before, hence these were not previously detected. They are bundled in this PR because their tests cannot validate before this PR. --- src/pass.h | 8 -- src/passes/AbstractTypeRefining.cpp | 10 +++ src/passes/GlobalTypeOptimization.cpp | 14 ++++ src/wasm/wasm-validator.cpp | 51 +----------- test/lit/passes/abstract-type-refining.wast | 52 ++++++++++++ test/lit/passes/gto-mutability.wast | 36 +++++++++ .../closed-world-interface-intrinsics.wast | 46 ----------- .../validation/closed-world-interface.wast | 79 ------------------- 8 files changed, 113 insertions(+), 183 deletions(-) delete mode 100644 test/lit/validation/closed-world-interface-intrinsics.wast delete mode 100644 test/lit/validation/closed-world-interface.wast diff --git a/src/pass.h b/src/pass.h index 8d81fce8e8b..daed8b6abc6 100644 --- a/src/pass.h +++ b/src/pass.h @@ -212,14 +212,6 @@ struct PassOptions { // but we also want to keep types of things on the boundary unchanged. For // example, we should not change an exported function's signature, as the // outside may need that type to properly call the export. - // - // * Since the goal of closedWorld is to optimize types aggressively but - // types on the module boundary cannot be changed, we assume the producer - // has made a mistake and we consider it a validation error if any user - // defined types besides the types of imported or exported functions - // themselves appear on the module boundary. For example, no user defined - // struct type may be a parameter or result of an exported function. This - // error may be relaxed or made more configurable in the future. bool closedWorld = false; // Whether to try to preserve debug info through, which are special calls. bool debugInfo = false; diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index 313ad5f4d2e..5094488516c 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -106,6 +106,16 @@ struct AbstractTypeRefining : public Pass { } } + // Assume all public types are created, which makes them non-abstract and + // hence ignored below. + // TODO: In principle we could assume such types are not created outside the + // module, given closed world, but we'd also need to make sure that + // we don't need to make any changes to public types that refer to + // them. + for (auto type : ModuleUtils::getPublicHeapTypes(*module)) { + createdTypes.insert(type); + } + SubTypes subTypes(*module); // Compute createdTypesOrSubTypes by starting with the created types and diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 1f0da2595ec..74107f9cdd3 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -198,6 +198,20 @@ struct GlobalTypeOptimization : public Pass { continue; } + // The propagation analysis ensures we update immutability in all + // supers and subs in concert, but it does not take into account + // visibility, so do that here: we can only become immutable if the + // parent can as well. + auto super = type.getDeclaredSuperType(); + if (super && !canBecomeImmutable.count(*super)) { + // No entry in canBecomeImmutable means nothing in the parent can + // become immutable. We don't need to check the specific field index, + // because visibility affects them all equally (i.e., if it is public + // then no field can be changed, and if it is private then this field + // can be changed, and perhaps more). + continue; + } + // No set exists. Mark it as something we can make immutable. auto& vec = canBecomeImmutable[type]; vec.resize(i + 1); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 0184e3284c8..d1ca5220c3b 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -63,7 +63,6 @@ struct ValidationInfo { bool validateWeb; bool validateGlobally; bool quiet; - bool closedWorld; std::atomic valid; @@ -4135,46 +4134,6 @@ static void validateFeatures(Module& module, ValidationInfo& info) { } } -static void validateClosedWorldInterface(Module& module, ValidationInfo& info) { - // Error if there are any publicly exposed heap types beyond the types of - // publicly exposed functions. Note that we must include all types in the rec - // groups that are used, as if a type if public then all types in its rec - // group are as well. - std::unordered_set publicRecGroups; - ModuleUtils::iterImportedFunctions(module, [&](Function* func) { - publicRecGroups.insert(func->type.getRecGroup()); - }); - for (auto& ex : module.exports) { - if (ex->kind == ExternalKind::Function) { - publicRecGroups.insert(module.getFunction(ex->value)->type.getRecGroup()); - } - } - - std::unordered_set publicTypes; - for (auto& group : publicRecGroups) { - for (auto type : group) { - publicTypes.insert(type); - } - } - - // Ignorable public types are public, but we can ignore them for purposes of - // erroring here: It is always ok that they are public. - auto ignorable = getIgnorablePublicTypes(); - - for (auto type : ModuleUtils::getPublicHeapTypes(module)) { - if (!publicTypes.count(type) && !ignorable.count(type)) { - auto name = type.toString(); - if (auto it = module.typeNames.find(type); it != module.typeNames.end()) { - name = it->second.name.toString(); - } - info.fail("publicly exposed type disallowed with a closed world: $" + - name, - type, - nullptr); - } - } -} - // TODO: If we want the validator to be part of libwasm rather than libpasses, // then Using PassRunner::getPassDebug causes a circular dependence. We should // fix that, perhaps by moving some of the pass infrastructure into libsupport. @@ -4183,7 +4142,6 @@ bool WasmValidator::validate(Module& module, Flags flags) { info.validateWeb = (flags & Web) != 0; info.validateGlobally = (flags & Globally) != 0; info.quiet = (flags & Quiet) != 0; - info.closedWorld = (flags & ClosedWorld) != 0; // Parallel function validation. PassRunner runner(&module); @@ -4210,9 +4168,6 @@ bool WasmValidator::validate(Module& module, Flags flags) { validateStart(module, info); validateModuleMaps(module, info); validateFeatures(module, info); - if (info.closedWorld) { - validateClosedWorldInterface(module, info); - } } // Validate additional internal IR details when in pass-debug mode. @@ -4231,11 +4186,7 @@ bool WasmValidator::validate(Module& module, Flags flags) { } bool WasmValidator::validate(Module& module, const PassOptions& options) { - Flags flags = options.validateGlobally ? Globally : Minimal; - if (options.closedWorld) { - flags |= ClosedWorld; - } - return validate(module, flags); + return validate(module, options.validateGlobally ? Globally : Minimal); } bool WasmValidator::validate(Function* func, Module& module, Flags flags) { diff --git a/test/lit/passes/abstract-type-refining.wast b/test/lit/passes/abstract-type-refining.wast index 814f5c1f005..e887894ea74 100644 --- a/test/lit/passes/abstract-type-refining.wast +++ b/test/lit/passes/abstract-type-refining.wast @@ -1304,3 +1304,55 @@ ) ) ) + +;; $A is never created, but $B is, so all appearances of $A, like in the cast +;; and the struct field, could be replaced by $B, except that $A is a public type, +;; so might be created outside the module. +(module + (rec + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $A (sub (struct (field (ref null $A))))) + ;; NO_TNH: (rec + ;; NO_TNH-NEXT: (type $A (sub (struct (field (ref null $A))))) + (type $A (sub (struct (field (ref null $A))))) + + ;; YESTNH: (type $B (sub $A (struct (field (ref null $A))))) + ;; NO_TNH: (type $B (sub $A (struct (field (ref null $A))))) + (type $B (sub $A (struct (field (ref null $A))))) + ) + + ;; YESTNH: (type $2 (func (param anyref))) + + ;; YESTNH: (global $global (ref $B) (struct.new_default $B)) + ;; NO_TNH: (type $2 (func (param anyref))) + + ;; NO_TNH: (global $global (ref $B) (struct.new_default $B)) + (global $global (ref $B) (struct.new_default $B)) + + ;; YESTNH: (export "global" (global $global)) + ;; NO_TNH: (export "global" (global $global)) + (export "global" (global $global)) + + ;; YESTNH: (func $new (type $2) (param $x anyref) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (ref.cast (ref $A) + ;; YESTNH-NEXT: (local.get $x) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $new (type $2) (param $x anyref) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (ref.cast (ref $A) + ;; NO_TNH-NEXT: (local.get $x) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $new (param $x anyref) + (drop + (ref.cast (ref $A) + (local.get $x) + ) + ) + ) +) + diff --git a/test/lit/passes/gto-mutability.wast b/test/lit/passes/gto-mutability.wast index 13b416b1bdd..3b01572c8c2 100644 --- a/test/lit/passes/gto-mutability.wast +++ b/test/lit/passes/gto-mutability.wast @@ -652,3 +652,39 @@ ) ) ) + +;; The parent is public, which prevents us from making any field immutable in +;; the child. +(module + ;; CHECK: (type $parent (sub (struct (field (mut i32))))) + (type $parent (sub (struct (field (mut i32))))) + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $child (sub $parent (struct (field (mut i32))))) + (type $child (sub $parent (struct (field (mut i32))))) + + ;; CHECK: (global $global (ref $parent) (struct.new $parent + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: )) + (global $global (ref $parent) (struct.new $parent + (i32.const 0) + )) + + ;; Make the parent public by exporting the global. + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) + + ;; CHECK: (func $func (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + ;; Create the child so the type is used. No sets to the fields exist, so + ;; in theory all fields could be immutable. + (drop + (struct.new_default $child) + ) + ) +) + diff --git a/test/lit/validation/closed-world-interface-intrinsics.wast b/test/lit/validation/closed-world-interface-intrinsics.wast deleted file mode 100644 index 50151b8c03a..00000000000 --- a/test/lit/validation/closed-world-interface-intrinsics.wast +++ /dev/null @@ -1,46 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: foreach %s %t wasm-opt -all --closed-world -S -o - | filecheck %s - -;; Test that we do not error on call.without.effects despite it being an import. -;; call.without.effects does not make the types in it public, and so it can -;; validate with --closed-world. - -(module - ;; CHECK: (type $struct (struct (field i32))) - (type $struct (struct i32)) - - ;; CHECK: (type $1 (func (param (ref $struct) funcref))) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (type $3 (func (param (ref $struct)))) - - ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe (type $1) (param (ref $struct) funcref))) - (import "binaryen-intrinsics" "call.without.effects" (func $cwe (param (ref $struct)) (param funcref))) - - ;; CHECK: (elem declare func $func) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (call $cwe - ;; CHECK-NEXT: (struct.new $struct - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.func $func) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (call $cwe - (struct.new $struct - (i32.const 100) - ) - (ref.func $func) - ) - ) - - ;; CHECK: (func $func (type $3) (param $ref (ref $struct)) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - (func $func (param $ref (ref $struct)) - ) -) diff --git a/test/lit/validation/closed-world-interface.wast b/test/lit/validation/closed-world-interface.wast deleted file mode 100644 index b4fe965cf0d..00000000000 --- a/test/lit/validation/closed-world-interface.wast +++ /dev/null @@ -1,79 +0,0 @@ -;; Test that we error out on nontrivial public types with --closed-world - -;; RUN: not wasm-opt -all --closed-world %s 2>&1 | filecheck %s - - -;; This is pulled in by a global. -;; CHECK: publicly exposed type disallowed with a closed world: $array, on -;; CHECK-NEXT: (array (mut i32)) - -;; This is pulled in only by a global, so it is disallowed even though it is a function type. -;; CHECK: publicly exposed type disallowed with a closed world: $private, on -;; CHECK-NEXT: (func (param v128)) - -;; This is referred to by the type of a function export, but is still not allowed. -;; CHECK: publicly exposed type disallowed with a closed world: $struct, on -;; CHECK-NEXT: (struct) - -(module - (type $struct (struct)) - (type $array (array (mut i32))) - - (type $void (func)) - (type $abstract (func (param anyref))) - (type $concrete (func (param (ref null $struct)))) - - (rec - (type $exported-pair-0 (func (param (ref $exported-pair-1)))) - (type $exported-pair-1 (func (param (ref $exported-pair-0)))) - ) - (rec - ;; This is on an exported function. - (type $partial-pair-0 (func)) - ;; The latter type types are not public, but allowed to be because the - ;; entire rec group is allowed due to the first. - (type $partial-pair-1 (func)) - ;; Test a non-function type. - (type $partial-pair-2 (struct)) - ) - - (type $private (func (param v128))) - - ;; Ok even though it is an import instead of an export. - (func $1 (import "env" "test5") (type $exported-pair-1)) - - (func $2 (export "test1") (type $void) - (unreachable) - ) - - ;; Ok because it only refers to basic heap types - (func $3 (export "test2") (type $abstract) - (unreachable) - ) - - ;; Not ok because it refers to $struct. - (func $4 (export "test3") (type $concrete) - (unreachable) - ) - - ;; Ok even though it is in a rec group because the rest of the group and the - ;; types this refers to are on the boundary as well. - (func $5 (export "test4") (type $exported-pair-0) - (unreachable) - ) - - ;; Ok, and we also allow the other type in the group. - (func $6 (export "test6") (type $partial-pair-0) - (unreachable) - ) - - ;; Not ok. - (global $1 (export "g1") (ref null $array) (ref.null none)) - - ;; Ok because this type is on the boundary anyway. - (global $2 (export "g2") (ref null $void) (ref.null func)) - - ;; Not ok even though it is a function type because it is not otherwise on the - ;; boundary. - (global $3 (export "g3") (ref null $private) (ref.null func)) -) From 0d9b7508e5de1ca7befef493ed3e357b8a5613a1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 22 Oct 2024 09:17:05 -0700 Subject: [PATCH 086/622] [GC] Fix assertion in GlobalTypeOptimization about public super (#7026) We only checked for the case of the immediate super being public while we are private, but it might be a grandsuper instead. That is, any ancestor that is public will prevent GTO from removing a field (since we can only add fields on top of our ancestors). Also, the ancestors might not all have the field, which would add more complexity to that particular assertion, so just remove it, and add comprehensive tests. --- src/passes/GlobalTypeOptimization.cpp | 13 ++--- test/lit/passes/gto-removals.wast | 77 +++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 74107f9cdd3..e35e30ac8da 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -311,15 +311,10 @@ struct GlobalTypeOptimization : public Pass { keptFieldsNotInSuper.push_back(i); } } else { - // The super kept this field, so we must keep it as well. The - // propagation analysis above ensures that we and the super are in - // agreement on keeping it (the reasons that prevent optimization - // propagate to both), except for the corner case of the parent - // being public but us being private (the propagation does not - // take into account visibility). - assert( - !removableIndexes.count(i) || - (publicTypesSet.count(*super) && !publicTypesSet.count(type))); + // The super kept this field, so we must keep it as well. This can + // happen when we need the field in both, but also in the corner + // case where we don't need the field but the super is public. + // We need to keep it at the same index so we remain compatible. indexesAfterRemoval[i] = superIndex; // Update |next| to refer to the next available index. Due to diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index 61396cf8f7a..99579f8ab1d 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -1495,3 +1495,80 @@ ) ) ) + +;; The type $A is public because it is on an exported global. As a result we +;; cannot remove the unused i32 field from its child or grandchild. +(module + ;; CHECK: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) + (type $B (sub $A (struct (field (mut i32))))) + ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) + (type $C (sub $B (struct (field (mut i32))))) + + ;; Use $C so it isn't removed trivially, which also keeps $B alive as its + ;; super. + ;; CHECK: (global $global (ref $A) (struct.new_default $C)) + (global $global (ref $A) (struct.new_default $C)) + + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) +) + +;; As above, but now there is an f64 field on $C that can be removed, since it +;; is not on the parents. +(module + ;; CHECK: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $B (sub $A (struct (field (mut i32))))) + (type $B (sub $A (struct (field (mut i32))))) + ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) + (type $C (sub $B (struct (field (mut i32)) (field (mut f64))))) + + ;; CHECK: (global $global (ref $A) (struct.new_default $C)) + (global $global (ref $A) (struct.new_default $C)) + + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) +) + +;; As above, but the f64 field is now on $B as well. We can still remove it. +(module + ;; CHECK: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $B (sub $A (struct (field (mut i32))))) + (type $B (sub $A (struct (field (mut i32)) (field (mut f64))))) + ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) + (type $C (sub $B (struct (field (mut i32)) (field (mut f64))))) + + ;; CHECK: (global $global (ref $A) (struct.new_default $C)) + (global $global (ref $A) (struct.new_default $C)) + + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) +) + +;; As above, but now $B is public as well. Now we cannot remove the f64. +(module + ;; CHECK: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + ;; CHECK: (type $B (sub $A (struct (field (mut i32)) (field (mut f64))))) + (type $B (sub $A (struct (field (mut i32)) (field (mut f64))))) + ;; CHECK: (type $C (sub $B (struct (field (mut i32)) (field (mut f64))))) + (type $C (sub $B (struct (field (mut i32)) (field (mut f64))))) + + ;; CHECK: (global $global (ref $A) (struct.new_default $C)) + (global $global (ref $A) (struct.new_default $C)) + + ;; CHECK: (global $globalB (ref $B) (struct.new_default $C)) + (global $globalB (ref $B) (struct.new_default $C)) + + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) + + ;; CHECK: (export "globalB" (global $globalB)) + (export "globalB" (global $globalB)) +) + From dcc70bbfb16c2f8fce29dad94d80d1b78123655f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 23 Oct 2024 10:17:13 -0700 Subject: [PATCH 087/622] [EH] Fuzz throws from JS (#7027) We already generated (throw ..) instructions in wasm, but it makes sense to model throws from outside as well, as they cross the module boundary. This adds a new fuzzer import to the generated modules, "throw", that just does a throw from JS etc. Also be more precise about handling fuzzing-support imports in fuzz-exec: we now check that logging functions start with "log*" and error otherwise (this check is now needed given we have "throw", which is not logging). Also fix a minor issue with name conflicts for logging functions by using getValidFunctionName for them, both for logging and for throw. --- scripts/fuzz_shell.js | 7 ++ src/tools/execution-results.h | 47 +++++++---- src/tools/fuzzing.h | 10 ++- src/tools/fuzzing/fuzzing.cpp | 66 ++++++++++----- test/lit/exec/fuzzing-api.wast | 39 +++++++++ test/lit/exec/host-limit.wast | 2 +- test/passes/fuzz_metrics_noprint.bin.txt | 52 ++++++------ test/passes/simplify-locals_all-features.txt | 6 +- test/passes/simplify-locals_all-features.wast | 6 +- ...ll-features_disable-exception-handling.txt | 6 +- ...l-features_disable-exception-handling.wast | 6 +- ...e-to-fuzz_all-features_metrics_noprint.txt | 83 ++++++++++--------- 12 files changed, 215 insertions(+), 115 deletions(-) create mode 100644 test/lit/exec/fuzzing-api.wast diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 3d29b197ce8..4cf3ba35866 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -138,6 +138,7 @@ function logValue(x, y) { var tempRet0; var imports = { 'fuzzing-support': { + // Logging. 'log-i32': logValue, 'log-i64': logValue, 'log-f32': logValue, @@ -147,7 +148,13 @@ var imports = { // we could avoid running JS on code with SIMD in it, but it is useful to // fuzz such code as much as we can.) 'log-v128': logValue, + + // Throw an exception from JS. + 'throw': () => { + throw 'some JS error'; + } }, + // Emscripten support. 'env': { 'setTempRet0': function(x) { tempRet0 = x }, 'getTempRet0': function() { return tempRet0 }, diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 920bb200ac6..d9fefc44ec7 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -27,6 +27,7 @@ using Loggings = std::vector; // Logs every relevant import call parameter. struct LoggingExternalInterface : public ShellExternalInterface { +private: Loggings& loggings; struct State { @@ -37,30 +38,40 @@ struct LoggingExternalInterface : public ShellExternalInterface { uint32_t tempRet0 = 0; } state; +public: LoggingExternalInterface(Loggings& loggings) : loggings(loggings) {} Literals callImport(Function* import, const Literals& arguments) override { if (import->module == "fuzzing-support") { - std::cout << "[LoggingExternalInterface logging"; - loggings.push_back(Literal()); // buffer with a None between calls - for (auto argument : arguments) { - if (argument.type == Type::i64) { - // To avoid JS legalization changing logging results, treat a logging - // of an i64 as two i32s (which is what legalization would turn us - // into). - auto low = Literal(int32_t(argument.getInteger())); - auto high = Literal(int32_t(argument.getInteger() >> int32_t(32))); - std::cout << ' ' << low; - loggings.push_back(low); - std::cout << ' ' << high; - loggings.push_back(high); - } else { - std::cout << ' ' << argument; - loggings.push_back(argument); + if (import->base.startsWith("log")) { + // This is a logging function like log-i32 or log-f64 + std::cout << "[LoggingExternalInterface logging"; + loggings.push_back(Literal()); // buffer with a None between calls + for (auto argument : arguments) { + if (argument.type == Type::i64) { + // To avoid JS legalization changing logging results, treat a + // logging of an i64 as two i32s (which is what legalization would + // turn us into). + auto low = Literal(int32_t(argument.getInteger())); + auto high = Literal(int32_t(argument.getInteger() >> int32_t(32))); + std::cout << ' ' << low; + loggings.push_back(low); + std::cout << ' ' << high; + loggings.push_back(high); + } else { + std::cout << ' ' << argument; + loggings.push_back(argument); + } } + std::cout << "]\n"; + return {}; + } else if (import->base == "throw") { + // Throw something. We use a (hopefully) private name here. + auto payload = std::make_shared("__private", Literals{}); + throwException(WasmException{Literal(payload)}); + } else { + WASM_UNREACHABLE("unknown fuzzer import"); } - std::cout << "]\n"; - return {}; } else if (import->module == ENV) { if (import->base == "log_execution") { std::cout << "[LoggingExternalInterface log-execution"; diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 92e99791341..69fee656c07 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -104,6 +104,10 @@ class TranslateToFuzzReader { Name funcrefTableName; + std::unordered_map logImportNames; + + Name throwImportName; + std::unordered_map> globalsByType; std::unordered_map> mutableGlobalsByType; std::unordered_map> immutableGlobalsByType; @@ -220,12 +224,16 @@ class TranslateToFuzzReader { void finalizeTable(); void prepareHangLimitSupport(); void addHangLimitSupport(); + // Imports that we call to log out values. void addImportLoggingSupport(); + // An import that we call to throw an exception from outside. + void addImportThrowingSupport(); void addHashMemorySupport(); // Special expression makers Expression* makeHangLimitCheck(); - Expression* makeLogging(); + Expression* makeImportLogging(); + Expression* makeImportThrowing(Type type); Expression* makeMemoryHashLogging(); // Function creation diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index d3f52d3b0ec..b7d075dccfb 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -176,9 +176,10 @@ void TranslateToFuzzReader::build() { setupGlobals(); if (wasm.features.hasExceptionHandling()) { setupTags(); + addImportThrowingSupport(); } - modifyInitialFunctions(); addImportLoggingSupport(); + modifyInitialFunctions(); // keep adding functions until we run out of input while (!random.finished()) { auto* func = addFunction(); @@ -583,16 +584,31 @@ void TranslateToFuzzReader::addHangLimitSupport() { void TranslateToFuzzReader::addImportLoggingSupport() { for (auto type : loggableTypes) { - auto* func = new Function; - Name name = std::string("log-") + type.toString(); - func->name = name; + auto func = std::make_unique(); + Name baseName = std::string("log-") + type.toString(); + func->name = Names::getValidFunctionName(wasm, baseName); + logImportNames[type] = func->name; func->module = "fuzzing-support"; - func->base = name; + func->base = baseName; func->type = Signature(type, Type::none); - wasm.addFunction(func); + wasm.addFunction(std::move(func)); } } +void TranslateToFuzzReader::addImportThrowingSupport() { + // Throw some kind of exception from JS. + // TODO: Send an index, which is which exported wasm Tag we should throw, or + // something not exported if out of bounds. First we must also export + // tags sometimes. + throwImportName = Names::getValidFunctionName(wasm, "throw"); + auto func = std::make_unique(); + func->name = throwImportName; + func->module = "fuzzing-support"; + func->base = "throw"; + func->type = Signature(Type::none, Type::none); + wasm.addFunction(std::move(func)); +} + void TranslateToFuzzReader::addHashMemorySupport() { // Add memory hasher helper (for the hash, see hash.h). The function looks // like: @@ -692,21 +708,30 @@ Expression* TranslateToFuzzReader::makeHangLimitCheck() { builder.makeConst(int32_t(1))))); } -Expression* TranslateToFuzzReader::makeLogging() { +Expression* TranslateToFuzzReader::makeImportLogging() { auto type = getLoggableType(); - return builder.makeCall( - std::string("log-") + type.toString(), {make(type)}, Type::none); + return builder.makeCall(logImportNames[type], {make(type)}, Type::none); +} + +Expression* TranslateToFuzzReader::makeImportThrowing(Type type) { + // We throw from the import, so this call appears to be none and not + // unreachable. + assert(type == Type::none); + + // TODO: This and makeThrow should probably be rare, as they halt the program. + return builder.makeCall(throwImportName, {}, Type::none); } Expression* TranslateToFuzzReader::makeMemoryHashLogging() { auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32); - return builder.makeCall(std::string("log-i32"), {hash}, Type::none); + return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none); } // TODO: return std::unique_ptr Function* TranslateToFuzzReader::addFunction() { LOGGING_PERCENT = upToSquared(100); - auto* func = new Function; + auto allocation = std::make_unique(); + auto* func = allocation.get(); func->name = Names::getValidFunctionName(wasm, "func"); FunctionCreationContext context(*this, func); assert(funcContext->typeLocals.empty()); @@ -765,7 +790,7 @@ Function* TranslateToFuzzReader::addFunction() { } // Add hang limit checks after all other operations on the function body. - wasm.addFunction(func); + wasm.addFunction(std::move(allocation)); // Export some functions, but not all (to allow inlining etc.). Try to export // at least one, though, to keep each testcase interesting. Only functions // with valid params and returns can be exported because the trap fuzzer @@ -1215,10 +1240,13 @@ void TranslateToFuzzReader::modifyInitialFunctions() { // the end (currently that is not needed atm, but it might in the future). for (Index i = 0; i < wasm.functions.size(); i++) { auto* func = wasm.functions[i].get(); + // We can't allow extra imports, as the fuzzing infrastructure wouldn't + // know what to provide. Keep only our own fuzzer imports. + if (func->imported() && func->module == "fuzzing-support") { + continue; + } FunctionCreationContext context(*this, func); if (func->imported()) { - // We can't allow extra imports, as the fuzzing infrastructure wouldn't - // know what to provide. func->module = func->base = Name(); func->body = make(func->getResults()); } @@ -1261,10 +1289,9 @@ void TranslateToFuzzReader::dropToLog(Function* func) { void visitDrop(Drop* curr) { if (parent.isLoggableType(curr->value->type) && parent.oneIn(2)) { - replaceCurrent(parent.builder.makeCall(std::string("log-") + - curr->value->type.toString(), - {curr->value}, - Type::none)); + auto target = parent.logImportNames[curr->value->type]; + replaceCurrent( + parent.builder.makeCall(target, {curr->value}, Type::none)); } } }; @@ -1430,7 +1457,7 @@ Expression* TranslateToFuzzReader::_makenone() { auto choice = upTo(100); if (choice < LOGGING_PERCENT) { if (choice < LOGGING_PERCENT / 2) { - return makeLogging(); + return makeImportLogging(); } else { return makeMemoryHashLogging(); } @@ -1455,6 +1482,7 @@ Expression* TranslateToFuzzReader::_makenone() { .add(FeatureSet::Atomics, &Self::makeAtomic) .add(FeatureSet::ExceptionHandling, &Self::makeTry) .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) + .add(FeatureSet::ExceptionHandling, &Self::makeImportThrowing) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeStructSet) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArraySet) diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast new file mode 100644 index 00000000000..d37d7ef4a35 --- /dev/null +++ b/test/lit/exec/fuzzing-api.wast @@ -0,0 +1,39 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -o /dev/null 2>&1 | filecheck %s + +;; Test the fuzzing-support module imports. + +(module + (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) + (import "fuzzing-support" "log-f64" (func $log-f64 (param f64))) + + (import "fuzzing-support" "throw" (func $throw)) + + ;; CHECK: [fuzz-exec] calling logging + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + (func $logging (export "logging") + (call $log-i32 + (i32.const 42) + ) + (call $log-f64 + (f64.const 3.14159) + ) + ) + + ;; CHECK: [fuzz-exec] calling throwing + ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: warning: no passes specified, not doing any work + (func $throwing (export "throwing") + (call $throw) + ) +) +;; CHECK: [fuzz-exec] calling logging +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + +;; CHECK: [fuzz-exec] calling throwing +;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [fuzz-exec] comparing logging +;; CHECK-NEXT: [fuzz-exec] comparing throwing diff --git a/test/lit/exec/host-limit.wast b/test/lit/exec/host-limit.wast index e64a47d8a3f..ba5087f6942 100644 --- a/test/lit/exec/host-limit.wast +++ b/test/lit/exec/host-limit.wast @@ -9,7 +9,7 @@ (module (type $type$0 (array i8)) - (import "fuzzing-support" "log" (func $log (param i32))) + (import "fuzzing-support" "log-i32" (func $log (param i32))) (global $global (mut (ref $type$0)) (array.new_default $type$0 (i32.const -1) diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index 2f1719633b8..fab2ccc9d63 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 23 - [funcs] : 34 + [exports] : 50 + [funcs] : 72 [globals] : 9 [imports] : 4 [memories] : 1 [memory-data] : 2 - [table-data] : 6 + [table-data] : 25 [tables] : 1 [tags] : 0 - [total] : 4303 - [vars] : 100 - Binary : 355 - Block : 684 - Break : 149 - Call : 219 + [total] : 4381 + [vars] : 218 + Binary : 335 + Block : 725 + Break : 120 + Call : 210 CallIndirect : 23 - Const : 643 - Drop : 50 - GlobalGet : 367 - GlobalSet : 258 - If : 206 - Load : 78 - LocalGet : 339 - LocalSet : 236 - Loop : 93 - Nop : 41 - RefFunc : 6 - Return : 45 - Select : 41 - Store : 36 - Switch : 1 - Unary : 304 - Unreachable : 129 + Const : 692 + Drop : 64 + GlobalGet : 391 + GlobalSet : 298 + If : 236 + Load : 71 + LocalGet : 285 + LocalSet : 209 + Loop : 76 + Nop : 63 + RefFunc : 25 + Return : 60 + Select : 23 + Store : 29 + Switch : 2 + Unary : 293 + Unreachable : 151 diff --git a/test/passes/simplify-locals_all-features.txt b/test/passes/simplify-locals_all-features.txt index befbe33efb7..9daee90a264 100644 --- a/test/passes/simplify-locals_all-features.txt +++ b/test/passes/simplify-locals_all-features.txt @@ -1220,9 +1220,9 @@ (type $9 (func (result f64))) (type $10 (func (param i32 i32) (result f64))) (type $11 (func (param i32 i32) (result i32))) - (import "fuzzing-support" "log1" (func $fimport$0 (type $FUNCSIG$i) (result i32))) - (import "fuzzing-support" "log2" (func $fimport$1 (type $4) (param i32))) - (import "fuzzing-support" "log3" (func $fimport$2 (type $7) (param f32))) + (import "env" "get1" (func $fimport$0 (type $FUNCSIG$i) (result i32))) + (import "fuzzing-support" "log-i32" (func $fimport$1 (type $4) (param i32))) + (import "fuzzing-support" "log-f32" (func $fimport$2 (type $7) (param f32))) (global $global$0 (mut i32) (i32.const 10)) (memory $0 256 256 shared) (func $nonatomics (type $FUNCSIG$i) (result i32) diff --git a/test/passes/simplify-locals_all-features.wast b/test/passes/simplify-locals_all-features.wast index 47bb7f1b588..0c296e378b4 100644 --- a/test/passes/simplify-locals_all-features.wast +++ b/test/passes/simplify-locals_all-features.wast @@ -1194,9 +1194,9 @@ (type $4 (func (param i32))) (type $5 (func (param i32) (result i32))) (type $6 (func (param i32 i32 i32 i32 i32 i32))) - (import "fuzzing-support" "log1" (func $fimport$0 (result i32))) - (import "fuzzing-support" "log2" (func $fimport$1 (param i32))) - (import "fuzzing-support" "log3" (func $fimport$2 (param f32))) + (import "env" "get1" (func $fimport$0 (result i32))) + (import "fuzzing-support" "log-i32" (func $fimport$1 (param i32))) + (import "fuzzing-support" "log-f32" (func $fimport$2 (param f32))) (memory 256 256 shared) (global $global$0 (mut i32) (i32.const 10)) (func $nonatomics (result i32) ;; loads are reordered diff --git a/test/passes/simplify-locals_all-features_disable-exception-handling.txt b/test/passes/simplify-locals_all-features_disable-exception-handling.txt index 111223c49c5..92356286763 100644 --- a/test/passes/simplify-locals_all-features_disable-exception-handling.txt +++ b/test/passes/simplify-locals_all-features_disable-exception-handling.txt @@ -1214,9 +1214,9 @@ (type $9 (func (result f64))) (type $10 (func (param i32 i32) (result f64))) (type $11 (func (param i32 i32) (result i32))) - (import "fuzzing-support" "log1" (func $fimport$0 (type $FUNCSIG$i) (result i32))) - (import "fuzzing-support" "log2" (func $fimport$1 (type $4) (param i32))) - (import "fuzzing-support" "log3" (func $fimport$2 (type $7) (param f32))) + (import "env" "get1" (func $fimport$0 (type $FUNCSIG$i) (result i32))) + (import "fuzzing-support" "log-i32" (func $fimport$1 (type $4) (param i32))) + (import "fuzzing-support" "log-f32" (func $fimport$2 (type $7) (param f32))) (global $global$0 (mut i32) (i32.const 10)) (memory $0 256 256 shared) (func $nonatomics (type $FUNCSIG$i) (result i32) diff --git a/test/passes/simplify-locals_all-features_disable-exception-handling.wast b/test/passes/simplify-locals_all-features_disable-exception-handling.wast index 0fc11069693..44c31e344b3 100644 --- a/test/passes/simplify-locals_all-features_disable-exception-handling.wast +++ b/test/passes/simplify-locals_all-features_disable-exception-handling.wast @@ -1194,9 +1194,9 @@ (type $4 (func (param i32))) (type $5 (func (param i32) (result i32))) (type $6 (func (param i32 i32 i32 i32 i32 i32))) - (import "fuzzing-support" "log1" (func $fimport$0 (result i32))) - (import "fuzzing-support" "log2" (func $fimport$1 (param i32))) - (import "fuzzing-support" "log3" (func $fimport$2 (param f32))) + (import "env" "get1" (func $fimport$0 (result i32))) + (import "fuzzing-support" "log-i32" (func $fimport$1 (param i32))) + (import "fuzzing-support" "log-f32" (func $fimport$2 (param f32))) (memory 256 256 shared) (global $global$0 (mut i32) (i32.const 10)) (func $nonatomics (result i32) ;; loads are reordered diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 3cc84161097..c17c9cd1eea 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,51 +1,58 @@ Metrics total - [exports] : 4 - [funcs] : 7 + [exports] : 3 + [funcs] : 4 [globals] : 26 - [imports] : 5 + [imports] : 6 [memories] : 1 [memory-data] : 20 - [table-data] : 2 + [table-data] : 0 [tables] : 1 [tags] : 2 - [total] : 510 - [vars] : 15 - ArrayNew : 14 - ArrayNewFixed : 2 - AtomicFence : 1 - Binary : 64 - Block : 45 - Break : 2 - Call : 8 + [total] : 665 + [vars] : 20 + ArrayGet : 2 + ArrayLen : 2 + ArrayNew : 15 + ArrayNewFixed : 3 + AtomicCmpxchg : 1 + AtomicRMW : 1 + Binary : 71 + Block : 63 + BrOn : 4 + Break : 7 + Call : 18 CallRef : 2 - Const : 127 - Drop : 5 - GlobalGet : 28 + Const : 142 + Drop : 10 + GlobalGet : 33 GlobalSet : 16 - If : 12 - Load : 18 - LocalGet : 46 - LocalSet : 29 - Loop : 3 - MemoryCopy : 1 + I31Get : 1 + If : 21 + Load : 20 + LocalGet : 61 + LocalSet : 52 + Loop : 6 + MemoryFill : 1 MemoryInit : 1 - Nop : 4 - Pop : 2 - RefCast : 1 - RefFunc : 7 - RefNull : 6 - Return : 9 + Nop : 7 + Pop : 1 + RefAs : 11 + RefEq : 1 + RefFunc : 3 + RefI31 : 1 + RefNull : 14 + Return : 5 SIMDExtract : 1 - Store : 1 - StringConst : 7 - StringEq : 1 - StringMeasure : 1 - StringSliceWTF : 1 - StructNew : 14 - Try : 2 + Select : 2 + StringConst : 5 + StringEncode : 1 + StructGet : 4 + StructNew : 16 + StructSet : 1 + Try : 1 TryTable : 1 - TupleExtract : 3 + TupleExtract : 2 TupleMake : 6 - Unary : 9 - Unreachable : 10 + Unary : 20 + Unreachable : 9 From de421db1a556f579507e4445af27763c89dd8391 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 24 Oct 2024 13:48:56 -0700 Subject: [PATCH 088/622] Fix TypeMerging bug with indirectly reachable public types (#7031) TypeMerging works by representing the type definition graph as a partitioned DFA and then refining the partitions to find mergeable types. #7023 was due to a bug where the DFA included edges from public types to their children, but did not necessarily include corresponding states for those children. One way to fix the bug would have been to traverse the type graph, finding all reachable public types and creating DFA states for them, but that might be expensive in cases where there are large graphs of public types. Instead, fix the problem by removing the edges from public types to their children entirely. Types reachable from public types are also public and therefore are not eligible to be merged, so these edges were never necessary for correctness. Fixes #7023. --- src/passes/TypeMerging.cpp | 18 +++++++++++++----- test/lit/passes/type-merging.wast | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 206addd2fed..22c2352f292 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -504,11 +504,19 @@ std::vector TypeMerging::getPublicChildren(HeapType type) { DFA::State TypeMerging::makeDFAState(HeapType type) { std::vector succs; - for (auto child : type.getHeapTypeChildren()) { - // Both private and public heap type children participate in the DFA and are - // eligible to be successors. - if (!child.isBasic()) { - succs.push_back(getMerged(child)); + // Both private and public heap type children participate in the DFA and are + // eligible to be successors, except that public types are terminal states + // that do not have successors. This is sufficient because public types are + // always in their own singleton partitions, so they already differentiate + // types that reach them without needing to consider their children. In the + // other direction, including the children is not necessary to differentiate + // types reached by the public types because all such reachable types are also + // public and not eligible to be merged. + if (privateTypes.count(type)) { + for (auto child : type.getHeapTypeChildren()) { + if (!child.isBasic()) { + succs.push_back(getMerged(child)); + } } } return {type, std::move(succs)}; diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast index b8d60286346..5d0c4bc5e7b 100644 --- a/test/lit/passes/type-merging.wast +++ b/test/lit/passes/type-merging.wast @@ -985,6 +985,25 @@ (global $g2 (ref $C') (struct.new_default $D2')) ) +;; Regression test for a bug where public types not directly reachable from +;; private types were included as successors but not given states in the DFA. +(module + ;; CHECK: (type $public-child (struct)) + (type $public-child (struct)) + ;; CHECK: (type $public (struct (field (ref $public-child)))) + (type $public (struct (ref $public-child))) + ;; CHECK: (type $private (struct (field (ref $public)))) + (type $private (struct (ref $public))) + + ;; CHECK: (global $public (ref null $public) (ref.null none)) + (global $public (ref null $public) (ref.null none)) + ;; CHECK: (global $private (ref null $private) (ref.null none)) + (global $private (ref null $private) (ref.null none)) + + ;; CHECK: (export "public" (global $public)) + (export "public" (global $public)) +) + ;; Check that a ref.test inhibits merging (ref.cast is already checked above). (module ;; CHECK: (rec From d407cda117704f9e7c2868e8159aa55a50c57de4 Mon Sep 17 00:00:00 2001 From: Angela Upreti Date: Fri, 25 Oct 2024 14:35:42 -0400 Subject: [PATCH 089/622] Fix typo in parsers.h (#7032) Corrected `maybeRefType` declaration to `maybeReftype`. --- src/parser/parsers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 85a1febb54f..02c2e9e4760 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -30,7 +30,7 @@ using namespace std::string_view_literals; template Result absheaptype(Ctx&, Shareability); template Result heaptype(Ctx&); -template MaybeResult maybeRefType(Ctx&); +template MaybeResult maybeReftype(Ctx&); template Result reftype(Ctx&); template MaybeResult tupletype(Ctx&); template Result valtype(Ctx&); From 690576cda63d59ae0cfc3906e1146a7a4278db43 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Oct 2024 12:51:07 -0700 Subject: [PATCH 090/622] Version 120 (#7033) --- CHANGELOG.md | 11 +++++++++++ CMakeLists.txt | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddfc1730490..a1dbd45919f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,19 @@ full changeset diff at the end of each section. Current Trunk ------------- +v120 +---- + + - Remove closed world validation checks. These checks were causing more harm + than good. All valid code will now validate with `--closed-world` (but also + it now provides fewer warnings to users that enable closed world on code + which does not conform to the requirements of that mode, which can lead to + changes in runtime behavior; for the long-term plans, see #6965). (#7019) - Many compile time speedups were implemented (2x overall improvement), see https://github.com/WebAssembly/binaryen/issues/4165#issuecomment-2372548271 + - Several `exnref` (newest version of Wasm EH) optimizations: #7013, #6996, + #6997, #6983, #6980 + - Source Maps: Support 5 segment mappings. (#6795) - [wasm-split] Add a multi-split mode. (#6943) - Add a `--preserve-type-order` option that minimizes text format changes in type ordering. (#6916) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd24a80a3bb..ed6af9394dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.10.2) # to reduce this for compatability with emsdk. set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version") -project(binaryen LANGUAGES C CXX VERSION 119) +project(binaryen LANGUAGES C CXX VERSION 120) include(GNUInstallDirs) # The C++ standard whose features are required to build Binaryen. From e3eaeef991445db2e6af782e202d67585398a43f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Oct 2024 13:34:09 -0700 Subject: [PATCH 091/622] Fix Apline compile error on uninitialized value (#7035) Similar to * https://github.com/WebAssembly/binaryen/pull/6330 * https://github.com/WebAssembly/binaryen/issues/6311 * https://github.com/WebAssembly/binaryen/pull/6912 * https://github.com/WebAssembly/binaryen/issues/5946 This extends the region we ignore the gcc warning in. The warning: ninja: job failed: /usr/bin/c++ -I/src/src -I/src/third_party/FP16/include -I/src/third_party/llvm-project/include -I/src -static -DBUILD_LLVM_DWARF -Wall -Werror -Wextra -Wno-unused-parameter -Wno-dangling-pointer -fno-omit-frame-pointer -fno-rtti -Wno-implicit-int-float-conversion -Wno-unknown-warning-option -Wswitch -Wimplicit-fallthrough -Wnon-virtual-dtor -fPIC -fdiagnostics-color=always -O3 -DNDEBUG -UNDEBUG -std=c++17 -MD -MT src/passes/CMakeFiles/passes.dir/Precompute.cpp.o -MF src/passes/CMakeFiles/passes.dir/Precompute.cpp.o.d -o src/passes/CMakeFiles/passes.dir/Precompute.cpp.o -c /src/src/passes/Precompute.cpp In file included from /src/src/literal.h:27, from /src/src/wasm.h:36, from /src/src/ir/boolean.h:20, from /src/src/ir/bits.h:20, from /src/src/ir/properties.h:20, from /src/src/ir/iteration.h:20, from /src/src/passes/Precompute.cpp:30: In copy constructor 'wasm::SmallVector::SmallVector(const wasm::SmallVector&) [with T = wasm::Expression*; long unsigned int N = 10]', inlined from 'constexpr std::pair<_T1, _T2>::pair(const _T1&, const _T2&) [with _U1 = wasm::Select* const; _U2 = wasm::SmallVector; typename std::enable_if<(std::_PCC::_ConstructiblePair<_U1, _U2>() && std::_PCC::_ImplicitlyConvertiblePair<_U1, _U2>()), bool>::type = true; _T1 = wasm::Select* const; _T2 = wasm::SmallVector]' at /usr/include/c++/13.2.1/bits/stl_pair.h:559:21, inlined from 'T& wasm::InsertOrderedMap::operator[](const Key&) [with Key = wasm::Select*; T = wasm::SmallVector]' at /src/src/support/insert_ordered.h:112:29, inlined from 'void wasm::Precompute::partiallyPrecompute(wasm::Function*)::StackFinder::visitSelect(wasm::Select*)' at /src/src/passes/Precompute.cpp:571:24, inlined from 'static void wasm::Walker::doVisitSelect(SubType*, wasm::Expression**) [with SubType = wasm::Precompute::partiallyPrecompute(wasm::Function*)::StackFinder; VisitorType = wasm::Visitor]' at /src/src/wasm-delegations.def:50:1: /src/src/support/small_vector.h:69:35: error: '.wasm::SmallVector::fixed' may be used uninitialized [-Werror=maybe-uninitialized] 69 | : usedFixed(other.usedFixed), fixed(other.fixed), flexible(other.flexible) { | ^~~~~~~~~~~~~~~~~~ In file included from /src/src/passes/Precompute.cpp:37: /src/src/support/insert_ordered.h: In static member function 'static void wasm::Walker::doVisitSelect(SubType*, wasm::Expression**) [with SubType = wasm::Precompute::partiallyPrecompute(wasm::Function*)::StackFinder; VisitorType = wasm::Visitor]': /src/src/support/insert_ordered.h:112:29: note: '' declared here 112 | std::pair kv = {k, {}}; | ^~ --- src/support/small_vector.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/support/small_vector.h b/src/support/small_vector.h index dd037055459..efd866cb550 100644 --- a/src/support/small_vector.h +++ b/src/support/small_vector.h @@ -53,14 +53,6 @@ template class SmallVector { // flexible additional storage std::vector flexible; -#if defined(__aarch64__) -#pragma GCC diagnostic pop -#endif - -#if defined(__riscv) && __riscv_xlen == 64 -#pragma GCC diagnostic pop -#endif - public: using value_type = T; @@ -286,6 +278,14 @@ struct ZeroInitSmallVector : public SmallVector { } }; +#if defined(__aarch64__) +#pragma GCC diagnostic pop +#endif + +#if defined(__riscv) && __riscv_xlen == 64 +#pragma GCC diagnostic pop +#endif + } // namespace wasm #endif // wasm_support_small_vector_h From 3d3a38700f29236eb7e66742fdc2df8616ab7599 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2024 11:41:28 -0700 Subject: [PATCH 092/622] [GC] RemoveUnusedBrs: Ensure refining of BrOnCast's castType does not unrefine the output (#7036) Paradoxically, when a BrOn's castType is refined, its own type (the type it flows out) can get un-refined: making the castType non-nullable means nulls no longer flow on the branch, so they may flow out directly, making the BrOn nullable. --- src/passes/RemoveUnusedBrs.cpp | 26 +++++++ test/lit/passes/remove-unused-brs-gc.wast | 84 +++++++++++++++++++---- 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 2452130cc87..2ccf3c76192 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -898,8 +898,34 @@ struct RemoveUnusedBrs : public WalkerPass> { auto glb = Type::getGreatestLowerBound(curr->castType, refType); if (glb != Type::unreachable && glb != curr->castType) { curr->castType = glb; + auto oldType = curr->type; curr->finalize(); worked = true; + + // We refined the castType, which may *un*-refine the BrOn itself. + // Imagine the castType was nullable before, then nulls would go on + // the branch, and so the BrOn could only flow out a non-nullable + // value, and that was its type. If we refine the castType to be + // non-nullable then nulls no longer go through, making the BrOn + // itself nullable. This should not normally happen, but can occur + // because we look at the fallthrough of the ref: + // + // (br_on_cast + // (local.tee $unrefined + // (refined + // + // That is, we may see a more refined type for our GLB computation + // than the wasm type system does, if a local.tee or such ends up + // unrefining the type. + // + // To check for this and fix it, see if we need a cast in order to be + // a subtype of the old type. + auto* rep = maybeCast(curr, oldType); + if (rep != curr) { + replaceCurrent(rep); + // Exit after doing so, leaving further work for other cycles. + return; + } } // Depending on what we know about the cast results, we may be able to diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 42c2f4b1c18..0a9e0885873 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -13,7 +13,10 @@ (type $substruct (sub $struct (struct))) ) - ;; CHECK: (func $br_on-if (type $7) (param $0 (ref struct)) + ;; CHECK: (type $struct-nn (struct (field (ref any)))) + (type $struct-nn (struct (field (ref any)))) + + ;; CHECK: (func $br_on-if (type $8) (param $0 (ref struct)) ;; CHECK-NEXT: (block $label ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (select (result (ref struct)) @@ -48,7 +51,7 @@ ) ) - ;; CHECK: (func $br_on_cast (type $4) (result (ref $struct)) + ;; CHECK: (func $br_on_cast (type $5) (result (ref $struct)) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (block $block (result (ref $struct)) ;; CHECK-NEXT: (drop @@ -99,7 +102,7 @@ ) ) - ;; CHECK: (func $br_on_cast-fallthrough (type $4) (result (ref $struct)) + ;; CHECK: (func $br_on_cast-fallthrough (type $5) (result (ref $struct)) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (local $any anyref) ;; CHECK-NEXT: (block $block (result (ref $struct)) @@ -162,7 +165,7 @@ ) ) - ;; CHECK: (func $nested_br_on_cast (type $8) (result i31ref) + ;; CHECK: (func $nested_br_on_cast (type $9) (result i31ref) ;; CHECK-NEXT: (block $label$1 (result (ref i31)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br $label$1 @@ -190,7 +193,7 @@ ) ) - ;; CHECK: (func $br_on_cast_unrelated (type $5) (result (ref null $struct)) + ;; CHECK: (func $br_on_cast_unrelated (type $6) (result (ref null $struct)) ;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2)) ;; CHECK-NEXT: (block $block (result nullref) ;; CHECK-NEXT: (drop @@ -244,7 +247,7 @@ ) ) - ;; CHECK: (func $br_on_cast_unrelated-fallthrough (type $5) (result (ref null $struct)) + ;; CHECK: (func $br_on_cast_unrelated-fallthrough (type $6) (result (ref null $struct)) ;; CHECK-NEXT: (local $any anyref) ;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2)) ;; CHECK-NEXT: (block $block (result nullref) @@ -254,8 +257,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $any - ;; CHECK-NEXT: (struct.new_default $struct2) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.tee $any + ;; CHECK-NEXT: (struct.new_default $struct2) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -285,7 +290,8 @@ ) ) (drop - ;; Still not taken. + ;; Still not taken. Note that we start by flowing out a non-nullable value, + ;; and will add a cast to ensure we still do after optimization. (br_on_cast $block anyref (ref null $struct) (local.tee $any (struct.new $struct2)) ) @@ -543,7 +549,7 @@ ) ) - ;; CHECK: (func $br_on_cast-unreachable (type $6) (param $i31ref i31ref) (result anyref) + ;; CHECK: (func $br_on_cast-unreachable (type $7) (param $i31ref i31ref) (result anyref) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -599,7 +605,7 @@ ) ) - ;; CHECK: (func $fallthrough-unreachable (type $6) (param $0 i31ref) (result anyref) + ;; CHECK: (func $fallthrough-unreachable (type $7) (param $0 i31ref) (result anyref) ;; CHECK-NEXT: (block $outer ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) @@ -638,7 +644,7 @@ ) ) - ;; CHECK: (func $casts-are-costly (type $9) (param $x i32) + ;; CHECK: (func $casts-are-costly (type $10) (param $x i32) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (if (result i32) @@ -783,7 +789,7 @@ ) ) - ;; CHECK: (func $threading (type $10) (param $x anyref) + ;; CHECK: (func $threading (type $11) (param $x anyref) ;; CHECK-NEXT: (block $outer ;; CHECK-NEXT: (block $inner ;; CHECK-NEXT: (drop @@ -806,4 +812,56 @@ ) ) ) + + ;; CHECK: (func $test (type $12) (param $x (ref any)) + ;; CHECK-NEXT: (local $temp anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref $struct-nn)) + ;; CHECK-NEXT: (struct.new $struct-nn + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (br_on_cast $block anyref (ref $struct-nn) + ;; CHECK-NEXT: (local.tee $temp + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $x (ref any)) + (local $temp anyref) + ;; Read the inline comments blow from the bottom to the top (order of + ;; execution). Basically, the story is that the br_on_cast begins as + ;; flowing out a non-nullable type, since the cast allows nulls (so only a + ;; non-null can flow out). We can see that the br_on_cast receives a non- + ;; nullable value, even though it flows through a local.tee that un-refines + ;; it. Using the non-nullability, we can refine the cast type (type sent on + ;; the branch) to be non-nullable. But then the type of the br_on_cast itself + ;; becomes nullable, since nulls no longer get sent on the branch, which + ;; breaks the parent that must receive a non-nullable value. + ;; + ;; To fix this, we add a cast on the br's output, forcing it to the exact + ;; same type it had before. + (drop + (block $block (result anyref) + (struct.new $struct-nn ;; must provide a NON- + ;; nullable value for the + ;; struct field + + (br_on_cast $block anyref (ref null $struct-nn) ;; GLB on the castType + ;; makes it non-nullable, + ;; which makes the type + ;; of the br_on_cast + ;; nullable + + (local.tee $temp ;; nullable + + (local.get $x) ;; non-nullable + ) + ) + ) + ) + ) + ) ) From 2a1cb314d6a028cfd54374f89a47e111678c3d25 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Oct 2024 11:41:43 -0700 Subject: [PATCH 093/622] [GC] Fix handling of public types in TypeRefining (#7037) --- src/passes/TypeRefining.cpp | 26 ++++-- test/lit/passes/type-refining.wast | 137 +++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 5 deletions(-) diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index c7a1fce6e44..ee12c388df9 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -137,6 +137,11 @@ struct TypeRefining : public Pass { // that we can avoid wasteful work later if not. bool canOptimize = false; + // We cannot modify public types. + auto publicTypes = ModuleUtils::getPublicHeapTypes(*module); + std::unordered_set publicTypesSet(publicTypes.begin(), + publicTypes.end()); + // We have combined all the information we have about writes to the fields, // but we still need to make sure that the new types makes sense. In // particular, subtyping cares about things like mutability, and we also @@ -155,6 +160,14 @@ struct TypeRefining : public Pass { while (!work.empty()) { auto type = work.pop(); + for (auto subType : subTypes.getImmediateSubTypes(type)) { + work.push(subType); + } + + if (publicTypesSet.count(type)) { + continue; + } + // First, find fields that have nothing written to them at all, and set // their value to their old type. We must pick some type for the field, // and we have nothing better to go on. (If we have a super, and it does @@ -173,7 +186,14 @@ struct TypeRefining : public Pass { if (auto super = type.getDeclaredSuperType()) { auto& superFields = super->getStruct().fields; for (Index i = 0; i < superFields.size(); i++) { - auto newSuperType = finalInfos[*super][i].getLUB(); + // The super's new type is either what we propagated, or, if it is + // public, unchanged since we cannot optimize it + Type newSuperType; + if (!publicTypesSet.count(*super)) { + newSuperType = finalInfos[*super][i].getLUB(); + } else { + newSuperType = superFields[i].type; + } auto& info = finalInfos[type][i]; auto newType = info.getLUB(); if (!Type::isSubType(newType, newSuperType)) { @@ -215,10 +235,6 @@ struct TypeRefining : public Pass { canOptimize = true; } } - - for (auto subType : subTypes.getImmediateSubTypes(type)) { - work.push(subType); - } } if (canOptimize) { diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 5689ff3dc59..8e8ccf24ccd 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1383,3 +1383,140 @@ ) ) ) + +;; We cannot refine the fields of public types. One of $A's children is public +;; here, $B. That makes $A public as well, leaving only $C as theoretically +;; optimizable, but we cannot refine a mutable field in a way that makes it +;; differ from the super, so we end up doing nothing here. +(module + ;; CHECK: (type $A (sub (struct (field (mut anyref))))) + (type $A (sub (struct (field (mut anyref))))) + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $B (sub $A (struct (field (mut anyref))))) + (type $B (sub $A (struct (field (mut anyref))))) + ;; CHECK: (type $brand (struct)) + (type $brand (struct)) + ) + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $C (sub $A (struct (field (mut anyref))))) + (type $C (sub $A (struct (field (mut anyref))))) + ;; CHECK: (type $brand2 (struct)) + (type $brand2 (struct)) + ;; CHECK: (type $brand3 (struct)) + (type $brand3 (struct)) + ) + + ;; CHECK: (type $6 (func (param (ref any)))) + + ;; CHECK: (global $global (ref null $B) (ref.null none)) + (global $global (ref null $B) (ref.null $B)) + + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) + + ;; CHECK: (func $work (type $6) (param $nn (ref any)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (local.get $nn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (local.get $nn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $C + ;; CHECK-NEXT: (local.get $nn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $nn (ref any)) + ;; All the types look refinable, as we write a non-nullable value. + (drop + (struct.new $A + (local.get $nn) + ) + ) + (drop + (struct.new $B + (local.get $nn) + ) + ) + (drop + (struct.new $C + (local.get $nn) + ) + ) + ) +) + +;; As above, but now the fields are all immutable. This allows us to refine $C, +;; but nothing else. +(module + ;; CHECK: (type $A (sub (struct (field anyref)))) + (type $A (sub (struct (field anyref)))) + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $B (sub $A (struct (field anyref)))) + (type $B (sub $A (struct (field anyref)))) + ;; CHECK: (type $brand (struct)) + (type $brand (struct)) + ) + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $C (sub $A (struct (field (ref any))))) + (type $C (sub $A (struct (field anyref)))) + (type $brand2 (struct)) + (type $brand3 (struct)) + ) + + ;; CHECK: (type $4 (func (param (ref any)))) + + ;; CHECK: (global $global (ref null $B) (ref.null none)) + (global $global (ref null $B) (ref.null $B)) + + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) + + ;; CHECK: (func $work (type $4) (param $nn (ref any)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (local.get $nn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (local.get $nn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $C + ;; CHECK-NEXT: (local.get $nn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $nn (ref any)) + (drop + (struct.new $A + (local.get $nn) + ) + ) + (drop + (struct.new $B + (local.get $nn) + ) + ) + (drop + (struct.new $C + (local.get $nn) + ) + ) + ) +) From a0f77adf4f05b47c5f2ecf87279f191195880393 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Oct 2024 08:23:46 -0700 Subject: [PATCH 094/622] [NFC] Use more precise types for Expression IDs (#7038) Make the ID enum an `int8_t`, and make the Specific ID a `constexpr` of that type. This seems more idiomatic and makes some code simpler, see the change to `find_all.h` which no longer needs a cast to compile. This has no performance impact. --- src/ir/find_all.h | 2 +- src/wasm.h | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ir/find_all.h b/src/ir/find_all.h index 77edafe8995..708517124f3 100644 --- a/src/ir/find_all.h +++ b/src/ir/find_all.h @@ -64,7 +64,7 @@ template struct FindAllPointers { // take \ast by reference. FindAllPointers(Expression*& ast) { PointerFinder finder; - finder.id = (Expression::Id)T::SpecificId; + finder.id = T::SpecificId; finder.list = &list; finder.walk(ast); } diff --git a/src/wasm.h b/src/wasm.h index 4901723fd59..85473b1f957 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -647,7 +647,7 @@ enum StringEqOp { class Expression { public: - enum Id { + enum Id : uint8_t { InvalidId = 0, BlockId, IfId, @@ -805,9 +805,8 @@ using ExpressionList = ArenaVector; template class SpecificExpression : public Expression { public: - enum { - SpecificId = SID // compile-time access to the type for the class - }; + // Compile-time access to the type for the class. + static constexpr Id SpecificId = SID; SpecificExpression() : Expression(SID) {} }; From 39bf87eb39543ca14198a16533f262a147816793 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Wed, 30 Oct 2024 16:20:38 -0700 Subject: [PATCH 095/622] Don't strip target features in wasm-emscripten-finalize (#7043) This makes the behavior consistent with emcc builds where we don't run finalization, and potentially makes testing and debugging easier. Emscripten still strips the target features section when optimizing. --- src/tools/wasm-emscripten-finalize.cpp | 2 -- test/lld/em_asm_pthread.wasm.out | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp index 505e783496d..53e3be196dc 100644 --- a/src/tools/wasm-emscripten-finalize.cpp +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -266,8 +266,6 @@ int main(int argc, const char* argv[]) { passRunner.add("legalize-js-interface"); } - passRunner.add("strip-target-features"); - // If DWARF is unused, strip it out. This avoids us keeping it alive // until wasm-opt strips it later. if (!DWARF) { diff --git a/test/lld/em_asm_pthread.wasm.out b/test/lld/em_asm_pthread.wasm.out index 3b51b896ab7..f275876a937 100644 --- a/test/lld/em_asm_pthread.wasm.out +++ b/test/lld/em_asm_pthread.wasm.out @@ -12830,4 +12830,5 @@ ) ) ;; custom section "producers", size 172 + ;; features section: threads, mutable-globals, bulk-memory, sign-ext ) From 73279d36fe08e867ca3d9adb85d53b4c67dd16d6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 31 Oct 2024 09:35:30 -0700 Subject: [PATCH 096/622] [NFC] Fix copy-paste error in TryTable printing (#7044) --- src/passes/Print.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 106beac39be..854ea0b5b4d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2818,7 +2818,7 @@ void PrintSExpression::visitTryTable(TryTable* curr) { maybePrintImplicitBlock(curr->body); decIndent(); if (full) { - o << " ;; end if"; + o << " ;; end try_table"; } controlFlowDepth--; } From e32b76d6325f0997fabef20cc546526075db09a4 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 31 Oct 2024 19:59:39 +0100 Subject: [PATCH 097/622] Require reference-types in addition to bulk-memory for table.fill (#7040) table.fill was introduced by the reference-types proposal (but also, only makes sense among the other bulk memory operations, so require both). --- src/wasm/wasm-validator.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index d1ca5220c3b..5867c04a06a 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2454,9 +2454,11 @@ void FunctionValidator::visitTableGrow(TableGrow* curr) { } void FunctionValidator::visitTableFill(TableFill* curr) { - shouldBeTrue(getModule()->features.hasBulkMemory(), + shouldBeTrue(getModule()->features.hasBulkMemory() && + getModule()->features.hasReferenceTypes(), curr, - "table.fill requires bulk-memory [--enable-bulk-memory]"); + "table.fill requires bulk-memory [--enable-bulk-memory] and " + "reference-types [--enable-reference-types]"); auto* table = getModule()->getTableOrNull(curr->table); if (shouldBeTrue(!!table, curr, "table.fill table must exist")) { shouldBeSubType(curr->value->type, From 1b066cb3101dade3fe5be69218a7de41fa79599f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 31 Oct 2024 13:54:21 -0700 Subject: [PATCH 098/622] Fuzz the Table from JS (#7042) Continues the work from #7027 which added throwing from JS, this adds table get/set operations from JS, to further increase our coverage of Wasm/JS interactions (the table can be used from both sides). --- scripts/fuzz_opt.py | 39 ++++++--- scripts/fuzz_shell.js | 10 ++- src/tools/execution-results.h | 48 ++++++++-- src/tools/fuzzing.h | 5 ++ src/tools/fuzzing/fuzzing.cpp | 70 +++++++++++++++ test/lit/exec/fuzzing-api.wast | 61 ++++++++++++- ...e-to-fuzz_all-features_metrics_noprint.txt | 87 +++++++++---------- 7 files changed, 254 insertions(+), 66 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 838ed54cc81..3d466350120 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1178,11 +1178,36 @@ def can_run_on_feature_opts(self, feature_opts): return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'multimemory']) +# given a wasm, find all the exports of particular kinds (for example, kinds +# can be ['func', 'table'] and then we would find exported functions and +# tables). +def get_exports(wasm, kinds): + wat = run([in_bin('wasm-dis'), wasm] + FEATURE_OPTS) + p = re.compile(r'^ [(]export "(.*[^\\]?)" [(](?:' + '|'.join(kinds) + ')') + exports = [] + for line in wat.splitlines(): + m = p.match(line) + if m: + export = m[1] + exports.append(export) + return exports + + # given a wasm and a list of exports we want to keep, remove all other exports. def filter_exports(wasm, output, keep): # based on # https://github.com/WebAssembly/binaryen/wiki/Pruning-unneeded-code-in-wasm-files-with-wasm-metadce#example-pruning-exports + # we append to keep; avoid modifying the object that was sent in. + keep = keep[:] + + # some exports must always be preserved, if they exist, like the table + # (which can be called from JS imports for table operations). + existing_exports = set(get_exports(wasm, ['func', 'table'])) + for export in ['table']: + if export in existing_exports: + keep.append(export) + # build json to represent the exports we want. graph = [{ 'name': 'outside', @@ -1304,18 +1329,10 @@ def handle(self, wasm): # get the expected execution results. wasm_exec = run_bynterp(wasm, ['--fuzz-exec-before']) - # get the list of exports, so we can tell ctor-eval what to eval. - wat = run([in_bin('wasm-dis'), wasm] + FEATURE_OPTS) - p = re.compile(r'^ [(]export "(.*[^\\]?)" [(]func') - exports = [] - for line in wat.splitlines(): - m = p.match(line) - if m: - export = m[1] - exports.append(export) - if not exports: + # get the list of func exports, so we can tell ctor-eval what to eval. + ctors = ','.join(get_exports(wasm, ['func'])) + if not ctors: return - ctors = ','.join(exports) # eval the wasm. # we can use --ignore-external-input because the fuzzer passes in 0 to diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 4cf3ba35866..c4c0056f0bb 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -152,7 +152,15 @@ var imports = { // Throw an exception from JS. 'throw': () => { throw 'some JS error'; - } + }, + + // Table operations. + 'table-get': (index) => { + return exports.table.get(index >>> 0); + }, + 'table-set': (index, value) => { + exports.table.set(index >>> 0, value); + }, }, // Emscripten support. 'env': { diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index d9fefc44ec7..eb302b4b2ba 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -38,8 +38,19 @@ struct LoggingExternalInterface : public ShellExternalInterface { uint32_t tempRet0 = 0; } state; + // The name of the table exported by the name 'table.' Imports access it. + Name exportedTable; + public: - LoggingExternalInterface(Loggings& loggings) : loggings(loggings) {} + LoggingExternalInterface(Loggings& loggings, Module& wasm) + : loggings(loggings) { + for (auto& exp : wasm.exports) { + if (exp->kind == ExternalKind::Table && exp->name == "table") { + exportedTable = exp->value; + break; + } + } + } Literals callImport(Function* import, const Literals& arguments) override { if (import->module == "fuzzing-support") { @@ -66,9 +77,28 @@ struct LoggingExternalInterface : public ShellExternalInterface { std::cout << "]\n"; return {}; } else if (import->base == "throw") { - // Throw something. We use a (hopefully) private name here. - auto payload = std::make_shared("__private", Literals{}); - throwException(WasmException{Literal(payload)}); + throwEmptyException(); + } else if (import->base == "table-get") { + // Check for errors here, duplicating tableLoad(), because that will + // trap, and we just want to throw an exception (the same as JS would). + if (!exportedTable) { + throwEmptyException(); + } + Index index = arguments[0].geti32(); + if (index >= tables[exportedTable].size()) { + throwEmptyException(); + } + return {tableLoad(exportedTable, index)}; + } else if (import->base == "table-set") { + if (!exportedTable) { + throwEmptyException(); + } + Index index = arguments[0].geti32(); + if (index >= tables[exportedTable].size()) { + throwEmptyException(); + } + tableStore(exportedTable, index, arguments[1]); + return {}; } else { WASM_UNREACHABLE("unknown fuzzer import"); } @@ -91,6 +121,12 @@ struct LoggingExternalInterface : public ShellExternalInterface { << import->module << " . " << import->base << '\n'; return {}; } + + void throwEmptyException() { + // Use a hopefully private tag. + auto payload = std::make_shared("__private", Literals{}); + throwException(WasmException{Literal(payload)}); + } }; // gets execution results from a wasm module. this is useful for fuzzing @@ -109,7 +145,7 @@ struct ExecutionResults { // get results of execution void get(Module& wasm) { - LoggingExternalInterface interface(loggings); + LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); // execute all exported methods (that are therefore preserved through @@ -259,7 +295,7 @@ struct ExecutionResults { bool operator!=(ExecutionResults& other) { return !((*this) == other); } FunctionResult run(Function* func, Module& wasm) { - LoggingExternalInterface interface(loggings); + LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); return run(func, wasm, instance); diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 69fee656c07..0ecc751a417 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -107,6 +107,8 @@ class TranslateToFuzzReader { std::unordered_map logImportNames; Name throwImportName; + Name tableGetImportName; + Name tableSetImportName; std::unordered_map> globalsByType; std::unordered_map> mutableGlobalsByType; @@ -228,12 +230,15 @@ class TranslateToFuzzReader { void addImportLoggingSupport(); // An import that we call to throw an exception from outside. void addImportThrowingSupport(); + void addImportTableSupport(); void addHashMemorySupport(); // Special expression makers Expression* makeHangLimitCheck(); Expression* makeImportLogging(); Expression* makeImportThrowing(Type type); + Expression* makeImportTableGet(); + Expression* makeImportTableSet(Type type); Expression* makeMemoryHashLogging(); // Function creation diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b7d075dccfb..5dcdc66d5b0 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -178,6 +178,9 @@ void TranslateToFuzzReader::build() { setupTags(); addImportThrowingSupport(); } + if (wasm.features.hasReferenceTypes()) { + addImportTableSupport(); + } addImportLoggingSupport(); modifyInitialFunctions(); // keep adding functions until we run out of input @@ -609,6 +612,49 @@ void TranslateToFuzzReader::addImportThrowingSupport() { wasm.addFunction(std::move(func)); } +void TranslateToFuzzReader::addImportTableSupport() { + // For the table imports to be able to do anything, we must export a table + // for them. For simplicity, use the funcref table we use internally, though + // we could pick one at random, support non-funcref ones, and even export + // multiple ones TODO + if (!funcrefTableName) { + return; + } + + // If a "table" export already exists, skip fuzzing these imports, as the + // current export may not contain a valid table for it. + if (wasm.getExportOrNull("table")) { + return; + } + + // Export the table. + wasm.addExport( + builder.makeExport("table", funcrefTableName, ExternalKind::Table)); + + // Get from the table. + { + tableGetImportName = Names::getValidFunctionName(wasm, "table-get"); + auto func = std::make_unique(); + func->name = tableGetImportName; + func->module = "fuzzing-support"; + func->base = "table-get"; + func->type = Signature({Type::i32}, Type(HeapType::func, Nullable)); + wasm.addFunction(std::move(func)); + } + + // Set into the table. + { + tableSetImportName = Names::getValidFunctionName(wasm, "table-set"); + auto func = std::make_unique(); + func->name = tableSetImportName; + func->module = "fuzzing-support"; + func->base = "table-set"; + func->type = + Signature({Type::i32, Type(HeapType::func, Nullable)}, Type::none); + wasm.addFunction(std::move(func)); + } +} + void TranslateToFuzzReader::addHashMemorySupport() { // Add memory hasher helper (for the hash, see hash.h). The function looks // like: @@ -722,6 +768,21 @@ Expression* TranslateToFuzzReader::makeImportThrowing(Type type) { return builder.makeCall(throwImportName, {}, Type::none); } +Expression* TranslateToFuzzReader::makeImportTableGet() { + assert(tableGetImportName); + return builder.makeCall( + tableGetImportName, {make(Type::i32)}, Type(HeapType::func, Nullable)); +} + +Expression* TranslateToFuzzReader::makeImportTableSet(Type type) { + assert(type == Type::none); + assert(tableSetImportName); + return builder.makeCall( + tableSetImportName, + {make(Type::i32), makeBasicRef(Type(HeapType::func, Nullable))}, + Type::none); +} + Expression* TranslateToFuzzReader::makeMemoryHashLogging() { auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32); return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none); @@ -1489,6 +1550,9 @@ Expression* TranslateToFuzzReader::_makenone() { .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArrayBulkMemoryOp); + if (tableSetImportName) { + options.add(FeatureSet::ReferenceTypes, &Self::makeImportTableSet); + } return (this->*pick(options))(Type::none); } @@ -2679,6 +2743,12 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return null; } case HeapType::func: { + // Rarely, emit a call to imported table.get (when nullable, unshared, and + // where we can emit a call). + if (type.isNullable() && share == Unshared && funcContext && + tableGetImportName && !oneIn(3)) { + return makeImportTableGet(); + } return makeRefFuncConst(type); } case HeapType::cont: { diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index d37d7ef4a35..0d0f25130c4 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -10,6 +10,13 @@ (import "fuzzing-support" "throw" (func $throw)) + (import "fuzzing-support" "table-set" (func $table.set (param i32 funcref))) + (import "fuzzing-support" "table-get" (func $table.get (param i32) (result funcref))) + + (table $table 10 20 funcref) + + (export "table" (table $table)) + ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] @@ -24,10 +31,52 @@ ;; CHECK: [fuzz-exec] calling throwing ;; CHECK-NEXT: [exception thrown: __private ()] - ;; CHECK-NEXT: warning: no passes specified, not doing any work (func $throwing (export "throwing") (call $throw) ) + + ;; CHECK: [fuzz-exec] calling table.setting + ;; CHECK-NEXT: [exception thrown: __private ()] + (func $table.setting (export "table.setting") + (call $table.set + (i32.const 5) + (ref.func $table.setting) + ) + ;; Out of bounds sets will throw. + (call $table.set + (i32.const 9999) + (ref.func $table.setting) + ) + ) + + ;; CHECK: [fuzz-exec] calling table.getting + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: warning: no passes specified, not doing any work + (func $table.getting (export "table.getting") + ;; There is a non-null value at 5, and a null at 6. + (call $log-i32 + (ref.is_null + (call $table.get + (i32.const 5) + ) + ) + ) + (call $log-i32 + (ref.is_null + (call $table.get + (i32.const 6) + ) + ) + ) + ;; Out of bounds gets will throw. + (drop + (call $table.get + (i32.const 9999) + ) + ) + ) ) ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -35,5 +84,15 @@ ;; CHECK: [fuzz-exec] calling throwing ;; CHECK-NEXT: [exception thrown: __private ()] + +;; CHECK: [fuzz-exec] calling table.setting +;; CHECK-NEXT: [exception thrown: __private ()] + +;; CHECK: [fuzz-exec] calling table.getting +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [exception thrown: __private ()] ;; CHECK-NEXT: [fuzz-exec] comparing logging +;; CHECK-NEXT: [fuzz-exec] comparing table.getting +;; CHECK-NEXT: [fuzz-exec] comparing table.setting ;; CHECK-NEXT: [fuzz-exec] comparing throwing diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index c17c9cd1eea..3189277b070 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,58 +1,51 @@ Metrics total - [exports] : 3 - [funcs] : 4 + [exports] : 4 + [funcs] : 3 [globals] : 26 - [imports] : 6 + [imports] : 8 [memories] : 1 [memory-data] : 20 [table-data] : 0 [tables] : 1 [tags] : 2 - [total] : 665 - [vars] : 20 - ArrayGet : 2 - ArrayLen : 2 - ArrayNew : 15 - ArrayNewFixed : 3 - AtomicCmpxchg : 1 - AtomicRMW : 1 - Binary : 71 - Block : 63 - BrOn : 4 - Break : 7 - Call : 18 - CallRef : 2 - Const : 142 - Drop : 10 - GlobalGet : 33 + [total] : 534 + [vars] : 21 + ArrayGet : 1 + ArrayLen : 1 + ArrayNew : 13 + ArrayNewFixed : 2 + AtomicNotify : 2 + Binary : 68 + Block : 53 + BrOn : 1 + Break : 8 + Call : 11 + CallRef : 1 + Const : 117 + DataDrop : 1 + Drop : 7 + GlobalGet : 27 GlobalSet : 16 - I31Get : 1 - If : 21 - Load : 20 - LocalGet : 61 - LocalSet : 52 + If : 13 + Load : 19 + LocalGet : 56 + LocalSet : 39 Loop : 6 - MemoryFill : 1 - MemoryInit : 1 - Nop : 7 - Pop : 1 - RefAs : 11 - RefEq : 1 - RefFunc : 3 - RefI31 : 1 - RefNull : 14 - Return : 5 - SIMDExtract : 1 - Select : 2 - StringConst : 5 - StringEncode : 1 - StructGet : 4 - StructNew : 16 - StructSet : 1 - Try : 1 + Nop : 2 + Pop : 3 + RefAs : 4 + RefFunc : 2 + RefNull : 7 + RefTest : 1 + Return : 3 + SIMDExtract : 3 + Store : 2 + StringConst : 2 + StringWTF16Get : 1 + StructNew : 11 + Try : 3 TryTable : 1 - TupleExtract : 2 - TupleMake : 6 - Unary : 20 - Unreachable : 9 + TupleMake : 3 + Unary : 14 + Unreachable : 10 From 3b301784b86eada1ed2a9c52cbfd3a04564420fc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 1 Nov 2024 16:12:02 -0700 Subject: [PATCH 099/622] [NFC] Use RAII to manage call depth tracking in the interpreter (#7049) The old code manually managed it for no good reason that I can see. After this, there is no difference between callFunction and callFunctionInternal, so fold them together. --- src/shell-interface.h | 2 +- src/tools/wasm-ctor-eval.cpp | 2 +- src/wasm-interpreter.h | 37 ++++++++++-------------------------- 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src/shell-interface.h b/src/shell-interface.h index 1efa31f79c2..6101df29a02 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -191,7 +191,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { if (func->imported()) { return callImport(func, arguments); } else { - return instance.callFunctionInternal(func->name, arguments); + return instance.callFunction(func->name, arguments); } } diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 464654d4f07..72ad459d329 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -350,7 +350,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { targetFunc.toString()); } if (!func->imported()) { - return instance.callFunctionInternal(targetFunc, arguments); + return instance.callFunction(targetFunc, arguments); } else { throw FailToEvalException( std::string("callTable on imported function: ") + diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c5019b2f3bc..5f20819a1c8 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3028,6 +3028,8 @@ class ModuleRunnerBase : public ExpressionRunner { : function(function), parent(parent) { oldScope = parent.scope; parent.scope = this; + parent.callDepth++; + parent.functionStack.push_back(function->name); if (function->getParams().size() != arguments.size()) { std::cerr << "Function `" << function->name << "` expects " @@ -3053,7 +3055,11 @@ class ModuleRunnerBase : public ExpressionRunner { } } - ~FunctionScope() { parent.scope = oldScope; } + ~FunctionScope() { + parent.scope = oldScope; + parent.callDepth--; + parent.functionStack.pop_back(); + } // The current delegate target, if delegation of an exception is in // progress. If no delegation is in progress, this will be an empty Name. @@ -3111,7 +3117,7 @@ class ModuleRunnerBase : public ExpressionRunner { return Flow(RETURN_CALL_FLOW, std::move(arguments)); } - Flow ret = callFunctionInternal(target, arguments); + Flow ret = callFunction(target, arguments); #ifdef WASM_INTERPRETER_DEBUG std::cout << "(returned to " << scope->function->name << ")\n"; #endif @@ -3176,7 +3182,7 @@ class ModuleRunnerBase : public ExpressionRunner { return Flow(RETURN_CALL_FLOW, std::move(arguments)); } - Flow ret = callFunctionInternal(targetRef.getFunc(), arguments); + Flow ret = callFunction(targetRef.getFunc(), arguments); #ifdef WASM_INTERPRETER_DEBUG std::cout << "(returned to " << scope->function->name << ")\n"; #endif @@ -4297,18 +4303,7 @@ class ModuleRunnerBase : public ExpressionRunner { return value; } - // Call a function, starting an invocation. - Literals callFunction(Name name, const Literals& arguments) { - // If the last call ended in a jump up the stack, it might have left stuff - // for us to clean up here - callDepth = 0; - functionStack.clear(); - return callFunctionInternal(name, arguments); - } - - // Internal function call. Must be public so that callTable implementations - // can use it (refactor?) - Literals callFunctionInternal(Name name, Literals arguments) { + Literals callFunction(Name name, Literals arguments) { if (callDepth > maxDepth) { hostLimit("stack limit"); } @@ -4332,11 +4327,6 @@ class ModuleRunnerBase : public ExpressionRunner { return externalInterface->callImport(function, arguments); } - auto previousCallDepth = callDepth; - callDepth++; - auto previousFunctionStackSize = functionStack.size(); - functionStack.push_back(name); - FunctionScope scope(function, arguments, *self()); #ifdef WASM_INTERPRETER_DEBUG @@ -4348,13 +4338,6 @@ class ModuleRunnerBase : public ExpressionRunner { flow = self()->visit(function->body); - // may decrease more than one, if we jumped up the stack - callDepth = previousCallDepth; - // if we jumped up the stack, we also need to pop higher frames - // TODO can FunctionScope handle this automatically? - while (functionStack.size() > previousFunctionStackSize) { - functionStack.pop_back(); - } #ifdef WASM_INTERPRETER_DEBUG std::cout << "exiting " << function->name << " with " << flow.values << '\n'; From 9f496abc5cf1ddbca8c8a4f4e740c412588708ef Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 1 Nov 2024 17:35:35 -0700 Subject: [PATCH 100/622] Module splitting: don't create new tables when splitting with Emscripten (#7050) Emscripten's JS loader code for wasm-split isn't prepared for handling multiple tables that binaryen automatically creates when reference types are enabled (especially in conjunction with dynamic loading). For now, disable creation of multiple tables when using Emscripten's table ABI (distinguished by importing or exporting a table named "__indirect_function_table". --- src/ir/module-splitting.cpp | 24 ++++++++++++++++++------ src/wasm/wasm.cpp | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index f299d7a3554..a7ee96fa28f 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -146,12 +146,24 @@ void TableSlotManager::addSlot(Name func, Slot slot) { } TableSlotManager::TableSlotManager(Module& module) : module(module) { - if (module.features.hasReferenceTypes()) { - // Just create a new table to manage all primary-to-secondary calls lazily. - // Do not re-use slots for functions that will already be in existing - // tables, since that is not correct in the face of table mutations. - // TODO: Reduce overhead by creating a separate table for each function type - // if WasmGC is enabled. + // If possible, just create a new table to manage all primary-to-secondary + // calls lazily. Do not re-use slots for functions that will already be in + // existing tables, since that is not correct in the face of table mutations. + // However, do not do this for emscripten; its loader code (and dynamic + // loading in particular) do not support this yet. + // TODO: Reduce overhead by creating a separate table for each function type + // if WasmGC is enabled. + Export* emscriptenTableExport = + module.getExportOrNull("__indirect_function_table"); + Table* singletonTable = + module.tables.size() == 1 ? module.tables[0].get() : nullptr; + bool emscriptenTableImport = + singletonTable && singletonTable->imported() && + singletonTable->module == "env" && + singletonTable->base == "__indirect_function_table"; + + if (module.features.hasReferenceTypes() && !emscriptenTableExport && + !emscriptenTableImport) { return; } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 4150af5b41a..a1ac076e08f 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1631,7 +1631,7 @@ Importable* Module::getImport(ModuleItemKind kind, Name name) { Importable* Module::getImportOrNull(ModuleItemKind kind, Name name) { auto doReturn = [](Importable* importable) { - return importable->imported() ? importable : nullptr; + return importable ? importable->imported() ? importable : nullptr : nullptr; }; switch (kind) { From c35e9871697b0b103228a96e9e79066e762d5e22 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 Nov 2024 12:56:46 -0800 Subject: [PATCH 101/622] Make 32-bit hashing identical to 64-bit in TypeSSA (#7048) This is NFC on 64-bit systems but noticeable on 32. Also remove the 32-bit path in hash_combine. That isn't necessary for this fix, but it makes the code simpler and also makes debugging between systems simpler. It might also avoid problems in future cases, if we are lucky. The only cost is perhaps a slight slowdown on 32-bit systems, which seems worth it. Fixes #7046 --- src/passes/TypeSSA.cpp | 10 +++++++--- src/support/hash.h | 22 ++++++++++++---------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 8ba6c10c490..cda78474bc1 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -92,8 +92,12 @@ std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, // "random" extra item in the rec group that is so outlandish it will // surely (?) never collide with anything. We must loop while doing so, // until we find a hash that does not collide. - auto hashSize = num + 10; - size_t random = num; + // + // Note that we use uint64_t here, and deterministic_hash_combine below, to + // ensure our output is fully deterministic - the types we add here are + // observable in the output. + uint64_t hashSize = num + 10; + uint64_t random = num; while (1) { // Make a builder and add a slot for the hash. TypeBuilder builder(num + 1); @@ -106,7 +110,7 @@ std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, for (Index i = 0; i < hashSize; i++) { // TODO: a denser encoding? auto type = (random & 1) ? Type::i32 : Type::f64; - hash_combine(random, hashSize + i); + deterministic_hash_combine(random, hashSize + i); hashStruct.fields.push_back(Field(type, Mutable)); } builder[num] = hashStruct; diff --git a/src/support/hash.h b/src/support/hash.h index b9990261641..ba1001d6674 100644 --- a/src/support/hash.h +++ b/src/support/hash.h @@ -28,29 +28,31 @@ template inline std::size_t hash(const T& value) { } // Combines two digests into the first digest. Use instead of `rehash` if -// `otherDigest` is another digest and not a `size_t` value. This is also useful -// when you want deterministic behavior across systems, as this method does not -// call std::hash, so it does not depend on the behavior of the local machine's -// C++ standard library implementation. +// `otherDigest` is another digest. This is also deterministic, aside from +// possible differences in size_t (see deterministic_hash_combine, below). inline void hash_combine(std::size_t& digest, const std::size_t otherDigest) { // see: boost/container_hash/hash.hpp // The constant is the N-bits reciprocal of the golden ratio: // phi = (1 + sqrt(5)) / 2 -#if SIZE_MAX == UINT64_MAX // trunc(2^64 / phi) = 0x9e3779b97f4a7c15 digest ^= otherDigest + 0x9e3779b97f4a7c15 + (digest << 12) + (digest >> 4); -#else - // trunc(2^32 / phi) = 0x9e3779b9 - digest ^= otherDigest + 0x9e3779b9 + (digest << 6) + (digest >> 2); -#endif } // Hashes `value` and combines the resulting digest into the existing digest. -// Use instead of `hash_combine` if `value` is not another digest. +// Use instead of `hash_combine` if `value` is not another digest (i.e., it +// needs to be hashed first). template inline void rehash(std::size_t& digest, const T& value) { hash_combine(digest, hash(value)); } +// Similar to hash_combine, but guaranteed to produce the exact same results on +// all machines, even ones where size_t differs. This is essentially identical +// to hash_combine, but the types are all uint64_t. +inline void deterministic_hash_combine(uint64_t& digest, + const uint64_t otherDigest) { + digest ^= otherDigest + 0x9e3779b97f4a7c15 + (digest << 12) + (digest >> 4); +} + } // namespace wasm namespace std { From d71161aedb8ae1d6650111a0de0c2bdf53ec127b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 Nov 2024 15:45:27 -0800 Subject: [PATCH 102/622] [GC] Fix GlobalTypeOptimization logic for public types handling (#7051) This fixes a regression from #7019. That PR fixed an error on situations with mixed public and private types, but it made us stop optimizing in valid cases, including cases with entirely private types. The specific regression was that we checked if we had an entry in the map of "can become immutable", and we thought that was enough. But we may have a private child type with a public parent, and still be able to optimize in the child if the field is not present in the parent. We also did not have exhaustive checking of all the states canBecomeImmutable can be, so add those + testing. --- src/passes/GlobalTypeOptimization.cpp | 25 +- test/lit/passes/gto-mutability.wast | 324 ++++++++++++++++++++++++++ 2 files changed, 342 insertions(+), 7 deletions(-) diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index e35e30ac8da..0baecdf43aa 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -203,13 +203,24 @@ struct GlobalTypeOptimization : public Pass { // visibility, so do that here: we can only become immutable if the // parent can as well. auto super = type.getDeclaredSuperType(); - if (super && !canBecomeImmutable.count(*super)) { - // No entry in canBecomeImmutable means nothing in the parent can - // become immutable. We don't need to check the specific field index, - // because visibility affects them all equally (i.e., if it is public - // then no field can be changed, and if it is private then this field - // can be changed, and perhaps more). - continue; + if (super) { + // The super may not contain the field, which is fine, so only check + // here if the field does exist in both. + if (i < super->getStruct().fields.size()) { + // No entry in canBecomeImmutable means nothing in the parent can + // become immutable, so check for both that and for an entry with + // "false". + auto iter = canBecomeImmutable.find(*super); + if (iter == canBecomeImmutable.end()) { + continue; + } + // The vector is grown only when needed to contain a "true" value, + // so |i| being out of bounds indicates "false". + auto& superVec = iter->second; + if (i >= superVec.size() || !superVec[i]) { + continue; + } + } } // No set exists. Mark it as something we can make immutable. diff --git a/test/lit/passes/gto-mutability.wast b/test/lit/passes/gto-mutability.wast index 3b01572c8c2..3cf0e77016d 100644 --- a/test/lit/passes/gto-mutability.wast +++ b/test/lit/passes/gto-mutability.wast @@ -688,3 +688,327 @@ ) ) +;; $sub has a field we can make immutable. That it does not exist in the super +;; should not confuse us. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct (field (ref string))))) + (type $sub (sub $super (struct (field (mut (ref string)))))) + ) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub 0 + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; Write and read the field. + (drop + (struct.get $sub 0 + (struct.new $sub + (string.const "foo") + ) + ) + ) + ) +) + +;; As above, but with another type in the middle, $mid, which also contains the +;; field. We can optimize both $mid and $sub. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $mid (sub $super (struct (field (ref string))))) + (type $mid (sub $super (struct (field (mut (ref string)))))) + ;; CHECK: (type $sub (sub $mid (struct (field (ref string))))) + (type $sub (sub $mid (struct (field (mut (ref string)))))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (func $test (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub 0 + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $mid 0 + ;; CHECK-NEXT: (struct.new $mid + ;; CHECK-NEXT: (string.const "bar") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $sub 0 + (struct.new $sub + (string.const "foo") + ) + ) + ) + (drop + (struct.get $mid 0 + (struct.new $mid + (string.const "bar") + ) + ) + ) + ) +) + +;; As above, but add another irrelevant field first. We can still optimize the +;; string, but the new mutable i32 must remain mutable, as it has a set. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $mid (sub $super (struct (field (mut i32)) (field (ref string))))) + (type $mid (sub $super (struct (field (mut i32)) (field (mut (ref string)))))) + ;; CHECK: (type $sub (sub $mid (struct (field (mut i32)) (field (ref string))))) + (type $sub (sub $mid (struct (field (mut i32)) (field (mut (ref string)))))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (func $test (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub 1 + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $mid 1 + ;; CHECK-NEXT: (struct.new $mid + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (string.const "bar") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (struct.new $super + ;; CHECK-NEXT: (i32.const 98765) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (struct.new $super + ;; CHECK-NEXT: (i32.const 999999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $sub 1 + (struct.new $sub + (i32.const 42) + (string.const "foo") + ) + ) + ) + (drop + (struct.get $mid 1 + (struct.new $mid + (i32.const 1337) + (string.const "bar") + ) + ) + ) + ;; A set and get of the first field. + (struct.set $super 0 + (struct.new $super + (i32.const 98765) + ) + (i32.const 42) + ) + (drop + (struct.get $super 0 + (struct.new $super + (i32.const 999999) + ) + ) + ) + ) +) + +;; As above, but without a set of the first field. Now we can optimize both +;; fields. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field i32)))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $mid (sub $super (struct (field i32) (field (ref string))))) + (type $mid (sub $super (struct (field (mut i32)) (field (mut (ref string)))))) + ;; CHECK: (type $sub (sub $mid (struct (field i32) (field (ref string))))) + (type $sub (sub $mid (struct (field (mut i32)) (field (mut (ref string)))))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (func $test (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub 1 + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $mid 1 + ;; CHECK-NEXT: (struct.new $mid + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (string.const "bar") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (struct.new $super + ;; CHECK-NEXT: (i32.const 999999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $sub 1 + (struct.new $sub + (i32.const 42) + (string.const "foo") + ) + ) + ) + (drop + (struct.get $mid 1 + (struct.new $mid + (i32.const 1337) + (string.const "bar") + ) + ) + ) + ;; Only a get of the first field. + (drop + (struct.get $super 0 + (struct.new $super + (i32.const 999999) + ) + ) + ) + ) +) + +;; The super is public, but we can still optimize the field in the sub. +(module + ;; CHECK: (type $super (sub (struct))) + (type $super (sub (struct))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $sub (sub $super (struct (field stringref)))) + (type $sub (sub $super (struct (field (mut stringref))))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (global $global (ref $super) (struct.new_default $super)) + (global $global (ref $super) (struct.new_default $super)) + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub 0 + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; Write and read the field. + (drop + (struct.get $sub 0 + (struct.new $sub + (string.const "foo") + ) + ) + ) + ) +) + +;; As above, and now the super has the field as well, preventing optimization. +(module + ;; CHECK: (type $super (sub (struct (field (mut stringref))))) + (type $super (sub (struct (field (mut stringref))))) + + ;; CHECK: (type $sub (sub $super (struct (field (mut stringref))))) + (type $sub (sub $super (struct (field (mut stringref))))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (global $global (ref $super) (struct.new_default $super)) + (global $global (ref $super) (struct.new_default $super)) + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub 0 + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; Write and read the field. + (drop + (struct.get $sub 0 + (struct.new $sub + (string.const "foo") + ) + ) + ) + ) +) + +;; Two mutable fields with a chain of three subtypes. The super is public, +;; preventing optimization of the field it has (but not the other; the other +;; is removable anyhow, though, so this just checks for the lack of an error +;; when deciding not to make the fields immutable or not). +(module + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $mid (sub $super (struct (field (mut i32))))) + (type $mid (sub $super (struct (field (mut i32)) (field (mut f64))))) + ;; CHECK: (type $sub (sub $mid (struct (field (mut i32))))) + (type $sub (sub $mid (struct (field (mut i32)) (field (mut f64))))) + + ;; CHECK: (global $global (ref $super) (struct.new_default $sub)) + (global $global (ref $super) (struct.new_default $sub)) + + ;; CHECK: (export "global" (global $global)) + (export "global" (global $global)) +) + From 884261afa7ff6d0158f3a1882e0247568c8d6cda Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 4 Nov 2024 16:13:15 -0800 Subject: [PATCH 103/622] Remove FeaturePrefix::FeatureRequired (NFC) (#7034) This has not been emitted in LLVM since https://github.com/llvm/llvm-project/commit/3f34e1b883351c7d98426b084386a7aa762aa366. The corresponding proposed tool-conventions change: https://github.com/WebAssembly/tool-conventions/pull/236 --- src/wasm-binary.h | 6 +----- src/wasm/wasm-binary.cpp | 8 ++------ 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 3c59ed3aacb..ca14bd41f25 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1156,11 +1156,7 @@ enum MemoryAccess { enum MemoryFlags { HasMaximum = 1 << 0, IsShared = 1 << 1, Is64 = 1 << 2 }; -enum FeaturePrefix { - FeatureUsed = '+', - FeatureRequired = '=', - FeatureDisallowed = '-' -}; +enum FeaturePrefix { FeatureUsed = '+', FeatureDisallowed = '-' }; } // namespace BinaryConsts diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index bc7ec8ac898..173a89163b2 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3816,15 +3816,11 @@ void WasmBinaryReader::readFeatures(size_t payloadLen) { uint8_t prefix = getInt8(); bool disallowed = prefix == BinaryConsts::FeatureDisallowed; - bool required = prefix == BinaryConsts::FeatureRequired; bool used = prefix == BinaryConsts::FeatureUsed; - if (!disallowed && !required && !used) { + if (!disallowed && !used) { throwError("Unrecognized feature policy prefix"); } - if (required) { - std::cerr << "warning: required features in feature section are ignored"; - } Name name = getInlineString(); if (pos > sectionPos + payloadLen) { @@ -3881,7 +3877,7 @@ void WasmBinaryReader::readFeatures(size_t payloadLen) { << "warning: feature " << feature.toString() << " was enabled by the user, but disallowed in the features section."; } - if (required || used) { + if (used) { wasm.features.enable(feature); } } From 320867a7c61432691faa62892d5fd89e4f6bae6a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 Nov 2024 16:49:17 -0800 Subject: [PATCH 104/622] Add a J2CL compiler script (#7052) J2CL runs a long pipeline of binaryen opts, including multiple invocations. This adds a tool that simulates the same process manually, which can be easier to debug with than running a full J2CL toolchain, which requires Bazel, etc. - this is just a few lines of bash that does the same thing (at least in simple cases). --- scripts/j2cl.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 scripts/j2cl.sh diff --git a/scripts/j2cl.sh b/scripts/j2cl.sh new file mode 100644 index 00000000000..884001aeaa1 --- /dev/null +++ b/scripts/j2cl.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e + +# +# Simplified version of +# +# https://github.com/google/j2cl/blob/e0dee1c7a726952c93d6ace95ddc39c7a6bcc74a/build_defs/internal_do_not_use/j2wasm_application.bzl#L4 +# +# This can be useful for local testing of issues with j2cl output, by running +# this instead of setting up a full j2cl toolchain. Usage: +# +# j2cl.sh input.wasm output.wasm +# +# This will emit one file for each stage of compilation, as output.wasm.N.wasm. +# + +COMMON="--enable-exception-handling --enable-gc --enable-reference-types --enable-sign-ext --enable-strings --enable-nontrapping-float-to-int --enable-bulk-memory --closed-world --traps-never-happen" + +echo "Stage 1" +bin/wasm-opt $COMMON "--no-inline=*__*" -O3 --cfp-reftest --optimize-j2cl --gufa --unsubtyping -O3 --cfp-reftest --optimize-j2cl -O3 --cfp-reftest --optimize-j2cl $1 -o $2.1.wasm + +echo "Stage 2" +bin/wasm-opt $COMMON "--no-inline=*__*" --partial-inlining-ifs=4 -fimfs=25 --gufa --unsubtyping -O3 --cfp-reftest --optimize-j2cl -O3 --cfp-reftest --optimize-j2cl -O3 --cfp-reftest --optimize-j2cl --gufa --unsubtyping -O3 --cfp-reftest --optimize-j2cl -O3 --cfp-reftest --optimize-j2cl $2.1.wasm -o $2.2.wasm + +echo "Stage 3" +bin/wasm-opt $COMMON "--no-full-inline=*__*" --partial-inlining-ifs=4 --intrinsic-lowering --gufa --unsubtyping -O3 --cfp-reftest --optimize-j2cl -O3 --optimize-j2cl --cfp-reftest --type-merging -O3 --cfp-reftest --optimize-j2cl --string-lowering --remove-unused-module-elements --reorder-globals --type-finalizing $2.2.wasm -o $2.3.wasm + From 92917c28ea6ef017e71a8a97eb364ed20bc52fe2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 5 Nov 2024 15:49:50 -0800 Subject: [PATCH 105/622] [GC] Fix ConstantFieldPropagation on incompatible types (#7054) CFP is less precise than GUFA, in particular, when it flows around types then it does not consider what field it is flowing them to, and its core data structure is "if a struct.get is done on this type's field, what can be read?". To see the issue this PR fixes, assume we have A / \ B C Then if we see struct.set $C, we know that can be read by a struct.get $A (we can store a reference to a C in such a local/param/etc.), so we propagate the value of that set to A. And, in general, anything in A can appear in B (say, if we see a copy, a struct.set of struct.get that operates on types A, then one of the sides might be a B), so we propagate from A to B. But now we have propagated something from C to B, which might be of an incompatible type. This cannot cause runtime issues, as it just means we are propagating more than we should, and will end up with less-useful results. But it can break validation if no other value is possible but one with an incompatible type, as we'd replace a struct.get $B with a value that only makes sense for C. (The qualifier "no other value is possible" was added in the previous sentence because if another one is possible then we'd end up with too many values to infer anything, and not optimize at all, avoiding any error.) --- src/passes/ConstantFieldPropagation.cpp | 21 ++++- test/lit/passes/cfp.wast | 116 ++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 26cc8316b14..a3dd6aa6f5b 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -179,7 +179,26 @@ struct FunctionOptimizer : public WalkerPass> { auto* value = info.makeExpression(*getModule()); auto field = GCTypeUtils::getField(type, curr->index); assert(field); - return Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule()); + // Apply packing, if needed. + value = + Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule()); + // Check if the value makes sense. The analysis below flows values around + // without considering where they are placed, that is, when we see a parent + // type can contain a value in a field then we assume a child may as well + // (which in general it can, e.g., using a reference to the parent, we can + // write that value to it, but the reference might actually point to a + // child instance). If we tracked the types of fields then we might avoid + // flowing values into places they cannot reside, like when a child field is + // a subtype, and so we could ignore things not refined enough for it (GUFA + // does a better job at this). For here, just check we do not break + // validation, and if we do, then we've inferred the only possible value is + // an impossible one, making the code unreachable. + if (!Type::isSubType(value->type, field->type)) { + Builder builder(*getModule()); + value = builder.makeSequence(builder.makeDrop(value), + builder.makeUnreachable()); + } + return value; } void optimizeUsingRefTest(StructGet* curr) { diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index d386e7f34ee..461baa37317 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2710,3 +2710,119 @@ ) ) ) + +;; $C is created with two values for its field: a global.get, and a copy from +;; another $C, which does not expand the set of possible values. We should not +;; get confused about $B, its sibling, which is never created, and whose field +;; has an incompatible type. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $X (sub (struct))) + (type $X (sub (struct))) + ;; CHECK: (type $Y (sub final $X (struct))) + (type $Y (sub final $X (struct))) + ;; CHECK: (type $Z (sub final $X (struct))) + (type $Z (sub final $X (struct))) + + ;; CHECK: (type $A (sub (struct (field (ref null $X))))) + (type $A (sub (struct (field (ref null $X))))) + ;; CHECK: (type $B (sub final $A (struct (field (ref null $Y))))) + (type $B (sub final $A (struct (field (ref null $Y))))) + ;; CHECK: (type $C (sub final $A (struct (field (ref null $Z))))) + (type $C (sub final $A (struct (field (ref null $Z))))) + ) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (type $7 (func (param (ref null $C)))) + + ;; CHECK: (type $8 (func (param (ref null $A)) (result (ref null $X)))) + + ;; CHECK: (type $9 (func (param (ref null $B)) (result (ref null $Y)))) + + ;; CHECK: (global $global (ref null $Z) (struct.new_default $Z)) + (global $global (ref null $Z) (struct.new_default $Z)) + + ;; CHECK: (func $new (type $6) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $C + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $new + (drop + (struct.new $C + (global.get $global) + ) + ) + ) + + ;; CHECK: (func $copy (type $7) (param $C (ref null $C)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $C + ;; CHECK-NEXT: (block (result (ref null $Z)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $C (ref null $C)) + ;; The struct.get here can be optimized to a global.get. + (drop + (struct.new $C + (struct.get $C 0 + (local.get $C) + ) + ) + ) + ) + + ;; CHECK: (func $get-A (type $8) (param $A (ref null $A)) (result (ref null $X)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + (func $get-A (param $A (ref null $A)) (result (ref null $X)) + ;; The struct.get here can be optimized to a global.get. While we never + ;; create an $A, a $C might be referred to by an $A reference, and the + ;; global.get is the only possible value. + (struct.get $A 0 + (local.get $A) + ) + ) + + ;; CHECK: (func $get-B (type $9) (param $B (ref null $B)) (result (ref null $Y)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-B (param $B (ref null $B)) (result (ref null $Y)) + ;; This should not be optimized to a global.get: no $B is created, and we + ;; cannot refer to anything that is actually created. If we mistakenly + ;; thought this field can contain the global.get (as we do for the parent + ;; $A) then we would error here: $B's field contains $Y, but the global is + ;; is of a sibling type $Z. Instead, we can add an unreachable here, as no + ;; valid value is possible. + (struct.get $B 0 + (local.get $B) + ) + ) +) From 40210446937cbef7d73606e4331cb6a782e2a875 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 6 Nov 2024 15:31:09 -0800 Subject: [PATCH 106/622] Fuzzer: Handle exported table for wasm-merge (#7055) When fuzzing wasm-merge, we need to avoid the first module not having an exported table but the second having one, as the way the table operation imports work, they are sensitive to the existence of such an export, so just merging in such an export can alter behavior. --- scripts/fuzz_opt.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 3d466350120..2bf5cd820a8 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1194,19 +1194,22 @@ def get_exports(wasm, kinds): # given a wasm and a list of exports we want to keep, remove all other exports. -def filter_exports(wasm, output, keep): +# we also keep a list of default exports, unless that is overridden (overriding +# it may lead to changes in behavior). +def filter_exports(wasm, output, keep, keep_defaults=True): # based on # https://github.com/WebAssembly/binaryen/wiki/Pruning-unneeded-code-in-wasm-files-with-wasm-metadce#example-pruning-exports # we append to keep; avoid modifying the object that was sent in. keep = keep[:] - # some exports must always be preserved, if they exist, like the table - # (which can be called from JS imports for table operations). - existing_exports = set(get_exports(wasm, ['func', 'table'])) - for export in ['table']: - if export in existing_exports: - keep.append(export) + if not keep_defaults: + # some exports must normally be preserved, if they exist, like the table + # (which can be called from JS imports for table operations). + existing_exports = set(get_exports(wasm, ['func', 'table'])) + for export in ['table']: + if export in existing_exports: + keep.append(export) # build json to represent the exports we want. graph = [{ @@ -1369,6 +1372,23 @@ def handle(self, wasm): second_wasm = abspath('second.wasm') run([in_bin('wasm-opt'), second_input, '-ttf', '-o', second_wasm] + GEN_ARGS + FEATURE_OPTS) + # the second wasm file must not have an export that can influence our + # execution. the JS exports have that behavior, as when "table-set" is + # called it will look for the export "table" on which to operate, then + # imagine we lack that export in the first module but add it in the + # second, then code that failed before will now use the exported table + # from the second module (and maybe work). to avoid that, remove the + # table export, if it exists (and if the first module doesn't export + # it). + second_exports = get_exports(second_wasm, ['func', 'table']) + wasm_exports = get_exports(wasm, ['table']) + if 'table' in second_exports and 'table' not in wasm_exports: + filtered = [e for e in second_exports if e != 'table'] + # note we override the set of default things to keep, as we want to + # remove the table export. doing so might change the behavior of + # second.wasm, but that is ok. + filter_exports(second_wasm, second_wasm, filtered, keep_defaults=False) + # sometimes also optimize the second module if random.random() < 0.5: opts = get_random_opts() From 6724ebe4b9e184a9a73ce82e8b8782f7f5a21d1a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 6 Nov 2024 15:33:14 -0800 Subject: [PATCH 107/622] [wasm64] Handle 64-bit overflow in optimizeMemoryAccess (#7057) When we combine a load/store offset with a const, we must not overflow, as the semantics of offsets do not wrap. --- src/passes/OptimizeInstructions.cpp | 9 ++++-- .../optimize-instructions-memory64.wast | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/optimize-instructions-memory64.wast diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index e1478b54f78..3a2841c1ed4 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -3501,8 +3502,12 @@ struct OptimizeInstructions uint64_t offset64 = offset; auto mem = getModule()->getMemory(memory); if (mem->is64()) { - last->value = Literal(int64_t(value64 + offset64)); - offset = 0; + // Check for a 64-bit overflow. + uint64_t sum; + if (!std::ckd_add(&sum, value64, offset64)) { + last->value = Literal(int64_t(sum)); + offset = 0; + } } else { // don't do this if it would wrap the pointer if (value64 <= uint64_t(std::numeric_limits::max()) && diff --git a/test/lit/passes/optimize-instructions-memory64.wast b/test/lit/passes/optimize-instructions-memory64.wast new file mode 100644 index 00000000000..f74dad7a812 --- /dev/null +++ b/test/lit/passes/optimize-instructions-memory64.wast @@ -0,0 +1,31 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --optimize-instructions -all -S -o - | filecheck %s + +(module + ;; CHECK: (memory $0 i64 16 17) + (memory $0 i64 16 17) + + ;; CHECK: (func $offsets (type $0) + ;; CHECK-NEXT: (i64.store + ;; CHECK-NEXT: (i64.const 10) + ;; CHECK-NEXT: (i64.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.store offset=4 + ;; CHECK-NEXT: (i64.const -3) + ;; CHECK-NEXT: (f32.const 3.141590118408203) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $offsets + ;; It is safe to combine the offset and constant here. + (i64.store offset=4 + (i64.const 6) + (i64.const 42) + ) + ;; This would overflow, so do not optimize. + (f32.store offset=4 + (i64.const -3) + (f32.const 3.14159) + ) + ) +) + From b57a6d74cb9edc88b2f7d203270f98467c0961af Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 6 Nov 2024 16:21:54 -0800 Subject: [PATCH 108/622] Fix flipped condition on keep_defaults in fuzzer (#7061) Followup to #7055 --- scripts/fuzz_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 2bf5cd820a8..433bc189f0a 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1203,7 +1203,7 @@ def filter_exports(wasm, output, keep, keep_defaults=True): # we append to keep; avoid modifying the object that was sent in. keep = keep[:] - if not keep_defaults: + if keep_defaults: # some exports must normally be preserved, if they exist, like the table # (which can be called from JS imports for table operations). existing_exports = set(get_exports(wasm, ['func', 'table'])) From de680182bcd1d64d597538cfa4ad6857ce298bc3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 6 Nov 2024 16:26:09 -0800 Subject: [PATCH 109/622] [wasm64] Fix 64-bit memory/table operations in interpreter (#7058) A bunch of places assumed a 32-bit index. --- src/wasm-interpreter.h | 23 +++++++++------- test/lit/exec/memory64.wast | 39 +++++++++++++++++++++++++++ test/lit/exec/simd.wast | 21 +++++++++++++++ test/lit/exec/table64.wast | 53 +++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 test/lit/exec/memory64.wast create mode 100644 test/lit/exec/simd.wast create mode 100644 test/lit/exec/table64.wast diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5f20819a1c8..7f1cf905424 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2957,18 +2957,22 @@ class ModuleRunnerBase : public ExpressionRunner { void initializeMemoryContents() { initializeMemorySizes(); - Const zero; - zero.value = Literal(uint32_t(0)); - zero.finalize(); - // apply active memory segments for (size_t i = 0, e = wasm.dataSegments.size(); i < e; ++i) { auto& segment = wasm.dataSegments[i]; if (segment->isPassive) { continue; } + + auto* memory = wasm.getMemory(segment->memory); + + Const zero; + zero.value = Literal::makeFromInt32(0, memory->indexType); + zero.finalize(); + Const size; - size.value = Literal(uint32_t(segment->data.size())); + size.value = + Literal::makeFromInt32(segment->data.size(), memory->indexType); size.finalize(); MemoryInit init; @@ -3136,13 +3140,14 @@ class ModuleRunnerBase : public ExpressionRunner { return target; } - Index index = target.getSingleValue().geti32(); + auto index = target.getSingleValue().getUnsigned(); auto info = getTableInstanceInfo(curr->table); if (curr->isReturn) { // Return calls are represented by their arguments followed by a reference // to the function to be called. + // TODO: switch tableLoad index from Index to Address, to support table64. auto funcref = info.interface()->tableLoad(info.name, index); if (!Type::isSubType(funcref.type, Type(curr->heapType, NonNullable))) { trap("cast failure in call_indirect"); @@ -3673,7 +3678,7 @@ class ModuleRunnerBase : public ExpressionRunner { return flow; } NOTE_EVAL1(flow); - Address src(uint32_t(flow.getSingleValue().geti32())); + Address src(flow.getSingleValue().getUnsigned()); auto info = getMemoryInstanceInfo(curr->memory); auto loadLane = [&](Address addr) { switch (curr->op) { @@ -3878,8 +3883,8 @@ class ModuleRunnerBase : public ExpressionRunner { auto* segment = wasm.getDataSegment(curr->segment); Address destVal(dest.getSingleValue().getUnsigned()); - Address offsetVal(uint32_t(offset.getSingleValue().geti32())); - Address sizeVal(uint32_t(size.getSingleValue().geti32())); + Address offsetVal(offset.getSingleValue().getUnsigned()); + Address sizeVal(size.getSingleValue().getUnsigned()); if (offsetVal + sizeVal > 0 && droppedDataSegments.count(curr->segment)) { trap("out of bounds segment access in memory.init"); diff --git a/test/lit/exec/memory64.wast b/test/lit/exec/memory64.wast new file mode 100644 index 00000000000..273f2679aad --- /dev/null +++ b/test/lit/exec/memory64.wast @@ -0,0 +1,39 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +(module + (memory $0 i64 16 17) + + (data $0 "\00\00\00\00\00") + + ;; CHECK: [fuzz-exec] calling memory.init.trap + ;; CHECK-NEXT: [trap out of bounds segment access in memory.init] + (func $memory.init.trap (export "memory.init.trap") + ;; Trap on OOB on the segment offset. + (memory.init $0 + (i64.const 0) + (i32.const -3) + (i32.const 1) + ) + ) + + ;; CHECK: [fuzz-exec] calling memory.init.trap2 + ;; CHECK-NEXT: [trap out of bounds segment access in memory.init] + (func $memory.init.trap2 (export "memory.init.trap2") + ;; Trap on OOB on the size. + (memory.init $0 + (i64.const 0) + (i32.const 1) + (i32.const 10) + ) + ) +) + +;; CHECK: [fuzz-exec] calling memory.init.trap +;; CHECK-NEXT: [trap out of bounds segment access in memory.init] + +;; CHECK: [fuzz-exec] calling memory.init.trap2 +;; CHECK-NEXT: [trap out of bounds segment access in memory.init] +;; CHECK-NEXT: [fuzz-exec] comparing memory.init.trap +;; CHECK-NEXT: [fuzz-exec] comparing memory.init.trap2 diff --git a/test/lit/exec/simd.wast b/test/lit/exec/simd.wast new file mode 100644 index 00000000000..5ab6489a2ad --- /dev/null +++ b/test/lit/exec/simd.wast @@ -0,0 +1,21 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +(module + (memory $0 i64 16 17 shared) + + (data $0 (i64.const 0) "abcdefg") + + ;; CHECK: [fuzz-exec] calling load8x8_s + ;; CHECK-NEXT: [fuzz-exec] note result: load8x8_s => i32x4 0x00620061 0x00640063 0x00660065 0x00000067 + (func $load8x8_s (export "load8x8_s") (result v128) + (v128.load8x8_s align=2 + (i64.const 0) + ) + ) +) + +;; CHECK: [fuzz-exec] calling load8x8_s +;; CHECK-NEXT: [fuzz-exec] note result: load8x8_s => i32x4 0x00620061 0x00640063 0x00660065 0x00000067 +;; CHECK-NEXT: [fuzz-exec] comparing load8x8_s diff --git a/test/lit/exec/table64.wast b/test/lit/exec/table64.wast new file mode 100644 index 00000000000..e24741838be --- /dev/null +++ b/test/lit/exec/table64.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +(module + (type $i32 (func (result i32))) + + (table $table i64 10 funcref) + (elem (i64.const 0) $i32) + + (func $i32 (result i32) + (i32.const 42) + ) + + ;; CHECK: [fuzz-exec] calling call + ;; CHECK-NEXT: [fuzz-exec] note result: call => 42 + (func $call (export "call") (result i32) + ;; This call succeeds, and calls $i32 which returns 42. + (call_indirect (type $i32) + (i64.const 0) + ) + ) + + ;; CHECK: [fuzz-exec] calling oob + ;; CHECK-NEXT: [trap callTable overflow] + (func $oob (export "oob") (result i32) + ;; This call traps on oob. + (call_indirect (type $i32) + (i64.const 999) + ) + ) + + ;; CHECK: [fuzz-exec] calling null + ;; CHECK-NEXT: [trap uninitialized table element] + (func $null (export "null") (result i32) + ;; This call traps on null + (call_indirect (type $i32) + (i64.const 1) + ) + ) +) + +;; CHECK: [fuzz-exec] calling call +;; CHECK-NEXT: [fuzz-exec] note result: call => 42 + +;; CHECK: [fuzz-exec] calling oob +;; CHECK-NEXT: [trap callTable overflow] + +;; CHECK: [fuzz-exec] calling null +;; CHECK-NEXT: [trap uninitialized table element] +;; CHECK-NEXT: [fuzz-exec] comparing call +;; CHECK-NEXT: [fuzz-exec] comparing null +;; CHECK-NEXT: [fuzz-exec] comparing oob From ab8a41c85ddb1ea783bc8a8832254f992262bed6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 6 Nov 2024 19:11:39 -0800 Subject: [PATCH 110/622] [wasm64] Fix wasm-ctor-eval + utils on 64-bit indexes for memory64 (#7059) Some places assumed a 32-bit index. --- src/ir/memory-utils.cpp | 9 +++++-- src/tools/wasm-ctor-eval.cpp | 8 +++--- test/lit/ctor-eval/memory64.wast | 42 ++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 test/lit/ctor-eval/memory64.wast diff --git a/src/ir/memory-utils.cpp b/src/ir/memory-utils.cpp index 0f6b776028b..552e4ff9aee 100644 --- a/src/ir/memory-utils.cpp +++ b/src/ir/memory-utils.cpp @@ -21,8 +21,12 @@ namespace wasm::MemoryUtils { bool flatten(Module& wasm) { + // If there are no memories then they are already flat, in the empty sense. + if (wasm.memories.empty()) { + return true; + } // Flatten does not currently have support for multimemory - if (wasm.memories.size() > 1) { + if (wasm.memories.size() != 1) { return false; } // The presence of any instruction that cares about segment identity is a @@ -105,7 +109,8 @@ bool flatten(Module& wasm) { } std::copy(segment->data.begin(), segment->data.end(), data.begin() + start); } - dataSegments[0]->offset->cast()->value = Literal(int32_t(0)); + dataSegments[0]->offset->cast()->value = + Literal::makeFromInt32(0, wasm.memories[0]->indexType); dataSegments[0]->data.swap(data); wasm.removeDataSegments( [&](DataSegment* curr) { return curr->name != dataSegments[0]->name; }); diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 72ad459d329..499c1bf6ca5 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -508,12 +508,14 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { void applyMemoryToModule() { // Memory must have already been flattened into the standard form: one // segment at offset 0, or none. + auto& memory = wasm->memories[0]; if (wasm->dataSegments.empty()) { Builder builder(*wasm); auto curr = builder.makeDataSegment(); - curr->offset = builder.makeConst(int32_t(0)); + curr->offset = + builder.makeConst(Literal::makeFromInt32(0, memory->indexType)); curr->setName(Name::fromInt(0), false); - curr->memory = wasm->memories[0]->name; + curr->memory = memory->name; wasm->addDataSegment(std::move(curr)); } auto& segment = wasm->dataSegments[0]; @@ -521,7 +523,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // Copy the current memory contents after execution into the Module's // memory. - segment->data = memories[wasm->memories[0]->name]; + segment->data = memories[memory->name]; } // Serializing GC data requires more work than linear memory, because diff --git a/test/lit/ctor-eval/memory64.wast b/test/lit/ctor-eval/memory64.wast new file mode 100644 index 00000000000..b1e7b16138f --- /dev/null +++ b/test/lit/ctor-eval/memory64.wast @@ -0,0 +1,42 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-ctor-eval %s --ctors=mem,tab --kept-exports=mem,tab --quiet -all -S -o - | filecheck %s + +(module + (memory $0 i64 16 17 shared) + (data $0 (i64.const 0) "abcd") + + (table i64 1 funcref) + (elem $foo) + + (func $mem (export "mem") (result i32) + (i32.load + (i64.const 0) + ) + ) + + ;; CHECK: (type $0 (func (result funcref))) + + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (table $0 i64 1 funcref) + + ;; CHECK: (export "mem" (func $mem_2)) + + ;; CHECK: (export "tab" (func $tab)) + + ;; CHECK: (func $tab (type $0) (result funcref) + ;; CHECK-NEXT: (table.get $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tab (export "tab") (result funcref) + ;; Not optimized yet, but we should not error either. + (table.get + (i64.const 0) + ) + ) +) + +;; CHECK: (func $mem_2 (type $1) (result i32) +;; CHECK-NEXT: (i32.const 1684234849) +;; CHECK-NEXT: ) From 0af8f1f2d7ff304837ee0698265c84985420fcae Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 Nov 2024 08:34:56 -0800 Subject: [PATCH 111/622] [wasm64] Fuzzer: Fix table import operations on table64 (#7056) The old code assumed the index was a JS number, but if the table has 64-bit indexes it must be a BigInt. Detect that and cast as needed. --- scripts/fuzz_shell.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index c4c0056f0bb..72120cf7f8b 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -134,6 +134,17 @@ function logValue(x, y) { console.log('[LoggingExternalInterface logging ' + printed(x, y) + ']'); } +// Table get/set operations need a BigInt if the table has 64-bit indexes. This +// adds a proper cast as needed. +function toAddressType(table, index) { + // First, cast to unsigned. We do not support larger indexes anyhow. + index = index >>> 0; + if (typeof table.length == 'bigint') { + return BigInt(index); + } + return index; +} + // Set up the imports. var tempRet0; var imports = { @@ -156,10 +167,10 @@ var imports = { // Table operations. 'table-get': (index) => { - return exports.table.get(index >>> 0); + return exports.table.get(toAddressType(exports.table, index)); }, 'table-set': (index, value) => { - exports.table.set(index >>> 0, value); + exports.table.set(toAddressType(exports.table, index), value); }, }, // Emscripten support. From e409660a5b4dff9891ddb7d4786cc510a5761d3e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 Nov 2024 09:04:36 -0800 Subject: [PATCH 112/622] [wasm64] Make interpreter table methods operate on Address, not Index (#7062) This allows 64-bit bounds checking to work properly. --- src/shell-interface.h | 7 +++-- src/tools/execution-results.h | 4 +-- src/tools/wasm-ctor-eval.cpp | 7 +++-- src/wasm-interpreter.h | 34 +++++++++------------- test/lit/exec/table64.wast | 55 ++++++++++++++++++++++++++++------- 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/shell-interface.h b/src/shell-interface.h index 6101df29a02..35f5772cf6e 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -151,7 +151,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { } Literals callTable(Name tableName, - Index index, + Address index, HeapType sig, Literals& arguments, Type results, @@ -287,7 +287,8 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { return (Index)tables[tableName].size(); } - void tableStore(Name tableName, Index index, const Literal& entry) override { + void + tableStore(Name tableName, Address index, const Literal& entry) override { auto& table = tables[tableName]; if (index >= table.size()) { trap("out of bounds table access"); @@ -296,7 +297,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { } } - Literal tableLoad(Name tableName, Index index) override { + Literal tableLoad(Name tableName, Address index) override { auto it = tables.find(tableName); if (it == tables.end()) { trap("tableGet on non-existing table"); diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index eb302b4b2ba..78cc5af1f04 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -84,7 +84,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { if (!exportedTable) { throwEmptyException(); } - Index index = arguments[0].geti32(); + auto index = arguments[0].getUnsigned(); if (index >= tables[exportedTable].size()) { throwEmptyException(); } @@ -93,7 +93,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { if (!exportedTable) { throwEmptyException(); } - Index index = arguments[0].geti32(); + auto index = arguments[0].getUnsigned(); if (index >= tables[exportedTable].size()) { throwEmptyException(); } diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 499c1bf6ca5..52c83b05364 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -290,7 +290,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // We assume the table is not modified FIXME Literals callTable(Name tableName, - Index index, + Address index, HeapType sig, Literals& arguments, Type result, @@ -363,12 +363,13 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { return wasm->getTableOrNull(tableName)->initial; } - Literal tableLoad(Name tableName, Index index) override { + Literal tableLoad(Name tableName, Address index) override { throw FailToEvalException("table.get: TODO"); } // called during initialization - void tableStore(Name tableName, Index index, const Literal& value) override { + void + tableStore(Name tableName, Address index, const Literal& value) override { // We allow stores to the table during initialization, but not after, as we // assume the table does not change at runtime. // TODO: Allow table changes by updating the table later like we do with the diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 7f1cf905424..1513b8f456e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2598,7 +2598,7 @@ class ModuleRunnerBase : public ExpressionRunner { virtual Literals callImport(Function* import, const Literals& arguments) = 0; virtual Literals callTable(Name tableName, - Index index, + Address index, HeapType sig, Literals& arguments, Type result, @@ -2789,10 +2789,11 @@ class ModuleRunnerBase : public ExpressionRunner { virtual Index tableSize(Name tableName) = 0; - virtual void tableStore(Name tableName, Index index, const Literal& entry) { + virtual void + tableStore(Name tableName, Address index, const Literal& entry) { WASM_UNREACHABLE("unimp"); } - virtual Literal tableLoad(Name tableName, Index index) { + virtual Literal tableLoad(Name tableName, Address index) { WASM_UNREACHABLE("unimp"); } }; @@ -3141,13 +3142,11 @@ class ModuleRunnerBase : public ExpressionRunner { } auto index = target.getSingleValue().getUnsigned(); - auto info = getTableInstanceInfo(curr->table); if (curr->isReturn) { // Return calls are represented by their arguments followed by a reference // to the function to be called. - // TODO: switch tableLoad index from Index to Address, to support table64. auto funcref = info.interface()->tableLoad(info.name, index); if (!Type::isSubType(funcref.type, Type(curr->heapType, NonNullable))) { trap("cast failure in call_indirect"); @@ -3201,29 +3200,22 @@ class ModuleRunnerBase : public ExpressionRunner { return index; } auto info = getTableInstanceInfo(curr->table); - auto* table = info.instance->wasm.getTable(info.name); - auto address = table->indexType == Type::i64 - ? index.getSingleValue().geti64() - : index.getSingleValue().geti32(); + auto address = index.getSingleValue().getUnsigned(); return info.interface()->tableLoad(info.name, address); } Flow visitTableSet(TableSet* curr) { NOTE_ENTER("TableSet"); - Flow indexFlow = self()->visit(curr->index); - if (indexFlow.breaking()) { - return indexFlow; + Flow index = self()->visit(curr->index); + if (index.breaking()) { + return index; } - Flow valueFlow = self()->visit(curr->value); - if (valueFlow.breaking()) { - return valueFlow; + Flow value = self()->visit(curr->value); + if (value.breaking()) { + return value; } auto info = getTableInstanceInfo(curr->table); - auto* table = info.instance->wasm.getTable(info.name); - auto address = table->indexType == Type::i64 - ? indexFlow.getSingleValue().geti64() - : indexFlow.getSingleValue().geti32(); - info.interface()->tableStore( - info.name, address, valueFlow.getSingleValue()); + auto address = index.getSingleValue().getUnsigned(); + info.interface()->tableStore(info.name, address, value.getSingleValue()); return Flow(); } diff --git a/test/lit/exec/table64.wast b/test/lit/exec/table64.wast index e24741838be..646634c0eab 100644 --- a/test/lit/exec/table64.wast +++ b/test/lit/exec/table64.wast @@ -6,21 +6,34 @@ (type $i32 (func (result i32))) (table $table i64 10 funcref) - (elem (i64.const 0) $i32) + (elem (i64.const 0) $i32-a $i32-b) - (func $i32 (result i32) + (func $i32-a (result i32) (i32.const 42) ) - ;; CHECK: [fuzz-exec] calling call - ;; CHECK-NEXT: [fuzz-exec] note result: call => 42 - (func $call (export "call") (result i32) - ;; This call succeeds, and calls $i32 which returns 42. + (func $i32-b (result i32) + (i32.const 1337) + ) + + ;; CHECK: [fuzz-exec] calling call-a + ;; CHECK-NEXT: [fuzz-exec] note result: call-a => 42 + (func $call-a (export "call-a") (result i32) + ;; This call succeeds, and calls $i32-a which returns 42. (call_indirect (type $i32) (i64.const 0) ) ) + ;; CHECK: [fuzz-exec] calling call-b + ;; CHECK-NEXT: [fuzz-exec] note result: call-b => 1337 + (func $call-b (export "call-b") (result i32) + ;; This call succeeds, and calls $i32-b which returns 1337. + (call_indirect (type $i32) + (i64.const 1) + ) + ) + ;; CHECK: [fuzz-exec] calling oob ;; CHECK-NEXT: [trap callTable overflow] (func $oob (export "oob") (result i32) @@ -30,24 +43,46 @@ ) ) + ;; CHECK: [fuzz-exec] calling oob-huge + ;; CHECK-NEXT: [trap callTable overflow] + (func $oob-huge (export "oob-huge") (result i32) + ;; This call traps on oob with a value over 32 bits, 2**32 + 1, which if we + ;; truncated to 32 bits, would seem in bounds, and end up calling a valid + ;; function. + (call_indirect (type $i32) + (i64.add + (i64.const 0x100000000) + (i64.const 1) + ) + ) + ) + ;; CHECK: [fuzz-exec] calling null ;; CHECK-NEXT: [trap uninitialized table element] (func $null (export "null") (result i32) ;; This call traps on null (call_indirect (type $i32) - (i64.const 1) + (i64.const 2) ) ) ) -;; CHECK: [fuzz-exec] calling call -;; CHECK-NEXT: [fuzz-exec] note result: call => 42 +;; CHECK: [fuzz-exec] calling call-a +;; CHECK-NEXT: [fuzz-exec] note result: call-a => 42 + +;; CHECK: [fuzz-exec] calling call-b +;; CHECK-NEXT: [fuzz-exec] note result: call-b => 1337 ;; CHECK: [fuzz-exec] calling oob ;; CHECK-NEXT: [trap callTable overflow] +;; CHECK: [fuzz-exec] calling oob-huge +;; CHECK-NEXT: [trap callTable overflow] + ;; CHECK: [fuzz-exec] calling null ;; CHECK-NEXT: [trap uninitialized table element] -;; CHECK-NEXT: [fuzz-exec] comparing call +;; CHECK-NEXT: [fuzz-exec] comparing call-a +;; CHECK-NEXT: [fuzz-exec] comparing call-b ;; CHECK-NEXT: [fuzz-exec] comparing null ;; CHECK-NEXT: [fuzz-exec] comparing oob +;; CHECK-NEXT: [fuzz-exec] comparing oob-huge From 88b36f5bbb0882f4861f3874d3a50cf7e8f2c7c2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 Nov 2024 10:09:45 -0800 Subject: [PATCH 113/622] [wasm64] Fix Directize on indexes > 32 bits (#7063) --- src/passes/Directize.cpp | 2 +- test/lit/passes/directize-wasm64.wast | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp index 7faf2e23a4f..93b3d04e747 100644 --- a/src/passes/Directize.cpp +++ b/src/passes/Directize.cpp @@ -130,7 +130,7 @@ struct FunctionDirectizer : public WalkerPass> { return CallUtils::Unknown{}; } - Index index = c->value.getInteger(); + Address index = c->value.getUnsigned(); // Check if index is invalid, or the type is wrong. auto& flatTable = *table.flatTable; diff --git a/test/lit/passes/directize-wasm64.wast b/test/lit/passes/directize-wasm64.wast index 8c3a0623f81..5a6b1f31189 100644 --- a/test/lit/passes/directize-wasm64.wast +++ b/test/lit/passes/directize-wasm64.wast @@ -34,4 +34,18 @@ (i64.const 1) ) ) + + ;; CHECK: (func $bar-32 (param $x i32) (param $y i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $bar-32 (param $x i32) (param $y i32) + ;; As above, but the constant has 2**32 added to it. If we operate on a 32-bit + ;; index, we might think we can optimize to a call to $foo. Instead, we should + ;; see that this traps, and optimize to that. + (call_indirect (type $ii) + (local.get $x) + (local.get $y) + (i64.const 4294967297) + ) + ) ) From d01620f88748825e136495824ce7f7312d90966d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 Nov 2024 13:19:25 -0800 Subject: [PATCH 114/622] [wasm64] Fuzz wasm64 memories (#7064) * Remove the code that prevented fuzzing wasm64 test files. * Ignore a run that hits the V8 implementation limit on memory size. * Disable wasm64 fuzzing in wasm2js (like almost all post-MVP features). * Add fuzzer logic to emit a 64-bit memory sometimes. * Fix various places in the fuzzer that assumed 32-bit indexes --- scripts/fuzz_opt.py | 6 +- src/tools/fuzzing.h | 1 - src/tools/fuzzing/fuzzing.cpp | 34 +++++++-- test/passes/fuzz_metrics_noprint.bin.txt | 57 +++++++------- ...e-to-fuzz_all-features_metrics_noprint.txt | 75 +++++++++---------- 5 files changed, 93 insertions(+), 80 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 433bc189f0a..c465fbcf34e 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -416,8 +416,6 @@ def pick_initial_contents(): global FEATURE_OPTS FEATURE_OPTS += [ - # has not been fuzzed in general yet - '--disable-memory64', # avoid multivalue for now due to bad interactions with gc non-nullable # locals in stacky code. for example, this fails to roundtrip as the # tuple code ends up creating stacky binary code that needs to spill @@ -692,6 +690,8 @@ def filter_known_issues(output): # (https://github.com/WebAssembly/binaryen/pull/6574) 'expected (ref stringview_wtf16), got nullref', 'expected type (ref stringview_wtf16), found ref.null of type nullref', + # wasm64 memories have a V8 limit + 'larger than implementation limit', ] for issue in known_issues: if issue in output: @@ -1175,7 +1175,7 @@ def can_run_on_feature_opts(self, feature_opts): # specifically for growth here if INITIAL_CONTENTS: return False - return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'multimemory']) + return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'multimemory', 'memory64']) # given a wasm, find all the exports of particular kinds (for example, kinds diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 0ecc751a417..6f73feca934 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -26,7 +26,6 @@ high chance for set at start of loop */ #include "ir/branch-utils.h" -#include "ir/memory-utils.h" #include "ir/struct-utils.h" #include "support/insert_ordered.h" #include "tools/fuzzing/random.h" diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 5dcdc66d5b0..26c32196143 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -199,8 +199,24 @@ void TranslateToFuzzReader::build() { } void TranslateToFuzzReader::setupMemory() { - // Add memory itself - MemoryUtils::ensureExists(&wasm); + // Add a memory, if one does not already exist. + if (wasm.memories.empty()) { + auto memory = Builder::makeMemory("0"); + // Add at least one page of memory. + memory->initial = 1 + upTo(10); + // Make the max potentially higher, or unlimited. + if (oneIn(2)) { + memory->max = memory->initial + upTo(4); + } else { + memory->max = Memory::kUnlimitedSize; + } + // Fuzz wasm64 when possible, sometimes. + if (wasm.features.hasMemory64() && oneIn(2)) { + memory->indexType = Type::i64; + } + wasm.addMemory(std::move(memory)); + } + auto& memory = wasm.memories[0]; if (wasm.features.hasBulkMemory()) { size_t memCovered = 0; @@ -217,7 +233,8 @@ void TranslateToFuzzReader::setupMemory() { segment->data[j] = upTo(512); } if (!segment->isPassive) { - segment->offset = builder.makeConst(int32_t(memCovered)); + segment->offset = builder.makeConst( + Literal::makeFromInt32(memCovered, memory->indexType)); memCovered += segSize; segment->memory = memory->name; } @@ -227,7 +244,8 @@ void TranslateToFuzzReader::setupMemory() { // init some data auto segment = builder.makeDataSegment(); segment->memory = memory->name; - segment->offset = builder.makeConst(int32_t(0)); + segment->offset = + builder.makeConst(Literal::makeFromInt32(0, memory->indexType)); segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(0)), false); auto num = upTo(USABLE_MEMORY * 2); @@ -364,10 +382,11 @@ void TranslateToFuzzReader::setupTables() { [&](auto& segment) { return segment->table.is() && segment->type == funcref; }); + auto indexType = wasm.getTable(funcrefTableName)->indexType; if (!hasFuncrefElemSegment) { // TODO: use a random table auto segment = std::make_unique( - table->name, builder.makeConst(int32_t(0))); + table->name, builder.makeConst(Literal::makeFromInt32(0, indexType))); segment->setName(Names::getValidElementSegmentName(wasm, "elem$"), false); wasm.addElementSegment(std::move(segment)); } @@ -1988,11 +2007,12 @@ Expression* TranslateToFuzzReader::makeCallIndirect(Type type) { } // with high probability, make sure the type is valid otherwise, most are // going to trap + auto indexType = wasm.getTable(funcrefTableName)->indexType; Expression* target; if (!allowOOB || !oneIn(10)) { - target = builder.makeConst(int32_t(i)); + target = builder.makeConst(Literal::makeFromInt32(i, indexType)); } else { - target = make(Type::i32); + target = make(indexType); } std::vector args; for (const auto& type : targetFn->getParams()) { diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index fab2ccc9d63..cf3692a1f5d 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,34 @@ Metrics total - [exports] : 50 - [funcs] : 72 - [globals] : 9 + [exports] : 14 + [funcs] : 17 + [globals] : 10 [imports] : 4 [memories] : 1 - [memory-data] : 2 - [table-data] : 25 + [memory-data] : 24 + [table-data] : 3 [tables] : 1 [tags] : 0 - [total] : 4381 - [vars] : 218 - Binary : 335 - Block : 725 - Break : 120 - Call : 210 - CallIndirect : 23 - Const : 692 - Drop : 64 - GlobalGet : 391 - GlobalSet : 298 - If : 236 - Load : 71 - LocalGet : 285 - LocalSet : 209 - Loop : 76 - Nop : 63 - RefFunc : 25 - Return : 60 - Select : 23 - Store : 29 - Switch : 2 - Unary : 293 - Unreachable : 151 + [total] : 8422 + [vars] : 42 + Binary : 633 + Block : 1449 + Break : 335 + Call : 123 + CallIndirect : 40 + Const : 1247 + Drop : 85 + GlobalGet : 707 + GlobalSet : 526 + If : 490 + Load : 156 + LocalGet : 627 + LocalSet : 520 + Loop : 235 + Nop : 126 + RefFunc : 3 + Return : 87 + Select : 82 + Store : 70 + Unary : 622 + Unreachable : 259 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 3189277b070..9d2b4e3da14 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,51 +1,46 @@ Metrics total - [exports] : 4 - [funcs] : 3 + [exports] : 8 + [funcs] : 9 [globals] : 26 [imports] : 8 [memories] : 1 - [memory-data] : 20 + [memory-data] : 112 [table-data] : 0 [tables] : 1 [tags] : 2 - [total] : 534 - [vars] : 21 + [total] : 487 + [vars] : 37 + ArrayFill : 1 ArrayGet : 1 - ArrayLen : 1 - ArrayNew : 13 - ArrayNewFixed : 2 - AtomicNotify : 2 - Binary : 68 - Block : 53 - BrOn : 1 - Break : 8 - Call : 11 - CallRef : 1 - Const : 117 - DataDrop : 1 - Drop : 7 - GlobalGet : 27 - GlobalSet : 16 + ArrayLen : 2 + ArrayNew : 7 + ArrayNewFixed : 5 + Binary : 69 + Block : 42 + Break : 2 + Call : 9 + Const : 103 + Drop : 3 + GlobalGet : 30 + GlobalSet : 20 If : 13 - Load : 19 - LocalGet : 56 - LocalSet : 39 - Loop : 6 - Nop : 2 - Pop : 3 - RefAs : 4 - RefFunc : 2 - RefNull : 7 - RefTest : 1 - Return : 3 - SIMDExtract : 3 - Store : 2 - StringConst : 2 - StringWTF16Get : 1 - StructNew : 11 - Try : 3 - TryTable : 1 - TupleMake : 3 - Unary : 14 + Load : 18 + LocalGet : 41 + LocalSet : 24 + Loop : 2 + Nop : 1 + Pop : 1 + RefFunc : 21 + RefNull : 5 + Return : 5 + Select : 2 + StringConst : 3 + StringEq : 1 + StructNew : 27 + Try : 1 + TryTable : 2 + TupleExtract : 1 + TupleMake : 4 + Unary : 11 Unreachable : 10 From 7a0e738e363d13880ec25018134e178d57c5ba6a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 Nov 2024 14:28:59 -0800 Subject: [PATCH 115/622] [wasm64] Fix copying of 64-bit tables, and fuzz them (#7065) `ModuleUtils::copyTable` was not copying the `indexType` property. --- src/ir/module-utils.cpp | 1 + src/tools/fuzzing/fuzzing.cpp | 22 ++++- test/lit/merge/table64.wat | 19 +++++ test/lit/merge/table64.wat.second | 12 +++ test/passes/fuzz_metrics_noprint.bin.txt | 55 +++++++------ ...e-to-fuzz_all-features_metrics_noprint.txt | 82 +++++++++++-------- 6 files changed, 126 insertions(+), 65 deletions(-) create mode 100644 test/lit/merge/table64.wat create mode 100644 test/lit/merge/table64.wat.second diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 2f26cfa779c..7aed263a97a 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -175,6 +175,7 @@ Table* copyTable(const Table* table, Module& out) { ret->initial = table->initial; ret->max = table->max; + ret->indexType = table->indexType; return out.addTable(std::move(ret)); } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 26c32196143..ed3287a7c1f 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -370,8 +370,26 @@ void TranslateToFuzzReader::setupTables() { if (iter != wasm.tables.end()) { table = iter->get(); } else { - auto tablePtr = builder.makeTable( - Names::getValidTableName(wasm, "fuzzing_table"), funcref, 0, 0); + // Start from a potentially empty table. + Address initial = upTo(10); + // Make the max potentially higher, or unlimited. + Address max; + if (oneIn(2)) { + max = initial + upTo(4); + } else { + max = Memory::kUnlimitedSize; + } + // Fuzz wasm64 when possible, sometimes. + auto indexType = Type::i32; + if (wasm.features.hasMemory64() && oneIn(2)) { + indexType = Type::i64; + } + auto tablePtr = + builder.makeTable(Names::getValidTableName(wasm, "fuzzing_table"), + funcref, + initial, + max, + indexType); tablePtr->hasExplicitName = true; table = wasm.addTable(std::move(tablePtr)); } diff --git a/test/lit/merge/table64.wat b/test/lit/merge/table64.wat new file mode 100644 index 00000000000..a313b5bc350 --- /dev/null +++ b/test/lit/merge/table64.wat @@ -0,0 +1,19 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-merge %s first %s.second second --rename-export-conflicts -all -S -o - | filecheck %s + +;; An empty module. The interesting part is in the second module: we should +;; copy the i64 table properly. +(module +) +;; CHECK: (type $0 (func)) + +;; CHECK: (table $table i64 15 15 funcref) + +;; CHECK: (elem $0 (i64.const 0) $second) + +;; CHECK: (export "table" (table $table)) + +;; CHECK: (func $second (type $0) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) diff --git a/test/lit/merge/table64.wat.second b/test/lit/merge/table64.wat.second new file mode 100644 index 00000000000..cbc5a78fb50 --- /dev/null +++ b/test/lit/merge/table64.wat.second @@ -0,0 +1,12 @@ +;; A module with a wasm64 table. We must copy it properly when we merge. +(module + (table $table i64 15 15 funcref) + + (elem $0 (i64.const 0) $second) + + (export "table" (table $table)) + + (func $second) +) + + diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index cf3692a1f5d..ba0ccaa10a4 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,34 +1,35 @@ Metrics total - [exports] : 14 - [funcs] : 17 - [globals] : 10 + [exports] : 25 + [funcs] : 40 + [globals] : 18 [imports] : 4 [memories] : 1 [memory-data] : 24 - [table-data] : 3 + [table-data] : 19 [tables] : 1 [tags] : 0 - [total] : 8422 - [vars] : 42 - Binary : 633 - Block : 1449 - Break : 335 - Call : 123 - CallIndirect : 40 - Const : 1247 - Drop : 85 - GlobalGet : 707 - GlobalSet : 526 - If : 490 - Load : 156 - LocalGet : 627 - LocalSet : 520 - Loop : 235 - Nop : 126 - RefFunc : 3 - Return : 87 - Select : 82 - Store : 70 - Unary : 622 - Unreachable : 259 + [total] : 5335 + [vars] : 170 + Binary : 403 + Block : 900 + Break : 163 + Call : 197 + CallIndirect : 11 + Const : 824 + Drop : 56 + GlobalGet : 464 + GlobalSet : 342 + If : 298 + Load : 87 + LocalGet : 402 + LocalSet : 304 + Loop : 126 + Nop : 74 + RefFunc : 19 + Return : 60 + Select : 34 + Store : 46 + Switch : 1 + Unary : 356 + Unreachable : 168 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 9d2b4e3da14..fd37193f5ba 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,46 +1,56 @@ Metrics total - [exports] : 8 - [funcs] : 9 - [globals] : 26 + [exports] : 7 + [funcs] : 8 + [globals] : 4 [imports] : 8 [memories] : 1 [memory-data] : 112 [table-data] : 0 [tables] : 1 - [tags] : 2 - [total] : 487 - [vars] : 37 - ArrayFill : 1 - ArrayGet : 1 - ArrayLen : 2 - ArrayNew : 7 + [tags] : 1 + [total] : 645 + [vars] : 33 + ArrayGet : 2 + ArrayLen : 1 + ArrayNew : 3 ArrayNewFixed : 5 - Binary : 69 - Block : 42 - Break : 2 - Call : 9 - Const : 103 - Drop : 3 - GlobalGet : 30 - GlobalSet : 20 - If : 13 - Load : 18 - LocalGet : 41 - LocalSet : 24 - Loop : 2 - Nop : 1 - Pop : 1 - RefFunc : 21 - RefNull : 5 + AtomicCmpxchg : 1 + AtomicFence : 1 + AtomicRMW : 1 + Binary : 76 + Block : 64 + Break : 4 + Call : 15 + Const : 149 + DataDrop : 1 + Drop : 1 + GlobalGet : 25 + GlobalSet : 22 + If : 22 + Load : 25 + LocalGet : 55 + LocalSet : 35 + Loop : 4 + MemoryFill : 1 + Nop : 7 + Pop : 6 + RefAs : 5 + RefCast : 1 + RefEq : 2 + RefFunc : 13 + RefIsNull : 1 + RefNull : 10 Return : 5 - Select : 2 - StringConst : 3 - StringEq : 1 - StructNew : 27 - Try : 1 + SIMDExtract : 1 + Select : 6 + StringConst : 1 + StringEncode : 1 + StructNew : 24 + StructSet : 1 + Try : 7 TryTable : 2 - TupleExtract : 1 - TupleMake : 4 - Unary : 11 - Unreachable : 10 + TupleExtract : 2 + TupleMake : 3 + Unary : 23 + Unreachable : 11 From a3d940ff8020ad8adb525b4ab018fcd86d08c54a Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 7 Nov 2024 15:53:01 -0800 Subject: [PATCH 116/622] Rename indexType -> addressType. NFC (#7060) See https://github.com/WebAssembly/memory64/pull/92 --- src/abi/stack.h | 2 +- src/binaryen-c.cpp | 2 +- src/ir/child-typer.h | 8 ++-- src/ir/memory-utils.cpp | 2 +- src/ir/module-utils.cpp | 4 +- src/parser/context-decls.cpp | 6 +-- src/parser/contexts.h | 14 +++--- src/parser/parsers.h | 48 ++++++++++---------- src/passes/AlignmentLowering.cpp | 56 +++++++++++------------ src/passes/AvoidReinterprets.cpp | 8 ++-- src/passes/GenerateDynCalls.cpp | 9 ++-- src/passes/InstrumentMemory.cpp | 28 +++++++----- src/passes/Memory64Lowering.cpp | 2 +- src/passes/MemoryPacking.cpp | 8 ++-- src/passes/MultiMemoryLowering.cpp | 6 +-- src/passes/SafeHeap.cpp | 64 +++++++++++++-------------- src/passes/SimplifyLocals.cpp | 6 +-- src/passes/SpillPointers.cpp | 2 +- src/passes/StackCheck.cpp | 8 ++-- src/passes/Table64Lowering.cpp | 2 +- src/tools/fuzzing/fuzzing.cpp | 30 ++++++------- src/tools/wasm-ctor-eval.cpp | 2 +- src/tools/wasm-split/instrumenter.cpp | 2 +- src/wasm-binary.h | 2 +- src/wasm-builder.h | 12 ++--- src/wasm-interpreter.h | 22 ++++----- src/wasm.h | 10 ++--- src/wasm/wasm-binary.cpp | 14 +++--- src/wasm/wasm-validator.cpp | 58 ++++++++++++------------ 29 files changed, 223 insertions(+), 214 deletions(-) diff --git a/src/abi/stack.h b/src/abi/stack.h index 752aecae46d..ae488e8c2a2 100644 --- a/src/abi/stack.h +++ b/src/abi/stack.h @@ -48,7 +48,7 @@ getStackSpace(Index local, Function* func, Index size, Module& wasm) { // align the size size = stackAlign(size); auto pointerType = - !wasm.memories.empty() ? wasm.memories[0]->indexType : Type::i32; + !wasm.memories.empty() ? wasm.memories[0]->addressType : Type::i32; // TODO: find existing stack usage, and add on top of that - carefully Builder builder(wasm); auto* block = builder.makeBlock(); diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 64462c35465..854b0c9953b 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -5088,7 +5088,7 @@ void BinaryenSetMemory(BinaryenModuleRef module, memory->initial = initial; memory->max = int32_t(maximum); // Make sure -1 extends. memory->shared = shared; - memory->indexType = memory64 ? Type::i64 : Type::i32; + memory->addressType = memory64 ? Type::i64 : Type::i32; if (exportName) { auto memoryExport = std::make_unique(); memoryExport->name = exportName; diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 499a7e4ddfc..154da0e455c 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -78,7 +78,7 @@ template struct ChildTyper : OverriddenVisitor { } void notePointer(Expression** ptrp, Name mem) { - note(ptrp, wasm.getMemory(mem)->indexType); + note(ptrp, wasm.getMemory(mem)->addressType); } void noteAny(Expression** childp) { self().noteAnyType(childp); } @@ -270,8 +270,8 @@ template struct ChildTyper : OverriddenVisitor { void visitDataDrop(DataDrop* curr) {} void visitMemoryCopy(MemoryCopy* curr) { - assert(wasm.getMemory(curr->destMemory)->indexType == - wasm.getMemory(curr->sourceMemory)->indexType); + assert(wasm.getMemory(curr->destMemory)->addressType == + wasm.getMemory(curr->sourceMemory)->addressType); notePointer(&curr->dest, curr->destMemory); notePointer(&curr->source, curr->sourceMemory); notePointer(&curr->size, curr->destMemory); @@ -762,7 +762,7 @@ template struct ChildTyper : OverriddenVisitor { } void visitTableInit(TableInit* curr) { - note(&curr->dest, wasm.getTable(curr->table)->indexType); + note(&curr->dest, wasm.getTable(curr->table)->addressType); note(&curr->offset, Type::i32); note(&curr->size, Type::i32); } diff --git a/src/ir/memory-utils.cpp b/src/ir/memory-utils.cpp index 552e4ff9aee..70f316028a9 100644 --- a/src/ir/memory-utils.cpp +++ b/src/ir/memory-utils.cpp @@ -110,7 +110,7 @@ bool flatten(Module& wasm) { std::copy(segment->data.begin(), segment->data.end(), data.begin() + start); } dataSegments[0]->offset->cast()->value = - Literal::makeFromInt32(0, wasm.memories[0]->indexType); + Literal::makeFromInt32(0, wasm.memories[0]->addressType); dataSegments[0]->data.swap(data); wasm.removeDataSegments( [&](DataSegment* curr) { return curr->name != dataSegments[0]->name; }); diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 7aed263a97a..2fd129a9c21 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -175,7 +175,7 @@ Table* copyTable(const Table* table, Module& out) { ret->initial = table->initial; ret->max = table->max; - ret->indexType = table->indexType; + ret->addressType = table->addressType; return out.addTable(std::move(ret)); } @@ -186,7 +186,7 @@ Memory* copyMemory(const Memory* memory, Module& out) { ret->initial = memory->initial; ret->max = memory->max; ret->shared = memory->shared; - ret->indexType = memory->indexType; + ret->addressType = memory->addressType; ret->module = memory->module; ret->base = memory->base; diff --git a/src/parser/context-decls.cpp b/src/parser/context-decls.cpp index 8e9638ae7b3..c124689d334 100644 --- a/src/parser/context-decls.cpp +++ b/src/parser/context-decls.cpp @@ -84,7 +84,7 @@ Result ParseDeclsCtx::addTableDecl(Index pos, ImportNames* importNames, TableType type) { auto t = std::make_unique(); - t->indexType = type.indexType; + t->addressType = type.addressType; t->initial = type.limits.initial; t->max = type.limits.max ? *type.limits.max : Table::kUnlimitedSize; if (name.is()) { @@ -139,7 +139,7 @@ Result ParseDeclsCtx::addMemoryDecl(Index pos, ImportNames* importNames, MemType type) { auto m = std::make_unique(); - m->indexType = type.indexType; + m->addressType = type.addressType; m->initial = type.limits.initial; m->max = type.limits.max ? *type.limits.max : Memory::kUnlimitedSize; m->shared = type.shared; @@ -178,7 +178,7 @@ Result<> ParseDeclsCtx::addImplicitData(DataStringT&& data) { auto d = std::make_unique(); d->memory = mem.name; d->isPassive = false; - d->offset = Builder(wasm).makeConstPtr(0, mem.indexType); + d->offset = Builder(wasm).makeConstPtr(0, mem.addressType); d->data = std::move(data); d->name = Names::getValidDataSegmentName(wasm, "implicit-data"); wasm.addDataSegment(std::move(d)); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index b0cd1458bb6..807b6c0030e 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -46,7 +46,7 @@ struct Limits { }; struct MemType { - Type indexType; + Type addressType; Limits limits; bool shared; }; @@ -57,7 +57,7 @@ struct Memarg { }; struct TableType { - Type indexType; + Type addressType; Limits limits; }; @@ -965,8 +965,8 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { Limits getLimitsFromElems(Index elems) { return {elems, elems}; } - TableType makeTableType(Type indexType, Limits limits, TypeT) { - return {indexType, limits}; + TableType makeTableType(Type addressType, Limits limits, TypeT) { + return {addressType, limits}; } std::vector makeDataString() { return {}; } @@ -979,8 +979,8 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { return {size, size}; } - MemType makeMemType(Type indexType, Limits limits, bool shared) { - return {indexType, limits, shared}; + MemType makeMemType(Type addressType, Limits limits, bool shared) { + return {addressType, limits, shared}; } Result @@ -1273,7 +1273,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, LimitsT getLimitsFromElems(ElemListT) { return Ok{}; } - Type makeTableType(Type indexType, LimitsT, Type type) { return type; } + Type makeTableType(Type addressType, LimitsT, Type type) { return type; } LimitsT getLimitsFromData(DataStringT) { return Ok{}; } MemTypeT makeMemType(Type, LimitsT, bool) { return Ok{}; } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 02c2e9e4760..b6699aab6a9 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -47,10 +47,10 @@ template Result limits32(Ctx&); template Result limits64(Ctx&); template Result memtype(Ctx&); template -Result memtypeContinued(Ctx&, Type indexType); +Result memtypeContinued(Ctx&, Type addressType); template Result tabletype(Ctx&); template -Result tabletypeContinued(Ctx&, Type indexType); +Result tabletypeContinued(Ctx&, Type addressType); template Result globaltype(Ctx&); template Result tupleArity(Ctx&); @@ -780,45 +780,46 @@ template Result limits64(Ctx& ctx) { // note: the index type 'i32' or 'i64' is already parsed to simplify parsing of // memory abbreviations. template Result memtype(Ctx& ctx) { - Type indexType = Type::i32; + Type addressType = Type::i32; if (ctx.in.takeKeyword("i64"sv)) { - indexType = Type::i64; + addressType = Type::i64; } else { ctx.in.takeKeyword("i32"sv); } - return memtypeContinued(ctx, indexType); + return memtypeContinued(ctx, addressType); } template -Result memtypeContinued(Ctx& ctx, Type indexType) { - assert(indexType == Type::i32 || indexType == Type::i64); - auto limits = indexType == Type::i32 ? limits32(ctx) : limits64(ctx); +Result memtypeContinued(Ctx& ctx, Type addressType) { + assert(addressType == Type::i32 || addressType == Type::i64); + auto limits = addressType == Type::i32 ? limits32(ctx) : limits64(ctx); CHECK_ERR(limits); bool shared = false; if (ctx.in.takeKeyword("shared"sv)) { shared = true; } - return ctx.makeMemType(indexType, *limits, shared); + return ctx.makeMemType(addressType, *limits, shared); } // tabletype ::= (limits32 | 'i32' limits32 | 'i64' limit64) reftype template Result tabletype(Ctx& ctx) { - Type indexType = Type::i32; + Type addressType = Type::i32; if (ctx.in.takeKeyword("i64"sv)) { - indexType = Type::i64; + addressType = Type::i64; } else { ctx.in.takeKeyword("i32"sv); } - return tabletypeContinued(ctx, indexType); + return tabletypeContinued(ctx, addressType); } template -Result tabletypeContinued(Ctx& ctx, Type indexType) { - auto limits = indexType == Type::i32 ? limits32(ctx) : limits64(ctx); +Result tabletypeContinued(Ctx& ctx, + Type addressType) { + auto limits = addressType == Type::i32 ? limits32(ctx) : limits64(ctx); CHECK_ERR(limits); auto type = reftype(ctx); CHECK_ERR(type); - return ctx.makeTableType(indexType, *limits, *type); + return ctx.makeTableType(addressType, *limits, *type); } // globaltype ::= t:valtype => const t @@ -3036,9 +3037,9 @@ template MaybeResult<> table(Ctx& ctx) { auto import = inlineImport(ctx.in); CHECK_ERR(import); - auto indexType = Type::i32; + auto addressType = Type::i32; if (ctx.in.takeKeyword("i64"sv)) { - indexType = Type::i64; + addressType = Type::i64; } else { ctx.in.takeKeyword("i32"sv); } @@ -3077,10 +3078,10 @@ template MaybeResult<> table(Ctx& ctx) { if (!ctx.in.takeRParen()) { return ctx.in.err("expected end of inline elems"); } - ttype = ctx.makeTableType(indexType, ctx.getLimitsFromElems(list), *type); + ttype = ctx.makeTableType(addressType, ctx.getLimitsFromElems(list), *type); elems = std::move(list); } else { - auto tabtype = tabletypeContinued(ctx, indexType); + auto tabtype = tabletypeContinued(ctx, addressType); CHECK_ERR(tabtype); ttype = *tabtype; } @@ -3119,9 +3120,9 @@ template MaybeResult<> memory(Ctx& ctx) { auto import = inlineImport(ctx.in); CHECK_ERR(import); - auto indexType = Type::i32; + auto addressType = Type::i32; if (ctx.in.takeKeyword("i64"sv)) { - indexType = Type::i64; + addressType = Type::i64; } else { ctx.in.takeKeyword("i32"sv); } @@ -3137,10 +3138,11 @@ template MaybeResult<> memory(Ctx& ctx) { if (!ctx.in.takeRParen()) { return ctx.in.err("expected end of inline data"); } - mtype = ctx.makeMemType(indexType, ctx.getLimitsFromData(*datastr), false); + mtype = + ctx.makeMemType(addressType, ctx.getLimitsFromData(*datastr), false); data = *datastr; } else { - auto type = memtypeContinued(ctx, indexType); + auto type = memtypeContinued(ctx, addressType); CHECK_ERR(type); mtype = *type; } diff --git a/src/passes/AlignmentLowering.cpp b/src/passes/AlignmentLowering.cpp index d0ceeb6107b..52849ebaccb 100644 --- a/src/passes/AlignmentLowering.cpp +++ b/src/passes/AlignmentLowering.cpp @@ -35,10 +35,10 @@ struct AlignmentLowering : public WalkerPass> { return curr; } auto mem = getModule()->getMemory(curr->memory); - auto indexType = mem->indexType; + auto addressType = mem->addressType; Builder builder(*getModule()); assert(curr->type == Type::i32); - auto temp = builder.addVar(getFunction(), indexType); + auto temp = builder.addVar(getFunction(), addressType); Expression* ret; if (curr->bytes == 2) { ret = builder.makeBinary( @@ -47,7 +47,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset, 1, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory), builder.makeBinary( @@ -56,7 +56,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset + 1, 1, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory), builder.makeConst(int32_t(8)))); @@ -73,7 +73,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset, 1, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory), builder.makeBinary( @@ -82,7 +82,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset + 1, 1, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory), builder.makeConst(int32_t(8)))), @@ -94,7 +94,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset + 2, 1, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory), builder.makeConst(int32_t(16))), @@ -104,7 +104,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset + 3, 1, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory), builder.makeConst(int32_t(24))))); @@ -115,7 +115,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset, 2, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory), builder.makeBinary( @@ -124,7 +124,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset + 2, 2, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory), builder.makeConst(int32_t(16)))); @@ -145,8 +145,8 @@ struct AlignmentLowering : public WalkerPass> { Builder builder(*getModule()); assert(curr->value->type == Type::i32); auto mem = getModule()->getMemory(curr->memory); - auto indexType = mem->indexType; - auto tempPtr = builder.addVar(getFunction(), indexType); + auto addressType = mem->addressType; + auto tempPtr = builder.addVar(getFunction(), addressType); auto tempValue = builder.addVar(getFunction(), Type::i32); auto* block = builder.makeBlock({builder.makeLocalSet(tempPtr, curr->ptr), @@ -156,7 +156,7 @@ struct AlignmentLowering : public WalkerPass> { builder.makeStore(1, curr->offset, 1, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), builder.makeLocalGet(tempValue, Type::i32), Type::i32, curr->memory)); @@ -164,7 +164,7 @@ struct AlignmentLowering : public WalkerPass> { 1, curr->offset + 1, 1, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), builder.makeBinary(ShrUInt32, builder.makeLocalGet(tempValue, Type::i32), builder.makeConst(int32_t(8))), @@ -176,7 +176,7 @@ struct AlignmentLowering : public WalkerPass> { builder.makeStore(1, curr->offset, 1, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), builder.makeLocalGet(tempValue, Type::i32), Type::i32, curr->memory)); @@ -184,7 +184,7 @@ struct AlignmentLowering : public WalkerPass> { 1, curr->offset + 1, 1, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), builder.makeBinary(ShrUInt32, builder.makeLocalGet(tempValue, Type::i32), builder.makeConst(int32_t(8))), @@ -194,7 +194,7 @@ struct AlignmentLowering : public WalkerPass> { 1, curr->offset + 2, 1, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), builder.makeBinary(ShrUInt32, builder.makeLocalGet(tempValue, Type::i32), builder.makeConst(int32_t(16))), @@ -204,7 +204,7 @@ struct AlignmentLowering : public WalkerPass> { 1, curr->offset + 3, 1, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), builder.makeBinary(ShrUInt32, builder.makeLocalGet(tempValue, Type::i32), builder.makeConst(int32_t(24))), @@ -215,7 +215,7 @@ struct AlignmentLowering : public WalkerPass> { builder.makeStore(2, curr->offset, 2, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), builder.makeLocalGet(tempValue, Type::i32), Type::i32, curr->memory)); @@ -223,7 +223,7 @@ struct AlignmentLowering : public WalkerPass> { 2, curr->offset + 2, 2, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), builder.makeBinary(ShrUInt32, builder.makeLocalGet(tempValue, Type::i32), builder.makeConst(int32_t(16))), @@ -275,15 +275,15 @@ struct AlignmentLowering : public WalkerPass> { } // Load two 32-bit pieces, and combine them. auto mem = getModule()->getMemory(curr->memory); - auto indexType = mem->indexType; - auto temp = builder.addVar(getFunction(), indexType); + auto addressType = mem->addressType; + auto temp = builder.addVar(getFunction(), addressType); auto* set = builder.makeLocalSet(temp, curr->ptr); Expression* low = lowerLoadI32(builder.makeLoad(4, false, curr->offset, curr->align, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory)); low = builder.makeUnary(ExtendUInt32, low); @@ -296,7 +296,7 @@ struct AlignmentLowering : public WalkerPass> { false, curr->offset + 4, curr->align, - builder.makeLocalGet(temp, indexType), + builder.makeLocalGet(temp, addressType), Type::i32, curr->memory)); high = builder.makeUnary(ExtendUInt32, high); @@ -357,8 +357,8 @@ struct AlignmentLowering : public WalkerPass> { } // Store as two 32-bit pieces. auto mem = getModule()->getMemory(curr->memory); - auto indexType = mem->indexType; - auto tempPtr = builder.addVar(getFunction(), indexType); + auto addressType = mem->addressType; + auto tempPtr = builder.addVar(getFunction(), addressType); auto* setPtr = builder.makeLocalSet(tempPtr, curr->ptr); auto tempValue = builder.addVar(getFunction(), Type::i64); auto* setValue = builder.makeLocalSet(tempValue, value); @@ -368,7 +368,7 @@ struct AlignmentLowering : public WalkerPass> { builder.makeStore(4, curr->offset, curr->align, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), low, Type::i32, curr->memory)); @@ -385,7 +385,7 @@ struct AlignmentLowering : public WalkerPass> { builder.makeStore(4, curr->offset + 4, curr->align, - builder.makeLocalGet(tempPtr, indexType), + builder.makeLocalGet(tempPtr, addressType), high, Type::i32, curr->memory)); diff --git a/src/passes/AvoidReinterprets.cpp b/src/passes/AvoidReinterprets.cpp index 94ae42b61a1..32a19018048 100644 --- a/src/passes/AvoidReinterprets.cpp +++ b/src/passes/AvoidReinterprets.cpp @@ -121,7 +121,7 @@ struct AvoidReinterprets : public WalkerPass> { if (info.reinterpreted && canReplaceWithReinterpret(load)) { // We should use another load here, to avoid reinterprets. auto mem = getModule()->getMemory(load->memory); - info.ptrLocal = Builder::addVar(func, mem->indexType); + info.ptrLocal = Builder::addVar(func, mem->addressType); info.reinterpretedLocal = Builder::addVar(func, load->type.reinterpret()); } else { @@ -176,8 +176,8 @@ struct AvoidReinterprets : public WalkerPass> { Builder builder(*module); auto* ptr = curr->ptr; auto mem = getModule()->getMemory(curr->memory); - auto indexType = mem->indexType; - curr->ptr = builder.makeLocalGet(info.ptrLocal, indexType); + auto addressType = mem->addressType; + curr->ptr = builder.makeLocalGet(info.ptrLocal, addressType); // Note that the other load can have its sign set to false - if the // original were an integer, the other is a float anyhow; and if // original were a float, we don't know what sign to use. @@ -186,7 +186,7 @@ struct AvoidReinterprets : public WalkerPass> { builder.makeLocalSet( info.reinterpretedLocal, makeReinterpretedLoad( - curr, builder.makeLocalGet(info.ptrLocal, indexType))), + curr, builder.makeLocalGet(info.ptrLocal, addressType))), curr})); } } diff --git a/src/passes/GenerateDynCalls.cpp b/src/passes/GenerateDynCalls.cpp index 6e6e0a71781..8d2f3fa24eb 100644 --- a/src/passes/GenerateDynCalls.cpp +++ b/src/passes/GenerateDynCalls.cpp @@ -146,11 +146,12 @@ void GenerateDynCalls::generateDynCallThunk(HeapType funcType) { auto* table = wasm->addTable(Builder::makeTable(Name::fromInt(0))); table->module = ENV; table->base = "__indirect_function_table"; - table->indexType = wasm->memories[0]->indexType; + table->addressType = wasm->memories[0]->addressType; } auto& table = wasm->tables[0]; - namedParams.emplace_back("fptr", table->indexType); // function pointer param - params.push_back(table->indexType); + namedParams.emplace_back("fptr", + table->addressType); // function pointer param + params.push_back(table->addressType); int p = 0; for (const auto& param : sig.params) { namedParams.emplace_back(std::to_string(p++), param); @@ -159,7 +160,7 @@ void GenerateDynCalls::generateDynCallThunk(HeapType funcType) { auto f = builder.makeFunction( name, std::move(namedParams), Signature(Type(params), sig.results), {}); f->hasExplicitName = true; - Expression* fptr = builder.makeLocalGet(0, table->indexType); + Expression* fptr = builder.makeLocalGet(0, table->addressType); std::vector args; Index i = 0; for (const auto& param : sig.params) { diff --git a/src/passes/InstrumentMemory.cpp b/src/passes/InstrumentMemory.cpp index b3c9aebbd35..9bba5f537c3 100644 --- a/src/passes/InstrumentMemory.cpp +++ b/src/passes/InstrumentMemory.cpp @@ -104,14 +104,14 @@ struct InstrumentMemory : public WalkerPass> { id++; Builder builder(*getModule()); auto mem = getModule()->getMemory(curr->memory); - auto indexType = mem->indexType; - auto offset = builder.makeConstPtr(curr->offset.addr, indexType); + auto addressType = mem->addressType; + auto offset = builder.makeConstPtr(curr->offset.addr, addressType); curr->ptr = builder.makeCall(load_ptr, {builder.makeConst(int32_t(id)), builder.makeConst(int32_t(curr->bytes)), offset, curr->ptr}, - indexType); + addressType); Name target; switch (curr->type.getBasic()) { case Type::i32: @@ -137,14 +137,14 @@ struct InstrumentMemory : public WalkerPass> { id++; Builder builder(*getModule()); auto mem = getModule()->getMemory(curr->memory); - auto indexType = mem->indexType; - auto offset = builder.makeConstPtr(curr->offset.addr, indexType); + auto addressType = mem->addressType; + auto offset = builder.makeConstPtr(curr->offset.addr, addressType); curr->ptr = builder.makeCall(store_ptr, {builder.makeConst(int32_t(id)), builder.makeConst(int32_t(curr->bytes)), offset, curr->ptr}, - indexType); + addressType); Name target; switch (curr->value->type.getBasic()) { case Type::i32: @@ -251,20 +251,24 @@ struct InstrumentMemory : public WalkerPass> { } void visitModule(Module* curr) { - auto indexType = - curr->memories.empty() ? Type::i32 : curr->memories[0]->indexType; + auto addressType = + curr->memories.empty() ? Type::i32 : curr->memories[0]->addressType; // Load. - addImport( - curr, load_ptr, {Type::i32, Type::i32, indexType, indexType}, indexType); + addImport(curr, + load_ptr, + {Type::i32, Type::i32, addressType, addressType}, + addressType); addImport(curr, load_val_i32, {Type::i32, Type::i32}, Type::i32); addImport(curr, load_val_i64, {Type::i32, Type::i64}, Type::i64); addImport(curr, load_val_f32, {Type::i32, Type::f32}, Type::f32); addImport(curr, load_val_f64, {Type::i32, Type::f64}, Type::f64); // Store. - addImport( - curr, store_ptr, {Type::i32, Type::i32, indexType, indexType}, indexType); + addImport(curr, + store_ptr, + {Type::i32, Type::i32, addressType, addressType}, + addressType); addImport(curr, store_val_i32, {Type::i32, Type::i32}, Type::i32); addImport(curr, store_val_i64, {Type::i32, Type::i64}, Type::i64); addImport(curr, store_val_f32, {Type::i32, Type::f32}, Type::f32); diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index 0a19d11c812..a3913c7598b 100644 --- a/src/passes/Memory64Lowering.cpp +++ b/src/passes/Memory64Lowering.cpp @@ -187,7 +187,7 @@ struct Memory64Lowering : public WalkerPass> { // we don't want to depend on that specific ordering. for (auto& memory : module->memories) { if (memory->is64()) { - memory->indexType = Type::i32; + memory->addressType = Type::i32; if (memory->hasMax() && memory->max > Memory::kMaxSize32) { memory->max = Memory::kMaxSize32; } diff --git a/src/passes/MemoryPacking.cpp b/src/passes/MemoryPacking.cpp index ad562faf49a..6c411378b23 100644 --- a/src/passes/MemoryPacking.cpp +++ b/src/passes/MemoryPacking.cpp @@ -92,7 +92,7 @@ makeGtShiftedMemorySize(Builder& builder, Module& module, MemoryInit* curr) { curr->dest, builder.makeBinary(mem->is64() ? ShlInt64 : ShlInt32, builder.makeMemorySize(mem->name), - builder.makeConstPtr(16, mem->indexType))); + builder.makeConstPtr(16, mem->addressType))); } } // anonymous namespace @@ -781,7 +781,7 @@ void MemoryPacking::createReplacements(Module* module, // Calculate dest, either as a const or as an addition to the dest local Expression* dest; - Type ptrType = module->getMemory(init->memory)->indexType; + Type ptrType = module->getMemory(init->memory)->addressType; if (auto* c = init->dest->dynCast()) { dest = builder.makeConstPtr(c->value.getInteger() + bytesWritten, ptrType); @@ -819,8 +819,8 @@ void MemoryPacking::createReplacements(Module* module, replacements[init] = [module, init, setVar, getVars, result](Function* function) { if (setVar != nullptr) { - auto indexType = module->getMemory(init->memory)->indexType; - Index destVar = Builder(*module).addVar(function, indexType); + auto addressType = module->getMemory(init->memory)->addressType; + Index destVar = Builder(*module).addVar(function, addressType); *setVar = destVar; for (auto* getVar : getVars) { *getVar = destVar; diff --git a/src/passes/MultiMemoryLowering.cpp b/src/passes/MultiMemoryLowering.cpp index b214959f710..c22477b79c5 100644 --- a/src/passes/MultiMemoryLowering.cpp +++ b/src/passes/MultiMemoryLowering.cpp @@ -430,7 +430,7 @@ struct MultiMemoryLowering : public Pass { Memory& getFirstMemory() { return *wasm->memories[0]; } void prepCombinedMemory() { - pointerType = getFirstMemory().indexType; + pointerType = getFirstMemory().addressType; memoryInfo = pointerType == Type::i32 ? Builder::MemoryInfo::Memory32 : Builder::MemoryInfo::Memory64; isShared = getFirstMemory().shared; @@ -439,7 +439,7 @@ struct MultiMemoryLowering : public Pass { // We are assuming that each memory is configured the same as the first // and assert if any of the memories does not match this configuration assert(memory->shared == isShared); - assert(memory->indexType == pointerType); + assert(memory->addressType == pointerType); // TODO: handle memory import for memories other than the first if (memory->name != getFirstMemory().name && memory->imported()) { @@ -690,7 +690,7 @@ struct MultiMemoryLowering : public Pass { void addCombinedMemory() { auto memory = Builder::makeMemory(combinedMemory); memory->shared = isShared; - memory->indexType = pointerType; + memory->addressType = pointerType; memory->initial = totalInitialPages; memory->max = totalMaxPages; if (isImported) { diff --git a/src/passes/SafeHeap.cpp b/src/passes/SafeHeap.cpp index 7baeb365e39..195900696e3 100644 --- a/src/passes/SafeHeap.cpp +++ b/src/passes/SafeHeap.cpp @@ -85,7 +85,7 @@ struct AccessInstrumenter : public WalkerPass> { auto memory = getModule()->getMemory(curr->memory); replaceCurrent(builder.makeCall( getLoadName(curr), - {curr->ptr, builder.makeConstPtr(curr->offset.addr, memory->indexType)}, + {curr->ptr, builder.makeConstPtr(curr->offset.addr, memory->addressType)}, curr->type)); } @@ -99,7 +99,7 @@ struct AccessInstrumenter : public WalkerPass> { replaceCurrent(builder.makeCall( getStoreName(curr), {curr->ptr, - builder.makeConstPtr(curr->offset.addr, memory->indexType), + builder.makeConstPtr(curr->offset.addr, memory->addressType), curr->value}, Type::none)); } @@ -156,7 +156,7 @@ struct SafeHeap : public Pass { void addImports(Module* module) { ImportInfo info(*module); - auto indexType = module->memories[0]->indexType; + auto addressType = module->memories[0]->addressType; if (auto* existing = info.getImportedFunction(ENV, GET_SBRK_PTR)) { getSbrkPtr = existing->name; } else if (auto* existing = module->getExportOrNull(GET_SBRK_PTR)) { @@ -165,7 +165,7 @@ struct SafeHeap : public Pass { sbrk = existing->name; } else { auto import = Builder::makeFunction( - GET_SBRK_PTR, Signature(Type::none, indexType), {}); + GET_SBRK_PTR, Signature(Type::none, addressType), {}); getSbrkPtr = GET_SBRK_PTR; import->module = ENV; import->base = GET_SBRK_PTR; @@ -283,17 +283,17 @@ struct SafeHeap : public Pass { } // pointer, offset auto memory = module->getMemory(style.memory); - auto indexType = memory->indexType; - auto funcSig = Signature({indexType, indexType}, style.type); - auto func = Builder::makeFunction(name, funcSig, {indexType}); + auto addressType = memory->addressType; + auto funcSig = Signature({addressType, addressType}, style.type); + auto func = Builder::makeFunction(name, funcSig, {addressType}); Builder builder(*module); auto* block = builder.makeBlock(); // stash the sum of the pointer (0) and the size (1) in a local (2) block->list.push_back(builder.makeLocalSet( 2, builder.makeBinary(memory->is64() ? AddInt64 : AddInt32, - builder.makeLocalGet(0, indexType), - builder.makeLocalGet(1, indexType)))); + builder.makeLocalGet(0, addressType), + builder.makeLocalGet(1, addressType)))); // check for reading past valid memory: if pointer + offset + bytes block->list.push_back(makeBoundsCheck(style.type, builder, @@ -301,7 +301,7 @@ struct SafeHeap : public Pass { 2, style.bytes, module, - memory->indexType, + memory->addressType, memory->is64(), memory->name)); // check proper alignment @@ -312,7 +312,7 @@ struct SafeHeap : public Pass { // do the load auto* load = module->allocator.alloc(); *load = style; // basically the same as the template we are given! - load->ptr = builder.makeLocalGet(2, indexType); + load->ptr = builder.makeLocalGet(2, addressType); Expression* last = load; if (load->isAtomic && load->signed_) { // atomic loads cannot be signed, manually sign it @@ -332,19 +332,19 @@ struct SafeHeap : public Pass { return; } auto memory = module->getMemory(style.memory); - auto indexType = memory->indexType; + auto addressType = memory->addressType; bool is64 = memory->is64(); // pointer, offset, value auto funcSig = - Signature({indexType, indexType, style.valueType}, Type::none); - auto func = Builder::makeFunction(name, funcSig, {indexType}); + Signature({addressType, addressType, style.valueType}, Type::none); + auto func = Builder::makeFunction(name, funcSig, {addressType}); Builder builder(*module); auto* block = builder.makeBlock(); block->list.push_back(builder.makeLocalSet( 3, builder.makeBinary(is64 ? AddInt64 : AddInt32, - builder.makeLocalGet(0, indexType), - builder.makeLocalGet(1, indexType)))); + builder.makeLocalGet(0, addressType), + builder.makeLocalGet(1, addressType)))); // check for reading past valid memory: if pointer + offset + bytes block->list.push_back(makeBoundsCheck(style.valueType, builder, @@ -352,7 +352,7 @@ struct SafeHeap : public Pass { 3, style.bytes, module, - indexType, + addressType, is64, memory->name)); // check proper alignment @@ -364,7 +364,7 @@ struct SafeHeap : public Pass { auto* store = module->allocator.alloc(); *store = style; // basically the same as the template we are given! store->memory = memory->name; - store->ptr = builder.makeLocalGet(3, indexType); + store->ptr = builder.makeLocalGet(3, addressType); store->value = builder.makeLocalGet(2, style.valueType); block->list.push_back(store); block->finalize(Type::none); @@ -378,8 +378,8 @@ struct SafeHeap : public Pass { Module* module, Name memoryName) { auto memory = module->getMemory(memoryName); - auto indexType = memory->indexType; - Expression* ptrBits = builder.makeLocalGet(local, indexType); + auto addressType = memory->addressType; + Expression* ptrBits = builder.makeLocalGet(local, addressType); if (memory->is64()) { ptrBits = builder.makeUnary(WrapInt64, ptrBits); } @@ -399,7 +399,7 @@ struct SafeHeap : public Pass { Index sumLocal, Index bytes, Module* module, - Type indexType, + Type addressType, bool is64, Name memory) { bool lowMemUnused = getPassOptions().lowMemoryUnused; @@ -408,38 +408,38 @@ struct SafeHeap : public Pass { auto upperBound = lowMemUnused ? PassOptions::LowMemoryBound : 0; Expression* brkLocation; if (sbrk.is()) { - brkLocation = - builder.makeCall(sbrk, {builder.makeConstPtr(0, indexType)}, indexType); + brkLocation = builder.makeCall( + sbrk, {builder.makeConstPtr(0, addressType)}, addressType); } else { Expression* sbrkPtr; if (dynamicTopPtr.is()) { - sbrkPtr = builder.makeGlobalGet(dynamicTopPtr, indexType); + sbrkPtr = builder.makeGlobalGet(dynamicTopPtr, addressType); } else { - sbrkPtr = builder.makeCall(getSbrkPtr, {}, indexType); + sbrkPtr = builder.makeCall(getSbrkPtr, {}, addressType); } auto size = is64 ? 8 : 4; brkLocation = - builder.makeLoad(size, false, 0, size, sbrkPtr, indexType, memory); + builder.makeLoad(size, false, 0, size, sbrkPtr, addressType, memory); } auto gtuOp = is64 ? GtUInt64 : GtUInt32; auto addOp = is64 ? AddInt64 : AddInt32; auto* upperCheck = builder.makeBinary(upperOp, - builder.makeLocalGet(sumLocal, indexType), - builder.makeConstPtr(upperBound, indexType)); + builder.makeLocalGet(sumLocal, addressType), + builder.makeConstPtr(upperBound, addressType)); auto* lowerCheck = builder.makeBinary( gtuOp, builder.makeBinary(addOp, - builder.makeLocalGet(sumLocal, indexType), - builder.makeConstPtr(bytes, indexType)), + builder.makeLocalGet(sumLocal, addressType), + builder.makeConstPtr(bytes, addressType)), brkLocation); // Check for an overflow when adding the pointer and the size, using the // rule that for any unsigned x and y, // x + y < x <=> x + y overflows auto* overflowCheck = builder.makeBinary(is64 ? LtUInt64 : LtUInt32, - builder.makeLocalGet(sumLocal, indexType), - builder.makeLocalGet(ptrLocal, indexType)); + builder.makeLocalGet(sumLocal, addressType), + builder.makeLocalGet(ptrLocal, addressType)); // Add an unreachable right after the call to segfault for performance // reasons: the call never returns, and this helps optimizations benefit // from that. diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp index eca503ad4c4..cf0b7495011 100644 --- a/src/passes/SimplifyLocals.cpp +++ b/src/passes/SimplifyLocals.cpp @@ -1088,15 +1088,15 @@ struct SimplifyLocals } auto bestType = func->getLocalType(best); - auto indexType = func->getLocalType(index); - if (!Type::isSubType(indexType, bestType)) { + auto addressType = func->getLocalType(index); + if (!Type::isSubType(addressType, bestType)) { // This is less refined than the current best; ignore. continue; } // This is better if it has a more refined type, or if it has more // uses. - if (indexType != bestType || + if (addressType != bestType || getNumGetsIgnoringCurr(index) > getNumGetsIgnoringCurr(best)) { best = index; } diff --git a/src/passes/SpillPointers.cpp b/src/passes/SpillPointers.cpp index db7fca85abf..cfa432a4ed4 100644 --- a/src/passes/SpillPointers.cpp +++ b/src/passes/SpillPointers.cpp @@ -79,7 +79,7 @@ struct SpillPointers Type pointerType; void spillPointers() { - pointerType = getModule()->memories[0]->indexType; + pointerType = getModule()->memories[0]->addressType; // we only care about possible pointers auto* func = getFunction(); diff --git a/src/passes/StackCheck.cpp b/src/passes/StackCheck.cpp index ce5d346b944..31bf791fd80 100644 --- a/src/passes/StackCheck.cpp +++ b/src/passes/StackCheck.cpp @@ -151,17 +151,17 @@ struct StackCheck : public Pass { Builder builder(*module); // Add the globals. - Type indexType = - module->memories.empty() ? Type::i32 : module->memories[0]->indexType; + Type addressType = + module->memories.empty() ? Type::i32 : module->memories[0]->addressType; auto stackBase = module->addGlobal(builder.makeGlobal(stackBaseName, stackPointer->type, - builder.makeConstPtr(0, indexType), + builder.makeConstPtr(0, addressType), Builder::Mutable)); auto stackLimit = module->addGlobal(builder.makeGlobal(stackLimitName, stackPointer->type, - builder.makeConstPtr(0, indexType), + builder.makeConstPtr(0, addressType), Builder::Mutable)); // Instrument all the code. diff --git a/src/passes/Table64Lowering.cpp b/src/passes/Table64Lowering.cpp index b4a538df9a9..1167515de17 100644 --- a/src/passes/Table64Lowering.cpp +++ b/src/passes/Table64Lowering.cpp @@ -144,7 +144,7 @@ struct Table64Lowering : public WalkerPass> { // we don't want to depend on that specific ordering. for (auto& table : module->tables) { if (table->is64()) { - table->indexType = Type::i32; + table->addressType = Type::i32; } } } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index ed3287a7c1f..1329e886e76 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -212,7 +212,7 @@ void TranslateToFuzzReader::setupMemory() { } // Fuzz wasm64 when possible, sometimes. if (wasm.features.hasMemory64() && oneIn(2)) { - memory->indexType = Type::i64; + memory->addressType = Type::i64; } wasm.addMemory(std::move(memory)); } @@ -234,7 +234,7 @@ void TranslateToFuzzReader::setupMemory() { } if (!segment->isPassive) { segment->offset = builder.makeConst( - Literal::makeFromInt32(memCovered, memory->indexType)); + Literal::makeFromInt32(memCovered, memory->addressType)); memCovered += segSize; segment->memory = memory->name; } @@ -245,7 +245,7 @@ void TranslateToFuzzReader::setupMemory() { auto segment = builder.makeDataSegment(); segment->memory = memory->name; segment->offset = - builder.makeConst(Literal::makeFromInt32(0, memory->indexType)); + builder.makeConst(Literal::makeFromInt32(0, memory->addressType)); segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(0)), false); auto num = upTo(USABLE_MEMORY * 2); @@ -380,16 +380,16 @@ void TranslateToFuzzReader::setupTables() { max = Memory::kUnlimitedSize; } // Fuzz wasm64 when possible, sometimes. - auto indexType = Type::i32; + auto addressType = Type::i32; if (wasm.features.hasMemory64() && oneIn(2)) { - indexType = Type::i64; + addressType = Type::i64; } auto tablePtr = builder.makeTable(Names::getValidTableName(wasm, "fuzzing_table"), funcref, initial, max, - indexType); + addressType); tablePtr->hasExplicitName = true; table = wasm.addTable(std::move(tablePtr)); } @@ -400,11 +400,11 @@ void TranslateToFuzzReader::setupTables() { [&](auto& segment) { return segment->table.is() && segment->type == funcref; }); - auto indexType = wasm.getTable(funcrefTableName)->indexType; + auto addressType = wasm.getTable(funcrefTableName)->addressType; if (!hasFuncrefElemSegment) { // TODO: use a random table auto segment = std::make_unique( - table->name, builder.makeConst(Literal::makeFromInt32(0, indexType))); + table->name, builder.makeConst(Literal::makeFromInt32(0, addressType))); segment->setName(Names::getValidElementSegmentName(wasm, "elem$"), false); wasm.addElementSegment(std::move(segment)); } @@ -705,7 +705,7 @@ void TranslateToFuzzReader::addHashMemorySupport() { std::vector contents; contents.push_back( builder.makeLocalSet(0, builder.makeConst(uint32_t(5381)))); - auto zero = Literal::makeFromInt32(0, wasm.memories[0]->indexType); + auto zero = Literal::makeFromInt32(0, wasm.memories[0]->addressType); for (Index i = 0; i < USABLE_MEMORY; i++) { contents.push_back(builder.makeLocalSet( 0, @@ -2025,12 +2025,12 @@ Expression* TranslateToFuzzReader::makeCallIndirect(Type type) { } // with high probability, make sure the type is valid otherwise, most are // going to trap - auto indexType = wasm.getTable(funcrefTableName)->indexType; + auto addressType = wasm.getTable(funcrefTableName)->addressType; Expression* target; if (!allowOOB || !oneIn(10)) { - target = builder.makeConst(Literal::makeFromInt32(i, indexType)); + target = builder.makeConst(Literal::makeFromInt32(i, addressType)); } else { - target = make(indexType); + target = make(addressType); } std::vector args; for (const auto& type : targetFn->getParams()) { @@ -2203,7 +2203,7 @@ Expression* TranslateToFuzzReader::makeTupleExtract(Type type) { } Expression* TranslateToFuzzReader::makePointer() { - auto* ret = make(wasm.memories[0]->indexType); + auto* ret = make(wasm.memories[0]->addressType); // with high probability, mask the pointer so it's in a reasonable // range. otherwise, most pointers are going to be out of range and // most memory ops will just trap @@ -4452,7 +4452,7 @@ Expression* TranslateToFuzzReader::makeMemoryCopy() { } Expression* dest = makePointer(); Expression* source = makePointer(); - Expression* size = make(wasm.memories[0]->indexType); + Expression* size = make(wasm.memories[0]->addressType); return builder.makeMemoryCopy( dest, source, size, wasm.memories[0]->name, wasm.memories[0]->name); } @@ -4463,7 +4463,7 @@ Expression* TranslateToFuzzReader::makeMemoryFill() { } Expression* dest = makePointer(); Expression* value = make(Type::i32); - Expression* size = make(wasm.memories[0]->indexType); + Expression* size = make(wasm.memories[0]->addressType); return builder.makeMemoryFill(dest, value, size, wasm.memories[0]->name); } diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 52c83b05364..89727d01220 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -514,7 +514,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { Builder builder(*wasm); auto curr = builder.makeDataSegment(); curr->offset = - builder.makeConst(Literal::makeFromInt32(0, memory->indexType)); + builder.makeConst(Literal::makeFromInt32(0, memory->addressType)); curr->setName(Name::fromInt(0), false); curr->memory = memory->name; wasm->addDataSegment(std::move(curr)); diff --git a/src/tools/wasm-split/instrumenter.cpp b/src/tools/wasm-split/instrumenter.cpp index 1c825f29fba..93971ed941b 100644 --- a/src/tools/wasm-split/instrumenter.cpp +++ b/src/tools/wasm-split/instrumenter.cpp @@ -195,7 +195,7 @@ void Instrumenter::addProfileExport(size_t numFuncs) { } } - auto ptrType = wasm->memories[0]->indexType; + auto ptrType = wasm->memories[0]->addressType; // Create and export a function to dump the profile into a given memory // buffer. The function takes the available address and buffer size as diff --git a/src/wasm-binary.h b/src/wasm-binary.h index ca14bd41f25..82f48708556 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1523,7 +1523,7 @@ class WasmBinaryReader { void getResizableLimits(Address& initial, Address& max, bool& shared, - Type& indexType, + Type& addressType, Address defaultIfNoMax); void readImports(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 8f8895781bd..15b7a2059e4 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -87,11 +87,11 @@ class Builder { Nullable), Address initial = 0, Address max = Table::kMaxSize, - Type indexType = Type::i32) { + Type addressType = Type::i32) { auto table = std::make_unique
(); table->name = name; table->type = type; - table->indexType = indexType; + table->addressType = addressType; table->initial = initial; table->max = max; return table; @@ -114,13 +114,13 @@ class Builder { Address initial = 0, Address max = Memory::kMaxSize32, bool shared = false, - Type indexType = Type::i32) { + Type addressType = Type::i32) { auto memory = std::make_unique(); memory->name = name; memory->initial = initial; memory->max = max; memory->shared = shared; - memory->indexType = indexType; + memory->addressType = addressType; return memory; } @@ -612,8 +612,8 @@ class Builder { ret->finalize(); return ret; } - Const* makeConstPtr(uint64_t val, Type indexType) { - return makeConst(Literal::makeFromInt64(val, indexType)); + Const* makeConstPtr(uint64_t val, Type addressType) { + return makeConst(Literal::makeFromInt64(val, addressType)); } Binary* makeBinary(BinaryOp op, Expression* left, Expression* right) { auto* ret = wasm.allocator.alloc(); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 1513b8f456e..fb3501252c0 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2968,12 +2968,12 @@ class ModuleRunnerBase : public ExpressionRunner { auto* memory = wasm.getMemory(segment->memory); Const zero; - zero.value = Literal::makeFromInt32(0, memory->indexType); + zero.value = Literal::makeFromInt32(0, memory->addressType); zero.finalize(); Const size; size.value = - Literal::makeFromInt32(segment->data.size(), memory->indexType); + Literal::makeFromInt32(segment->data.size(), memory->addressType); size.finalize(); MemoryInit init; @@ -3224,7 +3224,7 @@ class ModuleRunnerBase : public ExpressionRunner { auto info = getTableInstanceInfo(curr->table); auto* table = info.instance->wasm.getTable(info.name); Index tableSize = info.interface()->tableSize(curr->table); - return Literal::makeFromInt64(tableSize, table->indexType); + return Literal::makeFromInt64(tableSize, table->addressType); } Flow visitTableGrow(TableGrow* curr) { @@ -3241,8 +3241,8 @@ class ModuleRunnerBase : public ExpressionRunner { uint64_t tableSize = info.interface()->tableSize(info.name); auto* table = info.instance->wasm.getTable(info.name); - Flow ret = Literal::makeFromInt64(tableSize, table->indexType); - Flow fail = Literal::makeFromInt64(-1, table->indexType); + Flow ret = Literal::makeFromInt64(tableSize, table->addressType); + Flow fail = Literal::makeFromInt64(-1, table->addressType); uint64_t delta = deltaFlow.getSingleValue().getUnsigned(); uint64_t newSize; @@ -3818,7 +3818,7 @@ class ModuleRunnerBase : public ExpressionRunner { auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); auto* memory = info.instance->wasm.getMemory(info.name); - return Literal::makeFromInt64(memorySize, memory->indexType); + return Literal::makeFromInt64(memorySize, memory->addressType); } Flow visitMemoryGrow(MemoryGrow* curr) { NOTE_ENTER("MemoryGrow"); @@ -3829,14 +3829,14 @@ class ModuleRunnerBase : public ExpressionRunner { auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); auto* memory = info.instance->wasm.getMemory(info.name); - auto indexType = memory->indexType; - auto fail = Literal::makeFromInt64(-1, memory->indexType); - Flow ret = Literal::makeFromInt64(memorySize, indexType); + auto addressType = memory->addressType; + auto fail = Literal::makeFromInt64(-1, memory->addressType); + Flow ret = Literal::makeFromInt64(memorySize, addressType); uint64_t delta = flow.getSingleValue().getUnsigned(); - if (delta > uint32_t(-1) / Memory::kPageSize && indexType == Type::i32) { + if (delta > uint32_t(-1) / Memory::kPageSize && addressType == Type::i32) { return fail; } - if (memorySize >= uint32_t(-1) - delta && indexType == Type::i32) { + if (memorySize >= uint32_t(-1) - delta && addressType == Type::i32) { return fail; } auto newSize = memorySize + delta; diff --git a/src/wasm.h b/src/wasm.h index 85473b1f957..22e71560f4a 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2204,11 +2204,11 @@ class Table : public Importable { Address initial = 0; Address max = kMaxSize; - Type indexType = Type::i32; + Type addressType = Type::i32; Type type = Type(HeapType::func, Nullable); bool hasMax() { return max != kUnlimitedSize; } - bool is64() { return indexType == Type::i64; } + bool is64() { return addressType == Type::i64; } void clear() { name = ""; initial = 0; @@ -2238,16 +2238,16 @@ class Memory : public Importable { Address max = kMaxSize32; bool shared = false; - Type indexType = Type::i32; + Type addressType = Type::i32; bool hasMax() { return max != kUnlimitedSize; } - bool is64() { return indexType == Type::i64; } + bool is64() { return addressType == Type::i64; } void clear() { name = ""; initial = 0; max = kMaxSize32; shared = false; - indexType = Type::i32; + addressType = Type::i32; } }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 173a89163b2..7e1daf0b11d 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2246,7 +2246,7 @@ void WasmBinaryReader::readMemories() { getResizableLimits(memory->initial, memory->max, memory->shared, - memory->indexType, + memory->addressType, Memory::kUnlimitedSize); wasm.addMemory(std::move(memory)); } @@ -2491,7 +2491,7 @@ Memory* WasmBinaryReader::getMemory(Index index) { void WasmBinaryReader::getResizableLimits(Address& initial, Address& max, bool& shared, - Type& indexType, + Type& addressType, Address defaultIfNoMax) { auto flags = getU32LEB(); bool hasMax = (flags & BinaryConsts::HasMaximum) != 0; @@ -2502,7 +2502,7 @@ void WasmBinaryReader::getResizableLimits(Address& initial, throwError("shared memory must have max size"); } shared = isShared; - indexType = is64 ? Type::i64 : Type::i32; + addressType = is64 ? Type::i64 : Type::i32; if (hasMax) { max = is64 ? getU64LEB() : getU32LEB(); } else { @@ -2552,7 +2552,7 @@ void WasmBinaryReader::readImports() { getResizableLimits(table->initial, table->max, is_shared, - table->indexType, + table->addressType, Table::kUnlimitedSize); if (is_shared) { throwError("Tables may not be shared"); @@ -2568,7 +2568,7 @@ void WasmBinaryReader::readImports() { getResizableLimits(memory->initial, memory->max, memory->shared, - memory->indexType, + memory->addressType, Memory::kUnlimitedSize); wasm.addMemory(std::move(memory)); break; @@ -3393,7 +3393,7 @@ void WasmBinaryReader::readTableDeclarations() { getResizableLimits(table->initial, table->max, is_shared, - table->indexType, + table->addressType, Table::kUnlimitedSize); if (is_shared) { throwError("Tables may not be shared"); @@ -4706,7 +4706,7 @@ Index WasmBinaryReader::readMemoryAccess(Address& alignment, Address& offset) { throwError("Memory index out of range while reading memory alignment."); } auto* memory = wasm.memories[memIdx].get(); - offset = memory->indexType == Type::i32 ? getU32LEB() : getU64LEB(); + offset = memory->addressType == Type::i32 ? getU32LEB() : getU64LEB(); return memIdx; } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 5867c04a06a..339a3c7a193 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -993,7 +993,7 @@ void FunctionValidator::visitCallIndirect(CallIndirect* curr) { if (shouldBeTrue(!!table, curr, "call-indirect table must exist")) { shouldBeEqualOrFirstIsUnreachable( curr->target->type, - table->indexType, + table->addressType, curr, "call-indirect call target must match the table index type"); shouldBeTrue(!!table, curr, "call-indirect table must exist"); @@ -1095,7 +1095,7 @@ void FunctionValidator::visitLoad(Load* curr) { validateAlignment(curr->align, curr->type, curr->bytes, curr->isAtomic, curr); shouldBeEqualOrFirstIsUnreachable( curr->ptr->type, - memory->indexType, + memory->addressType, curr, "load pointer type must match memory index type"); if (curr->isAtomic) { @@ -1128,7 +1128,7 @@ void FunctionValidator::visitStore(Store* curr) { curr->align, curr->valueType, curr->bytes, curr->isAtomic, curr); shouldBeEqualOrFirstIsUnreachable( curr->ptr->type, - memory->indexType, + memory->addressType, curr, "store pointer must match memory index type"); shouldBeUnequal(curr->value->type, @@ -1152,7 +1152,7 @@ void FunctionValidator::visitAtomicRMW(AtomicRMW* curr) { validateMemBytes(curr->bytes, curr->type, curr); shouldBeEqualOrFirstIsUnreachable( curr->ptr->type, - memory->indexType, + memory->addressType, curr, "AtomicRMW pointer type must match memory index type"); shouldBeEqualOrFirstIsUnreachable(curr->type, @@ -1172,7 +1172,7 @@ void FunctionValidator::visitAtomicCmpxchg(AtomicCmpxchg* curr) { validateMemBytes(curr->bytes, curr->type, curr); shouldBeEqualOrFirstIsUnreachable( curr->ptr->type, - memory->indexType, + memory->addressType, curr, "cmpxchg pointer must match memory index type"); if (curr->expected->type != Type::unreachable && @@ -1206,7 +1206,7 @@ void FunctionValidator::visitAtomicWait(AtomicWait* curr) { curr->type, Type(Type::i32), curr, "AtomicWait must have type i32"); shouldBeEqualOrFirstIsUnreachable( curr->ptr->type, - memory->indexType, + memory->addressType, curr, "AtomicWait pointer must match memory index type"); shouldBeIntOrUnreachable( @@ -1232,7 +1232,7 @@ void FunctionValidator::visitAtomicNotify(AtomicNotify* curr) { curr->type, Type(Type::i32), curr, "AtomicNotify must have type i32"); shouldBeEqualOrFirstIsUnreachable( curr->ptr->type, - memory->indexType, + memory->addressType, curr, "AtomicNotify pointer must match memory index type"); shouldBeEqualOrFirstIsUnreachable( @@ -1408,7 +1408,7 @@ void FunctionValidator::visitSIMDLoad(SIMDLoad* curr) { curr->type, Type(Type::v128), curr, "load_splat must have type v128"); shouldBeEqualOrFirstIsUnreachable( curr->ptr->type, - memory->indexType, + memory->addressType, curr, "load_splat address must match memory index type"); Type memAlignType = Type::none; @@ -1450,7 +1450,7 @@ void FunctionValidator::visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { } shouldBeEqualOrFirstIsUnreachable( curr->ptr->type, - memory->indexType, + memory->addressType, curr, "loadX_lane or storeX_lane address must match memory index type"); shouldBeEqualOrFirstIsUnreachable( @@ -1500,7 +1500,7 @@ void FunctionValidator::visitMemoryInit(MemoryInit* curr) { curr->type, Type(Type::none), curr, "memory.init must have type none"); shouldBeEqualOrFirstIsUnreachable( curr->dest->type, - memory->indexType, + memory->addressType, curr, "memory.init dest must match memory index type"); shouldBeEqualOrFirstIsUnreachable(curr->offset->type, @@ -1542,22 +1542,22 @@ void FunctionValidator::visitMemoryCopy(MemoryCopy* curr) { shouldBeTrue(!!sourceMemory, curr, "memory.copy sourceMemory must exist"); shouldBeEqualOrFirstIsUnreachable( curr->dest->type, - destMemory->indexType, + destMemory->addressType, curr, "memory.copy dest must match destMemory index type"); shouldBeEqualOrFirstIsUnreachable( curr->source->type, - sourceMemory->indexType, + sourceMemory->addressType, curr, "memory.copy source must match sourceMemory index type"); shouldBeEqualOrFirstIsUnreachable( curr->size->type, - destMemory->indexType, + destMemory->addressType, curr, "memory.copy size must match destMemory index type"); shouldBeEqualOrFirstIsUnreachable( curr->size->type, - sourceMemory->indexType, + sourceMemory->addressType, curr, "memory.copy size must match destMemory index type"); } @@ -1572,7 +1572,7 @@ void FunctionValidator::visitMemoryFill(MemoryFill* curr) { curr->type, Type(Type::none), curr, "memory.fill must have type none"); shouldBeEqualOrFirstIsUnreachable( curr->dest->type, - memory->indexType, + memory->addressType, curr, "memory.fill dest must match memory index type"); shouldBeEqualOrFirstIsUnreachable(curr->value->type, @@ -1581,7 +1581,7 @@ void FunctionValidator::visitMemoryFill(MemoryFill* curr) { "memory.fill value must be an i32"); shouldBeEqualOrFirstIsUnreachable( curr->size->type, - memory->indexType, + memory->addressType, curr, "memory.fill size must match memory index type"); shouldBeTrue(!!memory, curr, "memory.fill memory must exist"); @@ -2257,7 +2257,7 @@ void FunctionValidator::visitMemoryGrow(MemoryGrow* curr) { auto* memory = getModule()->getMemoryOrNull(curr->memory); shouldBeTrue(!!memory, curr, "memory.grow memory must exist"); shouldBeEqualOrFirstIsUnreachable(curr->delta->type, - memory->indexType, + memory->addressType, curr, "memory.grow must match memory index type"); } @@ -2399,7 +2399,7 @@ void FunctionValidator::visitTableGet(TableGet* curr) { } shouldBeEqualOrFirstIsUnreachable( curr->index->type, - table->indexType, + table->addressType, curr, "table.get index must match the table index type."); } @@ -2419,7 +2419,7 @@ void FunctionValidator::visitTableSet(TableSet* curr) { } shouldBeEqualOrFirstIsUnreachable( curr->index->type, - table->indexType, + table->addressType, curr, "table.set index must match the table index type."); } @@ -2447,7 +2447,7 @@ void FunctionValidator::visitTableGrow(TableGrow* curr) { curr, "table.grow value must have right type"); shouldBeEqual(curr->delta->type, - table->indexType, + table->addressType, curr, "table.grow must match table index type"); } @@ -2467,12 +2467,12 @@ void FunctionValidator::visitTableFill(TableFill* curr) { "table.fill value must have right type"); shouldBeEqualOrFirstIsUnreachable( curr->dest->type, - table->indexType, + table->addressType, curr, "table.fill dest must match table index type"); shouldBeEqualOrFirstIsUnreachable( curr->size->type, - table->indexType, + table->addressType, curr, "table.fill size must match table index type"); } @@ -2492,11 +2492,11 @@ void FunctionValidator::visitTableCopy(TableCopy* curr) { "table.copy source must have right type for dest"); } shouldBeEqualOrFirstIsUnreachable(curr->dest->type, - destTable->indexType, + destTable->addressType, curr, "table.copy dest must be valid"); shouldBeEqualOrFirstIsUnreachable(curr->source->type, - sourceTable->indexType, + sourceTable->addressType, curr, "table.copy source must be valid"); Type sizeType = @@ -2518,8 +2518,10 @@ void FunctionValidator::visitTableInit(TableInit* curr) { curr, "table.init source must have right type for dest"); } - shouldBeEqualOrFirstIsUnreachable( - curr->dest->type, table->indexType, curr, "table.init dest must be valid"); + shouldBeEqualOrFirstIsUnreachable(curr->dest->type, + table->addressType, + curr, + "table.init dest must be valid"); shouldBeEqualOrFirstIsUnreachable(curr->offset->type, Type(Type::i32), curr, @@ -3905,7 +3907,7 @@ static void validateDataSegments(Module& module, ValidationInfo& info) { continue; } info.shouldBeEqual(segment->offset->type, - memory->indexType, + memory->addressType, segment->offset, "segment offset must match memory index type"); info.shouldBeTrue( @@ -4000,7 +4002,7 @@ static void validateTables(Module& module, ValidationInfo& info) { info.shouldBeTrue( !!segment->offset, "elem", "table segment offset must have an offset"); info.shouldBeEqual(segment->offset->type, - table->indexType, + table->addressType, segment->offset, "element segment offset must match table index type"); info.shouldBeTrue( From 12ef2030ad2e7ceb5d208d4a24f25142d8a5f556 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 Nov 2024 16:15:50 -0800 Subject: [PATCH 117/622] [NFC] Refactor fuzzer's can_run_on_feature_opts() (#7066) It never used the parameter, so remove that (we always access the features using a global anyhow). But add a new parameter, the wasm file, which does need to be passed in for a later PR (so in this PR it is just for future use). --- scripts/fuzz_opt.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index c465fbcf34e..7522dce8cc5 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -766,7 +766,7 @@ def handle_pair(self, input, before_wasm, after_wasm, opts): self.handle(before_wasm) self.handle(after_wasm) - def can_run_on_feature_opts(self, feature_opts): + def can_run_on_wasm(self, wasm): return True def increment_runs(self): @@ -1167,7 +1167,7 @@ def run(self, wasm): f.write(wrapper) return run_vm([shared.NODEJS, js_file, abspath('a.wasm')]) - def can_run_on_feature_opts(self, feature_opts): + def can_run_on_wasm(self, wasm): # TODO: properly handle memory growth. right now the wasm2js handler # uses --emscripten which assumes the Memory is created before, and # wasm2js.js just starts with a size of 1 and no limit. We should switch @@ -1522,7 +1522,7 @@ def optimize(name): if not (NANS and optimized): compare_between_vms(output, linked_output, 'Split') - def can_run_on_feature_opts(self, feature_opts): + def can_run_on_wasm(self, wasm): # to run the split wasm we use JS, that is, JS links the exports of one # to the imports of the other, etc. since we run in JS, the wasm must be # valid for JS. @@ -1623,8 +1623,9 @@ def test_one(random_input, given_wasm): bytes += wasm_size print('post wasm size:', wasm_size) - # first, find which handlers can even run here - relevant_handlers = [handler for handler in testcase_handlers if not hasattr(handler, 'get_commands') and handler.can_run_on_feature_opts(FEATURE_OPTS)] + # First, find which handlers can even run here. Note that we check a.wasm + # and not b.wasm (optimizations do not change fuzzability). + relevant_handlers = [handler for handler in testcase_handlers if not hasattr(handler, 'get_commands') and handler.can_run_on_wasm('a.wasm')] if len(relevant_handlers) == 0: return 0 # filter by frequency @@ -1642,7 +1643,7 @@ def test_one(random_input, given_wasm): if testcase_handler in used_handlers: continue used_handlers.add(testcase_handler) - assert testcase_handler.can_run_on_feature_opts(FEATURE_OPTS) + assert testcase_handler.can_run_on_wasm('a.wasm') print('running testcase handler:', testcase_handler.__class__.__name__) testcase_handler.increment_runs() From b30067658459ca167e58fe0dee9d85ea6100c223 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 8 Nov 2024 09:06:26 -0800 Subject: [PATCH 118/622] [wasm64] Fix 32-bit address computation in execution of SIMDLoadExtend (#7068) --- src/wasm-interpreter.h | 9 ++++++--- test/lit/exec/simd.wast | 13 +++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index fb3501252c0..f3471cfa85c 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3692,11 +3692,14 @@ class ModuleRunnerBase : public ExpressionRunner { WASM_UNREACHABLE("invalid op"); }; auto memorySize = info.instance->getMemorySize(info.name); + auto addressType = curr->ptr->type; auto fillLanes = [&](auto lanes, size_t laneBytes) { for (auto& lane : lanes) { - lane = loadLane(info.instance->getFinalAddress( - curr, Literal(uint32_t(src)), laneBytes, memorySize)); - src = Address(uint32_t(src) + laneBytes); + auto ptr = Literal::makeFromInt64(src, addressType); + lane = loadLane( + info.instance->getFinalAddress(curr, ptr, laneBytes, memorySize)); + src = + ptr.add(Literal::makeFromInt32(laneBytes, addressType)).getUnsigned(); } return Literal(lanes); }; diff --git a/test/lit/exec/simd.wast b/test/lit/exec/simd.wast index 5ab6489a2ad..a34a6827688 100644 --- a/test/lit/exec/simd.wast +++ b/test/lit/exec/simd.wast @@ -14,8 +14,21 @@ (i64.const 0) ) ) + + ;; CHECK: [fuzz-exec] calling load32x2_u + ;; CHECK-NEXT: [trap final > memory: 13835058055282163712 > 1048576] + (func $load32x2_u (export "load32x2_u") (result v128) + ;; This large 64-bit address is out of bounds, and this should trap. + (v128.load32x2_u + (i64.const -4611686018427387904) + ) + ) ) ;; CHECK: [fuzz-exec] calling load8x8_s ;; CHECK-NEXT: [fuzz-exec] note result: load8x8_s => i32x4 0x00620061 0x00640063 0x00660065 0x00000067 + +;; CHECK: [fuzz-exec] calling load32x2_u +;; CHECK-NEXT: [trap final > memory: 13835058055282163712 > 1048576] +;; CHECK-NEXT: [fuzz-exec] comparing load32x2_u ;; CHECK-NEXT: [fuzz-exec] comparing load8x8_s From 8c0429ac09d06d6056687e36fd4fb37f61681233 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 8 Nov 2024 10:16:52 -0800 Subject: [PATCH 119/622] [EH] Fuzz calls from JS by calling wasm exports, sometimes catching (#7067) This adds two new imports to fuzzer modules: * call-export, which gets an export index and calls it. * call-export-catch, which does the call in a try-catch, swallowing any error, and returning 1 if it saw an error. The former gives us calls back into the wasm, possibly making various trips between wasm and JS in interesting ways. The latter adds a try-catch which helps fuzz wasm EH. We do these calls using a wasm export index, i.e., the index in the list of exports. This is simple, but it does have the downside that it makes executing the wasm sensitive to changes in exports (e.g. wasm-merge adds more), which requires some handling in the fuzzer. --- scripts/fuzz_opt.py | 28 +++++++ scripts/fuzz_shell.js | 75 +++++++++++++++-- src/tools/execution-results.h | 50 ++++++++++- src/tools/fuzzing.h | 7 +- src/tools/fuzzing/fuzzing.cpp | 84 ++++++++++++++++++- test/lit/exec/fuzzing-api.wast | 56 ++++++++++++- test/passes/fuzz_metrics_noprint.bin.txt | 56 ++++++------- ...e-to-fuzz_all-features_metrics_noprint.txt | 80 ++++++++---------- 8 files changed, 351 insertions(+), 85 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 7522dce8cc5..bf712c82120 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1230,6 +1230,16 @@ def filter_exports(wasm, output, keep, keep_defaults=True): run([in_bin('wasm-metadce'), wasm, '-o', output, '--graph-file', 'graph.json'] + FEATURE_OPTS) +# Check if a wasm file would notice changes to exports. Normally removing an +# export that is not called, for example, would not be observable, but if the +# "call-export*" functions are present then such changes can break us. +def wasm_notices_export_changes(wasm): + # we could be more precise here and disassemble the wasm to look for an + # actual import with name "call-export*", but looking for the string should + # have practically no false positives. + return b'call-export' in open(wasm, 'rb').read() + + # Fuzz the interpreter with --fuzz-exec -tnh. The tricky thing with traps-never- # happen mode is that if a trap *does* happen then that is undefined behavior, # and the optimizer was free to make changes to observable behavior there. The @@ -1323,6 +1333,12 @@ def ignore_references(out): compare_between_vms(before, after, 'TrapsNeverHappen') + def can_run_on_wasm(self, wasm): + # If the wasm is sensitive to changes in exports then we cannot alter + # them, but we must remove trapping exports (see above), so we cannot + # run in such a case. + return not wasm_notices_export_changes(wasm) + # Tests wasm-ctor-eval class CtorEval(TestCaseHandler): @@ -1354,6 +1370,12 @@ def handle(self, wasm): compare_between_vms(fix_output(wasm_exec), fix_output(evalled_wasm_exec), 'CtorEval') + def can_run_on_wasm(self, wasm): + # ctor-eval modifies exports, because it assumes they are ctors and so + # are only called once (so if it evals them away, they can be + # removed). If the wasm might notice that, we cannot run. + return not wasm_notices_export_changes(wasm) + # Tests wasm-merge class Merge(TestCaseHandler): @@ -1427,6 +1449,12 @@ def handle(self, wasm): compare_between_vms(output, merged_output, 'Merge') + def can_run_on_wasm(self, wasm): + # wasm-merge combines exports, which can alter their indexes and lead to + # noticeable differences if the wasm is sensitive to such things, which + # prevents us from running. + return not wasm_notices_export_changes(wasm) + FUNC_NAMES_REGEX = re.compile(r'\n [(]func [$](\S+)') diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 72120cf7f8b..d9a994896ba 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -134,6 +134,29 @@ function logValue(x, y) { console.log('[LoggingExternalInterface logging ' + printed(x, y) + ']'); } +// Some imports need to access exports by index. +var exportsList; +function getExportByIndex(index) { + if (!exportsList) { + exportsList = []; + for (var e in exports) { + exportsList.push(e); + } + } + return exports[exportsList[index]]; +} + +// Given a wasm function, call it as best we can from JS, and return the result. +function callFunc(func) { + // Send the function a null for each parameter. Null can be converted without + // error to both a number and a reference. + var args = []; + for (var i = 0; i < func.length; i++) { + args.push(null); + } + return func.apply(null, args); +} + // Table get/set operations need a BigInt if the table has 64-bit indexes. This // adds a proper cast as needed. function toAddressType(table, index) { @@ -172,6 +195,50 @@ var imports = { 'table-set': (index, value) => { exports.table.set(toAddressType(exports.table, index), value); }, + + // Export operations. + 'call-export': (index) => { + callFunc(getExportByIndex(index)); + }, + 'call-export-catch': (index) => { + try { + callFunc(getExportByIndex(index)); + return 0; + } catch (e) { + // We only want to catch exceptions, not wasm traps: traps should still + // halt execution. Handling this requires different code in wasm2js, so + // check for that first (wasm2js does not define RuntimeError, so use + // that for the check - when wasm2js is run, we override the entire + // WebAssembly object with a polyfill, so we know exactly what it + // contains). + var wasm2js = !WebAssembly.RuntimeError; + if (!wasm2js) { + // When running native wasm, we can detect wasm traps. + if (e instanceof WebAssembly.RuntimeError) { + throw e; + } + } + var text = e + ''; + // We must not swallow host limitations here: a host limitation is a + // problem that means we must not compare the outcome here to any other + // VM. + var hostIssues = ['requested new array is too large', + 'out of memory', + 'Maximum call stack size exceeded']; + if (wasm2js) { + // When wasm2js does trap, it just throws an "abort" error. + hostIssues.push('abort'); + } + for (var hostIssue of hostIssues) { + if (text.includes(hostIssue)) { + throw e; + } + } + // Otherwise, this is a normal exception we want to catch (a wasm + // exception, or a conversion error on the wasm/JS boundary, etc.). + return 1; + } + }, }, // Emscripten support. 'env': { @@ -250,16 +317,10 @@ for (var e of exportsToCall) { if (typeof exports[e] !== 'function') { continue; } - // Send the function a null for each parameter. Null can be converted without - // error to both a number and a reference. var func = exports[e]; - var args = []; - for (var i = 0; i < func.length; i++) { - args.push(null); - } try { console.log('[fuzz-exec] calling ' + e); - var result = func.apply(null, args); + var result = callFunc(func); if (typeof result !== 'undefined') { console.log('[fuzz-exec] note result: ' + e + ' => ' + printed(result)); } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 78cc5af1f04..25d0c077237 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -40,10 +40,15 @@ struct LoggingExternalInterface : public ShellExternalInterface { // The name of the table exported by the name 'table.' Imports access it. Name exportedTable; + Module& wasm; + + // The ModuleRunner and this ExternalInterface end up needing links both ways, + // so we cannot init this in the constructor. + ModuleRunner* instance = nullptr; public: LoggingExternalInterface(Loggings& loggings, Module& wasm) - : loggings(loggings) { + : loggings(loggings), wasm(wasm) { for (auto& exp : wasm.exports) { if (exp->kind == ExternalKind::Table && exp->name == "table") { exportedTable = exp->value; @@ -99,6 +104,18 @@ struct LoggingExternalInterface : public ShellExternalInterface { } tableStore(exportedTable, index, arguments[1]); return {}; + } else if (import->base == "call-export") { + callExport(arguments[0].geti32()); + // Return nothing. If we wanted to return a value we'd need to have + // multiple such functions, one for each signature. + return {}; + } else if (import->base == "call-export-catch") { + try { + callExport(arguments[0].geti32()); + return {Literal(int32_t(0))}; + } catch (const WasmException& e) { + return {Literal(int32_t(1))}; + } } else { WASM_UNREACHABLE("unknown fuzzer import"); } @@ -127,6 +144,35 @@ struct LoggingExternalInterface : public ShellExternalInterface { auto payload = std::make_shared("__private", Literals{}); throwException(WasmException{Literal(payload)}); } + + Literals callExport(Index index) { + if (index >= wasm.exports.size()) { + // No export. + throwEmptyException(); + } + auto& exp = wasm.exports[index]; + if (exp->kind != ExternalKind::Function) { + // No callable export. + throwEmptyException(); + } + auto* func = wasm.getFunction(exp->value); + + // TODO JS traps on some types on the boundary, which we should behave the + // same on. For now, this is not needed because the fuzzer will prune all + // non-JS-compatible exports anyhow. + + // Send default values as arguments, or trap if we need anything else. + Literals arguments; + for (const auto& param : func->getParams()) { + if (!param.isDefaultable()) { + throwEmptyException(); + } + arguments.push_back(Literal::makeZero(param)); + } + return instance->callFunction(func->name, arguments); + } + + void setModuleRunner(ModuleRunner* instance_) { instance = instance_; } }; // gets execution results from a wasm module. this is useful for fuzzing @@ -148,6 +194,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + interface.setModuleRunner(&instance); // execute all exported methods (that are therefore preserved through // opts) for (auto& exp : wasm.exports) { @@ -298,6 +345,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + interface.setModuleRunner(&instance); return run(func, wasm, instance); } catch (const TrapException&) { // May throw in instance creation (init of offsets). diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 6f73feca934..3e8ec5b976a 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -104,10 +104,11 @@ class TranslateToFuzzReader { Name funcrefTableName; std::unordered_map logImportNames; - Name throwImportName; Name tableGetImportName; Name tableSetImportName; + Name callExportImportName; + Name callExportCatchImportName; std::unordered_map> globalsByType; std::unordered_map> mutableGlobalsByType; @@ -225,9 +226,8 @@ class TranslateToFuzzReader { void finalizeTable(); void prepareHangLimitSupport(); void addHangLimitSupport(); - // Imports that we call to log out values. void addImportLoggingSupport(); - // An import that we call to throw an exception from outside. + void addImportCallingSupport(); void addImportThrowingSupport(); void addImportTableSupport(); void addHashMemorySupport(); @@ -238,6 +238,7 @@ class TranslateToFuzzReader { Expression* makeImportThrowing(Type type); Expression* makeImportTableGet(); Expression* makeImportTableSet(Type type); + Expression* makeImportCallExport(Type type); Expression* makeMemoryHashLogging(); // Function creation diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 1329e886e76..f4e21d87089 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -182,6 +182,7 @@ void TranslateToFuzzReader::build() { addImportTableSupport(); } addImportLoggingSupport(); + addImportCallingSupport(); modifyInitialFunctions(); // keep adding functions until we run out of input while (!random.finished()) { @@ -635,6 +636,49 @@ void TranslateToFuzzReader::addImportLoggingSupport() { } } +void TranslateToFuzzReader::addImportCallingSupport() { + // Only add these some of the time, as they inhibit some fuzzing (things like + // wasm-ctor-eval and wasm-merge are sensitive to the wasm being able to call + // its own exports, and to care about the indexes of the exports): + // + // 0 - none + // 1 - call-export + // 2 - call-export-catch + // 3 - call-export & call-export-catch + // 4 - none + // 5 - none + // + auto choice = upTo(6); + if (choice >= 4) { + return; + } + + if (choice & 1) { + // Given an export index, call it from JS. + callExportImportName = Names::getValidFunctionName(wasm, "call-export"); + auto func = std::make_unique(); + func->name = callExportImportName; + func->module = "fuzzing-support"; + func->base = "call-export"; + func->type = Signature({Type::i32}, Type::none); + wasm.addFunction(std::move(func)); + } + + if (choice & 2) { + // Given an export index, call it from JS and catch all exceptions. Return + // whether we caught. Exceptions are common (if the index is invalid, in + // particular), so a variant that catches is useful to avoid halting. + callExportCatchImportName = + Names::getValidFunctionName(wasm, "call-export-catch"); + auto func = std::make_unique(); + func->name = callExportCatchImportName; + func->module = "fuzzing-support"; + func->base = "call-export-catch"; + func->type = Signature(Type::i32, Type::i32); + wasm.addFunction(std::move(func)); + } +} + void TranslateToFuzzReader::addImportThrowingSupport() { // Throw some kind of exception from JS. // TODO: Send an index, which is which exported wasm Tag we should throw, or @@ -820,6 +864,38 @@ Expression* TranslateToFuzzReader::makeImportTableSet(Type type) { Type::none); } +Expression* TranslateToFuzzReader::makeImportCallExport(Type type) { + // The none-returning variant just does the call. The i32-returning one + // catches any errors and returns 1 when it saw an error. Based on the + // variant, pick which to call, and the maximum index to call. + Name target; + Index maxIndex = wasm.exports.size(); + if (type == Type::none) { + target = callExportImportName; + } else if (type == Type::i32) { + target = callExportCatchImportName; + // This never traps, so we can be less careful, but we do still want to + // avoid trapping a lot as executing code is more interesting. (Note that + // even though we double here, the risk is not that great: we are still + // adding functions as we go, so the first half of functions/exports can + // double here and still end up in bounds by the time we've added them all.) + maxIndex = (maxIndex + 1) * 2; + } else { + WASM_UNREACHABLE("bad import.call"); + } + // We must have set up the target function. + assert(target); + + // Most of the time, call a valid export index in the range we picked, but + // sometimes allow anything at all. + auto* index = make(Type::i32); + if (!allowOOB || !oneIn(10)) { + index = builder.makeBinary( + RemUInt32, index, builder.makeConst(int32_t(maxIndex))); + } + return builder.makeCall(target, {index}, type); +} + Expression* TranslateToFuzzReader::makeMemoryHashLogging() { auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32); return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none); @@ -1511,6 +1587,9 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { options.add(FeatureSet::Atomics, &Self::makeAtomic); } if (type == Type::i32) { + if (callExportCatchImportName) { + options.add(FeatureSet::MVP, &Self::makeImportCallExport); + } options.add(FeatureSet::ReferenceTypes, &Self::makeRefIsNull); options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeRefEq, @@ -1590,6 +1669,9 @@ Expression* TranslateToFuzzReader::_makenone() { if (tableSetImportName) { options.add(FeatureSet::ReferenceTypes, &Self::makeImportTableSet); } + if (callExportImportName) { + options.add(FeatureSet::MVP, &Self::makeImportCallExport); + } return (this->*pick(options))(Type::none); } @@ -2023,7 +2105,7 @@ Expression* TranslateToFuzzReader::makeCallIndirect(Type type) { return makeTrivial(type); } } - // with high probability, make sure the type is valid otherwise, most are + // with high probability, make sure the type is valid - otherwise, most are // going to trap auto addressType = wasm.getTable(funcrefTableName)->addressType; Expression* target; diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 0d0f25130c4..38a8ce41bbe 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -13,8 +13,13 @@ (import "fuzzing-support" "table-set" (func $table.set (param i32 funcref))) (import "fuzzing-support" "table-get" (func $table.get (param i32) (result funcref))) + (import "fuzzing-support" "call-export" (func $call.export (param i32))) + (import "fuzzing-support" "call-export-catch" (func $call.export.catch (param i32) (result i32))) + (table $table 10 20 funcref) + ;; Note that the exported table appears first here, but in the binary and in + ;; the IR it is actually last, as we always add function exports first. (export "table" (table $table)) ;; CHECK: [fuzz-exec] calling logging @@ -53,7 +58,6 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] ;; CHECK-NEXT: [exception thrown: __private ()] - ;; CHECK-NEXT: warning: no passes specified, not doing any work (func $table.getting (export "table.getting") ;; There is a non-null value at 5, and a null at 6. (call $log-i32 @@ -77,6 +81,43 @@ ) ) ) + + ;; CHECK: [fuzz-exec] calling export.calling + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [exception thrown: __private ()] + (func $export.calling (export "export.calling") + ;; At index 0 in the exports we have $logging, so we will do those loggings. + (call $call.export + (i32.const 0) + ) + ;; At index 999 we have nothing, so we'll error. + (call $call.export + (i32.const 999) + ) + ) + + ;; CHECK: [fuzz-exec] calling export.calling.catching + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: warning: no passes specified, not doing any work + (func $export.calling.catching (export "export.calling.catching") + ;; At index 0 in the exports we have $logging, so we will do those loggings, + ;; then log a 0 as no exception happens. + (call $log-i32 + (call $call.export.catch + (i32.const 0) + ) + ) + ;; At index 999 we have nothing, so we'll error, catch it, and log 1. + (call $log-i32 + (call $call.export.catch + (i32.const 999) + ) + ) + ) ) ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -92,6 +133,19 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] ;; CHECK-NEXT: [exception thrown: __private ()] + +;; CHECK: [fuzz-exec] calling export.calling +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [exception thrown: __private ()] + +;; CHECK: [fuzz-exec] calling export.calling.catching +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [fuzz-exec] comparing export.calling +;; CHECK-NEXT: [fuzz-exec] comparing export.calling.catching ;; CHECK-NEXT: [fuzz-exec] comparing logging ;; CHECK-NEXT: [fuzz-exec] comparing table.getting ;; CHECK-NEXT: [fuzz-exec] comparing table.setting diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index ba0ccaa10a4..0fa206e0a1f 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 25 - [funcs] : 40 + [exports] : 45 + [funcs] : 60 [globals] : 18 - [imports] : 4 + [imports] : 5 [memories] : 1 [memory-data] : 24 - [table-data] : 19 + [table-data] : 15 [tables] : 1 [tags] : 0 - [total] : 5335 - [vars] : 170 - Binary : 403 - Block : 900 - Break : 163 - Call : 197 - CallIndirect : 11 - Const : 824 - Drop : 56 - GlobalGet : 464 - GlobalSet : 342 - If : 298 - Load : 87 - LocalGet : 402 - LocalSet : 304 - Loop : 126 - Nop : 74 - RefFunc : 19 - Return : 60 - Select : 34 - Store : 46 - Switch : 1 - Unary : 356 - Unreachable : 168 + [total] : 5475 + [vars] : 222 + Binary : 410 + Block : 870 + Break : 148 + Call : 271 + CallIndirect : 30 + Const : 915 + Drop : 51 + GlobalGet : 458 + GlobalSet : 323 + If : 293 + Load : 96 + LocalGet : 442 + LocalSet : 284 + Loop : 99 + Nop : 76 + RefFunc : 15 + Return : 78 + Select : 47 + Store : 44 + Switch : 2 + Unary : 365 + Unreachable : 158 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index fd37193f5ba..a77708b2e06 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,56 +1,48 @@ Metrics total - [exports] : 7 - [funcs] : 8 + [exports] : 6 + [funcs] : 12 [globals] : 4 [imports] : 8 [memories] : 1 [memory-data] : 112 - [table-data] : 0 + [table-data] : 3 [tables] : 1 - [tags] : 1 - [total] : 645 - [vars] : 33 - ArrayGet : 2 - ArrayLen : 1 - ArrayNew : 3 + [tags] : 0 + [total] : 608 + [vars] : 48 + ArrayNew : 5 ArrayNewFixed : 5 - AtomicCmpxchg : 1 - AtomicFence : 1 - AtomicRMW : 1 - Binary : 76 - Block : 64 + Binary : 75 + Block : 80 + BrOn : 5 Break : 4 - Call : 15 - Const : 149 - DataDrop : 1 - Drop : 1 - GlobalGet : 25 - GlobalSet : 22 - If : 22 - Load : 25 - LocalGet : 55 - LocalSet : 35 - Loop : 4 - MemoryFill : 1 + Call : 21 + CallRef : 1 + Const : 113 + Drop : 15 + GlobalGet : 39 + GlobalSet : 36 + If : 21 + Load : 17 + LocalGet : 45 + LocalSet : 20 + Loop : 7 Nop : 7 - Pop : 6 - RefAs : 5 - RefCast : 1 - RefEq : 2 - RefFunc : 13 + RefAs : 2 + RefEq : 1 + RefFunc : 9 + RefI31 : 1 RefIsNull : 1 - RefNull : 10 + RefNull : 8 Return : 5 - SIMDExtract : 1 - Select : 6 - StringConst : 1 - StringEncode : 1 - StructNew : 24 - StructSet : 1 - Try : 7 - TryTable : 2 - TupleExtract : 2 - TupleMake : 3 - Unary : 23 - Unreachable : 11 + Select : 2 + Store : 1 + StringConst : 4 + StringEq : 1 + StringMeasure : 2 + StructNew : 13 + TupleExtract : 1 + TupleMake : 4 + Unary : 19 + Unreachable : 18 From 9e11c5fd12a7c6392ac62979cc16c62a368f6e33 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2024 12:22:43 -0800 Subject: [PATCH 120/622] LocalGraph::canMoveSet (#7039) This new API lets us ask if a set can be safely moved to a new position. The new position must be the location of an expression from a particular class (this allows us to populate the IR once and then query any of those locations). --- src/ir/LocalGraph.cpp | 206 +++++++++++++++++----- src/ir/local-graph.h | 38 +++- test/gtest/CMakeLists.txt | 1 + test/gtest/local-graph.cpp | 347 +++++++++++++++++++++++++++++++++++++ 4 files changed, 551 insertions(+), 41 deletions(-) create mode 100644 test/gtest/local-graph.cpp diff --git a/src/ir/LocalGraph.cpp b/src/ir/LocalGraph.cpp index 7e877e65e18..33055ad491c 100644 --- a/src/ir/LocalGraph.cpp +++ b/src/ir/LocalGraph.cpp @@ -16,10 +16,11 @@ #include -#include -#include -#include -#include +#include "cfg/cfg-traversal.h" +#include "ir/find_all.h" +#include "ir/local-graph.h" +#include "support/unique_deferring_queue.h" +#include "wasm-builder.h" namespace wasm { @@ -42,21 +43,27 @@ struct Info { // flow helper class. flows the gets to their sets struct LocalGraphFlower - : public CFGWalker, Info> { + : public CFGWalker, + Info> { LocalGraph::GetSetsMap& getSetsMap; LocalGraph::Locations& locations; Function* func; + std::optional queryClass; LocalGraphFlower(LocalGraph::GetSetsMap& getSetsMap, LocalGraph::Locations& locations, Function* func, - Module* module) - : getSetsMap(getSetsMap), locations(locations), func(func) { + Module* module, + std::optional queryClass = std::nullopt) + : getSetsMap(getSetsMap), locations(locations), func(func), + queryClass(queryClass) { setFunction(func); setModule(module); // create the CFG by walking the IR - CFGWalker, Info>:: - doWalkFunction(func); + CFGWalker, + Info>::doWalkFunction(func); } BasicBlock* makeBasicBlock() { return new BasicBlock(); } @@ -67,25 +74,22 @@ struct LocalGraphFlower // cfg traversal work - static void doVisitLocalGet(LocalGraphFlower* self, Expression** currp) { - auto* curr = (*currp)->cast(); - // if in unreachable code, skip - if (!self->currBasicBlock) { + void visitExpression(Expression* curr) { + // If in unreachable code, skip. + if (!currBasicBlock) { return; } - self->currBasicBlock->contents.actions.emplace_back(curr); - self->locations[curr] = currp; - } - static void doVisitLocalSet(LocalGraphFlower* self, Expression** currp) { - auto* curr = (*currp)->cast(); - // if in unreachable code, skip - if (!self->currBasicBlock) { - return; + // If this is a relevant action (a get or set, or there is a query class + // and this is an instance of it) then note it. + if (curr->is() || curr->is() || + (queryClass && curr->_id == *queryClass)) { + currBasicBlock->contents.actions.emplace_back(curr); + locations[curr] = getCurrentPointer(); + if (auto* set = curr->dynCast()) { + currBasicBlock->contents.lastSets[set->index] = set; + } } - self->currBasicBlock->contents.actions.emplace_back(curr); - self->currBasicBlock->contents.lastSets[curr->index] = curr; - self->locations[curr] = currp; } // Each time we flow a get (or set of gets) to find its sets, we mark a @@ -203,9 +207,8 @@ struct LocalGraphFlower auto* action = actions[i]; if (auto* get = action->dynCast()) { allGets[get->index].push_back(get); - } else { + } else if (auto* set = action->dynCast()) { // This set is the only set for all those gets. - auto* set = action->cast(); auto& gets = allGets[set->index]; for (auto* get : gets) { getSetsMap[get].insert(set); @@ -302,12 +305,8 @@ struct LocalGraphFlower // initially, but instead fill in these data structures that let us do so // later for individual gets. Specifically we need to find the location of a // local.get in the CFG. - struct BlockLocation { - // The basic block an item is in. - FlowBlock* block = nullptr; - // The index in that block that the item is at. - Index index; - }; + using BlockLocation = std::pair; + std::unordered_map getLocations; // In lazy mode we also need to categorize gets and sets by their index. @@ -331,8 +330,6 @@ struct LocalGraphFlower getsByIndex[get->index].push_back(get); } else if (auto* set = actions[i]->dynCast()) { setsByIndex[set->index].push_back(set); - } else { - WASM_UNREACHABLE("bad action"); } } } @@ -391,9 +388,8 @@ struct LocalGraphFlower // It will have the same sets as us. gets.push_back(otherGet); } - } else { + } else if (auto* set = curr->dynCast()) { // This is a set. - auto* set = curr->cast(); if (set->index == index) { // This is the only set writing to our gets. for (auto* get : gets) { @@ -444,6 +440,86 @@ struct LocalGraphFlower } } } + + // Given a bunch of gets, see if any of them are reached by the given set + // despite the obstacle expression stopping the flow whenever it is reached. + // That is, the obstacle is considered as if it was a set of the same index, + // which would trample the value and stop the set from influencing it. + LocalGraphBase::SetInfluences + getSetInfluencesGivenObstacle(LocalSet* set, + const LocalGraphBase::SetInfluences& gets, + Expression* obstacle) { + LocalGraphBase::SetInfluences ret; + // Normally flowing backwards is faster, as we start from actual gets (and + // so we avoid flowing past all the gets to large swaths of the program that + // we don't care about; and in reverse, we might go all the way to the + // entry in a wasteful manner, but most gets have an actual set, and do not + // read the default value). The situation here is a bit different, though, + // in that we might expect that going forward from the set would quickly + // reach the obstacle and stop. Still, a single branch away would cause us + // to scan lots of blocks potentially, and might not be that rare in + // general, so go backwards. (Many uninteresting branches away, that reach + // no relevant gets, are common when exceptions are enabled, as every call + // gets a branch.) + for (auto* get : gets) { + auto [block, index] = getLocations[get]; + if (!block) { + // We did not find location info for this get, which means it is + // unreachable. + continue; + } + + // Use a work queue of block locations to scan backwards from. + // Specifically we must scan the first index above it (i.e., the original + // location has a local.get there, so we start one before it). + UniqueNonrepeatingDeferredQueue work; + work.push(BlockLocation{block, index}); + auto foundSet = false; + // Flow while there is stuff to flow, and while we haven't found the set + // (once we find it, we add the get and can move on to the next get). + while (!work.empty() && !foundSet) { + auto [block, index] = work.pop(); + + // Scan backwards through this block. + while (1) { + // If we finished scanning this block (we reached the top), flow to + // predecessors. + if (index == 0) { + for (auto* pred : block->in) { + // We will scan pred from its very end. + work.push(BlockLocation{pred, Index(pred->actions.size())}); + } + break; + } + + // Continue onwards. + index--; + auto* action = block->actions[index]; + if (auto* otherSet = action->dynCast()) { + if (otherSet == set) { + // We arrived at the set: add this get and stop flowing it. + ret.insert(get); + foundSet = true; + break; + } + if (otherSet->index == set->index) { + // This is another set of the same index, which halts the flow. + break; + } + } else if (action == obstacle) { + // We ran into the obstacle. Halt this flow. + break; + } + // TODO: If the action is one of the gets we are scanning, then + // either we have processed it already, or will do so later, and we + // can halt. As an optimization, we could check if we've processed + // it already and act accordingly. + } + } + } + + return ret; + } }; // LocalGraph implementation @@ -559,16 +635,18 @@ bool LocalGraph::isSSA(Index x) { return SSAIndexes.count(x); } // LazyLocalGraph -LazyLocalGraph::LazyLocalGraph(Function* func, Module* module) - : LocalGraphBase(func, module) {} +LazyLocalGraph::LazyLocalGraph(Function* func, + Module* module, + std::optional queryClass) + : LocalGraphBase(func, module), queryClass(queryClass) {} void LazyLocalGraph::makeFlower() const { // |locations| is set here and filled in by |flower|. assert(!locations); locations.emplace(); - flower = - std::make_unique(getSetsMap, *locations, func, module); + flower = std::make_unique( + getSetsMap, *locations, func, module, queryClass); flower->prepareLaziness(); @@ -670,4 +748,52 @@ void LazyLocalGraph::computeLocations() const { } } +LocalGraphBase::SetInfluences LazyLocalGraph::canMoveSet(LocalSet* set, + Expression* to) { + // We must have been initialized with the proper query class, so that we + // prepared the flower (if it was computed before) with that class in the + // graph. + assert(queryClass && to->_id == *queryClass); + + if (!flower) { + makeFlower(); + } + + // To compute this property, we'll do a flow from the gets that the set + // originally reaches. No other get is relevant. + auto originalGets = getSetInfluences(set); + + // To see which gets pose a problem, see which gets are still influenced by + // the set, if we consider |to| to be another set of that index, that is, an + // obstacle on the way, that tramples that local index's value. Any such + // influenced get is a problem, for example: + // + // 1. set + // 2. get + // 3. call + // 4. get + // + // The set can still influence the get on line 2, if we consider the call to + // be an obstacle. Looking at it another way, any get that is no longer + // influenced, given the obstacle, is a get that is only influenced by the + // obstacle itself, meaning that moving the set to the obstacle is valid. This + // is a slight simplification, though, since other sets may be involved: + // + // if (..) { + // x = ..; + // a(x) + // b(); + // c(x); + // } + // d(x); + // + // Say we consider moving the set of x to b(). a(x) uses x in a manner that + // will notice that, but not c(x) or d(x). c(x) is dominated by the set, but + // d(x) is not. That is, moving the set to b() leaves the set's influence + // unchanged on c(x), where that influence is full, and also on d(x), where it + // is only partial (shared with whatever value is present in x before the if). + // (But moving the set to b() does alter the set's influence on a(x)). + return flower->getSetInfluencesGivenObstacle(set, originalGets, to); +} + } // namespace wasm diff --git a/src/ir/local-graph.h b/src/ir/local-graph.h index 7b8001be698..e582751fa22 100644 --- a/src/ir/local-graph.h +++ b/src/ir/local-graph.h @@ -178,7 +178,11 @@ struct LocalGraph : public LocalGraphBase { struct LocalGraphFlower; struct LazyLocalGraph : public LocalGraphBase { - LazyLocalGraph(Function* func, Module* module = nullptr); + // We optionally receive an expression class to consider relevant for queries, + // see below. (Only expressions of this class can be queried later.) + LazyLocalGraph(Function* func, + Module* module = nullptr, + std::optional queryClass = std::nullopt); ~LazyLocalGraph(); // Similar APIs as in LocalGraph, but lazy versions. Each of them does a @@ -228,7 +232,39 @@ struct LazyLocalGraph : public LocalGraphBase { return *locations; } + // Query whether it is valid to move a LocalSet to a new position, that is, + // that moving it will not alter observable behavior. That means that no + // Localget is able to observe a different value than before. This returns the + // set of LocalGets that may do so, that is, the gets for which we detected a + // problem, hence if the set is empty, the set is valid to move. + // + // For example: + // + // 1. set + // 2. get + // 3. call + // 4. get + // + // If we move the set to the call, then the get on line 2 could observe a + // different value (the pre-existing value in that local, before line 1), and + // we'd return that get here. + // + // |to| must be of the class |queryClass| that is defined during construction. + // That is, we must decide ahead of time which places we want to query about + // moving LocalSets to, and can then issue queries on any of those, in an + // efficient manner. + // + // This assumes that |to| is in a position dominated by |set|, that is we are + // moving the set "forward". (In particular, that implies that the new + // position will have monotonically *less* influence than before - we don't + // need to scan all possible gets of that index in the entire function, we can + // look only on the gets influenced by the set, and see how the new position + // behaves regarding them.) + SetInfluences canMoveSet(LocalSet* set, Expression* to); + private: + std::optional queryClass; + // These data structures are mutable so that we can memoize. mutable GetSetsMap getSetsMap; diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 7ef72db135d..9b648fb654c 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -8,6 +8,7 @@ set(unittest_SOURCES disjoint_sets.cpp json.cpp lattices.cpp + local-graph.cpp possible-contents.cpp printing.cpp scc.cpp diff --git a/test/gtest/local-graph.cpp b/test/gtest/local-graph.cpp new file mode 100644 index 00000000000..5360813ceef --- /dev/null +++ b/test/gtest/local-graph.cpp @@ -0,0 +1,347 @@ +#include "ir/local-graph.h" +#include "parser/wat-parser.h" +#include "wasm.h" + +#include "gtest/gtest.h" + +using LocalGraphTest = ::testing::Test; + +using namespace wasm; + +TEST_F(LocalGraphTest, ObstacleBasics) { + auto moduleText = R"wasm( + (module + (func $foo (result i32) + ;; A local with a set and a get, and some nops in between. + (local $x i32) + (nop) + (local.set $x + (i32.const 10) + ) + (nop) + (local.get $x) + ) + ) + )wasm"; + + Module wasm; + WATParser::parseModule(wasm, moduleText); + + // Get access to the contents of the wasm. + auto* func = wasm.functions[0].get(); + auto* block = func->body->cast(); + auto* nopA = block->list[0]->cast(); + auto* set = block->list[1]->cast(); + auto* nopB = block->list[2]->cast(); + auto* get = block->list[3]->cast(); + + { + LazyLocalGraph graph(func, &wasm); + // The set has one get. + EXPECT_EQ(graph.getSetInfluences(set).size(), 1U); + } + + { + // Construct the graph with an obstacle class, Nop. + LazyLocalGraph graph(func, &wasm, Nop::SpecificId); + // The set has one get, like before. + EXPECT_EQ(graph.getSetInfluences(set).size(), 1U); + // If the first nop is an obstacle, nothing changes: the path between the + // set and get does not include it. + EXPECT_EQ(graph.canMoveSet(set, nopA).size(), 1U); + EXPECT_EQ(*graph.canMoveSet(set, nopA).begin(), get); + // But if the second one is an obstacle, it severs the connection. + EXPECT_EQ(graph.canMoveSet(set, nopB).size(), 0U); + } +} + +TEST_F(LocalGraphTest, ObstacleMultiblock) { + auto moduleText = R"wasm( + (module + (func $foo (result i32) + ;; An if between the set and get. + (local $x i32) + (local.set $x + (i32.const 10) + ) + (if + (i32.const 42) + (then + (nop) + ) + (else + (nop) + ) + ) + (nop) + (local.get $x) + ) + ) + )wasm"; + Module wasm; + WATParser::parseModule(wasm, moduleText); + auto* func = wasm.functions[0].get(); + auto* block = func->body->cast(); + auto* set = block->list[0]->cast(); + auto* iff = block->list[1]->cast(); + auto* nopA = iff->ifTrue->cast(); + auto* nopB = iff->ifTrue->cast(); + auto* nopC = block->list[2]->cast(); + + LazyLocalGraph graph(func, &wasm, Nop::SpecificId); + // No matter which if arm is an obstacle, we still connect. + EXPECT_EQ(graph.canMoveSet(set, nopA).size(), 1U); + EXPECT_EQ(graph.canMoveSet(set, nopB).size(), 1U); + // But the nop after the if stops us. + EXPECT_EQ(graph.canMoveSet(set, nopC).size(), 0U); +} + +TEST_F(LocalGraphTest, ObstacleUnreachable) { + auto moduleText = R"wasm( + (module + (func $foo (result i32) + ;; An unreachable between the set and get. + (local $x i32) + (local.set $x + (i32.const 10) + ) + (nop) + (unreachable) + (nop) + (local.get $x) + ) + ) + )wasm"; + Module wasm; + WATParser::parseModule(wasm, moduleText); + auto* func = wasm.functions[0].get(); + auto* block = func->body->cast(); + auto* set = block->list[0]->cast(); + auto* nopA = block->list[1]->cast(); + auto* nopB = block->list[3]->cast(); + + LazyLocalGraph graph(func, &wasm, Nop::SpecificId); + // The get is unreachable, and the set has no gets. + EXPECT_EQ(graph.getSetInfluences(set).size(), 0U); + EXPECT_EQ(graph.canMoveSet(set, nopA).size(), 0U); + EXPECT_EQ(graph.canMoveSet(set, nopB).size(), 0U); +} + +TEST_F(LocalGraphTest, ObstacleMultiGet) { + auto moduleText = R"wasm( + (module + (func $foo + ;; A set with multiple gets. + (local $x i32) + (local.set $x + (i32.const 10) + ) + (nop) + (drop + (local.get $x) + ) + (nop) + (drop + (local.get $x) + ) + (nop) + ) + ) + )wasm"; + Module wasm; + WATParser::parseModule(wasm, moduleText); + auto* func = wasm.functions[0].get(); + auto* block = func->body->cast(); + auto* set = block->list[0]->cast(); + auto* nopA = block->list[1]->cast(); + auto* nopB = block->list[3]->cast(); + auto* nopC = block->list[5]->cast(); + + LazyLocalGraph graph(func, &wasm, Nop::SpecificId); + // The first nop blocks them both, but not the second, and the third blocks + // nothing. + EXPECT_EQ(graph.canMoveSet(set, nopA).size(), 0U); + EXPECT_EQ(graph.canMoveSet(set, nopB).size(), 1U); + EXPECT_EQ(graph.canMoveSet(set, nopC).size(), 2U); +} + +TEST_F(LocalGraphTest, ObstacleMultiSet) { + auto moduleText = R"wasm( + (module + (func $foo + ;; Two sets. + (local $x i32) + (local.set $x + (i32.const 10) + ) + (local.set $x + (i32.const 20) + ) + (nop) + (drop + (local.get $x) + ) + ) + ) + )wasm"; + Module wasm; + WATParser::parseModule(wasm, moduleText); + auto* func = wasm.functions[0].get(); + auto* block = func->body->cast(); + auto* setA = block->list[0]->cast(); + auto* setB = block->list[1]->cast(); + auto* nop = block->list[2]->cast(); + + LazyLocalGraph graph(func, &wasm, Nop::SpecificId); + // The first set has no gets, the second has one. + EXPECT_EQ(graph.getSetInfluences(setA).size(), 0U); + EXPECT_EQ(graph.getSetInfluences(setB).size(), 1U); + // The nop blocks on the second (and the first, but it had none anyhow). + EXPECT_EQ(graph.canMoveSet(setA, nop).size(), 0U); + EXPECT_EQ(graph.canMoveSet(setB, nop).size(), 0U); +} + +TEST_F(LocalGraphTest, ObstacleMultiSetIndexes) { + auto moduleText = R"wasm( + (module + (func $foo + ;; Two sets of different indexes. + (local $x i32) + (local $y i32) + (local.set $x + (i32.const 10) + ) + (local.set $y + (i32.const 20) + ) + (nop) + (drop + (local.get $x) + ) + (nop) + (drop + (local.get $y) + ) + (nop) + ) + ) + )wasm"; + Module wasm; + WATParser::parseModule(wasm, moduleText); + auto* func = wasm.functions[0].get(); + auto* block = func->body->cast(); + auto* setA = block->list[0]->cast(); + auto* setB = block->list[1]->cast(); + auto* nopA = block->list[2]->cast(); + auto* nopB = block->list[4]->cast(); + auto* nopC = block->list[6]->cast(); + + LazyLocalGraph graph(func, &wasm, Nop::SpecificId); + // The first nop blocks them both. + EXPECT_EQ(graph.canMoveSet(setA, nopA).size(), 0U); + EXPECT_EQ(graph.canMoveSet(setB, nopA).size(), 0U); + // The second nop only blocks one. + EXPECT_EQ(graph.canMoveSet(setA, nopB).size(), 1U); + EXPECT_EQ(graph.canMoveSet(setB, nopB).size(), 0U); + // The last nop blocks nothing. + EXPECT_EQ(graph.canMoveSet(setA, nopC).size(), 1U); + EXPECT_EQ(graph.canMoveSet(setB, nopC).size(), 1U); +} + +TEST_F(LocalGraphTest, ObstacleMultiSetIf) { + auto moduleText = R"wasm( + (module + (func $foo + ;; Two sets in an if. + (local $x i32) + (if + (i32.const 42) + (then + (local.set $x + (i32.const 10) + ) + ) + (else + (local.set $x + (i32.const 20) + ) + ) + ) + (nop) + (drop + (local.get $x) + ) + (nop) + ) + ) + )wasm"; + Module wasm; + WATParser::parseModule(wasm, moduleText); + auto* func = wasm.functions[0].get(); + auto* block = func->body->cast(); + auto* iff = block->list[0]->cast(); + auto* setA = iff->ifTrue->cast(); + auto* setB = iff->ifFalse->cast(); + auto* nopA = block->list[1]->cast(); + auto* nopB = block->list[3]->cast(); + + LazyLocalGraph graph(func, &wasm, Nop::SpecificId); + // Both sets have a get. + EXPECT_EQ(graph.getSetInfluences(setA).size(), 1U); + EXPECT_EQ(graph.getSetInfluences(setB).size(), 1U); + // The first nop blocks both. + EXPECT_EQ(graph.canMoveSet(setA, nopA).size(), 0U); + EXPECT_EQ(graph.canMoveSet(setB, nopA).size(), 0U); + // The first nop blocks neither. + EXPECT_EQ(graph.canMoveSet(setA, nopB).size(), 1U); + EXPECT_EQ(graph.canMoveSet(setB, nopB).size(), 1U); +} + +TEST_F(LocalGraphTest, ObstacleStructSet) { + // Use something other than a nop to obstruct. Here we show a realistic + // situation using GC. + auto moduleText = R"wasm( + (module + (type $struct (struct (field (mut i32)))) + + (func $test + (local $struct (ref null $struct)) + (block $label + ;; A struct.set that may be skipped by the br in the if. + (struct.set $struct 0 + (local.tee $struct + (struct.new_default $struct) + ) + (if (result i32) + (i32.const 1) + (then + (br $label) + ) + (else + (i32.const 0) + ) + ) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) + ) + )wasm"; + Module wasm; + WATParser::parseModule(wasm, moduleText); + auto* func = wasm.functions[0].get(); + auto* outerBlock = func->body->cast(); + auto* block = outerBlock->list[0]->cast(); + auto* structSet = block->list[0]->cast(); + auto* tee = structSet->ref->cast(); + + LazyLocalGraph graph(func, &wasm, StructSet::SpecificId); + // The tee has one get. + EXPECT_EQ(graph.getSetInfluences(tee).size(), 1U); + // The struct.set blocks one path, but not the path that skips it via the br. + EXPECT_EQ(graph.canMoveSet(tee, structSet).size(), 1U); +} From f6cfc5647259c12036f58f0a3b6b3cfc83e4b914 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2024 14:41:56 -0800 Subject: [PATCH 121/622] Fix PickLoadSigns on SignExt feature instructions (#7069) I believe the history here is that 1. We added a PickLoadSigns pass. It checks if a load from memory is stored in a local that is only every used in a signed or an unsigned manner. If it is, we can adjust the sign of the load (load8_u/s) to do the sign/unsign during the load. 2. The pass finds each LocalGet and looks either 2 or 3 parents above it. For a sign operation, we need to look up 3, since the operation is x << K >> K. For an unsigned, we need only 2, since we have x & M. We hardcoded those numbers 2 and 3. 3. We added the SignExt feature, which adds i32.extend8_s. This does a sign extend with a single instruction, not two nested ones, so now we can sign- extend at depth 2, unlike before. Properties::getSignExtValue was updated for this, but not the pass PickLoadSigns. The bug that is fixed here is that we looked at depth 3 for a sign-extend, and we blindly accepted it if we found one. So we ended up accepting (i32.extend8_s (ANYTHING (x))), which is a sign-extend of something, but not of x, which is bad. We were also missing an optimization opportunity, as we didn't look for depth 2 sign extends. This bug is quite old, from when Properties got SignExt support, in #3910. But the blame isn't there - to notice this then, we'd have had to check each caller of getSignExtValue throughout the codebase, which isn't reasonable. The fault is mine, from the first write-up of PickLoadSigns in 2017: the code should have been fully general, handling 2/3 and checking the output when it does so (adding == curr, that the sign/zero-extended value is the one we expect). That is what this PR does. --- src/passes/PickLoadSigns.cpp | 39 +++++---- test/lit/passes/pick-load-signs_sign-ext.wast | 80 +++++++++++++++++++ 2 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 test/lit/passes/pick-load-signs_sign-ext.wast diff --git a/src/passes/PickLoadSigns.cpp b/src/passes/PickLoadSigns.cpp index 587314209e6..693fd4a68e6 100644 --- a/src/passes/PickLoadSigns.cpp +++ b/src/passes/PickLoadSigns.cpp @@ -20,10 +20,11 @@ namespace wasm { +// // Adjust load signedness based on usage. If a load only has uses that sign or // unsign it anyhow, then it could be either, and picking the popular one can -// help remove the most sign/unsign operations -// unsigned, then it could be either +// help remove the most sign/unsign operations. +// struct PickLoadSigns : public WalkerPass> { bool isFunctionParallel() override { return true; } @@ -59,13 +60,20 @@ struct PickLoadSigns : public WalkerPass> { } void visitLocalGet(LocalGet* curr) { - // this is a use. check from the context what it is, signed or unsigned, - // etc. + // This is a use. check from the context what it is, signed or unsigned, + // etc. Sign- and zero-extends may have two levels of nesting (x << K >> K) + // or one (x & K), so check both. auto& usage = usages[curr->index]; usage.totalUsages++; - if (expressionStack.size() >= 2) { - auto* parent = expressionStack[expressionStack.size() - 2]; - if (Properties::getZeroExtValue(parent)) { + for (Index i = 2; i <= 3; i++) { + if (expressionStack.size() < i) { + // We do not have that many expressions above us. (And if we don't have + // 2 then we definitely don't have 3.) + break; + } + + auto* parent = expressionStack[expressionStack.size() - i]; + if (Properties::getZeroExtValue(parent) == curr) { auto bits = Properties::getZeroExtBits(parent); if (usage.unsignedUsages == 0) { usage.unsignedBits = bits; @@ -73,17 +81,14 @@ struct PickLoadSigns : public WalkerPass> { usage.unsignedBits = 0; } usage.unsignedUsages++; - } else if (expressionStack.size() >= 3) { - auto* grandparent = expressionStack[expressionStack.size() - 3]; - if (Properties::getSignExtValue(grandparent)) { - auto bits = Properties::getSignExtBits(grandparent); - if (usage.signedUsages == 0) { - usage.signedBits = bits; - } else if (usage.signedBits != bits) { - usage.signedBits = 0; - } - usage.signedUsages++; + } else if (Properties::getSignExtValue(parent) == curr) { + auto bits = Properties::getSignExtBits(parent); + if (usage.signedUsages == 0) { + usage.signedBits = bits; + } else if (usage.signedBits != bits) { + usage.signedBits = 0; } + usage.signedUsages++; } } } diff --git a/test/lit/passes/pick-load-signs_sign-ext.wast b/test/lit/passes/pick-load-signs_sign-ext.wast new file mode 100644 index 00000000000..c50af32c53f --- /dev/null +++ b/test/lit/passes/pick-load-signs_sign-ext.wast @@ -0,0 +1,80 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --pick-load-signs -all -S -o - | filecheck %s + +(module + ;; CHECK: (memory $0 16 17 shared) + (memory $0 16 17 shared) + + ;; CHECK: (func $load-other-use (type $0) (result i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.load8_u + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $load-other-use (result i32) + (local $temp i32) + ;; The load here is unsigned, while the value in the local is used in two + ;; ways: it is sign-extended, and it is sent on a branch by a br_if. We must + ;; not ignore the branch, as if we did then we'd think the only use is signed, + ;; and we'd optimize load8_u => load8_s. + (block $label (result i32) + (local.set $temp + (i32.load8_u + (i32.const 22) + ) + ) + (drop + (i32.extend8_s + (br_if $label + (local.get $temp) + (i32.const 1) + ) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $load-valid (type $1) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.load8_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $load-valid + (local $temp i32) + ;; As above, but remove the br_if in the middle. Now this is a valid case to + ;; optimize, load8_u => load8_s. + (local.set $temp + (i32.load8_u + (i32.const 22) + ) + ) + (drop + (i32.extend8_s + (local.get $temp) + ) + ) + ) +) + From 67894e4989870807561e6e80ae6e18c1e0e9a4ef Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 11 Nov 2024 15:36:28 -0800 Subject: [PATCH 122/622] [wasm64] Fuzzer: Fix type of unimported offsets (#7071) When the fuzzer sees an imported segment, it makes it non-imported (because imported ones would trap when we tried to run them: we don't have the normal runtime they expect). We had hardcoded i32 offets there, which need to be generalized. --- src/tools/fuzzing/fuzzing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index f4e21d87089..cbdbff3cabf 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -535,7 +535,7 @@ void TranslateToFuzzReader::finalizeMemory() { // TODO: It would be better to avoid segment overlap so that // MemoryPacking can run. segment->offset = - builder.makeConst(Literal::makeFromInt32(0, Type::i32)); + builder.makeConst(Literal::makeFromInt32(0, memory->addressType)); } } if (auto* offset = segment->offset->dynCast()) { @@ -579,7 +579,7 @@ void TranslateToFuzzReader::finalizeTable() { assert(!wasm.getGlobal(get->name)->imported()); // TODO: the segments must not overlap... segment->offset = - builder.makeConst(Literal::makeFromInt32(0, Type::i32)); + builder.makeConst(Literal::makeFromInt32(0, table->addressType)); } } Address maxOffset = segment->data.size(); From 52cae1142bb8c06c98c4887ef9ffefff70dbeefc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Nov 2024 09:09:33 -0800 Subject: [PATCH 123/622] HeapStoreOptimization: Fix a bug with jumping from the later value (v2) (#7070) This PR fixes this situation: (block $out (local.set $x (struct.new X Y Z)) (struct.set $X 0 (local.get $x) (..br $out..)) ;; X' here has a br ) (local.get $x) => (block $out (local.set $x (struct.new (..br $out..) Y Z)) ) (local.get $x) We want to fold the struct.set into the struct.new, but the br is a problem: if it executes then we skip the struct.set, and the last local.get in fact reads the struct before the write. And, if we did this optimization, we'd end up with the br on the struct.new, so it would skip that instruction and even the local.set. To fix this, we use the new API from #7039, which lets us query, "is it ok to move the local.set to where the struct.set is?" --- src/passes/HeapStoreOptimization.cpp | 79 +- test/lit/passes/heap-store-optimization.wast | 813 ++++++++++++++++++- 2 files changed, 873 insertions(+), 19 deletions(-) diff --git a/src/passes/HeapStoreOptimization.cpp b/src/passes/HeapStoreOptimization.cpp index f227dc53631..605caba41c1 100644 --- a/src/passes/HeapStoreOptimization.cpp +++ b/src/passes/HeapStoreOptimization.cpp @@ -22,6 +22,7 @@ #include "cfg/cfg-traversal.h" #include "ir/effects.h" +#include "ir/local-graph.h" #include "pass.h" #include "wasm-builder.h" #include "wasm.h" @@ -90,7 +91,7 @@ struct HeapStoreOptimization // if (auto* tee = curr->ref->dynCast()) { if (auto* new_ = tee->value->dynCast()) { - if (optimizeSubsequentStructSet(new_, curr, tee->index)) { + if (optimizeSubsequentStructSet(new_, curr, tee)) { // Success, so we do not need the struct.set any more, and the tee // can just be a set instead of us. tee->makeSet(); @@ -147,7 +148,7 @@ struct HeapStoreOptimization } // The pattern matches, try to optimize. - if (!optimizeSubsequentStructSet(new_, structSet, localGet->index)) { + if (!optimizeSubsequentStructSet(new_, structSet, localSet)) { break; } else { // Success. Replace the set with a nop, and continue to perhaps @@ -209,7 +210,7 @@ struct HeapStoreOptimization // Returns true if we succeeded. bool optimizeSubsequentStructSet(StructNew* new_, StructSet* set, - Index refLocalIndex) { + LocalSet* localSet) { // Leave unreachable code for DCE, to avoid updating types here. if (new_->type == Type::unreachable || set->type == Type::unreachable) { return false; @@ -217,6 +218,7 @@ struct HeapStoreOptimization auto index = set->index; auto& operands = new_->operands; + auto refLocalIndex = localSet->index; // Check for effects that prevent us moving the struct.set's value (X' in // the function comment) into its new position in the struct.new. First, it @@ -246,6 +248,12 @@ struct HeapStoreOptimization } } + // We must also be careful of branches out from the value that skip the + // local.set, see below. + if (canSkipLocalSet(set, setValueEffects, localSet)) { + return false; + } + // We can optimize here! Builder builder(*getModule()); @@ -271,9 +279,74 @@ struct HeapStoreOptimization return true; } + // We must be careful of branches that skip the local.set. For example: + // + // (block $out + // (local.set $x (struct.new X Y Z)) + // (struct.set (local.get $x) (..br $out..)) ;; X' here has a br + // ) + // ..local.get $x.. + // + // Note how we do the local.set first. Imagine we optimized to this: + // + // (block $out + // (local.set $x (struct.new (..br $out..) Y Z)) + // ) + // ..local.get $x.. + // + // Now the br happens first, skipping the local.set entirely, and the use + // later down will not get the proper value. This is the problem we must + // detect here. + // + // We are given a struct.set, the computed effects of its value (the caller + // already has those, so this is an optimization to avoid recomputation), and + // the local.set. + bool canSkipLocalSet(StructSet* set, + const EffectAnalyzer& setValueEffects, + LocalSet* localSet) { + // First, if the set's value cannot branch at all, then we have no problem. + if (!setValueEffects.transfersControlFlow()) { + return false; + } + + // We may branch, so do an analysis using a LocalGraph. We will check + // whether it is valid to move the local.set to where the struct.set is, so + // we provide StructSet as the query class. + // + // It is valid to reuse the LocalGraph many times because the optimization + // that we do in this pass does not generate new, dangerous control flow. We + // only optimize if moving a LocalSet is valid, and that does not invalidate + // any other one. + if (!localGraph) { + localGraph.emplace(getFunction(), getModule(), StructSet::SpecificId); + } + auto badGets = localGraph->canMoveSet(localSet, set); + if (badGets.size() == 0) { + // No problems at all. + return false; + } + // It is ok to have a local.get in the struct.set itself, as if we optimize + // then that local.get goes away anyhow, that is, + // + // (local.set $x (struct.new X Y Z)) + // (struct.set (local.get $x) (X')) + // => + // (local.set $x (struct.new X' Y Z)) + // + // Both the local.get and the struct.set are removed. + if (badGets.size() >= 2) { + return true; + } + return *badGets.begin() != set->ref; + } + EffectAnalyzer effects(Expression* expr) { return EffectAnalyzer(getPassOptions(), *getModule(), expr); } + +private: + // A local graph that is constructed the first time we need it. + std::optional localGraph; }; } // anonymous namespace diff --git a/test/lit/passes/heap-store-optimization.wast b/test/lit/passes/heap-store-optimization.wast index 635963523c6..1c1abba4ae2 100644 --- a/test/lit/passes/heap-store-optimization.wast +++ b/test/lit/passes/heap-store-optimization.wast @@ -7,12 +7,18 @@ (module ;; CHECK: (type $struct (struct (field (mut i32)))) - (type $struct (struct (field (mut i32)))) ;; CHECK: (type $struct2 (struct (field (mut i32)) (field (mut i32)))) - (type $struct2 (struct (field (mut i32)) (field (mut i32)))) ;; CHECK: (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) + + ;; CHECK: (tag $tag) + (tag $tag) + + (type $struct (struct (field (mut i32)))) + + (type $struct2 (struct (field (mut i32)) (field (mut i32)))) + (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) ;; CHECK: (func $tee (type $1) @@ -816,26 +822,29 @@ ) ) - ;; CHECK: (func $helper-i32 (type $4) (param $x i32) (result i32) + ;; CHECK: (func $helper-i32 (type $7) (param $x i32) (result i32) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) (func $helper-i32 (param $x i32) (result i32) (i32.const 42) ) - ;; CHECK: (func $control-flow-in-set-value (type $5) (result i32) + ;; CHECK: (func $control-flow-in-set-value (type $4) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (block $label - ;; CHECK-NEXT: (local.set $ref - ;; CHECK-NEXT: (struct.new $struct - ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $label) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $label) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -846,8 +855,7 @@ ;; CHECK-NEXT: ) (func $control-flow-in-set-value (result i32) ;; Test we handle control flow in the struct.set's value when we combine a - ;; struct.set with a struct.new. - ;; XXX The output above is wrong atm, and the pass needs fixing! + ;; struct.set with a struct.new. We should not optimize here. (local $ref (ref null $struct)) (block $label (struct.set $struct 0 @@ -876,5 +884,778 @@ (local.get $ref) ) ) -) + ;; CHECK: (func $control-flow-in-set-value-safe (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $control-flow-in-set-value-safe (result i32) + ;; As above, but now the control flow in the value is safe: it does not + ;; escape out. We should optimize here. + (local $ref (ref null $struct)) + (block $label + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (if (result i32) + (i32.const 1) + (then + (i32.const 1337) ;; the break to $out was replaced + ) + (else + (i32.const 42) + ) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $control-flow-in-set-value-safe-call (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $control-flow-in-set-value-safe-call (result i32) + ;; As above, but now the possible control flow is a call. It may throw, but + ;; that would go outside of the function, which is fine. + (local $ref (ref null $struct)) + (block $label + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (call $helper-i32 (i32.const 42)) ;; the if was replaced by this call + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + (func $control-flow-in-set-value-safe-return (result i32) + ;; As above, but replace the call with a return in an if. We can still + ;; optimize (if the return is taken, we go outside of the function anyhow). + (local $ref (ref null $struct)) + (block $label + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (if (result i32) + (i32.const 1) + (then + (return (i32.const 42)) + ) + (else + (i32.const 42) + ) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $control-flow-in-set-value-unsafe-call (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (block $label + ;; CHECK-NEXT: (try_table (catch $tag $label) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $control-flow-in-set-value-unsafe-call (result i32) + ;; As above, but now the call's possible throw could be caught *inside* the + ;; function, which means it is dangerous, and we do not optimize. + (local $ref (ref null $struct)) + (block $label + (try_table (catch $tag $label) ;; this try was added + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (call $helper-i32 (i32.const 42)) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $control-flow-later (type $5) (param $x i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $control-flow-later (param $x i32) + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (i32.const 42) + ) + ;; This later control flow should not prevent optimizing the struct.set + ;; before it. + (block $out + (br_if $out + (local.get $x) + ) + ) + (if + (local.get $x) + (then + (nop) + ) + ) + ) + + ;; CHECK: (func $loop (type $1) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop + (local $ref (ref null $struct)) + (loop $loop + ;; There is a use of the reference at the top of the loop, and the br_if + ;; may get here, so this is a basic block before the struct.set that we + ;; need to be careful of reaching. We should not optimize here. + (drop + (struct.get $struct 0 + (local.get $ref) + ) + ) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (if (result i32) + (i32.const 1) + (then + (br $loop) + ) + (else + (i32.const 42) + ) + ) + ) + ) + ) + + ;; CHECK: (func $loop-more-flow (type $1) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-more-flow + (local $ref (ref null $struct)) + (loop $loop + ;; As above, but add this if which adds more control flow at the loop top. + ;; We should still not optimize here. + (if + (i32.const 1) + (then + (br $loop) + ) + ) + (drop + (struct.get $struct 0 + (local.get $ref) + ) + ) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (if (result i32) + (i32.const 1) + (then + (br $loop) + ) + (else + (i32.const 42) + ) + ) + ) + ) + ) + + ;; CHECK: (func $loop-in-value (type $5) (param $x i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (loop $loop (result i32) + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-in-value (param $x i32) + (local $ref (ref null $struct)) + ;; The struct.set's value has a loop in it, but that is fine, as while there + ;; are backedges there, they are still contained in the value: we can't skip + ;; the struct.set. We can optimize here. + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (loop $loop (result i32) + (br_if $loop + (local.get $x) + ) + (i32.const 42) + ) + ) + ) + + ;; CHECK: (func $in-if-arm (type $6) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $in-if-arm (param $x i32) (param $y i32) (result i32) + (local $ref (ref null $struct)) + (if + (local.get $x) + (then + (block $out + ;; We cannot optimize here, as the struct.get outside of the if can + ;; read different state if the br_if happens. + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (block (result i32) + (br_if $out + (local.get $x) + ) + (i32.const 42) + ) + ) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $in-if-arm-yes (type $6) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + (func $in-if-arm-yes (param $x i32) (param $y i32) (result i32) + (local $ref (ref null $struct)) + ;; As before, but the struct.get at the end is removed, so we can optimize. + (if + (local.get $x) + (then + (block $out + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 1) + ) + ) + (block (result i32) + (br_if $out + (local.get $x) + ) + (i32.const 42) + ) + ) + ) + ) + ) + (i32.const 1337) ;; this changed + ) + + ;; CHECK: (func $control-flow-in-set-value-sequence (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $out (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $control-flow-in-set-value-sequence (result i32) + (local $ref (ref null $struct)) + (drop + (block $out (result i32) + (local.set $ref + ;; Also test struct.new_default here, with control flow. + (struct.new_default $struct) + ) + ;; The struct.get outside is a problem, so we do not optimize here, + ;; nor the set after us. + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 1) + (i32.const 2) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (i32.const 3) + ) + ;; This struct.set is not a problem: if we branch, we don't reach it + ;; anyhow. + (drop + (struct.get $struct 0 + (local.get $ref) + ) + ) + (i32.const 42) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $control-flow-in-set-value-sequence-2 (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $out (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $control-flow-in-set-value-sequence-2 (result i32) + (local $ref (ref null $struct)) + (drop + (block $out (result i32) + (local.set $ref + (struct.new_default $struct) + ) + ;; As above, but the order of struct.sets is flipped. We can at least + ;; optimize the first here. + (struct.set $struct 0 + (local.get $ref) + (i32.const 3) + ) + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 1) + (i32.const 2) + ) + ) + (drop + (struct.get $struct 0 + (local.get $ref) + ) + ) + (i32.const 42) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $control-flow-in-set-value-sequence-yes (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $out (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + (func $control-flow-in-set-value-sequence-yes (result i32) + (local $ref (ref null $struct)) + ;; As above, but the struct.get at the end is removed, allowing us to + ;; optimize it all. + (drop + (block $out (result i32) + (local.set $ref + (struct.new_default $struct) + ) + (struct.set $struct 0 + (local.get $ref) + (i32.const 3) + ) + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 1) + (i32.const 2) + ) + ) + ;; Note how this struct.get remains and does not stop us. + (drop + (struct.get $struct 0 + (local.get $ref) + ) + ) + (i32.const 42) + ) + ) + (i32.const 1337) + ) + + ;; CHECK: (func $multi-control-flow-in-set-value-sequence-yes (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $out (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + (func $multi-control-flow-in-set-value-sequence-yes (result i32) + (local $ref (ref null $struct)) + ;; As above, but now we have multiple br_ifs. We optimize one, but then + ;; stop because of the control flow that is now in the struct.new. TODO we + ;; could be more precise here. + (drop + (block $out (result i32) + (local.set $ref + (struct.new_default $struct) + ) + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 1) + (i32.const 2) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 3) + (i32.const 4) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 5) + (i32.const 6) + ) + ) + (i32.const 42) + ) + ) + (i32.const 1337) + ) + + ;; CHECK: (func $multi-control-flow-in-set-value-sequence-no (type $8) (result anyref) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $out (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + (func $multi-control-flow-in-set-value-sequence-no (result anyref) + (local $ref (ref null $struct)) + ;; As above, but now we have a dangerous local.get at the end, stopping us + ;; from optimizing. + (drop + (block $out (result i32) + (local.set $ref + (struct.new_default $struct) + ) + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 1) + (i32.const 2) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 3) + (i32.const 4) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (br_if $out + (i32.const 5) + (i32.const 6) + ) + ) + (i32.const 42) + ) + ) + (local.get $ref) + ) +) From 496d92bfcf848d7c888b83cc354819ed87ba0b87 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Tue, 12 Nov 2024 17:33:35 -0800 Subject: [PATCH 124/622] Introduce pass to lower memory.copy and memory.fill (#7021) This pass lowers away memory.copy and memory.fill operations. It generates a function that implements the each of the instructions and replaces the instructions with calls to those functions. It does not handle other bulk memory operations (e.g. passive segments and table operations) because they are not used by emscripten to enable targeting old browsers that don't support bulk memory. --- src/passes/CMakeLists.txt | 1 + src/passes/MemoryCopyFillLowering.cpp | 260 +++++++++++++++++ src/passes/pass.cpp | 4 + src/passes/passes.h | 1 + test/lit/exec/memory-copy.wat | 267 ++++++++++++++++++ test/lit/exec/memory-fill.wat | 184 ++++++++++++ test/lit/help/wasm-metadce.test | 4 + test/lit/help/wasm-opt.test | 4 + test/lit/help/wasm2js.test | 4 + .../lit/passes/memory-copy-fill-lowering.wast | 168 +++++++++++ 10 files changed, 897 insertions(+) create mode 100644 src/passes/MemoryCopyFillLowering.cpp create mode 100644 test/lit/exec/memory-copy.wat create mode 100644 test/lit/exec/memory-fill.wat create mode 100644 test/lit/passes/memory-copy-fill-lowering.wast diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index a219438b410..6053c55222a 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -63,6 +63,7 @@ set(passes_SOURCES LogExecution.cpp LoopInvariantCodeMotion.cpp Memory64Lowering.cpp + MemoryCopyFillLowering.cpp MemoryPacking.cpp MergeBlocks.cpp MergeSimilarFunctions.cpp diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp new file mode 100644 index 00000000000..5855e545066 --- /dev/null +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -0,0 +1,260 @@ +/* + * Copyright 2024 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ir/names.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" + +// Replace memory.copy and memory.fill with a call to a function that +// implements the same semantics. This is intended to be used with LLVM output, +// so anything considered undefined behavior in LLVM is ignored. (In +// particular, pointer overflow is UB and not handled here). + +namespace wasm { +struct MemoryCopyFillLowering + : public WalkerPass> { + bool needsMemoryCopy = false; + bool needsMemoryFill = false; + Name memCopyFuncName; + Name memFillFuncName; + + void visitMemoryCopy(MemoryCopy* curr) { + assert(curr->destMemory == + curr->sourceMemory); // multi-memory not supported. + Builder builder(*getModule()); + replaceCurrent(builder.makeCall( + "__memory_copy", {curr->dest, curr->source, curr->size}, Type::none)); + needsMemoryCopy = true; + } + + void visitMemoryFill(MemoryFill* curr) { + Builder builder(*getModule()); + replaceCurrent(builder.makeCall( + "__memory_fill", {curr->dest, curr->value, curr->size}, Type::none)); + needsMemoryFill = true; + } + + void run(Module* module) override { + if (!module->features.hasBulkMemory()) { + return; + } + if (module->features.hasMemory64() || module->features.hasMultiMemory()) { + Fatal() + << "Memory64 and multi-memory not supported by memory.copy lowering"; + } + + // Check for the presence of any passive data or table segments. + for (auto& segment : module->dataSegments) { + if (segment->isPassive) { + Fatal() << "memory.copy lowering should only be run on modules with " + "no passive segments"; + } + } + for (auto& segment : module->elementSegments) { + if (!segment->table.is()) { + Fatal() << "memory.copy lowering should only be run on modules with" + " no passive segments"; + } + } + + // In order to introduce a call to a function, it must first exist, so + // create an empty stub. + Builder b(*module); + + memCopyFuncName = Names::getValidFunctionName(*module, "__memory_copy"); + memFillFuncName = Names::getValidFunctionName(*module, "__memory_fill"); + auto memCopyFunc = b.makeFunction( + memCopyFuncName, + {{"dst", Type::i32}, {"src", Type::i32}, {"size", Type::i32}}, + Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), + {{"start", Type::i32}, + {"end", Type::i32}, + {"step", Type::i32}, + {"i", Type::i32}}); + memCopyFunc->body = b.makeBlock(); + module->addFunction(memCopyFunc.release()); + auto memFillFunc = b.makeFunction( + memFillFuncName, + {{"dst", Type::i32}, {"val", Type::i32}, {"size", Type::i32}}, + Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), + {}); + memFillFunc->body = b.makeBlock(); + module->addFunction(memFillFunc.release()); + + Super::run(module); + + if (needsMemoryCopy) { + createMemoryCopyFunc(module); + } else { + module->removeFunction(memCopyFuncName); + } + + if (needsMemoryFill) { + createMemoryFillFunc(module); + } else { + module->removeFunction(memFillFuncName); + } + module->features.disable(FeatureSet::BulkMemory); + } + + void createMemoryCopyFunc(Module* module) { + Builder b(*module); + Index dst = 0, src = 1, size = 2, start = 3, end = 4, step = 5, i = 6; + Name memory = module->memories.front()->name; + Block* body = b.makeBlock(); + // end = memory size in bytes + body->list.push_back( + b.makeLocalSet(end, + b.makeBinary(BinaryOp::MulInt32, + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize)))); + // if dst + size > memsize or src + size > memsize, then trap. + body->list.push_back(b.makeIf( + b.makeBinary(BinaryOp::OrInt32, + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(end, Type::i32)), + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(end, Type::i32))), + b.makeUnreachable())); + // start and end are the starting and past-the-end indexes + // if src < dest: start = size - 1, end = -1, step = -1 + // else: start = 0, end = size, step = 1 + body->list.push_back( + b.makeIf(b.makeBinary(BinaryOp::LtUInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(dst, Type::i32)), + b.makeBlock({ + b.makeLocalSet(start, + b.makeBinary(BinaryOp::SubInt32, + b.makeLocalGet(size, Type::i32), + b.makeConst(1))), + b.makeLocalSet(end, b.makeConst(-1U)), + b.makeLocalSet(step, b.makeConst(-1U)), + }), + b.makeBlock({ + b.makeLocalSet(start, b.makeConst(0)), + b.makeLocalSet(end, b.makeLocalGet(size, Type::i32)), + b.makeLocalSet(step, b.makeConst(1)), + }))); + // i = start + body->list.push_back(b.makeLocalSet(i, b.makeLocalGet(start, Type::i32))); + body->list.push_back(b.makeBlock( + "out", + b.makeLoop( + "copy", + b.makeBlock( + {// break if i == end + b.makeBreak("out", + nullptr, + b.makeBinary(BinaryOp::EqInt32, + b.makeLocalGet(i, Type::i32), + b.makeLocalGet(end, Type::i32))), + // dst[i] = src[i] + b.makeStore(1, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(i, Type::i32)), + b.makeLoad(1, + false, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(i, Type::i32)), + Type::i32, + memory), + Type::i32, + memory), + // i += step + b.makeLocalSet(i, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(i, Type::i32), + b.makeLocalGet(step, Type::i32))), + // loop + b.makeBreak("copy", nullptr)})))); + module->getFunction(memCopyFuncName)->body = body; + } + + void createMemoryFillFunc(Module* module) { + Builder b(*module); + Index dst = 0, val = 1, size = 2; + Name memory = module->memories.front()->name; + Block* body = b.makeBlock(); + + // if dst + size > memsize in bytes, then trap. + body->list.push_back( + b.makeIf(b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeBinary(BinaryOp::MulInt32, + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize))), + b.makeUnreachable())); + + body->list.push_back(b.makeBlock( + "out", + b.makeLoop( + "copy", + b.makeBlock( + {// break if size == 0 + b.makeBreak( + "out", + nullptr, + b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))), + // size-- + b.makeLocalSet(size, + b.makeBinary(BinaryOp::SubInt32, + b.makeLocalGet(size, Type::i32), + b.makeConst(1))), + // *(dst+size) = val + b.makeStore(1, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(val, Type::i32), + Type::i32, + memory), + b.makeBreak("copy", nullptr)})))); + module->getFunction(memFillFuncName)->body = body; + } + + void VisitTableCopy(TableCopy* curr) { + Fatal() << "table.copy instruction found. Memory copy lowering is not " + "designed to work on modules with bulk table operations"; + } + void VisitTableFill(TableCopy* curr) { + Fatal() << "table.fill instruction found. Memory copy lowering is not " + "designed to work on modules with bulk table operations"; + } +}; + +Pass* createMemoryCopyFillLoweringPass() { + return new MemoryCopyFillLowering(); +} + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index d55e22111df..fcefb89ad49 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -272,6 +272,10 @@ void PassRegistry::registerPasses() { registerPass("table64-lowering", "lower 64-bit tables 32-bit ones", createTable64LoweringPass); + registerPass("memory-copy-fill-lowering", + "Lower memory.copy and memory.fill to wasm mvp and disable " + "the bulk-memory feature.", + createMemoryCopyFillLoweringPass); registerPass("memory-packing", "packs memory into separate segments, skipping zeros", createMemoryPackingPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index c100cd2f954..121dbcd9de7 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -116,6 +116,7 @@ Pass* createOptimizeForJSPass(); Pass* createOutliningPass(); #endif Pass* createPickLoadSignsPass(); +Pass* createMemoryCopyFillLoweringPass(); Pass* createModAsyncifyAlwaysOnlyUnwindPass(); Pass* createModAsyncifyNeverUnwindPass(); Pass* createPoppifyPass(); diff --git a/test/lit/exec/memory-copy.wat b/test/lit/exec/memory-copy.wat new file mode 100644 index 00000000000..793f94656a2 --- /dev/null +++ b/test/lit/exec/memory-copy.wat @@ -0,0 +1,267 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +;; Tests derived from bulk-memory.wast spec tests + +;; memory.copy +(module + (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) + (memory 1 1) + (data (i32.const 0) "\aa\bb\cc\dd") + + (func $assert_load (param i32 i32) + (if (i32.ne (local.get 1) (i32.load8_u (local.get 0))) + (then (unreachable))) + ) + + (func $print_memory + (local $i i32) + (local.set 0 (i32.const 9)) + (loop $loop + (call $log-i32 (local.get 0)) + (call $log-i32 (i32.load8_u (local.get 0))) + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + (br_if $loop (i32.ne (local.get 0) (i32.const 17))) + ) + ) + + ;; non-overlapping copy + ;; CHECK: [fuzz-exec] calling test1 + ;; CHECK-NEXT: [LoggingExternalInterface logging 9] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 11] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 13] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 15] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 16] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 9] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 170] + ;; CHECK-NEXT: [LoggingExternalInterface logging 11] + ;; CHECK-NEXT: [LoggingExternalInterface logging 187] + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 13] + ;; CHECK-NEXT: [LoggingExternalInterface logging 221] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 15] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 16] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test1 (export "test1") + (call $print_memory) + (memory.copy (i32.const 10) (i32.const 0) (i32.const 4)) + (call $print_memory) + (call $assert_load (i32.const 9) (i32.const 0)) + (call $assert_load (i32.const 10) (i32.const 0xaa)) + (call $assert_load (i32.const 11) (i32.const 0xbb)) + (call $assert_load (i32.const 12) (i32.const 0xcc)) + (call $assert_load (i32.const 13) (i32.const 0xdd)) + (call $assert_load (i32.const 14) (i32.const 0)) + ) + ;; Overlap, src > dst + ;; CHECK: [fuzz-exec] calling test2 + ;; CHECK-NEXT: [LoggingExternalInterface logging 9] + ;; CHECK-NEXT: [LoggingExternalInterface logging 187] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 11] + ;; CHECK-NEXT: [LoggingExternalInterface logging 221] + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 13] + ;; CHECK-NEXT: [LoggingExternalInterface logging 221] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 15] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 16] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test2 (export "test2") + (memory.copy (i32.const 8) (i32.const 10) (i32.const 4)) + (call $print_memory) + (call $assert_load (i32.const 8) (i32.const 0xaa)) + (call $assert_load (i32.const 9) (i32.const 0xbb)) + (call $assert_load (i32.const 10) (i32.const 0xcc)) + (call $assert_load (i32.const 11) (i32.const 0xdd)) + (call $assert_load (i32.const 12) (i32.const 0xcc)) + (call $assert_load (i32.const 13) (i32.const 0xdd)) + ) + ;; Overlap, src < dst + ;; CHECK: [fuzz-exec] calling test3 + ;; CHECK-NEXT: [LoggingExternalInterface logging 9] + ;; CHECK-NEXT: [LoggingExternalInterface logging 187] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 11] + ;; CHECK-NEXT: [LoggingExternalInterface logging 170] + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 187] + ;; CHECK-NEXT: [LoggingExternalInterface logging 13] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 221] + ;; CHECK-NEXT: [LoggingExternalInterface logging 15] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 16] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test3 (export "test3") + (memory.copy (i32.const 10) (i32.const 7) (i32.const 6)) + (call $print_memory) + (call $assert_load (i32.const 10) (i32.const 0x0)) + (call $assert_load (i32.const 11) (i32.const 0xaa)) + (call $assert_load (i32.const 12) (i32.const 0xbb)) + (call $assert_load (i32.const 13) (i32.const 0xcc)) + (call $assert_load (i32.const 14) (i32.const 0xdd)) + (call $assert_load (i32.const 15) (i32.const 0xcc)) + (call $assert_load (i32.const 16) (i32.const 0)) + ) + ;; Overlap src < dst but size is OOB (but the address does not overflow) + ;; CHECK: [fuzz-exec] calling test4a + ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy] + (func $test4a (export "test4a") + (memory.copy (i32.const 13) (i32.const 11) (i32.const 0x10000)) + (call $print_memory) ;; not reached. + ) + ;; CHECK: [fuzz-exec] calling test4b + (func $test4b (export "test4b") + (call $assert_load (i32.const 10) (i32.const 0x0)) + (call $assert_load (i32.const 11) (i32.const 0xaa)) + (call $assert_load (i32.const 12) (i32.const 0xbb)) + (call $assert_load (i32.const 13) (i32.const 0xcc)) + (call $assert_load (i32.const 14) (i32.const 0xdd)) + (call $assert_load (i32.const 15) (i32.const 0xcc)) + (call $assert_load (i32.const 16) (i32.const 0)) + ) + ;; Copy ending at memory limit is ok + ;; CHECK: [fuzz-exec] calling test5 + (func $test5 (export "test5") + (memory.copy (i32.const 0xff00) (i32.const 0) (i32.const 0x100)) + (memory.copy (i32.const 0xfe00) (i32.const 0xff00) (i32.const 0x100)) + ) + ;; Succeed when copying 0 bytes at the end of the region + ;; CHECK: [fuzz-exec] calling test6 + (func $test6 (export "test6") + (memory.copy (i32.const 0x10000) (i32.const 0) (i32.const 0)) + (memory.copy (i32.const 0x0) (i32.const 0x10000) (i32.const 0)) + ) + ;; copying 0 bytes outside of the limit is not allowed. + ;; CHECK: [fuzz-exec] calling test7 + ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy] + (func $test7 (export "test7") + (memory.copy (i32.const 0x10001) (i32.const 0) (i32.const 0x0)) + ) + ;; CHECK: [fuzz-exec] calling test8 + ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy] + (func $test8 (export "test8") + (memory.copy (i32.const 0x0) (i32.const 0x10001) (i32.const 0)) + ) +) + + + +;; CHECK: [fuzz-exec] calling test1 +;; CHECK-NEXT: [LoggingExternalInterface logging 9] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 10] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 11] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 13] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 14] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 15] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 16] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 9] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 10] +;; CHECK-NEXT: [LoggingExternalInterface logging 170] +;; CHECK-NEXT: [LoggingExternalInterface logging 11] +;; CHECK-NEXT: [LoggingExternalInterface logging 187] +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 13] +;; CHECK-NEXT: [LoggingExternalInterface logging 221] +;; CHECK-NEXT: [LoggingExternalInterface logging 14] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 15] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 16] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test2 +;; CHECK-NEXT: [LoggingExternalInterface logging 9] +;; CHECK-NEXT: [LoggingExternalInterface logging 187] +;; CHECK-NEXT: [LoggingExternalInterface logging 10] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 11] +;; CHECK-NEXT: [LoggingExternalInterface logging 221] +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 13] +;; CHECK-NEXT: [LoggingExternalInterface logging 221] +;; CHECK-NEXT: [LoggingExternalInterface logging 14] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 15] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 16] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test3 +;; CHECK-NEXT: [LoggingExternalInterface logging 9] +;; CHECK-NEXT: [LoggingExternalInterface logging 187] +;; CHECK-NEXT: [LoggingExternalInterface logging 10] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 11] +;; CHECK-NEXT: [LoggingExternalInterface logging 170] +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 187] +;; CHECK-NEXT: [LoggingExternalInterface logging 13] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 14] +;; CHECK-NEXT: [LoggingExternalInterface logging 221] +;; CHECK-NEXT: [LoggingExternalInterface logging 15] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 16] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test4a +;; CHECK-NEXT: [trap unreachable] + +;; CHECK: [fuzz-exec] calling test4b + +;; CHECK: [fuzz-exec] calling test5 + +;; CHECK: [fuzz-exec] calling test6 + +;; CHECK: [fuzz-exec] calling test7 +;; CHECK-NEXT: [trap unreachable] + +;; CHECK: [fuzz-exec] calling test8 +;; CHECK-NEXT: [trap unreachable] +;; CHECK-NEXT: [fuzz-exec] comparing test1 +;; CHECK-NEXT: [fuzz-exec] comparing test2 +;; CHECK-NEXT: [fuzz-exec] comparing test3 +;; CHECK-NEXT: [fuzz-exec] comparing test4a +;; CHECK-NEXT: [fuzz-exec] comparing test4b +;; CHECK-NEXT: [fuzz-exec] comparing test5 +;; CHECK-NEXT: [fuzz-exec] comparing test6 +;; CHECK-NEXT: [fuzz-exec] comparing test7 +;; CHECK-NEXT: [fuzz-exec] comparing test8 diff --git a/test/lit/exec/memory-fill.wat b/test/lit/exec/memory-fill.wat new file mode 100644 index 00000000000..dc9aead6da9 --- /dev/null +++ b/test/lit/exec/memory-fill.wat @@ -0,0 +1,184 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +;; Tests derived from bulk-memory.wast spec tests + +;; memory.fill +(module + (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) + (memory 1) + + + (func $assert_load (param i32 i32) + (if (i32.ne (local.get 1) (i32.load8_u (local.get 0))) + (then (unreachable))) + ) + + (func $print_memory (param i32 i32) + (loop $loop + (call $log-i32 (local.get 0)) + (call $log-i32 (i32.load8_u (local.get 0))) + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + (br_if $loop (i32.ne (local.get 0) (local.get 1))) + ) + ) + ;; basic fill test + ;; CHECK: [fuzz-exec] calling test1 + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 255] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 255] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 255] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test1 (export "test1") + (call $print_memory (i32.const 0) (i32.const 5)) + (memory.fill (i32.const 1) (i32.const 0xff) (i32.const 3)) + (call $print_memory (i32.const 0) (i32.const 5)) + (call $assert_load (i32.const 0) (i32.const 0)) + (call $assert_load (i32.const 1) (i32.const 0xff)) + (call $assert_load (i32.const 2) (i32.const 0xff)) + (call $assert_load (i32.const 3) (i32.const 0xff)) + (call $assert_load (i32.const 4) (i32.const 0x0)) + ) + ;; Fill value is stored as a byte + ;; CHECK: [fuzz-exec] calling test2 + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 170] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 170] + (func $test2 (export "test2") + (memory.fill (i32.const 0) (i32.const 0xbbaa) (i32.const 2)) + (call $print_memory (i32.const 0) (i32.const 2)) + (call $assert_load (i32.const 0) (i32.const 0xaa)) + (call $assert_load (i32.const 1) (i32.const 0xaa)) + ) + ;; Fill all of memory + ;; CHECK: [fuzz-exec] calling test3 + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 5] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test3 (export "test3") + (memory.fill (i32.const 0) (i32.const 0) (i32.const 0x10000)) + ;; let's not print all of memory; just beyond what we filled before + (call $print_memory (i32.const 0) (i32.const 6)) + ) + ;; Succeed when writing 0 bytes at the end of the region + ;; CHECK: [fuzz-exec] calling test4 + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 5] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 6] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test4 (export "test4") + (memory.fill (i32.const 0x10000) (i32.const 0) (i32.const 0)) + ;; let's not print all of memory; just beyond what we filled before + (call $print_memory (i32.const 0) (i32.const 7)) + ) + ;; Writing 0 bytes outside of memory is not allowed + ;; CHECK: [fuzz-exec] calling test5 + ;; CHECK-NEXT: [trap out of bounds memory access in memory.fill] + (func $test5 (export "test5") + (memory.fill (i32.const 0x10001) (i32.const 0) (i32.const 0)) + ;; should not be reached + (call $print_memory (i32.const 0) (i32.const 6)) + ) + ;; again we do not test negative/overflowing addresses as the spec test does. +) +;; CHECK: [fuzz-exec] calling test1 +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 2] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 3] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 4] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 255] +;; CHECK-NEXT: [LoggingExternalInterface logging 2] +;; CHECK-NEXT: [LoggingExternalInterface logging 255] +;; CHECK-NEXT: [LoggingExternalInterface logging 3] +;; CHECK-NEXT: [LoggingExternalInterface logging 255] +;; CHECK-NEXT: [LoggingExternalInterface logging 4] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test2 +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 170] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 170] + +;; CHECK: [fuzz-exec] calling test3 +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 2] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 3] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 4] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 5] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test4 +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 2] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 3] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 4] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 5] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 6] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test5 +;; CHECK-NEXT: [trap unreachable] +;; CHECK-NEXT: [fuzz-exec] comparing test1 +;; CHECK-NEXT: [fuzz-exec] comparing test2 +;; CHECK-NEXT: [fuzz-exec] comparing test3 +;; CHECK-NEXT: [fuzz-exec] comparing test4 +;; CHECK-NEXT: [fuzz-exec] comparing test5 diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 50295b1f02d..908b381e84f 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -239,6 +239,10 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 0691d1c1839..7d173b08421 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -248,6 +248,10 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 89dcaa0280d..514f638d912 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -202,6 +202,10 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast new file mode 100644 index 00000000000..540237ff7b5 --- /dev/null +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -0,0 +1,168 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt --enable-bulk-memory %s --memory-copy-fill-lowering -S -o - | filecheck %s + +(module + (memory 0) + ;; CHECK: (type $0 (func (param i32 i32 i32))) + + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $memcpy (param $dst i32) (param $src i32) (param $size i32) + ;; CHECK-NEXT: (call $__memory_copy + ;; CHECK-NEXT: (local.get $dst) + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: (local.get $size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $memcpy (param $dst i32) (param $src i32) (param $size i32) + (memory.copy (local.get $dst) (local.get $src) (local.get $size)) + ) + ;; CHECK: (func $memfill (param $dst i32) (param $val i32) (param $size i32) + ;; CHECK-NEXT: (call $__memory_fill + ;; CHECK-NEXT: (local.get $dst) + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: (local.get $size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $memfill (param $dst i32) (param $val i32) (param $size i32) + (memory.fill (local.get $dst) (local.get $val) (local.get $size)) + ) +) +;; CHECK: (func $__memory_copy (param $dst i32) (param $src i32) (param $size i32) +;; CHECK-NEXT: (local $start i32) +;; CHECK-NEXT: (local $end i32) +;; CHECK-NEXT: (local $step i32) +;; CHECK-NEXT: (local $i i32) +;; CHECK-NEXT: (local.set $end +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (memory.size) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.or +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $end) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $end) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.lt_u +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (local.set $start +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $end +;; CHECK-NEXT: (i32.const -1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $step +;; CHECK-NEXT: (i32.const -1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (else +;; CHECK-NEXT: (local.set $start +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $end +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $step +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $i +;; CHECK-NEXT: (local.get $start) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $out +;; CHECK-NEXT: (loop $copy +;; CHECK-NEXT: (br_if $out +;; CHECK-NEXT: (i32.eq +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: (local.get $end) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store8 +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load8_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $i +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: (local.get $step) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (br $copy) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $__memory_fill (param $dst i32) (param $val i32) (param $size i32) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (memory.size) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $out +;; CHECK-NEXT: (loop $copy +;; CHECK-NEXT: (br_if $out +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $size +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store8 +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $val) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (br $copy) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) From 24b5bdfd1bf550dae38a7a990379b315d9f11bae Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 12 Nov 2024 20:52:55 -0500 Subject: [PATCH 125/622] Consolidate printing of function signatures (#7073) There were previously two separate code paths for printing function signatures, one for imported functions and one for declared functions. The only intended difference was that parameter names were printed for declared functions but not for imported functions. Reduce duplication by consolidating the code paths, and add support for printing names for imported function parameters that have them. Also fix a bug where empty names were printed as `$` rather than the correct `$""`. --- src/passes/Print.cpp | 99 ++++++++----------- src/support/name.cpp | 2 +- test/lit/basic/imported-params.wast | 22 +++++ .../limit-segments_disable-bulk-memory.txt | 2 +- 4 files changed, 67 insertions(+), 58 deletions(-) create mode 100644 test/lit/basic/imported-params.wast diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 854ea0b5b4d..5a5d0129996 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -382,7 +382,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { } // Module-level visitors - void handleSignature(HeapType curr, Name name = Name()); + void handleSignature(Function* curr, bool printImplicitNames = false); void visitExport(Export* curr); void emitImportHeader(Importable* curr); void visitGlobal(Global* curr); @@ -2883,41 +2883,51 @@ static bool requiresExplicitFuncType(HeapType type) { return type.isOpen() || type.isShared() || type.getRecGroup().size() > 1; } -void PrintSExpression::handleSignature(HeapType curr, Name name) { - Signature sig = curr.getSignature(); - o << "(func"; - if (name.is()) { - o << ' '; - name.print(o); - if ((currModule && currModule->features.hasGC()) || - requiresExplicitFuncType(curr)) { - o << " (type "; - printHeapType(curr) << ')'; - } +void PrintSExpression::handleSignature(Function* curr, + bool printImplicitNames) { + o << '('; + printMajor(o, "func "); + curr->name.print(o); + if ((currModule && currModule->features.hasGC()) || + requiresExplicitFuncType(curr->type)) { + o << " (type "; + printHeapType(curr->type) << ')'; } - if (sig.params.size() > 0) { - o << maybeSpace; - o << "(param "; - auto sep = ""; - for (auto type : sig.params) { - o << sep; - printType(type); - sep = " "; + bool inParam = false; + Index i = 0; + for (const auto& param : curr->getParams()) { + auto hasName = printImplicitNames || curr->hasLocalName(i); + if (hasName && inParam) { + o << ')' << maybeSpace; + inParam = false; + } else if (inParam) { + o << ' '; + } else { + o << maybeSpace; + } + if (!inParam) { + o << '('; + printMinor(o, "param "); + inParam = true; + } + if (hasName) { + printLocal(i, currFunction, o); + o << ' '; + } + printType(param); + if (hasName) { + o << ')'; + inParam = false; } + ++i; + } + if (inParam) { o << ')'; } - if (sig.results.size() > 0) { + if (curr->getResults() != Type::none) { o << maybeSpace; - o << "(result "; - auto sep = ""; - for (auto type : sig.results) { - o << sep; - printType(type); - sep = " "; - } - o << ')'; + printResultType(curr->getResults()); } - o << ")"; } void PrintSExpression::visitExport(Export* curr) { @@ -3014,8 +3024,8 @@ void PrintSExpression::visitImportedFunction(Function* curr) { lastPrintedLocation = std::nullopt; o << '('; emitImportHeader(curr); - handleSignature(curr->type, curr->name); - o << ')'; + handleSignature(curr); + o << "))"; o << maybeNewLine; } @@ -3027,30 +3037,7 @@ void PrintSExpression::visitDefinedFunction(Function* curr) { if (currFunction->prologLocation.size()) { printDebugLocation(*currFunction->prologLocation.begin()); } - o << '('; - printMajor(o, "func "); - curr->name.print(o); - if ((currModule && currModule->features.hasGC()) || - requiresExplicitFuncType(curr->type)) { - o << " (type "; - printHeapType(curr->type) << ')'; - } - if (curr->getParams().size() > 0) { - Index i = 0; - for (const auto& param : curr->getParams()) { - o << maybeSpace; - o << '('; - printMinor(o, "param "); - printLocal(i, currFunction, o); - o << ' '; - printType(param) << ')'; - ++i; - } - } - if (curr->getResults() != Type::none) { - o << maybeSpace; - printResultType(curr->getResults()); - } + handleSignature(curr, true); incIndent(); for (size_t i = curr->getVarIndexBase(); i < curr->getNumLocals(); i++) { doIndent(o, indent); diff --git a/src/support/name.cpp b/src/support/name.cpp index 4e53e3f832c..4c599defca0 100644 --- a/src/support/name.cpp +++ b/src/support/name.cpp @@ -46,7 +46,7 @@ std::ostream& Name::print(std::ostream& o) const { // TODO: This is not spec-compliant since the spec does not yet support // quoted identifiers and has a limited set of valid idchars. o << '$'; - if (std::all_of(str.begin(), str.end(), isIDChar)) { + if (size() >= 1 && std::all_of(str.begin(), str.end(), isIDChar)) { return o << str; } else { return String::printEscaped(o, str); diff --git a/test/lit/basic/imported-params.wast b/test/lit/basic/imported-params.wast new file mode 100644 index 00000000000..c81bf0870dd --- /dev/null +++ b/test/lit/basic/imported-params.wast @@ -0,0 +1,22 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Test that parameter names are preserved for imported functions + +;; RUN: wasm-opt %s -S -o - | filecheck %s + +(module + (import "" "" (func (param i32 i64) (param $x i32) (param $y i64) (param f32 f64))) + (import "" "" (func (param $x i32) (param f32 f64) (param $y i64))) + (import "" "" (func (param $"" i32))) +) +;; CHECK: (type $0 (func (param i32 i64 i32 i64 f32 f64))) + +;; CHECK: (type $1 (func (param i32 f32 f64 i64))) + +;; CHECK: (type $2 (func (param i32))) + +;; CHECK: (import "" "" (func $fimport$0 (param i32 i64) (param $x i32) (param $y i64) (param f32 f64))) + +;; CHECK: (import "" "" (func $fimport$1 (param $x i32) (param f32 f64) (param $y i64))) + +;; CHECK: (import "" "" (func $fimport$2 (param $"" i32))) diff --git a/test/passes/limit-segments_disable-bulk-memory.txt b/test/passes/limit-segments_disable-bulk-memory.txt index ed3388dc4a6..0cbd4a645ba 100644 --- a/test/passes/limit-segments_disable-bulk-memory.txt +++ b/test/passes/limit-segments_disable-bulk-memory.txt @@ -99998,5 +99998,5 @@ (data $99995 (i32.const 299985) "a") (data $99996 (i32.const 299988) "a") (data $99997 (i32.const 299991) "a") - (data $ (i32.const 299994) "a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a") + (data $"" (i32.const 299994) "a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a\00\00a") ) From 24c81824c1d80c398c6adb27decc28048c5e8029 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 13 Nov 2024 14:29:36 -0500 Subject: [PATCH 126/622] Read the names section first (#7074) Rather than back-patching names when we get to the names section in the binary reader, skip ahead to read the names section before anything else so we can use the final names right away. This is a prerequisite for using IRBuilder in the binary reader. The only functional change is that we now allow empty local names. Empty names are perfectly valid. --- src/wasm-binary.h | 46 +- src/wasm/wasm-binary.cpp | 651 +++++++++++++------------- test/lit/binary/empty-param-name.test | 9 +- 3 files changed, 347 insertions(+), 359 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 82f48708556..67c1dec968a 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1547,44 +1547,31 @@ class WasmBinaryReader { Name getNextLabel(); - // We read functions and globals before we know their names, so we need to - // backpatch the names later + // We read the names section first so we know in advance what names various + // elements should have. Store the information for use when building + // expressions. + std::unordered_map functionNames; + std::unordered_map> localNames; + std::unordered_map typeNames; + std::unordered_map> fieldNames; + std::unordered_map tableNames; + std::unordered_map memoryNames; + std::unordered_map globalNames; + std::unordered_map tagNames; + std::unordered_map dataNames; + std::unordered_map elemNames; - // at index i we have all refs to the function i - std::map> functionRefs; Function* currFunction = nullptr; // before we see a function (like global init expressions), there is no end of // function to check Index endOfFunction = -1; - // at index i we have all references to the table i - std::map> tableRefs; - - std::map elemTables; - - // at index i we have all references to the memory i - std::map> memoryRefs; - - // at index i we have all refs to the global i - std::map> globalRefs; - - // at index i we have all refs to the tag i - std::map> tagRefs; - - // at index i we have all refs to the data segment i - std::map> dataRefs; - - // at index i we have all refs to the element segment i - std::map> elemRefs; - // Throws a parsing error if we are not in a function context void requireFunctionContext(const char* error); void readFunctions(); void readVars(); - std::map exportIndices; - std::vector> exportOrder; void readExports(); // The strings in the strings section (which are referred to by StringConst). @@ -1647,12 +1634,13 @@ class WasmBinaryReader { Expression* popTuple(size_t numElems); Expression* popTypedExpression(Type type); - void validateBinary(); // validations that cannot be performed on the Module - void processNames(); + // validations that cannot be performed on the Module + void validateBinary(); size_t dataCount = 0; bool hasDataCount = false; + void createDataSegments(Index count); void readDataSegments(); void readDataSegmentCount(); @@ -1662,7 +1650,7 @@ class WasmBinaryReader { void readTags(); static Name escape(Name name); - void readNames(size_t); + void findAndReadNames(); void readFeatures(size_t); void readDylink(size_t); void readDylink0(size_t); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 7e1daf0b11d..57a01bb1ce2 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1780,10 +1780,16 @@ void WasmBinaryReader::read() { } } + // Skip ahead and read the name section so we know what names to use when we + // construct module elements. + if (debugInfo) { + findAndReadNames(); + } + readHeader(); readSourceMapHeader(); - // read sections until the end + // Read sections until the end while (more()) { uint8_t sectionCode = getInt8(); uint32_t payloadLen = getU32LEB(); @@ -1793,7 +1799,7 @@ void WasmBinaryReader::read() { auto oldPos = pos; - // note the section in the list of seen sections, as almost no sections can + // Note the section in the list of seen sections, as almost no sections can // appear more than once, and verify those that shouldn't do not. if (sectionCode != BinaryConsts::Section::Custom && !seenSections.insert(sectionCode).second) { @@ -1870,8 +1876,26 @@ void WasmBinaryReader::read() { } } + // Set local names for imported and declared functions. + for (auto& [index, locals] : localNames) { + if (index >= wasm.functions.size()) { + std::cerr << "warning: function index out of bounds in name section: " + "locals at index " + << index << '\n'; + continue; + } + for (auto& [local, name] : locals) { + if (local >= wasm.functions[index]->getNumLocals()) { + std::cerr << "warning: local index out of bounds in name section: " + << name << " at index " << local << " in function " << index + << '\n'; + continue; + } + wasm.functions[index]->setLocalName(local, name); + } + } + validateBinary(); - processNames(); } void WasmBinaryReader::readCustomSection(size_t payloadLen) { @@ -1883,11 +1907,8 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { } payloadLen -= read; if (sectionName.equals(BinaryConsts::CustomSections::Name)) { - if (debugInfo) { - readNames(payloadLen); - } else { - pos += payloadLen; - } + // We already read the name section before anything else. + pos += payloadLen; } else if (sectionName.equals(BinaryConsts::CustomSections::TargetFeatures)) { readFeatures(payloadLen); } else if (sectionName.equals(BinaryConsts::CustomSections::Dylink)) { @@ -2233,16 +2254,47 @@ void WasmBinaryReader::readHeader() { } } -void WasmBinaryReader::readStart() { startIndex = getU32LEB(); } +void WasmBinaryReader::readStart() { + startIndex = getU32LEB(); + wasm.start = getFunctionName(startIndex); +} static Name makeName(std::string prefix, size_t counter) { return Name(prefix + std::to_string(counter)); } +// Look up a name from the names section or use a validated version of the +// provided name. Return the name and whether it is explicit in the input. +static std::pair +getOrMakeName(const std::unordered_map& nameMap, + Index i, + Name name, + std::unordered_set& usedNames) { + if (auto it = nameMap.find(i); it != nameMap.end()) { + return {it->second, true}; + } else { + auto valid = Names::getValidNameGivenExisting(name, usedNames); + usedNames.insert(valid); + return {valid, false}; + } +} + void WasmBinaryReader::readMemories() { auto num = getU32LEB(); + auto numImports = wasm.memories.size(); + std::unordered_set usedNames; + for (auto& [index, name] : memoryNames) { + if (index >= num + numImports) { + std::cerr << "warning: memory index out of bounds in name section: " + << name << " at index " << index << '\n'; + } + usedNames.insert(name); + } for (size_t i = 0; i < num; i++) { - auto memory = Builder::makeMemory(makeName("", i)); + auto [name, isExplicit] = + getOrMakeName(memoryNames, numImports + i, makeName("", i), usedNames); + auto memory = Builder::makeMemory(name); + memory->hasExplicitName = isExplicit; getResizableLimits(memory->initial, memory->max, memory->shared, @@ -2423,6 +2475,39 @@ void WasmBinaryReader::readTypes() { for (Index i = 0; i < types.size(); ++i) { wasm.typeIndices.insert({types[i], i}); } + + // Assign names from the names section. + for (auto& [index, name] : typeNames) { + if (index >= types.size()) { + std::cerr << "warning: type index out of bounds in name section: " << name + << " at index " << index << '\n'; + continue; + } + wasm.typeNames[types[index]].name = name; + } + for (auto& [index, fields] : fieldNames) { + if (index >= types.size()) { + std::cerr + << "warning: type index out of bounds in name section: fields at index " + << index << '\n'; + continue; + } + if (!types[index].isStruct()) { + std::cerr << "warning: field names applied to non-struct type at index " + << index << '\n'; + continue; + } + auto& names = wasm.typeNames[types[index]].fieldNames; + for (auto& [field, name] : fields) { + if (field >= types[index].getStruct().fields.size()) { + std::cerr << "warning: field index out of bounds in name section: " + << name << " at index " << field << " in type " << index + << '\n'; + continue; + } + names[field] = name; + } + } } Name WasmBinaryReader::getFunctionName(Index index) { @@ -2513,11 +2598,8 @@ void WasmBinaryReader::getResizableLimits(Address& initial, void WasmBinaryReader::readImports() { size_t num = getU32LEB(); Builder builder(wasm); - size_t tableCounter = 0; - size_t memoryCounter = 0; - size_t functionCounter = 0; - size_t globalCounter = 0; - size_t tagCounter = 0; + std::unordered_set usedFunctionNames, usedTableNames, usedMemoryNames, + usedGlobalNames, usedTagNames; for (size_t i = 0; i < num; i++) { auto module = getInlineString(); auto base = getInlineString(); @@ -2527,7 +2609,11 @@ void WasmBinaryReader::readImports() { // could occur later due to the names section. switch (kind) { case ExternalKind::Function: { - Name name = makeName("fimport$", functionCounter++); + auto [name, isExplicit] = + getOrMakeName(functionNames, + wasm.functions.size(), + makeName("fimport$", wasm.functions.size()), + usedFunctionNames); auto index = getU32LEB(); functionTypes.push_back(getTypeByIndex(index)); auto type = getTypeByIndex(index); @@ -2537,13 +2623,20 @@ void WasmBinaryReader::readImports() { "'s type must be a signature. Given: " + type.toString()); } auto curr = builder.makeFunction(name, type, {}); + curr->hasExplicitName = isExplicit; curr->module = module; curr->base = base; wasm.addFunction(std::move(curr)); break; } case ExternalKind::Table: { - auto table = builder.makeTable(makeName("timport$", tableCounter++)); + auto [name, isExplicit] = + getOrMakeName(tableNames, + wasm.tables.size(), + makeName("timport$", wasm.tables.size()), + usedTableNames); + auto table = builder.makeTable(name); + table->hasExplicitName = isExplicit; table->module = module; table->base = base; table->type = getType(); @@ -2562,7 +2655,13 @@ void WasmBinaryReader::readImports() { break; } case ExternalKind::Memory: { - auto memory = builder.makeMemory(makeName("mimport$", memoryCounter++)); + auto [name, isExplicit] = + getOrMakeName(memoryNames, + wasm.memories.size(), + makeName("mimport$", wasm.memories.size()), + usedMemoryNames); + auto memory = builder.makeMemory(name); + memory->hasExplicitName = isExplicit; memory->module = module; memory->base = base; getResizableLimits(memory->initial, @@ -2574,26 +2673,37 @@ void WasmBinaryReader::readImports() { break; } case ExternalKind::Global: { + auto [name, isExplicit] = + getOrMakeName(globalNames, + wasm.globals.size(), + makeName("gimport$", wasm.globals.size()), + usedGlobalNames); auto type = getConcreteType(); auto mutable_ = getU32LEB(); if (mutable_ & ~1) { throwError("Global mutability must be 0 or 1"); } auto curr = - builder.makeGlobal(makeName("gimport$", globalCounter++), + builder.makeGlobal(name, type, nullptr, mutable_ ? Builder::Mutable : Builder::Immutable); + curr->hasExplicitName = isExplicit; curr->module = module; curr->base = base; wasm.addGlobal(std::move(curr)); break; } case ExternalKind::Tag: { - Name name = makeName("eimport$", tagCounter++); + auto [name, isExplicit] = + getOrMakeName(tagNames, + wasm.tags.size(), + makeName("eimport$", wasm.tags.size()), + usedTagNames); getInt8(); // Reserved 'attribute' field auto index = getU32LEB(); auto curr = builder.makeTag(name, getSignatureByTypeIndex(index)); + curr->hasExplicitName = isExplicit; curr->module = module; curr->base = base; wasm.addTag(std::move(curr)); @@ -2620,14 +2730,26 @@ void WasmBinaryReader::requireFunctionContext(const char* error) { void WasmBinaryReader::readFunctionSignatures() { size_t num = getU32LEB(); + auto numImports = wasm.functions.size(); + std::unordered_set usedNames; + for (auto& [index, name] : functionNames) { + if (index >= num + numImports) { + std::cerr << "warning: function index out of bounds in name section: " + << name << " at index " << index << '\n'; + } + usedNames.insert(name); + } for (size_t i = 0; i < num; i++) { + auto [name, isExplicit] = + getOrMakeName(functionNames, numImports + i, makeName("", i), usedNames); auto index = getU32LEB(); HeapType type = getTypeByIndex(index); functionTypes.push_back(type); // Check that the type is a signature. getSignatureByTypeIndex(index); - wasm.addFunction( - Builder(wasm).makeFunction(makeName("", i), type, {}, nullptr)); + auto func = Builder(wasm).makeFunction(name, type, {}, nullptr); + func->hasExplicitName = isExplicit; + wasm.addFunction(std::move(func)); } } @@ -2779,9 +2901,28 @@ void WasmBinaryReader::readExports() { throwError("duplicate export name"); } curr->kind = (ExternalKind)getU32LEB(); + auto* ex = wasm.addExport(std::move(curr)); auto index = getU32LEB(); - exportIndices[curr.get()] = index; - exportOrder.push_back(std::move(curr)); + switch (ex->kind) { + case ExternalKind::Function: + ex->value = getFunctionName(index); + continue; + case ExternalKind::Table: + ex->value = getTableName(index); + continue; + case ExternalKind::Memory: + ex->value = getMemoryName(index); + continue; + case ExternalKind::Global: + ex->value = getGlobalName(index); + continue; + case ExternalKind::Tag: + ex->value = getTagName(index); + continue; + case ExternalKind::Invalid: + break; + } + throwError("invalid export kind"); } } @@ -3052,18 +3193,28 @@ void WasmBinaryReader::readStrings() { void WasmBinaryReader::readGlobals() { size_t num = getU32LEB(); + auto numImports = wasm.globals.size(); + std::unordered_set usedNames; + for (auto& [index, name] : globalNames) { + if (index >= num + numImports) { + std::cerr << "warning: global index out of bounds in name section: " + << name << " at index " << index << '\n'; + } + usedNames.insert(name); + } for (size_t i = 0; i < num; i++) { + auto [name, isExplicit] = getOrMakeName( + globalNames, numImports + i, makeName("global$", i), usedNames); auto type = getConcreteType(); auto mutable_ = getU32LEB(); if (mutable_ & ~1) { throwError("Global mutability must be 0 or 1"); } auto* init = readExpression(); - wasm.addGlobal( - Builder::makeGlobal(makeName("global$", i), - type, - init, - mutable_ ? Builder::Mutable : Builder::Immutable)); + auto global = Builder::makeGlobal( + name, type, init, mutable_ ? Builder::Mutable : Builder::Immutable); + global->hasExplicitName = isExplicit; + wasm.addGlobal(std::move(global)); } } @@ -3255,77 +3406,22 @@ void WasmBinaryReader::validateBinary() { } } -void WasmBinaryReader::processNames() { - // now that we have names, apply things - - if (startIndex != static_cast(-1)) { - wasm.start = getFunctionName(startIndex); - } - - for (auto& curr : exportOrder) { - auto index = exportIndices[curr.get()]; - switch (curr->kind) { - case ExternalKind::Function: { - curr->value = getFunctionName(index); - break; - } - case ExternalKind::Table: - curr->value = getTableName(index); - break; - case ExternalKind::Memory: - curr->value = getMemoryName(index); - break; - case ExternalKind::Global: - curr->value = getGlobalName(index); - break; - case ExternalKind::Tag: - curr->value = getTagName(index); - break; - default: - throwError("bad export kind"); - } - wasm.addExport(std::move(curr)); - } - - for (auto& [index, refs] : functionRefs) { - for (auto* ref : refs) { - *ref = getFunctionName(index); - } - } - for (auto& [index, refs] : tableRefs) { - for (auto* ref : refs) { - *ref = getTableName(index); - } - } - for (auto& [index, refs] : memoryRefs) { - for (auto ref : refs) { - *ref = getMemoryName(index); - } - } - for (auto& [index, refs] : globalRefs) { - for (auto* ref : refs) { - *ref = getGlobalName(index); - } - } - for (auto& [index, refs] : tagRefs) { - for (auto* ref : refs) { - *ref = getTagName(index); - } - } - for (auto& [index, refs] : dataRefs) { - for (auto* ref : refs) { - *ref = getDataName(index); +void WasmBinaryReader::createDataSegments(Index count) { + std::unordered_set usedNames; + for (auto& [index, name] : dataNames) { + if (index >= count) { + std::cerr << "warning: data index out of bounds in name section: " << name + << " at index " << index << '\n'; } + usedNames.insert(name); } - for (auto& [index, refs] : elemRefs) { - for (auto* ref : refs) { - *ref = getElemName(index); - } + for (size_t i = 0; i < count; ++i) { + auto [name, isExplicit] = + getOrMakeName(dataNames, i, makeName("", i), usedNames); + auto curr = Builder::makeDataSegment(name); + curr->hasExplicitName = isExplicit; + wasm.addDataSegment(std::move(curr)); } - - // Everything now has its proper name. - - wasm.updateMaps(); } void WasmBinaryReader::readDataSegmentCount() { @@ -3333,11 +3429,7 @@ void WasmBinaryReader::readDataSegmentCount() { dataCount = getU32LEB(); // Eagerly create the data segments so they are available during parsing of // the code section. - for (size_t i = 0; i < dataCount; ++i) { - auto curr = Builder::makeDataSegment(); - curr->setName(Name::fromInt(i), false); - wasm.addDataSegment(std::move(curr)); - } + createDataSegments(dataCount); } void WasmBinaryReader::readDataSegments() { @@ -3348,11 +3440,7 @@ void WasmBinaryReader::readDataSegments() { } } else { // We haven't already created the data segments, so create them now. - for (size_t i = 0; i < num; ++i) { - auto curr = Builder::makeDataSegment(); - curr->setName(Name::fromInt(i), false); - wasm.addDataSegment(std::move(curr)); - } + createDataSegments(num); } assert(wasm.dataSegments.size() == num); for (size_t i = 0; i < num; i++) { @@ -3371,7 +3459,7 @@ void WasmBinaryReader::readDataSegments() { if (flags & BinaryConsts::HasIndex) { memIdx = getU32LEB(); } - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); curr->offset = readExpression(); } auto size = getU32LEB(); @@ -3381,14 +3469,25 @@ void WasmBinaryReader::readDataSegments() { } void WasmBinaryReader::readTableDeclarations() { - auto numTables = getU32LEB(); - - for (size_t i = 0; i < numTables; i++) { + auto num = getU32LEB(); + auto numImports = wasm.tables.size(); + std::unordered_set usedNames; + for (auto& [index, name] : tableNames) { + if (index >= num + numImports) { + std::cerr << "warning: table index out of bounds in name section: " + << name << " at index " << index << '\n'; + } + usedNames.insert(name); + } + for (size_t i = 0; i < num; i++) { + auto [name, isExplicit] = + getOrMakeName(tableNames, numImports + i, makeName("", i), usedNames); auto elemType = getType(); if (!elemType.isRef()) { throwError("Table type must be a reference type"); } - auto table = Builder::makeTable(makeName("", i), elemType); + auto table = Builder::makeTable(name, elemType); + table->hasExplicitName = isExplicit; bool is_shared; getResizableLimits(table->initial, table->max, @@ -3403,11 +3502,21 @@ void WasmBinaryReader::readTableDeclarations() { } void WasmBinaryReader::readElementSegments() { - auto numSegments = getU32LEB(); - if (numSegments >= Table::kMaxSize) { + auto num = getU32LEB(); + if (num >= Table::kMaxSize) { throwError("Too many segments"); } - for (size_t i = 0; i < numSegments; i++) { + std::unordered_set usedNames; + for (auto& [index, name] : elemNames) { + if (index >= num) { + std::cerr << "warning: elem index out of bounds in name section: " << name + << " at index " << index << '\n'; + } + usedNames.insert(name); + } + for (size_t i = 0; i < num; i++) { + auto [name, isExplicit] = + getOrMakeName(elemNames, i, makeName("", i), usedNames); auto flags = getU32LEB(); bool isPassive = (flags & BinaryConsts::IsPassive) != 0; bool hasTableIdx = !isPassive && ((flags & BinaryConsts::HasIndex) != 0); @@ -3431,7 +3540,7 @@ void WasmBinaryReader::readElementSegments() { } auto segment = std::make_unique(); - segment->setName(Name::fromInt(i), false); + segment->setName(name, isExplicit); if (!isPassive) { Index tableIdx = 0; @@ -3468,9 +3577,7 @@ void WasmBinaryReader::readElementSegments() { for (Index j = 0; j < size; j++) { Index index = getU32LEB(); auto sig = getTypeByFunctionIndex(index); - // Use a placeholder name for now - auto* refFunc = Builder(wasm).makeRefFunc(Name::fromInt(index), sig); - functionRefs[index].push_back(&refFunc->func); + auto* refFunc = Builder(wasm).makeRefFunc(getFunctionName(index), sig); segmentData.push_back(refFunc); } } @@ -3480,12 +3587,24 @@ void WasmBinaryReader::readElementSegments() { } void WasmBinaryReader::readTags() { - size_t numTags = getU32LEB(); - for (size_t i = 0; i < numTags; i++) { + size_t num = getU32LEB(); + auto numImports = wasm.tags.size(); + std::unordered_set usedNames; + for (auto& [index, name] : tagNames) { + if (index >= num + numImports) { + std::cerr << "warning: tag index out of bounds in name section: " << name + << " at index " << index << '\n'; + } + usedNames.insert(name); + } + for (size_t i = 0; i < num; i++) { getInt8(); // Reserved 'attribute' field + auto [name, isExplicit] = + getOrMakeName(tagNames, numImports + i, makeName("tag$", i), usedNames); auto typeIndex = getU32LEB(); - wasm.addTag(Builder::makeTag(makeName("tag$", i), - getSignatureByTypeIndex(typeIndex))); + auto tag = Builder::makeTag(name, getSignatureByTypeIndex(typeIndex)); + tag->hasExplicitName = isExplicit; + wasm.addTag(std::move(tag)); } } @@ -3531,19 +3650,6 @@ namespace { // Performs necessary processing of names from the name section before using // them. Specifically it escapes and deduplicates them. -// -// Deduplication is not trivial, since we can't only consider things in the name -// section itself. The issue is that we have already given everything a -// temporary name. Consider if we gave these two temp names: -// -// $foo$0 -// $foo$1 -// -// and imagine that the first appears in the name section, where it is given the -// the name $foo$1, and the second does not appear in the name section. In that -// case, we'd rename the second to the same name as the first. If we left things -// there that would be invalid, so we need to pick another temp name for the -// second item to resolve that. class NameProcessor { public: // Returns a unique, escaped name. Notes that name for the items to follow to @@ -3552,20 +3658,6 @@ class NameProcessor { return deduplicate(WasmBinaryReader::escape(name)); } - // After processing the names section entries, which set explicit names, we - // also handle the remaining items here, which handles the corner case - // described above. - // - // TODO: This handles vectors of Named objects; we should also do this for - // local names and type names etc. - template void deduplicateUnexplicitlyNamed(std::vector& vec) { - for (auto& x : vec) { - if (!x->hasExplicitName) { - x->name = deduplicate(x->name); - } - } - } - private: std::unordered_set usedNames; @@ -3578,8 +3670,33 @@ class NameProcessor { } // anonymous namespace -void WasmBinaryReader::readNames(size_t payloadLen) { - auto sectionPos = pos; +void WasmBinaryReader::findAndReadNames() { + // Find the names section. Skip the magic and version. + assert(pos == 0); + getInt32(); + getInt32(); + Index payloadLen, sectionPos; + bool found = false; + while (more()) { + uint8_t sectionCode = getInt8(); + payloadLen = getU32LEB(); + sectionPos = pos; + if (sectionCode == BinaryConsts::Section::Custom) { + auto sectionName = getInlineString(); + if (sectionName.equals(BinaryConsts::CustomSections::Name)) { + found = true; + break; + } + } + pos = sectionPos + payloadLen; + } + if (!found) { + // No names section to read. + pos = 0; + return; + } + + // Read the names. uint32_t lastType = 0; while (pos < sectionPos + payloadLen) { auto nameType = getU32LEB(); @@ -3600,51 +3717,19 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto index = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - if (index < wasm.functions.size()) { - wasm.functions[index]->setExplicitName(name); - } else { - std::cerr << "warning: function index out of bounds in name section, " - "function subsection: " - << std::string(rawName.str) << " at index " - << std::to_string(index) << std::endl; - } + functionNames[index] = name; } - processor.deduplicateUnexplicitlyNamed(wasm.functions); } else if (nameType == Subsection::NameLocal) { auto numFuncs = getU32LEB(); for (size_t i = 0; i < numFuncs; i++) { auto funcIndex = getU32LEB(); - Function* func = nullptr; - if (funcIndex < wasm.functions.size()) { - func = wasm.functions[funcIndex].get(); - } else { - std::cerr - << "warning: function index out of bounds in name section, local " - "subsection: " - << std::to_string(funcIndex) << std::endl; - } auto numLocals = getU32LEB(); NameProcessor processor; for (size_t j = 0; j < numLocals; j++) { auto localIndex = getU32LEB(); - auto rawLocalName = getInlineString(); - if (!func) { - continue; // read and discard in case of prior error - } - auto localName = processor.process(rawLocalName); - if (localName.size() == 0) { - std::cerr << "warning: empty local name at index " - << std::to_string(localIndex) << " in function " - << std::string(func->name.str) << std::endl; - } else if (localIndex < func->getNumLocals()) { - func->localNames[localIndex] = localName; - } else { - std::cerr << "warning: local index out of bounds in name " - "section, local subsection: " - << std::string(rawLocalName.str) << " at index " - << std::to_string(localIndex) << " in function " - << std::string(func->name.str) << std::endl; - } + auto rawName = getInlineString(); + auto name = processor.process(rawName); + localNames[funcIndex][localIndex] = name; } } } else if (nameType == Subsection::NameType) { @@ -3654,14 +3739,7 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto index = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - if (index < types.size()) { - wasm.typeNames[types[index]].name = name; - } else { - std::cerr << "warning: type index out of bounds in name section, " - "type subsection: " - << std::string(rawName.str) << " at index " - << std::to_string(index) << std::endl; - } + typeNames[index] = name; } } else if (nameType == Subsection::NameTable) { auto num = getU32LEB(); @@ -3670,23 +3748,8 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto index = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - - if (index < wasm.tables.size()) { - auto* table = wasm.tables[index].get(); - for (auto& segment : wasm.elementSegments) { - if (segment->table == table->name) { - segment->table = name; - } - } - table->setExplicitName(name); - } else { - std::cerr << "warning: table index out of bounds in name section, " - "table subsection: " - << std::string(rawName.str) << " at index " - << std::to_string(index) << std::endl; - } + tableNames[index] = name; } - processor.deduplicateUnexplicitlyNamed(wasm.tables); } else if (nameType == Subsection::NameElem) { auto num = getU32LEB(); NameProcessor processor; @@ -3694,17 +3757,8 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto index = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - - if (index < wasm.elementSegments.size()) { - wasm.elementSegments[index]->setExplicitName(name); - } else { - std::cerr << "warning: elem index out of bounds in name section, " - "elem subsection: " - << std::string(rawName.str) << " at index " - << std::to_string(index) << std::endl; - } + elemNames[index] = name; } - processor.deduplicateUnexplicitlyNamed(wasm.elementSegments); } else if (nameType == Subsection::NameMemory) { auto num = getU32LEB(); NameProcessor processor; @@ -3712,16 +3766,8 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto index = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - if (index < wasm.memories.size()) { - wasm.memories[index]->setExplicitName(name); - } else { - std::cerr << "warning: memory index out of bounds in name section, " - "memory subsection: " - << std::string(rawName.str) << " at index " - << std::to_string(index) << std::endl; - } + memoryNames[index] = name; } - processor.deduplicateUnexplicitlyNamed(wasm.memories); } else if (nameType == Subsection::NameData) { auto num = getU32LEB(); NameProcessor processor; @@ -3729,16 +3775,8 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto index = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - if (index < wasm.dataSegments.size()) { - wasm.dataSegments[index]->setExplicitName(name); - } else { - std::cerr << "warning: data index out of bounds in name section, " - "data subsection: " - << std::string(rawName.str) << " at index " - << std::to_string(index) << std::endl; - } + dataNames[index] = name; } - processor.deduplicateUnexplicitlyNamed(wasm.dataSegments); } else if (nameType == Subsection::NameGlobal) { auto num = getU32LEB(); NameProcessor processor; @@ -3746,16 +3784,8 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto index = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - if (index < wasm.globals.size()) { - wasm.globals[index]->setExplicitName(name); - } else { - std::cerr << "warning: global index out of bounds in name section, " - "global subsection: " - << std::string(rawName.str) << " at index " - << std::to_string(index) << std::endl; - } + globalNames[index] = name; } - processor.deduplicateUnexplicitlyNamed(wasm.globals); } else if (nameType == Subsection::NameField) { auto numTypes = getU32LEB(); for (size_t i = 0; i < numTypes; i++) { @@ -3771,9 +3801,7 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto fieldIndex = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - if (validType) { - wasm.typeNames[types[typeIndex]].fieldNames[fieldIndex] = name; - } + fieldNames[typeIndex][fieldIndex] = name; } } } else if (nameType == Subsection::NameTag) { @@ -3783,16 +3811,8 @@ void WasmBinaryReader::readNames(size_t payloadLen) { auto index = getU32LEB(); auto rawName = getInlineString(); auto name = processor.process(rawName); - if (index < wasm.tags.size()) { - wasm.tags[index]->setExplicitName(name); - } else { - std::cerr << "warning: tag index out of bounds in name section, " - "tag subsection: " - << std::string(rawName.str) << " at index " - << std::to_string(index) << std::endl; - } + tagNames[index] = name; } - processor.deduplicateUnexplicitlyNamed(wasm.tags); } else { std::cerr << "warning: unknown name subsection with id " << std::to_string(nameType) << " at " << pos << std::endl; @@ -3805,6 +3825,9 @@ void WasmBinaryReader::readNames(size_t payloadLen) { if (pos != sectionPos + payloadLen) { throwError("bad names section position change"); } + + // Reset the position; we were just reading ahead. + pos = 0; } void WasmBinaryReader::readFeatures(size_t payloadLen) { @@ -4614,8 +4637,7 @@ void WasmBinaryReader::visitCall(Call* curr) { curr->operands[num - i - 1] = popNonVoidExpression(); } curr->type = sig.results; - // We don't know function names yet. - functionRefs[index].push_back(&curr->target); + curr->target = getFunctionName(index); curr->finalize(); } @@ -4630,8 +4652,7 @@ void WasmBinaryReader::visitCallIndirect(CallIndirect* curr) { for (size_t i = 0; i < num; i++) { curr->operands[num - i - 1] = popNonVoidExpression(); } - // Defer setting the table name for later, when we know it. - tableRefs[tableIdx].push_back(&curr->table); + curr->table = getTableName(tableIdx); curr->finalize(); } @@ -4668,7 +4689,6 @@ void WasmBinaryReader::visitGlobalGet(GlobalGet* curr) { auto* global = wasm.globals[index].get(); curr->name = global->name; curr->type = global->type; - globalRefs[index].push_back(&curr->name); // we don't know the final name yet } void WasmBinaryReader::visitGlobalSet(GlobalSet* curr) { @@ -4678,7 +4698,6 @@ void WasmBinaryReader::visitGlobalSet(GlobalSet* curr) { } curr->name = wasm.globals[index]->name; curr->value = popNonVoidExpression(); - globalRefs[index].push_back(&curr->name); // we don't know the final name yet curr->finalize(); } @@ -4853,7 +4872,7 @@ bool WasmBinaryReader::maybeVisitLoad( curr->isAtomic = prefix == BinaryConsts::AtomicPrefix; Index memIdx = readMemoryAccess(curr->align, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); curr->ptr = popNonVoidExpression(); curr->finalize(); out = curr; @@ -4971,7 +4990,7 @@ bool WasmBinaryReader::maybeVisitStore( curr->isAtomic = prefix == BinaryConsts::AtomicPrefix; Index memIdx = readMemoryAccess(curr->align, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); curr->value = popNonVoidExpression(); curr->ptr = popNonVoidExpression(); curr->finalize(); @@ -5031,7 +5050,7 @@ bool WasmBinaryReader::maybeVisitAtomicRMW(Expression*& out, uint8_t code) { Address readAlign; Index memIdx = readMemoryAccess(readAlign, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); if (readAlign != curr->bytes) { throwError("Align of AtomicRMW must match size"); } @@ -5082,7 +5101,7 @@ bool WasmBinaryReader::maybeVisitAtomicCmpxchg(Expression*& out, uint8_t code) { Address readAlign; Index memIdx = readMemoryAccess(readAlign, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); if (readAlign != curr->bytes) { throwError("Align of AtomicCpxchg must match size"); } @@ -5117,7 +5136,7 @@ bool WasmBinaryReader::maybeVisitAtomicWait(Expression*& out, uint8_t code) { curr->ptr = popNonVoidExpression(); Address readAlign; Index memIdx = readMemoryAccess(readAlign, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); if (readAlign != curr->expectedType.getByteSize()) { throwError("Align of AtomicWait must match size"); } @@ -5137,7 +5156,7 @@ bool WasmBinaryReader::maybeVisitAtomicNotify(Expression*& out, uint8_t code) { curr->ptr = popNonVoidExpression(); Address readAlign; Index memIdx = readMemoryAccess(readAlign, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); if (readAlign != curr->type.getByteSize()) { throwError("Align of AtomicNotify must match size"); } @@ -5464,9 +5483,9 @@ bool WasmBinaryReader::maybeVisitMemoryInit(Expression*& out, uint32_t code) { curr->offset = popNonVoidExpression(); curr->dest = popNonVoidExpression(); Index segIdx = getU32LEB(); - dataRefs[segIdx].push_back(&curr->segment); + curr->segment = getDataName(segIdx); Index memIdx = getU32LEB(); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); curr->finalize(); out = curr; return true; @@ -5478,7 +5497,7 @@ bool WasmBinaryReader::maybeVisitDataDrop(Expression*& out, uint32_t code) { } auto* curr = allocator.alloc(); Index segIdx = getU32LEB(); - dataRefs[segIdx].push_back(&curr->segment); + curr->segment = getDataName(segIdx); curr->finalize(); out = curr; return true; @@ -5495,8 +5514,8 @@ bool WasmBinaryReader::maybeVisitMemoryCopy(Expression*& out, uint32_t code) { Index destIdx = getU32LEB(); Index sourceIdx = getU32LEB(); curr->finalize(); - memoryRefs[destIdx].push_back(&curr->destMemory); - memoryRefs[sourceIdx].push_back(&curr->sourceMemory); + curr->destMemory = getMemoryName(destIdx); + curr->sourceMemory = getMemoryName(sourceIdx); out = curr; return true; } @@ -5511,7 +5530,7 @@ bool WasmBinaryReader::maybeVisitMemoryFill(Expression*& out, uint32_t code) { curr->dest = popNonVoidExpression(); Index memIdx = getU32LEB(); curr->finalize(); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); out = curr; return true; } @@ -5528,9 +5547,8 @@ bool WasmBinaryReader::maybeVisitTableSize(Expression*& out, uint32_t code) { if (getTable(tableIdx)->is64()) { curr->type = Type::i64; } + curr->table = getTableName(tableIdx); curr->finalize(); - // Defer setting the table name for later, when we know it. - tableRefs[tableIdx].push_back(&curr->table); out = curr; return true; } @@ -5549,9 +5567,8 @@ bool WasmBinaryReader::maybeVisitTableGrow(Expression*& out, uint32_t code) { if (getTable(tableIdx)->is64()) { curr->type = Type::i64; } + curr->table = getTableName(tableIdx); curr->finalize(); - // Defer setting the table name for later, when we know it. - tableRefs[tableIdx].push_back(&curr->table); out = curr; return true; } @@ -5568,7 +5585,7 @@ bool WasmBinaryReader::maybeVisitTableFill(Expression*& out, uint32_t code) { auto* value = popNonVoidExpression(); auto* dest = popNonVoidExpression(); auto* ret = Builder(wasm).makeTableFill(Name(), dest, value, size); - tableRefs[tableIdx].push_back(&ret->table); + ret->table = getTableName(tableIdx); out = ret; return true; } @@ -5589,8 +5606,8 @@ bool WasmBinaryReader::maybeVisitTableCopy(Expression*& out, uint32_t code) { auto* source = popNonVoidExpression(); auto* dest = popNonVoidExpression(); auto* ret = Builder(wasm).makeTableCopy(dest, source, size, Name(), Name()); - tableRefs[destTableIdx].push_back(&ret->destTable); - tableRefs[sourceTableIdx].push_back(&ret->sourceTable); + ret->destTable = getTableName(destTableIdx); + ret->sourceTable = getTableName(sourceTableIdx); out = ret; return true; } @@ -5604,9 +5621,9 @@ bool WasmBinaryReader::maybeVisitTableInit(Expression*& out, uint32_t code) { curr->offset = popNonVoidExpression(); curr->dest = popNonVoidExpression(); Index segIdx = getU32LEB(); - elemRefs[segIdx].push_back(&curr->segment); - Index memIdx = getU32LEB(); - tableRefs[memIdx].push_back(&curr->table); + curr->segment = getElemName(segIdx); + Index tableIdx = getU32LEB(); + curr->table = getTableName(tableIdx); curr->finalize(); out = curr; return true; @@ -6620,7 +6637,7 @@ bool WasmBinaryReader::maybeVisitSIMDStore(Expression*& out, uint32_t code) { curr->bytes = 16; curr->valueType = Type::v128; Index memIdx = readMemoryAccess(curr->align, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); curr->isAtomic = false; curr->value = popNonVoidExpression(); curr->ptr = popNonVoidExpression(); @@ -6878,7 +6895,7 @@ bool WasmBinaryReader::maybeVisitSIMDLoad(Expression*& out, uint32_t code) { curr->type = Type::v128; curr->bytes = 16; Index memIdx = readMemoryAccess(curr->align, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); curr->isAtomic = false; curr->ptr = popNonVoidExpression(); curr->finalize(); @@ -6939,7 +6956,7 @@ bool WasmBinaryReader::maybeVisitSIMDLoad(Expression*& out, uint32_t code) { return false; } Index memIdx = readMemoryAccess(curr->align, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); curr->ptr = popNonVoidExpression(); curr->finalize(); out = curr; @@ -6989,7 +7006,7 @@ bool WasmBinaryReader::maybeVisitSIMDLoadStoreLane(Expression*& out, auto* curr = allocator.alloc(); curr->op = op; Index memIdx = readMemoryAccess(curr->align, curr->offset); - memoryRefs[memIdx].push_back(&curr->memory); + curr->memory = getMemoryName(memIdx); curr->index = getLaneIndex(lanes); curr->vec = popNonVoidExpression(); curr->ptr = popNonVoidExpression(); @@ -7035,8 +7052,8 @@ void WasmBinaryReader::visitMemorySize(MemorySize* curr) { if (getMemory(index)->is64()) { curr->type = Type::i64; } + curr->memory = getMemoryName(index); curr->finalize(); - memoryRefs[index].push_back(&curr->memory); } void WasmBinaryReader::visitMemoryGrow(MemoryGrow* curr) { @@ -7045,7 +7062,7 @@ void WasmBinaryReader::visitMemoryGrow(MemoryGrow* curr) { if (getMemory(index)->is64()) { curr->type = Type::i64; } - memoryRefs[index].push_back(&curr->memory); + curr->memory = getMemoryName(index); } void WasmBinaryReader::visitNop(Nop* curr) {} @@ -7068,12 +7085,7 @@ void WasmBinaryReader::visitRefIsNull(RefIsNull* curr) { void WasmBinaryReader::visitRefFunc(RefFunc* curr) { Index index = getU32LEB(); - // We don't know function names yet, so record this use to be updated later. - // Note that we do not need to check that 'index' is in bounds, as that will - // be verified in the next line. (Also, note that functionRefs[index] may - // write to an odd place in the functionRefs map if index is invalid, but that - // is harmless.) - functionRefs[index].push_back(&curr->func); + curr->func = getFunctionName(index); // To support typed function refs, we give the reference not just a general // funcref, but a specific subtype with the actual signature. curr->finalize(Type(getTypeByFunctionIndex(index), NonNullable)); @@ -7092,9 +7104,8 @@ void WasmBinaryReader::visitTableGet(TableGet* curr) { } curr->index = popNonVoidExpression(); curr->type = wasm.tables[tableIdx]->type; + curr->table = getTableName(tableIdx); curr->finalize(); - // Defer setting the table name for later, when we know it. - tableRefs[tableIdx].push_back(&curr->table); } void WasmBinaryReader::visitTableSet(TableSet* curr) { @@ -7104,9 +7115,8 @@ void WasmBinaryReader::visitTableSet(TableSet* curr) { } curr->value = popNonVoidExpression(); curr->index = popNonVoidExpression(); + curr->table = getTableName(tableIdx); curr->finalize(); - // Defer setting the table name for later, when we know it. - tableRefs[tableIdx].push_back(&curr->table); } void WasmBinaryReader::visitTryOrTryInBlock(Expression*& out) { @@ -7169,8 +7179,7 @@ void WasmBinaryReader::visitTryOrTryInBlock(Expression*& out) { breakStack.pop_back(); for (Index i = 0; i < tagIndexes.size(); i++) { - // We don't know the final name yet. - tagRefs[tagIndexes[i]].push_back(&curr->catchTags[i]); + curr->catchTags[i] = getTagName(tagIndexes[i]); } if (lastSeparator == BinaryConsts::Delegate) { @@ -7293,8 +7302,7 @@ void WasmBinaryReader::visitTryTable(TryTable* curr) { for (Index i = 0; i < tagIndexes.size(); i++) { if (curr->catchTags[i]) { - // We don't know the final name yet. - tagRefs[tagIndexes[i]].push_back(&curr->catchTags[i]); + curr->catchTags[i] = getTagName(tagIndexes[i]); } } @@ -7312,7 +7320,6 @@ void WasmBinaryReader::visitThrow(Throw* curr) { } auto* tag = wasm.tags[index].get(); curr->tag = tag->name; - tagRefs[index].push_back(&curr->tag); // we don't know the final name yet size_t num = tag->sig.params.size(); curr->operands.resize(num); for (size_t i = 0; i < num; i++) { @@ -7563,14 +7570,12 @@ bool WasmBinaryReader::maybeVisitArrayNewElem(Expression*& out, uint32_t code) { auto* size = popNonVoidExpression(); auto* offset = popNonVoidExpression(); if (isData) { - auto* curr = - Builder(wasm).makeArrayNewData(heapType, Name(), offset, size); - dataRefs[segIdx].push_back(&curr->segment); + auto* curr = Builder(wasm).makeArrayNewData( + heapType, getDataName(segIdx), offset, size); out = curr; } else { - auto* curr = - Builder(wasm).makeArrayNewElem(heapType, Name(), offset, size); - elemRefs[segIdx].push_back(&curr->segment); + auto* curr = Builder(wasm).makeArrayNewElem( + heapType, getElemName(segIdx), offset, size); out = curr; } return true; @@ -7708,14 +7713,12 @@ bool WasmBinaryReader::maybeVisitArrayInit(Expression*& out, uint32_t code) { auto* ref = popNonVoidExpression(); validateHeapTypeUsingChild(ref, heapType); if (isData) { - auto* curr = - Builder(wasm).makeArrayInitData(Name(), ref, index, offset, size); - dataRefs[segIdx].push_back(&curr->segment); + auto* curr = Builder(wasm).makeArrayInitData( + getDataName(segIdx), ref, index, offset, size); out = curr; } else { - auto* curr = - Builder(wasm).makeArrayInitElem(Name(), ref, index, offset, size); - elemRefs[segIdx].push_back(&curr->segment); + auto* curr = Builder(wasm).makeArrayInitElem( + getElemName(segIdx), ref, index, offset, size); out = curr; } return true; @@ -7941,9 +7944,6 @@ void WasmBinaryReader::visitResume(Resume* curr) { curr->handlerTags[i] = tag; curr->handlerBlocks[i] = handler; - - // We don't know the final name yet - tagRefs[tagIndex].push_back(&curr->handlerTags[i]); } curr->cont = popNonVoidExpression(); @@ -7966,7 +7966,6 @@ void WasmBinaryReader::visitSuspend(Suspend* curr) { } auto* tag = wasm.tags[tagIndex].get(); curr->tag = tag->name; - tagRefs[tagIndex].push_back(&curr->tag); auto numArgs = tag->sig.params.size(); curr->operands.resize(numArgs); diff --git a/test/lit/binary/empty-param-name.test b/test/lit/binary/empty-param-name.test index 2216d0bbb7f..aa89f3afcc1 100644 --- a/test/lit/binary/empty-param-name.test +++ b/test/lit/binary/empty-param-name.test @@ -1,4 +1,5 @@ -;; Test that we show a warning on an empty local name, and do not crash. +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; Test that we handle an empty local name, and do not crash. ;; ;; The binary contains this, processed by wabt with debug names: ;; @@ -8,7 +9,7 @@ ;; Wabt emits a name for that parameter, but it is the empty string. See ;; https://github.com/WebAssembly/wabt/issues/1799 -;; RUN: wasm-opt %s.wasm 2>&1 | filecheck %s - -;; CHECK: warning: empty local name at index 0 in function foo +;; RUN: wasm-opt %s.wasm -S -o - | filecheck %s +;; CHECK: (type $0 (func (param i32))) +;; CHECK: (import "imports" "foo" (func $foo (param $"" i32))) From 9002cc6f87570afdcb000760f54abdad6861f1bd Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 13 Nov 2024 19:33:18 -0500 Subject: [PATCH 127/622] Fixup pops when necessary in IRBuilder (#7075) IRBuilder introduces scratch locals to hoist values from underneath stacky code to the top of the stack for consumption by the next instruction. When it does so, the sequence of instructions from the set to the get of the scratch local is packaged in a block so the entire sequence can be made a child of the next instruction. In cases where the hoisted value comes from a `pop`, this packaging can make the IR invalid, since `pop`s are not allowed to appear inside blocks. Detect when this problem might occur and fix it by running `EHUtils::handleBlockNestedPops` after the function containing the problem has been constructed. --- src/wasm-ir-builder.h | 25 ++++ src/wasm/wasm-ir-builder.cpp | 16 ++- test/lit/basic/pop-fixup.wast | 209 ++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 test/lit/basic/pop-fixup.wast diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index d7a1dde87b9..40689a879e6 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -259,6 +259,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { struct NoScope {}; struct FuncScope { Function* func; + // Used to determine whether we need to run a fixup after creating the + // function. + bool hasSyntheticBlock = false; + bool hasPop = false; }; struct BlockScope { Block* block; @@ -369,6 +373,27 @@ class IRBuilder : public UnifiedExpressionVisitor> { } return nullptr; } + void noteSyntheticBlock() { + if (auto* funcScope = std::get_if(&scope)) { + funcScope->hasSyntheticBlock = true; + } + } + void notePop() { + if (auto* funcScope = std::get_if(&scope)) { + funcScope->hasPop = true; + } + } + bool needsPopFixup() { + // If the function has a synthetic block and it has a pop, then it's + // possible that the pop is inside the synthetic block and we should run + // the fixup. Determining more precisely that a pop is inside the + // synthetic block when it is created would be complicated and expensive, + // so we are conservative here. + if (auto* funcScope = std::get_if(&scope)) { + return funcScope->hasSyntheticBlock && funcScope->hasPop; + } + return false; + } Block* getBlock() { if (auto* blockScope = std::get_if(&scope)) { return blockScope->block; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index b238a926c42..4cb9514f59e 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -17,6 +17,7 @@ #include #include "ir/child-typer.h" +#include "ir/eh-utils.h" #include "ir/names.h" #include "ir/properties.h" #include "ir/utils.h" @@ -98,7 +99,10 @@ Result<> IRBuilder::packageHoistedValue(const HoistedVal& hoisted, auto packageAsBlock = [&](Type type) { // Create a block containing the producer of the hoisted value, the final - // get of the hoisted value, and everything in between. + // get of the hoisted value, and everything in between. Record the fact that + // we are synthesizing a block to help us determine later whether we need to + // run the nested pop fixup. + scopeStack[0].noteSyntheticBlock(); std::vector exprs(scope.exprStack.begin() + hoisted.valIndex, scope.exprStack.end()); auto* block = builder.makeBlock(exprs, type); @@ -865,9 +869,12 @@ Result<> IRBuilder::visitCatch(Name tag) { tryy->catchTags.push_back(tag); pushScope( ScopeCtx::makeCatch(tryy, originalLabel, label, labelUsed, branchLabel)); - // Push a pop for the exception payload. + // Push a pop for the exception payload if necessary. auto params = wasm.getTag(tag)->sig.params; if (params != Type::none) { + // Note that we have a pop to help determine later whether we need to run + // the fixup for pops within blocks. + scopeStack[0].notePop(); push(builder.makePop(params)); } return Ok{}; @@ -935,7 +942,7 @@ Result<> IRBuilder::visitEnd() { if (scope.isNone()) { return Err{"unexpected end"}; } - if (auto* func = scope.getFunction(); func) { + if (auto* func = scope.getFunction()) { if (auto* loc = std::get_if(&debugLoc)) { func->epilogLocation.insert(*loc); } @@ -970,6 +977,9 @@ Result<> IRBuilder::visitEnd() { if (auto* func = scope.getFunction()) { func->body = maybeWrapForLabel(*expr); labelDepths.clear(); + if (scope.needsPopFixup()) { + EHUtils::handleBlockNestedPops(func, wasm); + } } else if (auto* block = scope.getBlock()) { assert(*expr == block); block->name = scope.label; diff --git a/test/lit/basic/pop-fixup.wast b/test/lit/basic/pop-fixup.wast new file mode 100644 index 00000000000..371365a0cc9 --- /dev/null +++ b/test/lit/basic/pop-fixup.wast @@ -0,0 +1,209 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s -all -S -o - | filecheck %s + +(module + ;; CHECK: (tag $e (param i32)) + (tag $e (param i32)) + ;; CHECK: (tag $e2 (param i32 i32)) + (tag $e2 (param i32 i32)) + + ;; CHECK: (func $stacky (type $0) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $stacky + try + catch $e + nop + drop + end + ) + + ;; CHECK: (func $stacky-later (type $0) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $stacky-later + try + catch $e + i32.eqz + nop + drop + end + ) + + ;; CHECK: (func $tuple (type $0) + ;; CHECK-NEXT: (local $scratch (tuple i32 i32)) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (local $2 (tuple i32 i32)) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e2 + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (pop (tuple i32 i32)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tuple + try + catch $e2 + drop + drop + end + ) + + ;; CHECK: (func $stacky-tuple (type $0) + ;; CHECK-NEXT: (local $scratch (tuple i32 i32)) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (local $2 (tuple i32 i32)) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e2 + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (pop (tuple i32 i32)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $stacky-tuple + try + catch $e2 + nop + drop + drop + end + ) + + ;; CHECK: (func $stacky-later-tuple (type $0) + ;; CHECK-NEXT: (local $0 (tuple i32 i32)) + ;; CHECK-NEXT: (local $scratch (tuple i32 i32)) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local $3 (tuple i32 i32)) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e2 + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (pop (tuple i32 i32)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $stacky-later-tuple + (local (tuple i32 i32)) + try + catch $e2 + local.tee 0 + nop + drop + drop + end + ) +) From 74a910bc298c95856e5bc09fcd2424d08c5df12f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Nov 2024 16:34:37 -0800 Subject: [PATCH 128/622] [SignExt] OptimizeInstructions: Remove signexts of already-extended values (#7072) --- src/passes/OptimizeInstructions.cpp | 52 ++- .../optimize-instructions-sign_ext.wast | 367 ++++++++++++++++++ 2 files changed, 404 insertions(+), 15 deletions(-) create mode 100644 test/lit/passes/optimize-instructions-sign_ext.wast diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 3a2841c1ed4..c9fe5f652d2 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -80,8 +80,8 @@ static bool isSignedOp(BinaryOp op) { struct LocalInfo { static const Index kUnknown = Index(-1); - Index maxBits; - Index signExtedBits; + Index maxBits = -1; + Index signExtBits = 0; }; struct LocalScanner : PostWalker { @@ -99,9 +99,9 @@ struct LocalScanner : PostWalker { auto& info = localInfo[i]; if (func->isParam(i)) { info.maxBits = getBitsForType(func->getLocalType(i)); // worst-case - info.signExtedBits = LocalInfo::kUnknown; // we will never know anything + info.signExtBits = LocalInfo::kUnknown; // we will never know anything } else { - info.maxBits = info.signExtedBits = 0; // we are open to learning + info.maxBits = info.signExtBits = 0; // we are open to learning } } // walk @@ -109,8 +109,8 @@ struct LocalScanner : PostWalker { // finalize for (Index i = 0; i < func->getNumLocals(); i++) { auto& info = localInfo[i]; - if (info.signExtedBits == LocalInfo::kUnknown) { - info.signExtedBits = 0; + if (info.signExtBits == LocalInfo::kUnknown) { + info.signExtBits = 0; } } } @@ -137,11 +137,11 @@ struct LocalScanner : PostWalker { signExtBits = load->bytes * 8; } } - if (info.signExtedBits == 0) { - info.signExtedBits = signExtBits; // first info we see - } else if (info.signExtedBits != signExtBits) { + if (info.signExtBits == 0) { + info.signExtBits = signExtBits; // first info we see + } else if (info.signExtBits != signExtBits) { // contradictory information, give up - info.signExtedBits = LocalInfo::kUnknown; + info.signExtBits = LocalInfo::kUnknown; } } @@ -1006,6 +1006,22 @@ struct OptimizeInstructions } } + // Simple sign extends can be removed if the value is already sign-extended. + auto signExtBits = getSignExtBits(curr->value); + if (signExtBits > 0) { + // Note that we can handle the case of |curr| having a larger sign-extend: + // if we have an 8-bit value in 32-bit, then there are 24 sign bits, and + // doing a sign-extend to 16 will only affect 16 of those 24, and the + // effect is to leave them as they are. + if ((curr->op == ExtendS8Int32 && signExtBits <= 8) || + (curr->op == ExtendS16Int32 && signExtBits <= 16) || + (curr->op == ExtendS8Int64 && signExtBits <= 8) || + (curr->op == ExtendS16Int64 && signExtBits <= 16) || + (curr->op == ExtendS32Int64 && signExtBits <= 32)) { + return replaceCurrent(curr->value); + } + } + if (Abstract::hasAnyReinterpret(curr->op)) { // i32.reinterpret_f32(f32.reinterpret_i32(x)) => x // i64.reinterpret_f64(f64.reinterpret_i64(x)) => x @@ -3611,16 +3627,22 @@ struct OptimizeInstructions return inner; } - // check if an expression is already sign-extended + // Check if an expression is already sign-extended to an exact number of bits. bool isSignExted(Expression* curr, Index bits) { + return getSignExtBits(curr) == bits; + } + + // Returns the number of bits an expression is sign-extended (or 0 if it is + // not). + Index getSignExtBits(Expression* curr) { if (Properties::getSignExtValue(curr)) { - return Properties::getSignExtBits(curr) == bits; + return Properties::getSignExtBits(curr); } if (auto* get = curr->dynCast()) { - // check what we know about the local - return localInfo[get->index].signExtedBits == bits; + // Check what we know about the local. + return localInfo[get->index].signExtBits; } - return false; + return 0; } // optimize trivial math operations, given that the right side of a binary diff --git a/test/lit/passes/optimize-instructions-sign_ext.wast b/test/lit/passes/optimize-instructions-sign_ext.wast new file mode 100644 index 00000000000..19e6c45c828 --- /dev/null +++ b/test/lit/passes/optimize-instructions-sign_ext.wast @@ -0,0 +1,367 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (memory $0 16 17) + (memory $0 16 17) + + ;; CHECK: (func $i32-direct (type $1) (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i32-direct (param $x i32) + ;; We do not need to sign-extend twice, and can emit just one extend8. + (drop + (i32.extend8_s + (i32.shr_s + (i32.shl + (local.get $x) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + ) + + ;; CHECK: (func $i32-local (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.load8_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i32-local + ;; The local is sign-extended, so the i32.extend can be removed. + (local $temp i32) + (local.set $temp + (i32.load8_s + (i32.const 22) + ) + ) + (drop + (i32.extend8_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i32-local-i16 (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.load16_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i32-local-i16 + ;; As above with i16. + (local $temp i32) + (local.set $temp + (i32.load16_s + (i32.const 22) + ) + ) + (drop + (i32.extend16_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i32-local-i16-mismatch-bad (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.load16_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i32-local-i16-mismatch-bad + ;; As above with in i8/i16 mismatch. We do not optimize. + (local $temp i32) + (local.set $temp + (i32.load16_s + (i32.const 22) + ) + ) + (drop + (i32.extend8_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i32-local-i16-mismatch-good (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.load8_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i32-local-i16-mismatch-good + ;; As above with in i8/i16 mismatch, but in the direction we can handle. + (local $temp i32) + (local.set $temp + (i32.load8_s + (i32.const 22) + ) + ) + (drop + (i32.extend16_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64 (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load8_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64 + ;; As above, but with i64. + (local $temp i64) + (local.set $temp + (i64.load8_s + (i32.const 22) + ) + ) + (drop + (i64.extend8_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64-i16 (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load16_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-i16 + (local $temp i64) + (local.set $temp + (i64.load16_s + (i32.const 22) + ) + ) + (drop + (i64.extend16_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64-i32 (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-i32 + (local $temp i64) + (local.set $temp + (i64.load32_s + (i32.const 22) + ) + ) + (drop + (i64.extend32_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64-mismatch-good (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load8_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-mismatch-good + (local $temp i64) + (local.set $temp + (i64.load8_s + (i32.const 22) + ) + ) + (drop + (i64.extend16_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64-mismatch-good2 (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load8_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-mismatch-good2 + (local $temp i64) + (local.set $temp + (i64.load8_s + (i32.const 22) + ) + ) + (drop + (i64.extend32_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64-mismatch-good3 (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load16_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-mismatch-good3 + (local $temp i64) + (local.set $temp + (i64.load16_s + (i32.const 22) + ) + ) + (drop + (i64.extend32_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64-mismatch-bad (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load16_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend8_s + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-mismatch-bad + (local $temp i64) + (local.set $temp + (i64.load16_s + (i32.const 22) + ) + ) + (drop + (i64.extend8_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64-mismatch-bad2 (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend8_s + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-mismatch-bad2 + (local $temp i64) + (local.set $temp + (i64.load32_s + (i32.const 22) + ) + ) + (drop + (i64.extend8_s + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $i64-mismatch-bad3 (type $0) + ;; CHECK-NEXT: (local $temp i64) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend16_s + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-mismatch-bad3 + (local $temp i64) + (local.set $temp + (i64.load32_s + (i32.const 22) + ) + ) + (drop + (i64.extend16_s + (local.get $temp) + ) + ) + ) +) From 614fc7d3a01482ec6b8d48e806e2b43524212c06 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 14 Nov 2024 15:20:50 -0500 Subject: [PATCH 129/622] [NFC] Eagerly set local names in binary reader (#7076) Instead of setting the local names at the end of binary reading, eagerly set them before parsing function bodies. This is NFC now, but will fix a future bug once the binary reader uses IRBuilder. IRBuilder can introduce new scratch locals, and it gives them the names `$scratch`, `$scratch_1`, etc. If the name section includes locals with the same names and we set those local names after parsing function bodies, then we can end up with multiple locals with the same names. Setting the names before parsing the function bodies ensures that IRBuilder will generate different names for the scratch locals. The alternative fix would be to generate fresh names when setting names from the name section, but it is better to respect the names in the name section and use fresh names for the newly introduced scratch locals instead. --- src/wasm-binary.h | 1 + src/wasm/wasm-binary.cpp | 44 +++++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 67c1dec968a..59114cdb8a5 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1571,6 +1571,7 @@ class WasmBinaryReader { void readFunctions(); void readVars(); + void setLocalNames(Function& func, Index i); void readExports(); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 57a01bb1ce2..f5bf11206f9 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1876,25 +1876,6 @@ void WasmBinaryReader::read() { } } - // Set local names for imported and declared functions. - for (auto& [index, locals] : localNames) { - if (index >= wasm.functions.size()) { - std::cerr << "warning: function index out of bounds in name section: " - "locals at index " - << index << '\n'; - continue; - } - for (auto& [local, name] : locals) { - if (local >= wasm.functions[index]->getNumLocals()) { - std::cerr << "warning: local index out of bounds in name section: " - << name << " at index " << local << " in function " << index - << '\n'; - continue; - } - wasm.functions[index]->setLocalName(local, name); - } - } - validateBinary(); } @@ -2626,6 +2607,7 @@ void WasmBinaryReader::readImports() { curr->hasExplicitName = isExplicit; curr->module = module; curr->base = base; + setLocalNames(*curr, wasm.functions.size()); wasm.addFunction(std::move(curr)); break; } @@ -2728,6 +2710,20 @@ void WasmBinaryReader::requireFunctionContext(const char* error) { } } +void WasmBinaryReader::setLocalNames(Function& func, Index i) { + if (auto it = localNames.find(i); it != localNames.end()) { + for (auto& [local, name] : it->second) { + if (local >= func.getNumLocals()) { + std::cerr << "warning: local index out of bounds in name section: " + << name << " at index " << local << " in function " << i + << '\n'; + continue; + } + func.setLocalName(local, name); + } + } +} + void WasmBinaryReader::readFunctionSignatures() { size_t num = getU32LEB(); auto numImports = wasm.functions.size(); @@ -2739,6 +2735,15 @@ void WasmBinaryReader::readFunctionSignatures() { } usedNames.insert(name); } + // Also check that the function indices in the local names subsection are + // in-bounds, even though we don't use them here. + for (auto& [index, locals] : localNames) { + if (index >= num + numImports) { + std::cerr << "warning: function index out of bounds in name section: " + "locals at index " + << index << '\n'; + } + } for (size_t i = 0; i < num; i++) { auto [name, isExplicit] = getOrMakeName(functionNames, numImports + i, makeName("", i), usedNames); @@ -2810,6 +2815,7 @@ void WasmBinaryReader::readFunctions() { readNextDebugLocation(); readVars(); + setLocalNames(*func, numFuncImports + i); func->prologLocation = debugLocation; { From d4e7fee97c9fa7ae2c6f069d4428964cc1ea1795 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 14 Nov 2024 16:28:43 -0500 Subject: [PATCH 130/622] Update lit test output (#7077) heap-store-optimization.wast had a test without its accompanying generated output. --- test/lit/passes/heap-store-optimization.wast | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/lit/passes/heap-store-optimization.wast b/test/lit/passes/heap-store-optimization.wast index 1c1abba4ae2..d317888be37 100644 --- a/test/lit/passes/heap-store-optimization.wast +++ b/test/lit/passes/heap-store-optimization.wast @@ -967,6 +967,29 @@ ) ) + ;; CHECK: (func $control-flow-in-set-value-safe-return (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) (func $control-flow-in-set-value-safe-return (result i32) ;; As above, but replace the call with a return in an if. We can still ;; optimize (if the return is taken, we go outside of the function anyhow). From 6efd41779272d2ac7eb75705c21c9f437a1409c9 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 14 Nov 2024 16:29:37 -0500 Subject: [PATCH 131/622] Record binary locations for nested blocks (#7078) The binary reader has special handling for blocks immediately nested inside other blocks to eliminate recursion while parsing very deep stacks of blocks. This special handling did not record binary locations for the nested blocks, though. Add logic to record binary locations for nested blocks. This binary reading code is about to be replaced with completely different code that uses IRBuilder instead, but this change will eliminate some test differences that we would otherwise see when we make that change. --- src/wasm/wasm-binary.cpp | 20 ++++++++++++++++++++ test/passes/dwarf-local-order.bin.txt | 1 + test/passes/fannkuch0_dwarf.bin.txt | 5 +++++ test/passes/fannkuch3_dwarf.bin.txt | 7 +++++++ test/passes/fannkuch3_manyopts_dwarf.bin.txt | 4 ++++ test/passes/reverse_dwarf_abbrevs.bin.txt | 4 ++++ 6 files changed, 41 insertions(+) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f5bf11206f9..73718e636a8 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4453,13 +4453,21 @@ void WasmBinaryReader::visitBlock(Block* curr) { // special-case Block and de-recurse nested blocks in their first position, as // that is a common pattern that can be very highly nested. std::vector stack; + // Track start positions for all blocks except for the outermost block, which + // is already handled in the caller. + std::vector startPosStack; + size_t startPos = -1; while (1) { curr->type = getType(); curr->name = getNextLabel(); breakStack.push_back({curr->name, curr->type}); stack.push_back(curr); + if (startPos != size_t(-1)) { + startPosStack.push_back(startPos); + } if (more() && input[pos] == BinaryConsts::Block) { // a recursion + startPos = pos; readNextDebugLocation(); curr = allocator.alloc(); startControlFlow(curr); @@ -4478,6 +4486,12 @@ void WasmBinaryReader::visitBlock(Block* curr) { while (stack.size() > 0) { curr = stack.back(); stack.pop_back(); + if (startPosStack.empty()) { + startPos = -1; + } else { + startPos = startPosStack.back(); + startPosStack.pop_back(); + } // everything after this, that is left when we see the marker, is ours size_t start = expressionStack.size(); if (last) { @@ -4497,6 +4511,12 @@ void WasmBinaryReader::visitBlock(Block* curr) { : Block::NoBreak); breakStack.pop_back(); breakTargetNames.erase(curr->name); + + if (DWARF && currFunction && startPos != size_t(-1)) { + currFunction->expressionLocations[curr] = + BinaryLocations::Span{BinaryLocation(startPos - codeSectionLocation), + BinaryLocation(pos - codeSectionLocation)}; + } } } diff --git a/test/passes/dwarf-local-order.bin.txt b/test/passes/dwarf-local-order.bin.txt index f6b827702ba..509db04b1d6 100644 --- a/test/passes/dwarf-local-order.bin.txt +++ b/test/passes/dwarf-local-order.bin.txt @@ -341,6 +341,7 @@ ) ;; code offset: 0xa9 (block $label$1 + ;; code offset: 0xab (block $label$2 ;; code offset: 0xaf (br_if $label$2 diff --git a/test/passes/fannkuch0_dwarf.bin.txt b/test/passes/fannkuch0_dwarf.bin.txt index e761baf7009..bee23dc186c 100644 --- a/test/passes/fannkuch0_dwarf.bin.txt +++ b/test/passes/fannkuch0_dwarf.bin.txt @@ -7740,6 +7740,7 @@ file_names[ 3]: ) ;; code offset: 0xa1f (block $label$17 + ;; code offset: 0xa21 (block $label$18 ;; code offset: 0xa27 (br_if $label$18 @@ -7927,6 +7928,7 @@ file_names[ 3]: ) ;; code offset: 0xaed (block $label$1 + ;; code offset: 0xaef (block $label$2 ;; code offset: 0xaf4 (br_if $label$2 @@ -8041,6 +8043,7 @@ file_names[ 3]: ) ;; code offset: 0xb4a (block $label$3 + ;; code offset: 0xb4c (block $label$4 ;; code offset: 0xb51 (br_if $label$4 @@ -8914,6 +8917,7 @@ file_names[ 3]: ) ;; code offset: 0xefd (block $label$7 + ;; code offset: 0xeff (block $label$8 ;; code offset: 0xf04 (br_if $label$8 @@ -9818,6 +9822,7 @@ file_names[ 3]: ) ;; code offset: 0x1207 (block $label$17 + ;; code offset: 0x1209 (block $label$18 ;; code offset: 0x120f (br_if $label$18 diff --git a/test/passes/fannkuch3_dwarf.bin.txt b/test/passes/fannkuch3_dwarf.bin.txt index f58068ec384..74b6446a5f3 100644 --- a/test/passes/fannkuch3_dwarf.bin.txt +++ b/test/passes/fannkuch3_dwarf.bin.txt @@ -4874,7 +4874,9 @@ file_names[ 4]: ) ;; code offset: 0x47 (block $label$1 + ;; code offset: 0x49 (block $label$2 + ;; code offset: 0x4b (block $label$3 ;; code offset: 0x52 (br_if $label$3 @@ -6022,7 +6024,9 @@ file_names[ 4]: ) ;; code offset: 0x3bd (block $label$1 + ;; code offset: 0x3bf (block $label$2 + ;; code offset: 0x3c1 (block $label$3 ;; code offset: 0x3c8 (br_if $label$3 @@ -6192,8 +6196,11 @@ file_names[ 4]: ) ;; code offset: 0x444 (block $label$6 + ;; code offset: 0x446 (block $label$7 + ;; code offset: 0x448 (block $label$8 + ;; code offset: 0x44a (block $label$9 ;; code offset: 0x451 (br_if $label$9 diff --git a/test/passes/fannkuch3_manyopts_dwarf.bin.txt b/test/passes/fannkuch3_manyopts_dwarf.bin.txt index fb6008541b2..3ffb81efeaf 100644 --- a/test/passes/fannkuch3_manyopts_dwarf.bin.txt +++ b/test/passes/fannkuch3_manyopts_dwarf.bin.txt @@ -4752,6 +4752,7 @@ file_names[ 4]: ) ;; code offset: 0x43 (block $label$1 + ;; code offset: 0x45 (block $label$2 ;; code offset: 0x4c (if @@ -5886,6 +5887,7 @@ file_names[ 4]: ) ;; code offset: 0x39b (block $label$1 + ;; code offset: 0x39d (block $label$2 ;; code offset: 0x3a4 (if @@ -6050,7 +6052,9 @@ file_names[ 4]: ) ;; code offset: 0x41a (block $label$6 + ;; code offset: 0x41c (block $label$7 + ;; code offset: 0x41e (block $label$8 ;; code offset: 0x425 (if diff --git a/test/passes/reverse_dwarf_abbrevs.bin.txt b/test/passes/reverse_dwarf_abbrevs.bin.txt index 2500b625b1b..5427acc6cf0 100644 --- a/test/passes/reverse_dwarf_abbrevs.bin.txt +++ b/test/passes/reverse_dwarf_abbrevs.bin.txt @@ -293,7 +293,9 @@ file_names[ 1]: (local.set $4 ;; code offset: 0x89 (block $label$1 (result i32) + ;; code offset: 0x8b (block $label$2 + ;; code offset: 0x8d (block $label$3 ;; code offset: 0xa5 (if @@ -1387,6 +1389,7 @@ file_names[ 1]: (block $label$1 ;; code offset: 0x43a (if + ;; code offset: 0x412 (block $label$2 (result i32) ;; code offset: 0x41c (if @@ -2102,6 +2105,7 @@ file_names[ 1]: ) ;; code offset: 0x65a (block $label$1 + ;; code offset: 0x65c (block $label$2 ;; code offset: 0x664 (br_if $label$2 From 49c45ac1675d787e7151f9beafcae479936aa9f3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 14 Nov 2024 21:22:34 -0500 Subject: [PATCH 132/622] Use empty blocks instead of nops for empty scopes in IRBuilder (#7080) When IRBuilder builds an empty non-block scope such as a function body, an if arm, a try block, etc, it needs to produce some expression to represent the empty contents. Previously it produced a nop, but change it to produce an empty block instead. The binary writer and printer have special logic to elide empty blocks, so this produces smaller output. Update J2CLOpts to recognize functions containing empty blocks as trivial to avoid regressing one of its tests. --- src/passes/J2CLOpts.cpp | 1 + src/wasm/wasm-ir-builder.cpp | 7 +-- test/binaryen.js/debug-info.js.txt | 3 -- test/binaryen.js/debug-names.js.txt | 3 -- test/example/module-splitting.txt | 4 -- test/gtest/cfg.cpp | 2 +- test/lit/basic/complexTextNames.wast | 3 -- test/lit/basic/duplicate_types.wast | 3 -- test/lit/basic/exception-handling-legacy.wast | 45 ---------------- test/lit/basic/exception-handling.wast | 6 --- test/lit/basic/multi-table.wast | 6 --- test/lit/basic/pop-fixup.wast | 5 -- test/lit/basic/print-explicit-typeuse.wast | 4 -- test/lit/basic/reference-types.wast | 15 ------ test/lit/basic/shared-types.wast | 2 - test/lit/basic/types-function-references.wast | 12 ----- test/lit/debug/source-map-smearing.wast | 2 +- test/lit/if-then-else.wast | 2 - test/lit/merge/chain.wat | 1 - test/lit/merge/names.wat | 6 --- test/lit/merge/renamings.wat | 4 -- test/lit/merge/table64.wat | 1 - test/lit/non-nullable-locals.wast | 2 - test/lit/passes/abstract-type-refining.wast | 8 --- .../passes/asyncify_enable-multivalue.wast | 2 - .../lit/passes/asyncify_optimize-level=1.wast | 1 - ...syncify_pass-arg=asyncify-addlist@foo.wast | 1 - ...foo_pass-arg=asyncify-ignore-indirect.wast | 1 - ...o_pass-arg=asyncify-propagate-addlist.wast | 1 - ...yncify-imports@env.import,env.import2.wast | 2 - .../lit/passes/coalesce-locals-eh-legacy.wast | 1 - test/lit/passes/code-folding-eh-legacy.wast | 2 - test/lit/passes/code-folding-eh.wast | 1 - test/lit/passes/code-pushing-eh-legacy.wast | 4 -- test/lit/passes/code-pushing-eh.wast | 1 - test/lit/passes/dae-gc-refine-params.wast | 1 - test/lit/passes/dae-gc.wast | 3 -- test/lit/passes/dae_all-features.wast | 8 +-- test/lit/passes/dae_tnh.wast | 2 - test/lit/passes/dce-eh-legacy.wast | 4 -- test/lit/passes/dce-eh.wast | 1 - test/lit/passes/denan.wast | 5 +- test/lit/passes/flatten-eh-legacy.wast | 1 - .../passes/flatten_i64-to-i32-lowering.wast | 2 - ...g_souperify-single-use_enable-threads.wast | 1 - ...ls-nonesting_souperify_enable-threads.wast | 2 - test/lit/passes/fpcast-emu.wast | 4 -- test/lit/passes/global-refining.wast | 4 -- test/lit/passes/gto-removals.wast | 8 --- test/lit/passes/gufa-refs.wast | 5 -- test/lit/passes/heap2local.wast | 1 - test/lit/passes/inlining-eh-legacy.wast | 11 ++-- test/lit/passes/inlining-gc.wast | 6 ++- test/lit/passes/inlining_all-features.wast | 7 +-- test/lit/passes/inlining_splitting.wast | 1 - .../passes/instrument-locals-eh-legacy.wast | 1 - test/lit/passes/j2cl-merge-itables.wast | 2 - test/lit/passes/j2cl.wast | 5 +- ...egalize-js-interface-exported-helpers.wast | 1 - test/lit/passes/local-cse_all-features.wast | 1 - test/lit/passes/monomorphize-context.wast | 25 +++++---- test/lit/passes/monomorphize-limits.wast | 8 +-- test/lit/passes/name-types.wast | 1 - test/lit/passes/once-reduction.wast | 3 -- test/lit/passes/optimize-casts-noeh.wast | 1 - test/lit/passes/optimize-casts.wast | 1 - ...imize-instructions-call_ref-roundtrip.wast | 3 -- .../optimize-instructions-call_ref.wast | 1 - .../optimize-instructions-eh-legacy.wast | 3 -- .../passes/optimize-instructions-gc-iit.wast | 2 - test/lit/passes/optimize-instructions-gc.wast | 1 - .../optimize-instructions-iit-eh-legacy.wast | 1 - .../lit/passes/optimize-instructions-mvp.wast | 1 - test/lit/passes/remove-unused-brs-eh.wast | 2 - test/lit/passes/remove-unused-brs.wast | 1 - .../remove-unused-brs_all-features.wast | 1 - .../remove-unused-module-elements-refs.wast | 52 ------------------- ...e-unused-module-elements_all-features.wast | 4 -- .../remove-unused-module-elements_tnh.wast | 6 --- .../passes/remove-unused-names-eh-legacy.wast | 1 - test/lit/passes/rse-eh-legacy.wast | 12 ----- test/lit/passes/rse-eh.wast | 2 - test/lit/passes/rse-gc.wast | 1 - test/lit/passes/signature-pruning.wast | 16 ------ test/lit/passes/signature-refining.wast | 18 ------- .../lit/passes/simplify-locals-eh-legacy.wast | 3 -- test/lit/passes/simplify-locals-gc.wast | 3 -- test/lit/passes/simplify-locals-global.wast | 1 - test/lit/passes/ssa.wast | 1 - .../passes/stack-ir-roundtrip-eh-legacy.wast | 2 - test/lit/passes/string-lowering_types.wast | 1 - test/lit/passes/translate-to-exnref.wast | 15 +++--- test/lit/passes/type-finalizing.wast | 2 - test/lit/passes/type-generalizing.wast | 1 - test/lit/passes/type-merging-shared.wast | 3 -- test/lit/passes/type-merging.wast | 17 ------ test/lit/passes/type-refining.wast | 1 - test/lit/string.as_wtf16.wast | 1 - test/lit/validation/nn-tuples.wast | 1 - test/lit/wasm-split/passive.wast | 2 - test/lit/wasm-split/ref.func.wast | 2 - test/lit/wat-kitchen-sink.wast | 46 ---------------- test/lld/basic_safe_stack.wat.out | 2 - test/lld/duplicate_imports.wat.out | 1 - test/lld/em_asm.wat.out | 1 - test/lld/em_asm64.wat.out | 1 - test/lld/em_asm_O0.wat.out | 1 - test/lld/em_asm_main_thread.wat.out | 1 - test/lld/em_asm_shared.wat.out | 2 - test/lld/hello_world.wat.out | 1 - test/lld/longjmp.wat.out | 1 - test/lld/main_module_table.wat.out | 1 - test/lld/main_module_table_2.wat.out | 1 - test/lld/main_module_table_3.wat.out | 1 - test/lld/main_module_table_4.wat.out | 1 - test/lld/main_module_table_5.wat.out | 3 -- test/lld/recursive.wat.out | 1 - test/lld/recursive_safe_stack.wat.out | 1 - test/lld/reserved_func_ptr.wat.out | 3 -- test/lld/safe_stack_standalone-wasm.wat.out | 1 - test/lld/shared.wat.out | 1 - test/lld/shared_longjmp.wat.out | 2 - test/metadce/name_collision.wast.dced | 1 - test/metadce/outside.wast.dced | 1 - test/metadce/tag.wast.dced | 1 - ...cate-function-elimination_all-features.txt | 1 - test/passes/func-metrics.txt | 5 +- test/passes/licm.txt | 1 - ...inify-imports-and-exports_all-features.txt | 2 - test/passes/minify-imports_all-features.txt | 2 - test/passes/post-emscripten.txt | 3 -- test/passes/print-function-map.txt | 2 - .../remove-unused-brs_enable-multivalue.txt | 1 - ...unused-names_merge-blocks_all-features.txt | 1 - ...nfunction-module-elements_all-features.txt | 3 -- test/passes/spill-pointers.txt | 2 - 136 files changed, 53 insertions(+), 520 deletions(-) diff --git a/src/passes/J2CLOpts.cpp b/src/passes/J2CLOpts.cpp index cc73e873e26..d0df446e1ff 100644 --- a/src/passes/J2CLOpts.cpp +++ b/src/passes/J2CLOpts.cpp @@ -43,6 +43,7 @@ Expression* getTrivialFunctionBody(Function* func) { // Only consider trivial the following instructions which can be safely // inlined and note that their size is at most 2. if (body->is() || body->is() || body->is() || + (body->is() && body->cast()->list.empty()) || // Call with no arguments. (body->is() && body->dynCast()->operands.size() == 0) || // Simple global.set with a constant. diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 4cb9514f59e..5e5decca42b 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -157,7 +157,7 @@ void IRBuilder::push(Expression* expr) { Result IRBuilder::build() { if (scopeStack.empty()) { - return builder.makeNop(); + return builder.makeBlock(); } if (scopeStack.size() > 1 || !scopeStack.back().isNone()) { return Err{"unfinished block context"}; @@ -792,12 +792,13 @@ Result IRBuilder::finishScope(Block* block) { Expression* ret = nullptr; if (scope.exprStack.size() == 0) { // No expressions for this scope, but we need something. If we were given a - // block, we can empty it out and return it, but otherwise we need a nop. + // block, we can empty it out and return it, but otherwise create a new + // empty block. if (block) { block->list.clear(); ret = block; } else { - ret = builder.makeNop(); + ret = builder.makeBlock(); } } else if (scope.exprStack.size() == 1) { // We can put our single expression directly into the surrounding scope. diff --git a/test/binaryen.js/debug-info.js.txt b/test/binaryen.js/debug-info.js.txt index 7802e341539..027961741d2 100644 --- a/test/binaryen.js/debug-info.js.txt +++ b/test/binaryen.js/debug-info.js.txt @@ -5,7 +5,6 @@ debugInfo=false (memory $0 0) (export "test" (func $0)) (func $0 - (nop) ) ) @@ -16,7 +15,6 @@ debugInfo=true (memory $0 0) (export "test" (func $test)) (func $test - (nop) ) ) @@ -27,7 +25,6 @@ debugInfo=false (memory $0 0) (export "test" (func $0)) (func $0 - (nop) ) ) diff --git a/test/binaryen.js/debug-names.js.txt b/test/binaryen.js/debug-names.js.txt index dc66048ca80..34de198cd22 100644 --- a/test/binaryen.js/debug-names.js.txt +++ b/test/binaryen.js/debug-names.js.txt @@ -16,7 +16,6 @@ (table $wor 0 0 funcref) (func $of (param $wasm i32) (local $!#$%&'*+-./:<=>?@\^_`|~ f64) - (nop) ) ) @@ -28,7 +27,6 @@ (table $wor 0 0 funcref) (func $of (param $js i32) (local $!#$%&'*+-./:<=>?@\5c^_`|~ f64) - (nop) ) ) @@ -40,7 +38,6 @@ (table $wor 0 0 funcref) (func $of (param $js i32) (local $!#$%&'*+-./:<=>?@\5c^_`|~ f64) - (nop) ) ) diff --git a/test/example/module-splitting.txt b/test/example/module-splitting.txt index fe878d739ab..709ebd4d755 100644 --- a/test/example/module-splitting.txt +++ b/test/example/module-splitting.txt @@ -585,7 +585,6 @@ Before: (elem $0 (global.get $base) $null $foo) (export "foo" (func $foo)) (func $null (type $0) - (nop) ) (func $foo (type $1) (param $0 i32) (result i32) (local.get $0) @@ -607,7 +606,6 @@ After: (export "%table_2" (table $0)) (export "%global" (global $base)) (func $null (type $0) - (nop) ) (func $foo (type $1) (param $0 i32) (result i32) (call_indirect $0 (type $1) @@ -1144,7 +1142,6 @@ Before: (export "foo1" (func $foo)) (export "foo2" (func $foo)) (func $foo (type $0) - (nop) ) ) Keeping: @@ -1169,7 +1166,6 @@ Secondary: (import "primary" "%table" (table $0 1 funcref)) (elem $0 (i32.const 0) $foo) (func $foo (type $0) - (nop) ) ) diff --git a/test/gtest/cfg.cpp b/test/gtest/cfg.cpp index b8d26caa4b6..35ccf855bb8 100644 --- a/test/gtest/cfg.cpp +++ b/test/gtest/cfg.cpp @@ -149,7 +149,7 @@ TEST_F(CFGTest, Empty) { ;; entry ;; exit 0: - 0: nop + 0: block )cfg"; Module wasm; diff --git a/test/lit/basic/complexTextNames.wast b/test/lit/basic/complexTextNames.wast index 88240e038d8..16c19aadf9a 100644 --- a/test/lit/basic/complexTextNames.wast +++ b/test/lit/basic/complexTextNames.wast @@ -13,12 +13,10 @@ ;; CHECK-TEXT: (type $0 (func)) ;; CHECK-TEXT: (func $foo\20\28.bar\29 (type $0) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (type $0 (func)) ;; CHECK-BIN: (func $foo\20\28.bar\29 (type $0) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $foo\20\28.bar\29) @@ -34,7 +32,6 @@ ;; CHECK-BIN-NODEBUG: (type $0 (func)) ;; CHECK-BIN-NODEBUG: (func $0 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $1 (type $0) diff --git a/test/lit/basic/duplicate_types.wast b/test/lit/basic/duplicate_types.wast index 244d97bc9fe..1c1462b3704 100644 --- a/test/lit/basic/duplicate_types.wast +++ b/test/lit/basic/duplicate_types.wast @@ -25,12 +25,10 @@ ;; CHECK-TEXT: (type $1 (func (param i32) (result i32))) ;; CHECK-TEXT: (func $f0 (type $0) (param $0 i32) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (type $1 (func (param i32) (result i32))) ;; CHECK-BIN: (func $f0 (type $0) (param $0 i32) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $f0 (param i32)) @@ -47,7 +45,6 @@ ;; CHECK-BIN-NODEBUG: (type $1 (func (param i32) (result i32))) ;; CHECK-BIN-NODEBUG: (func $0 (type $0) (param $0 i32) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $1 (type $1) (param $0 i32) (result i32) diff --git a/test/lit/basic/exception-handling-legacy.wast b/test/lit/basic/exception-handling-legacy.wast index 6d6c82e83eb..70cc3f5bca6 100644 --- a/test/lit/basic/exception-handling-legacy.wast +++ b/test/lit/basic/exception-handling-legacy.wast @@ -47,18 +47,14 @@ (tag $e-empty) ;; CHECK-TEXT: (func $foo (type $0) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $foo (type $0) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $foo) ;; CHECK-TEXT: (func $bar (type $0) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $bar (type $0) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $bar) @@ -223,7 +219,6 @@ ;; CHECK-TEXT: (func $empty-try-body (type $0) ;; CHECK-TEXT-NEXT: (try ;; CHECK-TEXT-NEXT: (do - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch $e-i32 ;; CHECK-TEXT-NEXT: (drop @@ -235,7 +230,6 @@ ;; CHECK-BIN: (func $empty-try-body (type $0) ;; CHECK-BIN-NEXT: (try $label$3 ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-i32 ;; CHECK-BIN-NEXT: (drop @@ -357,7 +351,6 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch_all - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -369,7 +362,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -461,7 +453,6 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch_all - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -483,7 +474,6 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch_all - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -504,7 +494,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -526,7 +515,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -605,7 +593,6 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch_all - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -628,7 +615,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -674,7 +660,6 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch_all - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -703,7 +688,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -780,20 +764,16 @@ ;; CHECK-TEXT: (func $empty-catch-body (type $0) ;; CHECK-TEXT-NEXT: (try ;; CHECK-TEXT-NEXT: (do - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch $e-empty - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $empty-catch-body (type $0) ;; CHECK-BIN-NEXT: (try $label$3 ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-empty - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1068,7 +1048,6 @@ ;; CHECK-TEXT-NEXT: (rethrow $l0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch_all - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -1083,7 +1062,6 @@ ;; CHECK-TEXT-NEXT: (rethrow $l00) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch_all - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -1100,7 +1078,6 @@ ;; CHECK-BIN-NEXT: (rethrow $label$6) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1115,7 +1092,6 @@ ;; CHECK-BIN-NEXT: (rethrow $label$12) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1154,7 +1130,6 @@ ;; CHECK-TEXT: (func $pop-within-if-condition (type $0) ;; CHECK-TEXT-NEXT: (try ;; CHECK-TEXT-NEXT: (do - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch $e-i32 ;; CHECK-TEXT-NEXT: (throw $e-i32 @@ -1174,7 +1149,6 @@ ;; CHECK-BIN: (func $pop-within-if-condition (type $0) ;; CHECK-BIN-NEXT: (try $label$5 ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-i32 ;; CHECK-BIN-NEXT: (throw $e-i32 @@ -1214,7 +1188,6 @@ ;; CHECK-TEXT: (func $pop-can-be-supertype (type $0) ;; CHECK-TEXT-NEXT: (try ;; CHECK-TEXT-NEXT: (do - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (catch $e-eqref ;; CHECK-TEXT-NEXT: (drop @@ -1226,7 +1199,6 @@ ;; CHECK-BIN: (func $pop-can-be-supertype (type $0) ;; CHECK-BIN-NEXT: (try $label$3 ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-eqref ;; CHECK-BIN-NEXT: (drop @@ -1297,7 +1269,6 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (try ;; CHECK-TEXT-NEXT: (do - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (delegate 1) ;; CHECK-TEXT-NEXT: ) @@ -1310,7 +1281,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (try $label$5 ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (delegate 1) ;; CHECK-BIN-NEXT: ) @@ -1351,11 +1321,9 @@ ;; CHECK-BIN-NODEBUG: (tag $tag$4) ;; CHECK-BIN-NODEBUG: (func $0 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $1 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $2 (type $0) @@ -1430,7 +1398,6 @@ ;; CHECK-BIN-NODEBUG: (func $5 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (drop @@ -1484,7 +1451,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1528,7 +1494,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1550,7 +1515,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1586,7 +1550,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1615,7 +1578,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1640,10 +1602,8 @@ ;; CHECK-BIN-NODEBUG: (func $15 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$4 -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1745,7 +1705,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$6) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1760,7 +1719,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$12) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1770,7 +1728,6 @@ ;; CHECK-BIN-NODEBUG: (func $21 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (try $label$5 ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 @@ -1791,7 +1748,6 @@ ;; CHECK-BIN-NODEBUG: (func $22 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$3 ;; CHECK-BIN-NODEBUG-NEXT: (drop @@ -1824,7 +1780,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (try $label$5 ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (delegate 1) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/exception-handling.wast b/test/lit/basic/exception-handling.wast index 05e7c0e31f3..97be09c04ca 100644 --- a/test/lit/basic/exception-handling.wast +++ b/test/lit/basic/exception-handling.wast @@ -67,10 +67,8 @@ (tag $e-empty) ;; CHECK-TEXT: (func $foo (type $0) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $foo (type $0) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $foo) @@ -138,7 +136,6 @@ ;; CHECK-TEXT: (func $catchless-try-table (type $0) ;; CHECK-TEXT-NEXT: (try_table - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (try_table ;; CHECK-TEXT-NEXT: (throw $e-empty) @@ -146,7 +143,6 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $catchless-try-table (type $0) ;; CHECK-BIN-NEXT: (try_table - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (try_table ;; CHECK-BIN-NEXT: (throw $e-empty) @@ -753,7 +749,6 @@ ;; CHECK-BIN-NODEBUG: (tag $tag$4) ;; CHECK-BIN-NODEBUG: (func $0 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $1 (type $3) (result exnref) @@ -780,7 +775,6 @@ ;; CHECK-BIN-NODEBUG: (func $2 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (try_table -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (try_table ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$4) diff --git a/test/lit/basic/multi-table.wast b/test/lit/basic/multi-table.wast index 71dd275aa72..2d6b436a62d 100644 --- a/test/lit/basic/multi-table.wast +++ b/test/lit/basic/multi-table.wast @@ -97,18 +97,14 @@ (func $f (drop (ref.func $h))) ;; CHECK-TEXT: (func $g (type $none_=>_none) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $g (type $none_=>_none) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $g) ;; CHECK-TEXT: (func $h (type $none_=>_none) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $h (type $none_=>_none) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $h) ) @@ -155,9 +151,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $1 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $2 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/pop-fixup.wast b/test/lit/basic/pop-fixup.wast index 371365a0cc9..9ef51dad82d 100644 --- a/test/lit/basic/pop-fixup.wast +++ b/test/lit/basic/pop-fixup.wast @@ -12,7 +12,6 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e ;; CHECK-NEXT: (local.set $1 @@ -43,7 +42,6 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e ;; CHECK-NEXT: (local.set $1 @@ -78,7 +76,6 @@ ;; CHECK-NEXT: (local $2 (tuple i32 i32)) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e2 ;; CHECK-NEXT: (local.set $2 @@ -118,7 +115,6 @@ ;; CHECK-NEXT: (local $2 (tuple i32 i32)) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e2 ;; CHECK-NEXT: (local.set $2 @@ -164,7 +160,6 @@ ;; CHECK-NEXT: (local $3 (tuple i32 i32)) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e2 ;; CHECK-NEXT: (local.set $3 diff --git a/test/lit/basic/print-explicit-typeuse.wast b/test/lit/basic/print-explicit-typeuse.wast index d299a3eb430..367159f373d 100644 --- a/test/lit/basic/print-explicit-typeuse.wast +++ b/test/lit/basic/print-explicit-typeuse.wast @@ -33,19 +33,15 @@ (import "" "" (func $rec-import (type $rec))) ;; CHECK: (func $mvp - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $mvp (type $mvp)) ;; CHECK: (func $open (type $open) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $open (type $open)) ;; CHECK: (func $shared (type $shared) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $shared (type $shared)) ;; CHECK: (func $rec (type $rec) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $rec (type $rec)) ) diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 33593957382..6ef51c23d39 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -80,7 +80,6 @@ ;; CHECK-TEXT: (export "export_global" (global $import_global)) ;; CHECK-TEXT: (func $take_eqref (type $sig_eqref) (param $0 eqref) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (global $global_eqref (mut eqref) (ref.null none)) @@ -105,31 +104,24 @@ ;; CHECK-BIN: (export "export_global" (global $import_global)) ;; CHECK-BIN: (func $take_eqref (type $sig_eqref) (param $0 eqref) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $take_eqref (param eqref)) ;; CHECK-TEXT: (func $take_funcref (type $sig_funcref) (param $0 funcref) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $take_funcref (type $sig_funcref) (param $0 funcref) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $take_funcref (param funcref)) ;; CHECK-TEXT: (func $take_anyref (type $sig_anyref) (param $0 anyref) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $take_anyref (type $sig_anyref) (param $0 anyref) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $take_anyref (param anyref)) ;; CHECK-TEXT: (func $foo (type $5) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $foo (type $3) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $foo) @@ -2092,10 +2084,8 @@ ) ;; CHECK-TEXT: (func $ref-taken-but-not-in-table (type $5) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $ref-taken-but-not-in-table (type $3) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $ref-taken-but-not-in-table) ) @@ -2106,19 +2096,15 @@ ;; CHECK-BIN-NODEBUG: (export "export_global" (global $gimport$0)) ;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 eqref) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $1 (type $2) (param $0 funcref) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $2 (type $1) (param $0 anyref) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $3 (type $3) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $4 (type $3) @@ -2817,5 +2803,4 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $23 (type $3) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/shared-types.wast b/test/lit/basic/shared-types.wast index 25e593fd2a7..bcf9f6c54c4 100644 --- a/test/lit/basic/shared-types.wast +++ b/test/lit/basic/shared-types.wast @@ -30,7 +30,6 @@ ;; CHECK-NEXT: (local $3 (ref $bot)) ;; CHECK-NEXT: (local $4 (ref $func)) ;; CHECK-NEXT: (local $5 (ref $array)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $use-types (local (ref $final)) @@ -55,7 +54,6 @@ ;; CHECK-NEXT: (local $10 (ref (shared noextern))) ;; CHECK-NEXT: (local $11 (ref (shared nofunc))) ;; CHECK-NEXT: (local $12 (ref (shared noexn))) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $use-basic-types (local (ref (shared extern))) diff --git a/test/lit/basic/types-function-references.wast b/test/lit/basic/types-function-references.wast index 792536cfe30..9500fa4c94c 100644 --- a/test/lit/basic/types-function-references.wast +++ b/test/lit/basic/types-function-references.wast @@ -174,13 +174,11 @@ ;; CHECK-TEXT: (func $type-only-in-tuple-local (type $void) ;; CHECK-TEXT-NEXT: (local $x (tuple i32 (ref null $=>anyref) f64)) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $type-only-in-tuple-local (type $void) ;; CHECK-BIN-NEXT: (local $x i32) ;; CHECK-BIN-NEXT: (local $1 f64) ;; CHECK-BIN-NEXT: (local $2 (ref null $=>anyref)) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $type-only-in-tuple-local (local $x (tuple i32 (ref null $=>anyref) f64)) @@ -246,7 +244,6 @@ ;; CHECK-TEXT-NEXT: (local $i3 i64) ;; CHECK-TEXT-NEXT: (local $r5 anyref) ;; CHECK-TEXT-NEXT: (local $r6 funcref) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $ref-types-first (type $void) ;; CHECK-BIN-NEXT: (local $r1 (ref null $mixed_results)) @@ -258,7 +255,6 @@ ;; CHECK-BIN-NEXT: (local $i1 i32) ;; CHECK-BIN-NEXT: (local $i2 i64) ;; CHECK-BIN-NEXT: (local $i3 i64) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $ref-types-first ;; 6 reference types and 3 MVP types. The binary format should emit all the @@ -286,7 +282,6 @@ ;; CHECK-TEXT-NEXT: (local $i3 i64) ;; CHECK-TEXT-NEXT: (local $r5 anyref) ;; CHECK-TEXT-NEXT: (local $r6 funcref) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $mvp-types-first (type $void) ;; CHECK-BIN-NEXT: (local $i1 i32) @@ -298,7 +293,6 @@ ;; CHECK-BIN-NEXT: (local $r4 anyref) ;; CHECK-BIN-NEXT: (local $r5 anyref) ;; CHECK-BIN-NEXT: (local $r6 funcref) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $mvp-types-first ;; Reversed from before, now an MVP type appears first, so they should all @@ -324,7 +318,6 @@ ;; CHECK-TEXT-NEXT: (local $i3 i64) ;; CHECK-TEXT-NEXT: (local $r5 anyref) ;; CHECK-TEXT-NEXT: (local $r6 funcref) - ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $mvp-types-first-param (type $10) (param $r0 (ref null $mixed_results)) ;; CHECK-BIN-NEXT: (local $i1 i32) @@ -336,7 +329,6 @@ ;; CHECK-BIN-NEXT: (local $r4 anyref) ;; CHECK-BIN-NEXT: (local $r5 anyref) ;; CHECK-BIN-NEXT: (local $r6 funcref) - ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $mvp-types-first-param (param $r0 (ref null $mixed_results)) ;; As before, but now there is a reference type *parameter*. We should @@ -428,7 +420,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local $0 i32) ;; CHECK-BIN-NODEBUG-NEXT: (local $1 f64) ;; CHECK-BIN-NODEBUG-NEXT: (local $2 (ref null $9)) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $8 (type $2) @@ -477,7 +468,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local $6 i32) ;; CHECK-BIN-NODEBUG-NEXT: (local $7 i64) ;; CHECK-BIN-NODEBUG-NEXT: (local $8 i64) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $10 (type $2) @@ -490,7 +480,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local $6 anyref) ;; CHECK-BIN-NODEBUG-NEXT: (local $7 anyref) ;; CHECK-BIN-NODEBUG-NEXT: (local $8 funcref) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $11 (type $10) (param $0 (ref null $0)) @@ -503,5 +492,4 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local $7 anyref) ;; CHECK-BIN-NODEBUG-NEXT: (local $8 anyref) ;; CHECK-BIN-NODEBUG-NEXT: (local $9 funcref) -;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/debug/source-map-smearing.wast b/test/lit/debug/source-map-smearing.wast index 875bec9d3f1..eeeb49fea07 100644 --- a/test/lit/debug/source-map-smearing.wast +++ b/test/lit/debug/source-map-smearing.wast @@ -11,7 +11,7 @@ ;; Check that the debug locations do not smear beyond a function ;; epilogue to the next function. The encoded segment 'C' means that ;; the previous segment is indeed one-byte long. -;; CHECK: {"version":3,"sources":["foo"],"names":[],"mappings":"yBAAC,C,GACC"} +;; CHECK: {"version":3,"sources":["foo"],"names":[],"mappings":"wBAAC,C,EACC"} (module (func $0 ;;@ foo:1:1 diff --git a/test/lit/if-then-else.wast b/test/lit/if-then-else.wast index c1247af96ec..7fa59fad0e5 100644 --- a/test/lit/if-then-else.wast +++ b/test/lit/if-then-else.wast @@ -7,7 +7,6 @@ ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (return @@ -23,7 +22,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return diff --git a/test/lit/merge/chain.wat b/test/lit/merge/chain.wat index 528b20d0eed..9faca1143a9 100644 --- a/test/lit/merge/chain.wat +++ b/test/lit/merge/chain.wat @@ -16,7 +16,6 @@ ;; CHECK: (export "h" (func $0_2)) ;; CHECK: (func $0 (type $0) -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK: (func $0_2 (type $0) diff --git a/test/lit/merge/names.wat b/test/lit/merge/names.wat index 504dac102c2..6f51b54cac5 100644 --- a/test/lit/merge/names.wat +++ b/test/lit/merge/names.wat @@ -89,7 +89,6 @@ ;; CHECK: (export "func2" (func $5)) ;; CHECK: (func $func0 (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func0 (export "f0")) (func (export "f1")) @@ -117,21 +116,16 @@ (func (export "func") (param $x (ref $t))) ) ;; CHECK: (func $1 (type $0) -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK: (func $2 (type $3) (param $x (ref $t)) -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK: (func $func2 (type $0) -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK: (func $4 (type $0) -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK: (func $5 (type $4) (param $0 (ref $u)) -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) diff --git a/test/lit/merge/renamings.wat b/test/lit/merge/renamings.wat index 9c54f3514ec..e680b276b3e 100644 --- a/test/lit/merge/renamings.wat +++ b/test/lit/merge/renamings.wat @@ -141,7 +141,6 @@ ;; CHECK: (func $uses (type $3) (param $array (ref $array)) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $foo ;; CHECK-NEXT: (drop @@ -151,7 +150,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $bar ;; CHECK-NEXT: (drop @@ -323,7 +321,6 @@ ;; CHECK: (func $uses.second (type $3) (param $array (ref $array)) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $foo_2 ;; CHECK-NEXT: (drop @@ -333,7 +330,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $other ;; CHECK-NEXT: (drop diff --git a/test/lit/merge/table64.wat b/test/lit/merge/table64.wat index a313b5bc350..e1e41dca936 100644 --- a/test/lit/merge/table64.wat +++ b/test/lit/merge/table64.wat @@ -15,5 +15,4 @@ ;; CHECK: (export "table" (table $table)) ;; CHECK: (func $second (type $0) -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) diff --git a/test/lit/non-nullable-locals.wast b/test/lit/non-nullable-locals.wast index 21c12d4ad76..6349a0ed425 100644 --- a/test/lit/non-nullable-locals.wast +++ b/test/lit/non-nullable-locals.wast @@ -15,7 +15,6 @@ ;; CHECK: (func $no-uses (type $0) ;; CHECK-NEXT: (local $x (ref func)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $no-uses ;; A local with no uses validates. @@ -171,7 +170,6 @@ ) ;; CHECK: (func $helper (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $helper) diff --git a/test/lit/passes/abstract-type-refining.wast b/test/lit/passes/abstract-type-refining.wast index e887894ea74..5f6010ca07c 100644 --- a/test/lit/passes/abstract-type-refining.wast +++ b/test/lit/passes/abstract-type-refining.wast @@ -242,7 +242,6 @@ ;; YESTNH-NEXT: (local $C (ref $C)) ;; YESTNH-NEXT: (local $D (ref $E)) ;; YESTNH-NEXT: (local $E (ref $E)) - ;; YESTNH-NEXT: (nop) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $locals (type $6) ;; NO_TNH-NEXT: (local $A (ref $A)) @@ -250,7 +249,6 @@ ;; NO_TNH-NEXT: (local $C (ref $C)) ;; NO_TNH-NEXT: (local $D (ref $D)) ;; NO_TNH-NEXT: (local $E (ref $E)) - ;; NO_TNH-NEXT: (nop) ;; NO_TNH-NEXT: ) (func $locals ;; Local variable types are also updated. @@ -799,14 +797,12 @@ ;; YESTNH-NEXT: (local $B (ref none)) ;; YESTNH-NEXT: (local $C1 (ref none)) ;; YESTNH-NEXT: (local $C2 nullref) - ;; YESTNH-NEXT: (nop) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $locals (type $1) ;; NO_TNH-NEXT: (local $A (ref none)) ;; NO_TNH-NEXT: (local $B (ref none)) ;; NO_TNH-NEXT: (local $C1 (ref none)) ;; NO_TNH-NEXT: (local $C2 nullref) - ;; NO_TNH-NEXT: (nop) ;; NO_TNH-NEXT: ) (func $locals ;; All these locals can become nullable or even non-nullable null types. @@ -1002,21 +998,17 @@ ;; YESTNH: (type $3 (func (param funcref))) ;; YESTNH: (func $A (type $A) - ;; YESTNH-NEXT: (nop) ;; YESTNH-NEXT: ) ;; NO_TNH: (type $3 (func (param funcref))) ;; NO_TNH: (func $A (type $A) - ;; NO_TNH-NEXT: (nop) ;; NO_TNH-NEXT: ) (func $A (type $A) ) ;; YESTNH: (func $C (type $A) - ;; YESTNH-NEXT: (nop) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $C (type $A) - ;; NO_TNH-NEXT: (nop) ;; NO_TNH-NEXT: ) (func $C (type $A) ) diff --git a/test/lit/passes/asyncify_enable-multivalue.wast b/test/lit/passes/asyncify_enable-multivalue.wast index f29edd4a544..86a8bc47e65 100644 --- a/test/lit/passes/asyncify_enable-multivalue.wast +++ b/test/lit/passes/asyncify_enable-multivalue.wast @@ -193,7 +193,6 @@ (call $stuff) ;; do some more work ) ;; CHECK: (func $stuff - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $stuff) ;; the first event called from the main event loop: just call into $work @@ -2484,7 +2483,6 @@ (call $import) ) ;; CHECK: (func $boring - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $boring) ;; CHECK: (func $calls-mix-deep diff --git a/test/lit/passes/asyncify_optimize-level=1.wast b/test/lit/passes/asyncify_optimize-level=1.wast index d7627f40781..4839b08bbae 100644 --- a/test/lit/passes/asyncify_optimize-level=1.wast +++ b/test/lit/passes/asyncify_optimize-level=1.wast @@ -1425,7 +1425,6 @@ (call $import) ) ;; CHECK: (func $boring - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $boring) ;; CHECK: (func $calls-mix-deep diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast index 92dbe2bb230..2803d243d06 100644 --- a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast +++ b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast @@ -114,7 +114,6 @@ (call $nothing) ) ;; CHECK: (func $nothing - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $nothing ) diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-ignore-indirect.wast b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-ignore-indirect.wast index 81107cb164c..5f4fb89def7 100644 --- a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-ignore-indirect.wast +++ b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-ignore-indirect.wast @@ -155,7 +155,6 @@ (call_indirect (type $t) (i32.const 0)) ) ;; CHECK: (func $nothing - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $nothing ) diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-propagate-addlist.wast b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-propagate-addlist.wast index b0b758b4921..37075e800a4 100644 --- a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-propagate-addlist.wast +++ b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-propagate-addlist.wast @@ -113,7 +113,6 @@ (call $nothing) ) ;; CHECK: (func $nothing - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $nothing ) diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-imports@env.import,env.import2.wast b/test/lit/passes/asyncify_pass-arg=asyncify-imports@env.import,env.import2.wast index 801ab5eb954..36e1aa68ca1 100644 --- a/test/lit/passes/asyncify_pass-arg=asyncify-imports@env.import,env.import2.wast +++ b/test/lit/passes/asyncify_pass-arg=asyncify-imports@env.import,env.import2.wast @@ -193,7 +193,6 @@ (call $stuff) ;; do some more work ) ;; CHECK: (func $stuff - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $stuff) ;; the first event called from the main event loop: just call into $work @@ -1610,7 +1609,6 @@ (call $import) ) ;; CHECK: (func $boring - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $boring) ;; CHECK: (func $calls-mix-deep diff --git a/test/lit/passes/coalesce-locals-eh-legacy.wast b/test/lit/passes/coalesce-locals-eh-legacy.wast index 9091fdcb960..b5b41b66154 100644 --- a/test/lit/passes/coalesce-locals-eh-legacy.wast +++ b/test/lit/passes/coalesce-locals-eh-legacy.wast @@ -50,7 +50,6 @@ ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $any ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/code-folding-eh-legacy.wast b/test/lit/passes/code-folding-eh-legacy.wast index 852ec126a92..81180d04463 100644 --- a/test/lit/passes/code-folding-eh-legacy.wast +++ b/test/lit/passes/code-folding-eh-legacy.wast @@ -12,7 +12,6 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e-i32 ;; CHECK-NEXT: (drop @@ -112,7 +111,6 @@ ;; CHECK: (func $foo (type $1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) diff --git a/test/lit/passes/code-folding-eh.wast b/test/lit/passes/code-folding-eh.wast index 5a7cd68c783..94eb0b38d32 100644 --- a/test/lit/passes/code-folding-eh.wast +++ b/test/lit/passes/code-folding-eh.wast @@ -53,7 +53,6 @@ ;; CHECK: (func $foo (type $1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) diff --git a/test/lit/passes/code-pushing-eh-legacy.wast b/test/lit/passes/code-pushing-eh-legacy.wast index 9511d244a41..06b2e3e7140 100644 --- a/test/lit/passes/code-pushing-eh-legacy.wast +++ b/test/lit/passes/code-pushing-eh-legacy.wast @@ -17,7 +17,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -53,7 +52,6 @@ ) ;; CHECK: (func $foo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) @@ -166,7 +164,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -224,7 +221,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/code-pushing-eh.wast b/test/lit/passes/code-pushing-eh.wast index ee2798c4605..3c6d005b123 100644 --- a/test/lit/passes/code-pushing-eh.wast +++ b/test/lit/passes/code-pushing-eh.wast @@ -118,7 +118,6 @@ ) ;; CHECK: (func $foo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index f9e9c4651c9..8cefbe88184 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -316,7 +316,6 @@ ;; CHECK: (func $unused-and-refinable (type $2) ;; CHECK-NEXT: (local $0 structref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $unused-and-refinable (param $0 structref) ;; This function does not use $0. It is called with $"{}", so it is also diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 9878230207f..1f39567d146 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -155,15 +155,12 @@ ;; Helper functions so we have something to take the reference of. ;; CHECK: (func $a (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $a) ;; CHECK: (func $b (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $b) ;; CHECK: (func $c (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $c) ) diff --git a/test/lit/passes/dae_all-features.wast b/test/lit/passes/dae_all-features.wast index da9558dd8e0..145ae1ff032 100644 --- a/test/lit/passes/dae_all-features.wast +++ b/test/lit/passes/dae_all-features.wast @@ -33,7 +33,8 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $a (param $x i32)) ;; CHECK: (func $b (type $0) @@ -114,7 +115,8 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $a4 (param $x i32) ;; This function is called with one constant and one unreachable. We can @@ -219,7 +221,6 @@ (call $a7 (i32.const 1) (call $get-f64)) ) ;; CHECK: (func $a8 (type $1) (param $x i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $a8 (param $x i32)) ;; exported, do not optimize ;; CHECK: (func $b8 (type $0) @@ -231,7 +232,6 @@ (call $a8 (i32.const 1)) ) ;; CHECK: (func $a9 (type $1) (param $x i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $a9 (param $x i32)) ;; tabled, do not optimize ;; CHECK: (func $b9 (type $0) diff --git a/test/lit/passes/dae_tnh.wast b/test/lit/passes/dae_tnh.wast index 2bba6570b7c..6e78283fdce 100644 --- a/test/lit/passes/dae_tnh.wast +++ b/test/lit/passes/dae_tnh.wast @@ -57,7 +57,6 @@ ) ;; CHECK: (func $target (type $1) (param $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $target (param i32) ) @@ -105,7 +104,6 @@ ) ;; CHECK: (func $target (type $1) (param $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $target (param i32) ) diff --git a/test/lit/passes/dce-eh-legacy.wast b/test/lit/passes/dce-eh-legacy.wast index 107c35609cd..31ba4fac2de 100644 --- a/test/lit/passes/dce-eh-legacy.wast +++ b/test/lit/passes/dce-eh-legacy.wast @@ -25,7 +25,6 @@ (tag $e-eqref (param (ref null eq))) ;; CHECK: (func $foo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) @@ -35,7 +34,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $foo) @@ -53,7 +51,6 @@ ;; CHECK: (func $catch_unreachable (type $0) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (unreachable) @@ -96,7 +93,6 @@ ;; CHECK: (func $rethrow (type $0) ;; CHECK-NEXT: (try $l0 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/dce-eh.wast b/test/lit/passes/dce-eh.wast index 413a278d0b8..b2742913b54 100644 --- a/test/lit/passes/dce-eh.wast +++ b/test/lit/passes/dce-eh.wast @@ -11,7 +11,6 @@ (tag $e-i32 (param i32)) ;; CHECK: (func $foo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) diff --git a/test/lit/passes/denan.wast b/test/lit/passes/denan.wast index bc42ab04e57..9f167b15d91 100644 --- a/test/lit/passes/denan.wast +++ b/test/lit/passes/denan.wast @@ -59,7 +59,8 @@ ;; CHECK-NEXT: (local.get $w) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $various (param $x i32) (param $y f32) (param $z i64) (param $w f64) ) @@ -251,11 +252,9 @@ ;; CHECK: (type $2 (func (param f64) (result f64))) ;; CHECK: (func $deNan32 - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $deNan32) ;; CHECK: (func $deNan64 - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $deNan64) ;; CHECK: (func $foo32 (param $x f32) (result f32) diff --git a/test/lit/passes/flatten-eh-legacy.wast b/test/lit/passes/flatten-eh-legacy.wast index 56057b7798e..34b5cf08b82 100644 --- a/test/lit/passes/flatten-eh-legacy.wast +++ b/test/lit/passes/flatten-eh-legacy.wast @@ -61,7 +61,6 @@ ;; CHECK-NEXT: (block $l0 ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e-i32 ;; CHECK-NEXT: (local.set $1 diff --git a/test/lit/passes/flatten_i64-to-i32-lowering.wast b/test/lit/passes/flatten_i64-to-i32-lowering.wast index 5a185cc0aeb..e5e556c79ab 100644 --- a/test/lit/passes/flatten_i64-to-i32-lowering.wast +++ b/test/lit/passes/flatten_i64-to-i32-lowering.wast @@ -480,7 +480,6 @@ ;; CHECK: (export "unreach" (func $unreach)) ;; CHECK: (func $call (type $1) (param $0 i32) (param $0$hi i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $call (param i64)) ;; CHECK: (func $exp (type $0) @@ -586,7 +585,6 @@ ;; CHECK: (export "exp" (func $exp)) ;; CHECK: (func $call (type $0) (param $0 i32) (param $0$hi i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $call (param i64)) ;; CHECK: (func $exp (type $1) diff --git a/test/lit/passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast b/test/lit/passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast index 76b4e6e51f0..f71970035af 100644 --- a/test/lit/passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast +++ b/test/lit/passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast @@ -2477,7 +2477,6 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (loop $loopy - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (nop) diff --git a/test/lit/passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast b/test/lit/passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast index 5c11e34e364..d57df657913 100644 --- a/test/lit/passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast +++ b/test/lit/passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast @@ -318,7 +318,6 @@ ) ) ;; CHECK: (func $send-i32 (param $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $send-i32 (param i32)) ;; flipping of greater than/or equals ops, which are not in Souper IR @@ -2545,7 +2544,6 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (loop $loopy - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (nop) diff --git a/test/lit/passes/fpcast-emu.wast b/test/lit/passes/fpcast-emu.wast index 8ded8264724..9da081c4ac5 100644 --- a/test/lit/passes/fpcast-emu.wast +++ b/test/lit/passes/fpcast-emu.wast @@ -370,19 +370,15 @@ ;; CHECK: (export "dynCall_vd" (func $min_vd)) (export "dynCall_vd" (func $min_vd)) ;; CHECK: (func $a (param $0 f32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $a (param $0 f32)) ;; CHECK: (func $b (param $0 f64) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $b (param $0 f64)) ;; CHECK: (func $dynCall_vf (param $0 f32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $dynCall_vf (param $0 f32)) ;; CHECK: (func $min_vd (param $0 f32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $min_vd (param $0 f32)) ) diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index 1ede2009eb9..927dfd1f26c 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -18,10 +18,8 @@ ;; CLOSD: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) (global $func-func-init (mut funcref) (ref.func $foo)) ;; CHECK: (func $foo (type $foo_t) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CLOSD: (func $foo (type $foo_t) - ;; CLOSD-NEXT: (nop) ;; CLOSD-NEXT: ) (func $foo (type $foo_t)) ) @@ -188,10 +186,8 @@ (global $b (ref $super) (global.get $a)) ;; CHECK: (func $func (type $sub) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CLOSD: (func $func (type $sub) - ;; CLOSD-NEXT: (nop) ;; CLOSD-NEXT: ) (func $func (type $sub) ) diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index 99579f8ab1d..a6fd9c22857 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -12,7 +12,6 @@ ;; CHECK: (type $1 (func (param (ref $struct)))) ;; CHECK: (func $func (type $1) (param $x (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (param $x (ref $struct)) ) @@ -328,23 +327,18 @@ ) ;; CHECK: (func $func-0 (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-0) ;; CHECK: (func $func-1 (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-1) ;; CHECK: (func $func-2 (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-2) ;; CHECK: (func $func-3 (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-3) ;; CHECK: (func $func-4 (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-4) ) @@ -1254,7 +1248,6 @@ ;; CHECK: (type $2 (func (param (ref $B)))) ;; CHECK: (func $func (type $2) (param $x (ref $B)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (param $x (ref $B)) ;; Use $B in a param to keep it alive, and lead us to process it and $A. @@ -1273,7 +1266,6 @@ ;; CHECK: (type $2 (func (param (ref $B)))) ;; CHECK: (func $func (type $2) (param $x (ref $B)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (param $x (ref $B)) ) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 6722d0ed452..9772745bc28 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -1917,7 +1917,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $nothing ;; CHECK-NEXT: (local.set $0 @@ -1943,7 +1942,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $something ;; CHECK-NEXT: (drop @@ -2146,7 +2144,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag ;; CHECK-NEXT: (local.set $0 @@ -2164,7 +2161,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag ;; CHECK-NEXT: (drop @@ -2393,7 +2389,6 @@ ) ;; CHECK: (func $foo (type $1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) ) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 179437c3874..bad4a33bf30 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -485,7 +485,6 @@ ) ;; CHECK: (func $send-ref (type $5) (param $0 (ref null $struct.A)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $send-ref (param (ref null $struct.A)) ) diff --git a/test/lit/passes/inlining-eh-legacy.wast b/test/lit/passes/inlining-eh-legacy.wast index 9135f786ed0..4d9d3a7aa94 100644 --- a/test/lit/passes/inlining-eh-legacy.wast +++ b/test/lit/passes/inlining-eh-legacy.wast @@ -25,7 +25,6 @@ ;; CHECK-NEXT: (block $__inlined_func$callee-with-label ;; CHECK-NEXT: (try $label0 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag$0 ;; CHECK-NEXT: (drop @@ -52,7 +51,6 @@ ;; CHECK: (func $callee-with-try-delegate (type $0) ;; CHECK-NEXT: (try $label$3 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 0) ;; CHECK-NEXT: ) @@ -83,7 +81,6 @@ ;; CHECK: (func $caller-with-try-delegate (type $2) (result i32) ;; CHECK-NEXT: (try $label$3 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 0) ;; CHECK-NEXT: ) @@ -107,14 +104,14 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag$0 ;; CHECK-NEXT: (block $__inlined_func$callee-b$2 ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -144,7 +141,6 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag$0 ;; CHECK-NEXT: (local.set $2 @@ -159,7 +155,8 @@ ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/inlining-gc.wast b/test/lit/passes/inlining-gc.wast index 8da522e1d15..445cbf04208 100644 --- a/test/lit/passes/inlining-gc.wast +++ b/test/lit/passes/inlining-gc.wast @@ -8,7 +8,8 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller-nullable @@ -27,7 +28,8 @@ ;; CHECK: (func $caller-non-nullable (type $0) ;; CHECK-NEXT: (local $0 (ref func)) ;; CHECK-NEXT: (block $__inlined_func$target-non-nullable$1 - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller-non-nullable diff --git a/test/lit/passes/inlining_all-features.wast b/test/lit/passes/inlining_all-features.wast index 38d24ab72bb..860f5b8f75f 100644 --- a/test/lit/passes/inlining_all-features.wast +++ b/test/lit/passes/inlining_all-features.wast @@ -15,13 +15,13 @@ ;; $foo should not be removed after being inlined, because there is 'ref.func' ;; instruction that refers to it ;; CHECK: (func $foo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) ;; CHECK: (func $ref_func_test (type $1) (result funcref) ;; CHECK-NEXT: (block $__inlined_func$foo - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: ) @@ -160,7 +160,8 @@ ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller-with-pop-twice diff --git a/test/lit/passes/inlining_splitting.wast b/test/lit/passes/inlining_splitting.wast index 84c0b9832a3..f3d7f44a4d2 100644 --- a/test/lit/passes/inlining_splitting.wast +++ b/test/lit/passes/inlining_splitting.wast @@ -856,7 +856,6 @@ ) ;; CHECK: (func $byn-split-outlined-A$colliding-name (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $byn-split-outlined-A$colliding-name ;; This function's name might collide with the split function we create for diff --git a/test/lit/passes/instrument-locals-eh-legacy.wast b/test/lit/passes/instrument-locals-eh-legacy.wast index 8ee5535544f..23d944a0cff 100644 --- a/test/lit/passes/instrument-locals-eh-legacy.wast +++ b/test/lit/passes/instrument-locals-eh-legacy.wast @@ -9,7 +9,6 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e ;; CHECK-NEXT: (local.set $x diff --git a/test/lit/passes/j2cl-merge-itables.wast b/test/lit/passes/j2cl-merge-itables.wast index 499f598e127..8506724fd1a 100644 --- a/test/lit/passes/j2cl-merge-itables.wast +++ b/test/lit/passes/j2cl-merge-itables.wast @@ -65,7 +65,6 @@ (struct.new $Object.vtable)) ;; CHECK: (func $SubObject.f (type $function) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $SubObject.f (type $function) @@ -180,7 +179,6 @@ (struct.new_default $Object.itable)) ;; CHECK: (func $SubObject.f (type $function) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $SubObject.f (type $function) diff --git a/test/lit/passes/j2cl.wast b/test/lit/passes/j2cl.wast index 4fe661e5c96..e17d9f43c1c 100644 --- a/test/lit/passes/j2cl.wast +++ b/test/lit/passes/j2cl.wast @@ -212,7 +212,6 @@ ;; CHECK: (func $notOnceFunction@Zoo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $notOnceFunction@Zoo ) @@ -225,7 +224,6 @@ ) ;; CHECK: (func $empty__@Zoo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $empty__@Zoo ) @@ -255,7 +253,8 @@ ;; CHECK: (func $caller_@Zoo (type $1) (result i32) ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $notOnceFunction@Zoo) ;; CHECK-NEXT: (global.set $$var2@Zoo ;; CHECK-NEXT: (i32.const 3) diff --git a/test/lit/passes/legalize-js-interface-exported-helpers.wast b/test/lit/passes/legalize-js-interface-exported-helpers.wast index 707cbe5c9b1..bed768f1a0d 100644 --- a/test/lit/passes/legalize-js-interface-exported-helpers.wast +++ b/test/lit/passes/legalize-js-interface-exported-helpers.wast @@ -31,7 +31,6 @@ (i64.const 0) ) ;; CHECK: (func $__set_temp_ret (param $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $__set_temp_ret (param i32)) ;; CHECK: (func $__get_temp_ret (result i32) diff --git a/test/lit/passes/local-cse_all-features.wast b/test/lit/passes/local-cse_all-features.wast index 8878d595fe9..e8d7da4c7f5 100644 --- a/test/lit/passes/local-cse_all-features.wast +++ b/test/lit/passes/local-cse_all-features.wast @@ -509,7 +509,6 @@ ) ;; CHECK: (func $callee (type $2) (param $x i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $callee (param $x i32) ) diff --git a/test/lit/passes/monomorphize-context.wast b/test/lit/passes/monomorphize-context.wast index cc4bdfc9d2e..7f9cb2f566e 100644 --- a/test/lit/passes/monomorphize-context.wast +++ b/test/lit/passes/monomorphize-context.wast @@ -177,7 +177,6 @@ ) ;; ALWAYS: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 anyref) (param $11 funcref) (param $12 i32) (param $13 f64) (param $14 i32) (param $15 anyref) (param $16 anyref) - ;; ALWAYS-NEXT: (nop) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 anyref) (param $11 funcref) (param $12 i32) (param $13 f64) (param $14 i32) (param $15 anyref) (param $16 anyref) ;; CAREFUL-NEXT: (nop) @@ -285,7 +284,8 @@ ;; ALWAYS-NEXT: (local.set $22 ;; ALWAYS-NEXT: (struct.new_default $struct) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target_2 (type $2) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) @@ -1333,7 +1333,6 @@ ) ;; ALWAYS: (func $target (type $1) (param $0 i32) (param $1 i32) - ;; ALWAYS-NEXT: (nop) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target (type $1) (param $0 i32) (param $1 i32) ;; CAREFUL-NEXT: (nop) @@ -1353,7 +1352,8 @@ ;; ALWAYS-NEXT: (i32.const 0) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) (module (memory 10 20) @@ -1414,7 +1414,6 @@ ) ;; ALWAYS: (func $target (type $0) (param $0 i32) - ;; ALWAYS-NEXT: (nop) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target (type $1) (param $0 i32) ;; CAREFUL-NEXT: (nop) @@ -1432,7 +1431,8 @@ ;; ALWAYS-NEXT: (i32.const 1) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) (module ;; ALWAYS: (type $0 (func)) @@ -1551,7 +1551,6 @@ ) ;; ALWAYS: (func $target (type $2) (param $0 anyref) - ;; ALWAYS-NEXT: (nop) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target (type $1) (param $0 anyref) ;; CAREFUL-NEXT: (nop) @@ -1569,7 +1568,8 @@ ;; ALWAYS-NEXT: (local.get $1) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS: (func $target_4 (type $4) (param $0 i32) @@ -1582,7 +1582,8 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target_3 (type $2) (param $0 i32) (param $1 i32) @@ -1719,7 +1720,6 @@ ) ;; ALWAYS: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) - ;; ALWAYS-NEXT: (nop) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) ;; CAREFUL-NEXT: (nop) @@ -1756,7 +1756,8 @@ ;; ALWAYS-NEXT: (i32.const 0) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) (module ;; ALWAYS: (type $0 (func)) @@ -1795,10 +1796,8 @@ ) ;; ALWAYS: (func $target (type $1) (param $0 f32) - ;; ALWAYS-NEXT: (nop) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target (type $1) (param $0 f32) - ;; CAREFUL-NEXT: (nop) ;; CAREFUL-NEXT: ) (func $target (param $0 f32) ) diff --git a/test/lit/passes/monomorphize-limits.wast b/test/lit/passes/monomorphize-limits.wast index 344f55b51a0..29dd9de20d5 100644 --- a/test/lit/passes/monomorphize-limits.wast +++ b/test/lit/passes/monomorphize-limits.wast @@ -112,7 +112,6 @@ ) ;; ALWAYS: (func $many-params (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 i32) (param $11 i32) (param $12 i32) (param $13 i32) (param $14 i32) (param $15 i32) (param $16 i32) (param $17 i32) (param $18 i32) (param $19 i32) (param $20 i32) (param $21 i32) (param $22 i32) (param $23 i32) (param $24 i32) - ;; ALWAYS-NEXT: (nop) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $many-params (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 i32) (param $11 i32) (param $12 i32) (param $13 i32) (param $14 i32) (param $15 i32) (param $16 i32) (param $17 i32) (param $18 i32) (param $19 i32) (param $20 i32) (param $21 i32) (param $22 i32) (param $23 i32) (param $24 i32) ;; CAREFUL-NEXT: (nop) @@ -227,7 +226,8 @@ ;; ALWAYS-NEXT: (local.set $24 ;; ALWAYS-NEXT: (i32.const 25) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $many-params_3 (type $0) @@ -353,7 +353,6 @@ ) ;; ALWAYS: (func $target (type $3) (param $array (ref $array)) - ;; ALWAYS-NEXT: (nop) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target (type $3) (param $0 (ref $array)) ;; CAREFUL-NEXT: (nop) @@ -392,7 +391,8 @@ ;; ALWAYS-NEXT: (i32.const 25) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; CAREFUL: (func $target_3 (type $1) diff --git a/test/lit/passes/name-types.wast b/test/lit/passes/name-types.wast index bb31ffc8230..bde56309f70 100644 --- a/test/lit/passes/name-types.wast +++ b/test/lit/passes/name-types.wast @@ -34,7 +34,6 @@ ;; CHECK: (type $type (func (param (ref $type_1) (ref $reasonable-name) (ref $lintable-name) (ref $unlintable-name_7) (ref $unlintable-name) (ref $onelintable-name) (ref $onelintable-name_8)))) ;; CHECK: (func $foo (type $type) (param $x (ref $type_1)) (param $y (ref $reasonable-name)) (param $z (ref $lintable-name)) (param $w (ref $unlintable-name_7)) (param $t (ref $unlintable-name)) (param $a (ref $onelintable-name)) (param $b (ref $onelintable-name_8)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; Use the types to keep them alive. diff --git a/test/lit/passes/once-reduction.wast b/test/lit/passes/once-reduction.wast index af9f4f113ed..640d14461f1 100644 --- a/test/lit/passes/once-reduction.wast +++ b/test/lit/passes/once-reduction.wast @@ -201,7 +201,6 @@ ) ;; CHECK: (func $caller-empty (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller-empty ;; A tiny function with nothing at all, just to verify we do not crash on @@ -1481,7 +1480,6 @@ ) ;; CHECK: (func $bad-B (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $bad-B ) @@ -2022,7 +2020,6 @@ ) ;; CHECK: (func $other (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $other ) diff --git a/test/lit/passes/optimize-casts-noeh.wast b/test/lit/passes/optimize-casts-noeh.wast index 3cee5f3d441..3c3b5f8c504 100644 --- a/test/lit/passes/optimize-casts-noeh.wast +++ b/test/lit/passes/optimize-casts-noeh.wast @@ -63,7 +63,6 @@ ) ;; CHECK: (func $none (type $2) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $none ;; Helper for the above. diff --git a/test/lit/passes/optimize-casts.wast b/test/lit/passes/optimize-casts.wast index acf8fc7b425..0719cbd91f9 100644 --- a/test/lit/passes/optimize-casts.wast +++ b/test/lit/passes/optimize-casts.wast @@ -1398,7 +1398,6 @@ ) ;; CHECK: (func $void (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $void ;; Helper for the above. diff --git a/test/lit/passes/optimize-instructions-call_ref-roundtrip.wast b/test/lit/passes/optimize-instructions-call_ref-roundtrip.wast index 05f03110c40..c3e51d04984 100644 --- a/test/lit/passes/optimize-instructions-call_ref-roundtrip.wast +++ b/test/lit/passes/optimize-instructions-call_ref-roundtrip.wast @@ -46,15 +46,12 @@ (ref.func $helper-3)) ;; CHECK: (func $helper-1 (type $v1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $helper-1 (type $v1)) ;; CHECK: (func $helper-2 (type $v2) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $helper-2 (type $v2)) ;; CHECK: (func $helper-3 (type $v3) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $helper-3 (type $v3)) diff --git a/test/lit/passes/optimize-instructions-call_ref.wast b/test/lit/passes/optimize-instructions-call_ref.wast index 937fbe6a766..66e651a0694 100644 --- a/test/lit/passes/optimize-instructions-call_ref.wast +++ b/test/lit/passes/optimize-instructions-call_ref.wast @@ -189,7 +189,6 @@ ;; Helper function for the above test. ;; CHECK: (func $return-nothing (type $none_=>_none) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $return-nothing) diff --git a/test/lit/passes/optimize-instructions-eh-legacy.wast b/test/lit/passes/optimize-instructions-eh-legacy.wast index a6c65018380..dd51d6c1753 100644 --- a/test/lit/passes/optimize-instructions-eh-legacy.wast +++ b/test/lit/passes/optimize-instructions-eh-legacy.wast @@ -6,7 +6,6 @@ ;; CHECK: (tag $e (param i32)) ;; CHECK: (func $dummy (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $dummy) (tag $e (param i32)) @@ -164,7 +163,6 @@ ;; CHECK-NEXT: (call $dummy) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) @@ -209,7 +207,6 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (call $dummy) diff --git a/test/lit/passes/optimize-instructions-gc-iit.wast b/test/lit/passes/optimize-instructions-gc-iit.wast index ab3005af531..224e53d4050 100644 --- a/test/lit/passes/optimize-instructions-gc-iit.wast +++ b/test/lit/passes/optimize-instructions-gc-iit.wast @@ -19,10 +19,8 @@ (type $other (struct (field i64) (field f32))) ;; CHECK: (func $foo (type $3) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; TNH: (func $foo (type $3) - ;; TNH-NEXT: (nop) ;; TNH-NEXT: ) (func $foo) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 21d6291a921..4b88507ffc7 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -472,7 +472,6 @@ ) ;; CHECK: (func $nothing (type $5) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $nothing) diff --git a/test/lit/passes/optimize-instructions-iit-eh-legacy.wast b/test/lit/passes/optimize-instructions-iit-eh-legacy.wast index c0e4a05da92..79cb6f6f1c8 100644 --- a/test/lit/passes/optimize-instructions-iit-eh-legacy.wast +++ b/test/lit/passes/optimize-instructions-iit-eh-legacy.wast @@ -11,7 +11,6 @@ ;; CHECK: (func $ref-cast-statically-removed (type $2) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e ;; CHECK-NEXT: (throw $e diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 8b1afa76aa8..f18c94c5c05 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -15688,7 +15688,6 @@ ) ) ;; CHECK: (func $send-i32 (param $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $send-i32 (param i32)) ;; CHECK: (func $ternary-identical-arms-call (param $x i32) (param $y i32) (param $z i32) diff --git a/test/lit/passes/remove-unused-brs-eh.wast b/test/lit/passes/remove-unused-brs-eh.wast index 8ce578101c3..336b3b5a807 100644 --- a/test/lit/passes/remove-unused-brs-eh.wast +++ b/test/lit/passes/remove-unused-brs-eh.wast @@ -313,7 +313,6 @@ ;; CHECK-NEXT: (block $middle ;; CHECK-NEXT: (block $inner ;; CHECK-NEXT: (try_table (catch $e $outer) (catch $f $outer) (catch_all $outer) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -340,7 +339,6 @@ ;; CHECK-NEXT: (block $middle ;; CHECK-NEXT: (block $inner ;; CHECK-NEXT: (try_table (catch $e $outer) (catch $f $middle) (catch_all $outer) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer) diff --git a/test/lit/passes/remove-unused-brs.wast b/test/lit/passes/remove-unused-brs.wast index 6fcc5b066a3..3380db6b70b 100644 --- a/test/lit/passes/remove-unused-brs.wast +++ b/test/lit/passes/remove-unused-brs.wast @@ -105,7 +105,6 @@ ) ;; CHECK: (func $nothing (type $1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $nothing) diff --git a/test/lit/passes/remove-unused-brs_all-features.wast b/test/lit/passes/remove-unused-brs_all-features.wast index 98f0202745c..4e722a04380 100644 --- a/test/lit/passes/remove-unused-brs_all-features.wast +++ b/test/lit/passes/remove-unused-brs_all-features.wast @@ -115,7 +115,6 @@ (unreachable) ) ;; CHECK: (func $i32_=>_none (type $2) (param $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $i32_=>_none (param i32) ) diff --git a/test/lit/passes/remove-unused-module-elements-refs.wast b/test/lit/passes/remove-unused-module-elements-refs.wast index 146a25ea608..bd4940037c8 100644 --- a/test/lit/passes/remove-unused-module-elements-refs.wast +++ b/test/lit/passes/remove-unused-module-elements-refs.wast @@ -113,10 +113,8 @@ ) ;; CHECK: (func $target-A (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A (type $A) ;; This function is reachable from the export "foo": there is a RefFunc and @@ -129,10 +127,8 @@ ) ;; CHECK: (func $target-A-sub (type $A-sub) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-sub (type $A-sub) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-sub (type $A-sub) ;; This function is reachable because we have a CallRef of a supertype ($A). @@ -142,7 +138,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-super (type $A-super) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-super (type $A-super) ;; This function is not reachable. We have a CallRef of a subtype ($A), but @@ -153,7 +148,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-B (type $B) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-B (type $B) ;; This function is not reachable. We have a RefFunc in "foo" but no @@ -213,10 +207,8 @@ ) ;; CHECK: (func $target-A (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A (type $A) ;; This function is reachable. @@ -290,20 +282,16 @@ ;; WORLD_OPEN-NEXT: ) ;; CHECK: (func $target-A-1 (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-1 (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-1 (type $A) ;; This function is reachable. ) ;; CHECK: (func $target-A-2 (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-2 (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-2 (type $A) ;; This function is reachable. @@ -377,20 +365,16 @@ ) ;; CHECK: (func $target-A-1 (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-1 (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-1 (type $A) ;; This function is reachable. ) ;; CHECK: (func $target-A-2 (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-2 (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-2 (type $A) ;; This function is reachable. @@ -524,10 +508,8 @@ ) ;; CHECK: (func $target-keep (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-keep (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-keep (type $A) ) @@ -536,7 +518,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-drop (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-drop (type $A) ;; In a closed world we can turn this body into unreachable. @@ -617,19 +598,15 @@ ) ;; CHECK: (func $target-keep (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-keep (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-keep (type $A) ) ;; CHECK: (func $target-keep-2 (type $A) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-keep-2 (type $A) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-keep-2 (type $A) ) @@ -974,10 +951,8 @@ ) ;; CHECK: (func $void (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $void (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $void (type $void) ;; Helper function. This is reached via a call_ref. @@ -987,7 +962,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $a (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $a (type $void) ;; This is unreachable (in closed world) since a reference to it only exists @@ -995,10 +969,8 @@ ) ;; CHECK: (func $b (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $b (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $b (type $void) ;; This is reachable. It is in field #1, which is read, and the global @@ -1009,7 +981,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $c (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $c (type $void) ;; Like $a, this is unreachable. That it is in a nested struct.new, and not @@ -1017,10 +988,8 @@ ) ;; CHECK: (func $d (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $d (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $d (type $void) ;; Like $b, this is reachable. That it is in a nested struct.new, and not @@ -1031,7 +1000,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $e (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $e (type $void) ;; Side effects on the struct field are not enough to make this reachable: @@ -1040,10 +1008,8 @@ ) ;; CHECK: (func $f (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f (type $void) ;; Like $b, this is reachable (the tee does not matter). @@ -1053,7 +1019,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $g (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $g (type $void) ;; This is in a struct written to a field that is never read in $struct, so @@ -1064,7 +1029,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $h (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $h (type $void) ;; This is in a struct written to a field that is never read in $struct, so @@ -1141,10 +1105,8 @@ ) ;; CHECK: (func $void (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $void (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $void (type $void) ;; Helper function. This is reached via a call_ref. @@ -1154,7 +1116,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $a (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $a (type $void) ;; This is unreachable (in closed world) because we have no reads from the @@ -1162,10 +1123,8 @@ ) ;; CHECK: (func $b (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $b (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $b (type $void) ;; The local.tee makes this reachable: the value is not known to only reside @@ -1503,7 +1462,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f (type $void) ;; This is unreachable in closed world. The global it is in has a reference @@ -1511,20 +1469,16 @@ ) ;; CHECK: (func $subf (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $subf (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $subf (type $void) ;; There is a read of $substruct's field, which makes this reachable. ) ;; CHECK: (func $subsubf (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $subsubf (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $subsubf (type $void) ;; There is a read of $substruct's field, which may read from any subtype, @@ -1607,10 +1561,8 @@ ) ;; CHECK: (func $f1 (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f1 (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f1 (type $void) ;; The global containing this function's reference is used. @@ -1620,7 +1572,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f2 (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f2 (type $void) ;; This is unreachable in closed world as the global is referred to from a @@ -1727,7 +1678,6 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f (type $void) ;; This is unreachable in closed world since $B's field is not read, so the @@ -1851,10 +1801,8 @@ ) ;; CHECK: (func $f (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f (type $void) - ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f (type $void) ) diff --git a/test/lit/passes/remove-unused-module-elements_all-features.wast b/test/lit/passes/remove-unused-module-elements_all-features.wast index 7c2ad11d801..8b4d7f18638 100644 --- a/test/lit/passes/remove-unused-module-elements_all-features.wast +++ b/test/lit/passes/remove-unused-module-elements_all-features.wast @@ -186,7 +186,6 @@ ;; CHECK: (elem $1 (i32.const 0) $f) ;; CHECK: (func $f (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $f) ) @@ -227,7 +226,6 @@ ;; CHECK: (elem $0 (i32.const 0) $waka) ;; CHECK: (func $waka (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $waka) ) @@ -459,7 +457,6 @@ ;; CHECK: (elem $0 (global.get $tableBase) $waka) ;; CHECK: (func $waka (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $waka) ;; used in table ) @@ -819,7 +816,6 @@ ) ;; CHECK: (func $internal (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $internal ) diff --git a/test/lit/passes/remove-unused-module-elements_tnh.wast b/test/lit/passes/remove-unused-module-elements_tnh.wast index 22c09740dfc..5378431912e 100644 --- a/test/lit/passes/remove-unused-module-elements_tnh.wast +++ b/test/lit/passes/remove-unused-module-elements_tnh.wast @@ -162,12 +162,10 @@ (elem $bad (i32.const 10) $func) ;; CHECK: (func $func (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; T_N_H: (type $0 (func)) ;; T_N_H: (func $func (type $0) - ;; T_N_H-NEXT: (nop) ;; T_N_H-NEXT: ) (func $func) ) @@ -190,12 +188,10 @@ (elem $bad (i32.const 9) $func $func) ;; CHECK: (func $func (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; T_N_H: (type $0 (func)) ;; T_N_H: (func $func (type $0) - ;; T_N_H-NEXT: (nop) ;; T_N_H-NEXT: ) (func $func) ) @@ -211,12 +207,10 @@ ;; CHECK: (type $0 (func)) ;; CHECK: (func $func (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; T_N_H: (type $0 (func)) ;; T_N_H: (func $func (type $0) - ;; T_N_H-NEXT: (nop) ;; T_N_H-NEXT: ) (func $func) ) diff --git a/test/lit/passes/remove-unused-names-eh-legacy.wast b/test/lit/passes/remove-unused-names-eh-legacy.wast index 729e50addb6..797dabb55cd 100644 --- a/test/lit/passes/remove-unused-names-eh-legacy.wast +++ b/test/lit/passes/remove-unused-names-eh-legacy.wast @@ -8,7 +8,6 @@ ;; CHECK: (func $func0 (type $0) ;; CHECK-NEXT: (try $label$9 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (try $label$8 diff --git a/test/lit/passes/rse-eh-legacy.wast b/test/lit/passes/rse-eh-legacy.wast index 7cdb2bccf7f..95a1459c3ba 100644 --- a/test/lit/passes/rse-eh-legacy.wast +++ b/test/lit/passes/rse-eh-legacy.wast @@ -11,7 +11,6 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (local.set $x @@ -47,7 +46,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x @@ -102,7 +100,6 @@ ) ;; CHECK: (func $foo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) @@ -116,7 +113,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x @@ -147,7 +143,6 @@ ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -235,7 +230,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -285,7 +279,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x @@ -498,7 +491,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -560,7 +552,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x @@ -690,7 +681,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x @@ -754,7 +744,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -762,7 +751,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/rse-eh.wast b/test/lit/passes/rse-eh.wast index 100543ae980..8c26946b1ac 100644 --- a/test/lit/passes/rse-eh.wast +++ b/test/lit/passes/rse-eh.wast @@ -14,7 +14,6 @@ (tag $e-empty) ;; CHECK: (func $foo (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) @@ -23,7 +22,6 @@ ;; CHECK-NEXT: (block $outer ;; CHECK-NEXT: (block $catch_all ;; CHECK-NEXT: (try_table (catch_all $catch_all) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/rse-gc.wast b/test/lit/passes/rse-gc.wast index 26a973da099..d00f6d3c434 100644 --- a/test/lit/passes/rse-gc.wast +++ b/test/lit/passes/rse-gc.wast @@ -13,7 +13,6 @@ ;; CHECK: (func $test (type $3) ;; CHECK-NEXT: (local $single (ref func)) ;; CHECK-NEXT: (local $tuple (tuple (ref any) (ref any))) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $test ;; A non-nullable local. The pass should ignore it (as we cannot optimize diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast index b5c5b26e28d..6a20d281551 100644 --- a/test/lit/passes/signature-pruning.wast +++ b/test/lit/passes/signature-pruning.wast @@ -308,7 +308,6 @@ ;; CHECK-NEXT: (local $1 f32) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64) ;; Use nothing at all: all params can be removed. @@ -394,7 +393,6 @@ ;; CHECK: (func $foo (type $sig) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (type $sig) (param $i32 i32) ;; This function does not use the parameter. It also has no calls, but that @@ -416,7 +414,6 @@ ;; CHECK: (memory $0 1 1) ;; CHECK: (func $foo (type $sig) (param $i32 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (type $sig) (param $i32 i32) ) @@ -431,7 +428,6 @@ ;; CHECK: (memory $0 1 1) ;; CHECK: (func $foo (type $sig) (param $i32 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (type $sig) (param $i32 i32) ) @@ -469,7 +465,6 @@ ;; CHECK: (func $foo (type $sig) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (type $sig) (param $i32 i32) ) @@ -505,14 +500,12 @@ ;; CHECK: (func $foo (type $sig) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (type $sig) (param $i32 i32) ) ;; CHECK: (func $bar (type $sig) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $bar (type $sig) (param $i32 i32) ;; As above, but the second function also does not use the parameter, and @@ -574,7 +567,6 @@ ;; CHECK: (table $0 1 1 anyref) ;; CHECK: (func $foo (type $sig) (param $i32 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (type $sig) (param $i32 i32) ) @@ -593,13 +585,11 @@ ;; CHECK: (export "bar" (func $bar)) ;; CHECK: (func $foo (type $sig) (param $i32 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (export "foo") (type $sig) (param $i32 i32) ) ;; CHECK: (func $bar (type $sig) (param $i32 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $bar (export "bar") (type $sig) (param $i32 i32) ) @@ -632,14 +622,12 @@ ;; CHECK: (func $foo1 (type $sig1) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo1 (type $sig1) (param $i32 i32) ) ;; CHECK: (func $foo2 (type $sig2) ;; CHECK-NEXT: (local $0 f64) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo2 (type $sig2) (param $f64 f64) ) @@ -1174,13 +1162,11 @@ ;; CHECK: (export "exported" (func $exported)) ;; CHECK: (func $exported (type $none) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $exported (export "exported") (type $none) ) ;; CHECK: (func $unused-param (type $much) (param $param i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $unused-param (type $much) (param $param i32) ) @@ -1219,14 +1205,12 @@ ;; CHECK: (export "exported" (func $exported)) ;; CHECK: (func $exported (type $none) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $exported (export "exported") (type $none) ;; This makes the rec group public. ) ;; CHECK: (func $unused-param (type $much) (param $param i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $unused-param (type $much) (param $param i32) ) diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast index f6f88940847..b401237b8a7 100644 --- a/test/lit/passes/signature-refining.wast +++ b/test/lit/passes/signature-refining.wast @@ -17,7 +17,6 @@ (type $sig (sub (func (param anyref)))) ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -49,7 +48,6 @@ ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -85,7 +83,6 @@ ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x eqref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -140,13 +137,11 @@ ) ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-1 (type $sig) (param $x anyref) ) ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-2 (type $sig) (param $x anyref) ) @@ -184,13 +179,11 @@ (type $struct (struct)) ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-1 (type $sig) (param $x anyref) ) ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-2 (type $sig) (param $x anyref) ) @@ -288,7 +281,6 @@ ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -327,7 +319,6 @@ ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x anyref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -356,7 +347,6 @@ (type $sig (sub (func (param anyref)))) ;; CHECK: (func $func (type $sig) (param $x anyref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -381,13 +371,11 @@ ;; CHECK: (elem declare func $func-2) ;; CHECK: (func $func-1 (type $sig-1) (param $x structref) (param $y anyref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-1 (type $sig-1) (param $x anyref) (param $y anyref) ) ;; CHECK: (func $func-2 (type $sig-2) (param $x eqref) (param $y (ref $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-2 (type $sig-2) (param $x anyref) (param $y anyref) ) @@ -455,7 +443,6 @@ ;; CHECK: (table $0 1 1 anyref) ;; CHECK: (func $func (type $sig) (param $x anyref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -487,7 +474,6 @@ (type $struct (struct)) ;; CHECK: (func $func (type $sig) (param $x (ref null $struct)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -692,7 +678,6 @@ ;; CHECK: (export "prevent-opts" (func $func)) ;; CHECK: (func $func (type $sig) (param $x anyref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (export "prevent-opts") (type $sig) (param $x anyref) ) @@ -1057,7 +1042,6 @@ ) ;; CHECK: (func $target (type $5) (param $x (ref $A)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $target (param $x (ref $A)) ;; Because of the two calls above, this cannot be refined. @@ -1109,7 +1093,6 @@ ) ;; CHECK: (func $target (type $1) (param $x (ref $B)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $target (param $x (ref $A)) ;; The two calls above both send $B, so we can refine the parameter to $B. @@ -1142,7 +1125,6 @@ (export "struct" (global $struct)) ;; CHECK: (func $func (type $sig) (param $x anyref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) diff --git a/test/lit/passes/simplify-locals-eh-legacy.wast b/test/lit/passes/simplify-locals-eh-legacy.wast index d7fb757769c..f9ab2f8c197 100644 --- a/test/lit/passes/simplify-locals-eh-legacy.wast +++ b/test/lit/passes/simplify-locals-eh-legacy.wast @@ -5,14 +5,12 @@ ;; CHECK: (tag $e-i32 (param i32)) (tag $e-i32 (param i32)) ;; CHECK: (func $foo (type $3) (param $0 i32) (param $1 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (param i32 i32)) ;; CHECK: (func $pop-cannot-be-sinked (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e-i32 ;; CHECK-NEXT: (local.set $0 @@ -44,7 +42,6 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (nop) diff --git a/test/lit/passes/simplify-locals-gc.wast b/test/lit/passes/simplify-locals-gc.wast index b3f6ac2fc26..85ec16d99ca 100644 --- a/test/lit/passes/simplify-locals-gc.wast +++ b/test/lit/passes/simplify-locals-gc.wast @@ -262,7 +262,6 @@ ) ;; CHECK: (func $helper (type $8) (param $ref (ref func)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $helper (param $ref (ref func)) ) @@ -491,14 +490,12 @@ ) ;; CHECK: (func $use-nn-any (type $15) (param $nn-any (ref any)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $use-nn-any (param $nn-any (ref any)) ;; Helper function for the above. ) ;; CHECK: (func $use-any (type $7) (param $any anyref) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $use-any (param $any anyref) ;; Helper function for the above. diff --git a/test/lit/passes/simplify-locals-global.wast b/test/lit/passes/simplify-locals-global.wast index 215b32a3c25..a471225d9b8 100644 --- a/test/lit/passes/simplify-locals-global.wast +++ b/test/lit/passes/simplify-locals-global.wast @@ -45,7 +45,6 @@ ) ;; CHECK: (func $helper - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $helper) ) diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast index 68b85360719..b082cd88884 100644 --- a/test/lit/passes/ssa.wast +++ b/test/lit/passes/ssa.wast @@ -6,7 +6,6 @@ (type $A (struct )) ;; CHECK: (func $foo (type $1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) diff --git a/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast b/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast index cedcdbf4537..439681a8fd5 100644 --- a/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast +++ b/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast @@ -9,7 +9,6 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: (try $label$7 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag ;; CHECK-NEXT: (drop @@ -17,7 +16,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try $label$6 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 2) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/string-lowering_types.wast b/test/lit/passes/string-lowering_types.wast index cab84a1b8c9..2274fcaf9f5 100644 --- a/test/lit/passes/string-lowering_types.wast +++ b/test/lit/passes/string-lowering_types.wast @@ -65,7 +65,6 @@ ;; CHECK: (func $export (type $4) ;; CHECK-NEXT: (local $0 (ref $private)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $export (export "export") ;; Keep the private type alive. diff --git a/test/lit/passes/translate-to-exnref.wast b/test/lit/passes/translate-to-exnref.wast index 8bac0dc39fa..388bcf8bda8 100644 --- a/test/lit/passes/translate-to-exnref.wast +++ b/test/lit/passes/translate-to-exnref.wast @@ -43,19 +43,16 @@ (tag $e-i32-i64 (param i32 i64)) ;; CHECK: (func $foo (type $1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; STACKIR-OPT: (func $foo (type $1) ;; STACKIR-OPT-NEXT: ) (func $foo) ;; CHECK: (func $bar (type $1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; STACKIR-OPT: (func $bar (type $1) ;; STACKIR-OPT-NEXT: ) (func $bar) ;; CHECK: (func $baz (type $1) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; STACKIR-OPT: (func $baz (type $1) ;; STACKIR-OPT-NEXT: ) @@ -1621,7 +1618,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; STACKIR-OPT: (func $delegate-target-outer-try-none (type $1) @@ -1678,7 +1676,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; STACKIR-OPT: (func $multiple-delegates-target-outer-try-none (type $1) @@ -1963,14 +1962,16 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; STACKIR-OPT: (func $delegate-nested-more (type $1) diff --git a/test/lit/passes/type-finalizing.wast b/test/lit/passes/type-finalizing.wast index 6897c6abf82..f1511246b0b 100644 --- a/test/lit/passes/type-finalizing.wast +++ b/test/lit/passes/type-finalizing.wast @@ -78,7 +78,6 @@ ;; UNFINAL-NEXT: (local $parent (ref $parent)) ;; UNFINAL-NEXT: (local $child-final (ref $child-final)) ;; UNFINAL-NEXT: (local $child-open (ref $child-open)) - ;; UNFINAL-NEXT: (nop) ;; UNFINAL-NEXT: ) ;; DOFINAL: (type $3 (func)) @@ -86,7 +85,6 @@ ;; DOFINAL-NEXT: (local $parent (ref $parent)) ;; DOFINAL-NEXT: (local $child-final (ref $child-final)) ;; DOFINAL-NEXT: (local $child-open (ref $child-open)) - ;; DOFINAL-NEXT: (nop) ;; DOFINAL-NEXT: ) (func $keepalive (local $parent (ref $parent)) diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index 00a809648d5..66e3520056d 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -54,7 +54,6 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y anyref) ;; CHECK-NEXT: (local $z (tuple anyref i32)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $unconstrained ;; This non-ref local should be unmodified diff --git a/test/lit/passes/type-merging-shared.wast b/test/lit/passes/type-merging-shared.wast index fb7fa89b7a5..99b030f016a 100644 --- a/test/lit/passes/type-merging-shared.wast +++ b/test/lit/passes/type-merging-shared.wast @@ -28,7 +28,6 @@ ;; CHECK-NEXT: (local $b' (ref null $B')) ;; CHECK-NEXT: (local $c (ref null $C)) ;; CHECK-NEXT: (local $c' (ref null $C')) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $a (ref null $A)) @@ -64,7 +63,6 @@ ;; CHECK-NEXT: (local $b' (ref null $B)) ;; CHECK-NEXT: (local $c (ref null $C)) ;; CHECK-NEXT: (local $c' (ref null $C)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $a (ref null $A)) @@ -90,7 +88,6 @@ ;; CHECK: (func $foo (type $2) ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (local $a' (ref null $A')) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $a (ref null $A)) diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast index 5d0c4bc5e7b..942e15b021a 100644 --- a/test/lit/passes/type-merging.wast +++ b/test/lit/passes/type-merging.wast @@ -95,7 +95,6 @@ ;; CHECK-NEXT: (local $e (ref null $D)) ;; CHECK-NEXT: (local $f (ref null $D)) ;; CHECK-NEXT: (local $g (ref null $D)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $a (ref null $A)) @@ -139,7 +138,6 @@ ;; CHECK-NEXT: (local $e (ref null $D)) ;; CHECK-NEXT: (local $f (ref null $D)) ;; CHECK-NEXT: (local $g (ref null $D)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $a (ref null $A)) @@ -169,7 +167,6 @@ ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (local $b (ref null $A)) ;; CHECK-NEXT: (local $c (ref null $C)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; B can be merged into A even though it refines A's field because that @@ -192,7 +189,6 @@ ;; CHECK: (func $foo (type $1) ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (local $b (ref null $A)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; A recursive subtype can be merged even though its field is a refinement @@ -220,7 +216,6 @@ ;; CHECK-NEXT: (local $b (ref null $A)) ;; CHECK-NEXT: (local $x (ref null $X)) ;; CHECK-NEXT: (local $y (ref null $X)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; Two mutually referential chains, A->B and X->Y, can be merged into a pair @@ -249,7 +244,6 @@ ;; CHECK-NEXT: (local $b (ref null $X)) ;; CHECK-NEXT: (local $x (ref null $X)) ;; CHECK-NEXT: (local $y (ref null $X)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; As above, but now the A->B and X->Y chains are not differentiated by the @@ -278,7 +272,6 @@ ;; CHECK-NEXT: (local $b (ref null $A)) ;; CHECK-NEXT: (local $x (ref null $X)) ;; CHECK-NEXT: (local $y (ref null $X)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; As above with the differentiated chains, but now the types are top-level @@ -306,7 +299,6 @@ ;; CHECK-NEXT: (local $b (ref null $X)) ;; CHECK-NEXT: (local $x (ref null $X)) ;; CHECK-NEXT: (local $y (ref null $X)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; As above, but with all the types merging into a single type. @@ -429,7 +421,6 @@ ;; CHECK-NEXT: (local $l' (ref null $L)) ;; CHECK-NEXT: (local $m (ref null $M)) ;; CHECK-NEXT: (local $m' (ref null $M)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $a (ref null $A)) @@ -479,7 +470,6 @@ ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (local $b (ref null $B)) ;; CHECK-NEXT: (local $c (ref null $B)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; B and C cannot be merged into A because they refine A's field, but B and @@ -506,7 +496,6 @@ ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (local $b (ref null $B)) ;; CHECK-NEXT: (local $c (ref null $B)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; This is the same as above, but now B and C refine A such that they have a @@ -537,7 +526,6 @@ ;; CHECK-NEXT: (local $c (ref null $A)) ;; CHECK-NEXT: (local $d (ref null $D)) ;; CHECK-NEXT: (local $e (ref null $D)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; D and E should be mergeable because they have identical shapes and will @@ -587,7 +575,6 @@ ;; CHECK-NEXT: (local $c' (ref null $C)) ;; CHECK-NEXT: (local $d (ref null $D)) ;; CHECK-NEXT: (local $d' (ref null $D)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $a (ref null $A)) @@ -696,7 +683,6 @@ ;; CHECK: (func $foo (type $3) ;; CHECK-NEXT: (local $a (ref null $intarray)) ;; CHECK-NEXT: (local $b (ref null $intarray)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; $A will remain the same. @@ -709,7 +695,6 @@ ;; CHECK-NEXT: (local $a (ref null $refarray)) ;; CHECK-NEXT: (local $b (ref null $refarray)) ;; CHECK-NEXT: (local $c (ref null $sub-refarray-nn)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $bar (local $a (ref null $refarray)) @@ -735,7 +720,6 @@ ;; CHECK-NEXT: (local $a (ref null $func)) ;; CHECK-NEXT: (local $b (ref null $func)) ;; CHECK-NEXT: (local $c (ref null $sub-func-refined)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo ;; $func will remain the same. @@ -830,7 +814,6 @@ ;; CHECK: (func $foo (type $3) ;; CHECK-NEXT: (local $b (ref null $A')) ;; CHECK-NEXT: (local $x (ref null $X)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $b (ref null $A')) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 8e8ccf24ccd..d045dbc1a62 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -168,7 +168,6 @@ ;; CHECK: (func $keepalive (type $4) ;; CHECK-NEXT: (local $temp (ref null $child-B)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $keepalive ;; Add a reference to $child-B just to keep it alive in the output for easier diff --git a/test/lit/string.as_wtf16.wast b/test/lit/string.as_wtf16.wast index 916e11d39a1..60bdcf5ecb3 100644 --- a/test/lit/string.as_wtf16.wast +++ b/test/lit/string.as_wtf16.wast @@ -9,7 +9,6 @@ (module ;; CHECK: (func $empty (type $2) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; RTRIP: (func $empty (type $2) ;; RTRIP-NEXT: ) diff --git a/test/lit/validation/nn-tuples.wast b/test/lit/validation/nn-tuples.wast index a673ec9003d..c3c5e4e4f63 100644 --- a/test/lit/validation/nn-tuples.wast +++ b/test/lit/validation/nn-tuples.wast @@ -8,7 +8,6 @@ (module ;; CHECK: (func $foo (type $0) ;; CHECK-NEXT: (local $tuple (tuple (ref any) (ref any))) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo (local $tuple (tuple (ref any) (ref any))) diff --git a/test/lit/wasm-split/passive.wast b/test/lit/wasm-split/passive.wast index 743d7d14e0c..d335d79a831 100644 --- a/test/lit/wasm-split/passive.wast +++ b/test/lit/wasm-split/passive.wast @@ -26,7 +26,6 @@ ;; PRIMARY: (export "table_1" (table $1)) ;; PRIMARY: (func $in-table (type $0) - ;; PRIMARY-NEXT: (nop) ;; PRIMARY-NEXT: ) (func $in-table ;; This is in a passive segment, but it is in the main module so we need no @@ -40,7 +39,6 @@ ;; SECONDARY: (elem $0 (i32.const 0) $second-in-table) ;; SECONDARY: (func $second-in-table (type $0) - ;; SECONDARY-NEXT: (nop) ;; SECONDARY-NEXT: ) (func $second-in-table ;; This is in a passive segment, and it is in the secondary module, so we will diff --git a/test/lit/wasm-split/ref.func.wast b/test/lit/wasm-split/ref.func.wast index d9a30890ac5..f97f790ff36 100644 --- a/test/lit/wasm-split/ref.func.wast +++ b/test/lit/wasm-split/ref.func.wast @@ -89,7 +89,6 @@ ) ;; PRIMARY: (func $in-table (type $0) - ;; PRIMARY-NEXT: (nop) ;; PRIMARY-NEXT: ) (func $in-table ;; This empty function is in the table. Just being present in the table is not @@ -98,7 +97,6 @@ ) ;; SECONDARY: (func $second-in-table (type $0) - ;; SECONDARY-NEXT: (nop) ;; SECONDARY-NEXT: ) (func $second-in-table ;; As above, but in the secondary module. We still don't need a trampoline diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index 3936354a15b..97d9f57e835 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -475,15 +475,12 @@ (func) ;; CHECK: (func $2 (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK: (func $f1 (type $18) (param $0 i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $f1 (param i32)) ;; CHECK: (func $f2 (type $18) (param $x i32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $f2 (param $x i32)) ;; CHECK: (func $f3 (type $1) (result i32) @@ -496,12 +493,10 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $l f32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $f4 (type 18) (local i32 i64) (local $l f32)) ;; CHECK: (func $"[quoted_name]" (type $0) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $"[quoted_name]") @@ -1138,10 +1133,8 @@ ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1336,10 +1329,8 @@ ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1507,10 +1498,8 @@ ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if @@ -1518,10 +1507,8 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else -;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1659,7 +1646,6 @@ ;; CHECK: (func $loop-empty (type $0) ;; CHECK-NEXT: (loop - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $loop-empty @@ -1755,7 +1741,6 @@ ;; CHECK: (func $loop-folded-empty (type $0) ;; CHECK-NEXT: (loop - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $loop-folded-empty @@ -1839,10 +1824,8 @@ ;; CHECK: (func $try-catch (type $0) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1898,10 +1881,8 @@ ;; CHECK: (func $try-catch_all (type $0) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1914,16 +1895,12 @@ ;; CHECK: (func $try-catch-catch_all (type $0) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $eimport$0 - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1938,7 +1915,6 @@ ;; CHECK: (func $try-delegate (type $0) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 0) ;; CHECK-NEXT: ) @@ -1952,7 +1928,6 @@ ;; CHECK-NEXT: (block $l ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 1) ;; CHECK-NEXT: ) @@ -1969,7 +1944,6 @@ ;; CHECK-NEXT: (block $l ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 1) ;; CHECK-NEXT: ) @@ -1986,7 +1960,6 @@ ;; CHECK-NEXT: (block $l ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 1) ;; CHECK-NEXT: ) @@ -2004,7 +1977,6 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate $label) ;; CHECK-NEXT: ) @@ -2025,7 +1997,6 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate $l) ;; CHECK-NEXT: ) @@ -2046,7 +2017,6 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate $label) ;; CHECK-NEXT: ) @@ -2068,7 +2038,6 @@ ;; CHECK-NEXT: (block $l ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate $label) ;; CHECK-NEXT: ) @@ -2091,7 +2060,6 @@ ;; CHECK-NEXT: (block $l0 ;; CHECK-NEXT: (try $l1 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate $l) ;; CHECK-NEXT: ) @@ -2113,12 +2081,10 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: (try $l0 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (try $l1 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate $l) ;; CHECK-NEXT: ) @@ -2143,12 +2109,10 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: (try $l0 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (try $l1 ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate $l) ;; CHECK-NEXT: ) @@ -2262,7 +2226,6 @@ ;; CHECK: (func $try-delegate-folded (type $0) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 0) ;; CHECK-NEXT: ) @@ -2277,7 +2240,6 @@ ;; CHECK: (func $rethrow (type $0) ;; CHECK-NEXT: (try $label ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (rethrow $label) @@ -2294,7 +2256,6 @@ ;; CHECK: (func $rethrow-named (type $0) ;; CHECK-NEXT: (try $l ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (rethrow $l) @@ -2311,12 +2272,10 @@ ;; CHECK: (func $rethrow-nested (type $0) ;; CHECK-NEXT: (try $label ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (rethrow $label) @@ -2340,12 +2299,10 @@ ;; CHECK: (func $rethrow-nested-named (type $0) ;; CHECK-NEXT: (try $l ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (rethrow $l) @@ -2369,7 +2326,6 @@ ;; CHECK: (func $rethrow-try-nested (type $0) ;; CHECK-NEXT: (try $label ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (try @@ -2396,7 +2352,6 @@ ;; CHECK: (func $rethrow-try-nested-named (type $0) ;; CHECK-NEXT: (try $l ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (try @@ -5035,7 +4990,6 @@ ) ;; CHECK: (func $use-types (type $96) (param $0 (ref $s0)) (param $1 (ref $s1)) (param $2 (ref $s2)) (param $3 (ref $s3)) (param $4 (ref $s4)) (param $5 (ref $s5)) (param $6 (ref $s6)) (param $7 (ref $s7)) (param $8 (ref $s8)) (param $9 (ref $a0)) (param $10 (ref $a1)) (param $11 (ref $a2)) (param $12 (ref $a3)) (param $13 (ref $subvoid)) (param $14 (ref $submany)) (param $15 (ref $all-types)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $use-types (param (ref $s0)) diff --git a/test/lld/basic_safe_stack.wat.out b/test/lld/basic_safe_stack.wat.out index 7d554cc8839..b92a3048c5a 100644 --- a/test/lld/basic_safe_stack.wat.out +++ b/test/lld/basic_safe_stack.wat.out @@ -15,7 +15,6 @@ (export "main" (func $main)) (export "__set_stack_limits" (func $__set_stack_limits)) (func $__wasm_call_ctors - (nop) ) (func $stackRestore (param $0 i32) (local $1 i32) @@ -81,7 +80,6 @@ (local.get $1) ) (func $main - (nop) ) (func $__set_stack_limits (param $0 i32) (param $1 i32) (global.set $__stack_base diff --git a/test/lld/duplicate_imports.wat.out b/test/lld/duplicate_imports.wat.out index 7e49b393cc4..e2d822f0da6 100644 --- a/test/lld/duplicate_imports.wat.out +++ b/test/lld/duplicate_imports.wat.out @@ -34,7 +34,6 @@ (i32.const 0) ) (func $__wasm_call_ctors - (nop) ) (func $dynCall_ffd (param $fptr i32) (param $0 f32) (param $1 f64) (result f32) (call_indirect (type $8) diff --git a/test/lld/em_asm.wat.out b/test/lld/em_asm.wat.out index 8ed3d72ba0b..3165aa75a62 100644 --- a/test/lld/em_asm.wat.out +++ b/test/lld/em_asm.wat.out @@ -17,7 +17,6 @@ (export "__start_em_asm" (global $global$1)) (export "__stop_em_asm" (global $global$2)) (func $__wasm_call_ctors - (nop) ) (func $__original_main (result i32) (local $0 i32) diff --git a/test/lld/em_asm64.wat.out b/test/lld/em_asm64.wat.out index 33e5bb1768b..297933163e4 100644 --- a/test/lld/em_asm64.wat.out +++ b/test/lld/em_asm64.wat.out @@ -17,7 +17,6 @@ (export "__start_em_asm" (global $global$1)) (export "__stop_em_asm" (global $global$2)) (func $__wasm_call_ctors - (nop) ) (func $__original_main (result i32) (local $0 i64) diff --git a/test/lld/em_asm_O0.wat.out b/test/lld/em_asm_O0.wat.out index fb2495476eb..6635354bfb2 100644 --- a/test/lld/em_asm_O0.wat.out +++ b/test/lld/em_asm_O0.wat.out @@ -16,7 +16,6 @@ (export "__start_em_asm" (global $global$1)) (export "__stop_em_asm" (global $global$2)) (func $__wasm_call_ctors - (nop) ) (func $__original_main (result i32) (local $0 i32) diff --git a/test/lld/em_asm_main_thread.wat.out b/test/lld/em_asm_main_thread.wat.out index 5d817ca8677..8ee66c6225b 100644 --- a/test/lld/em_asm_main_thread.wat.out +++ b/test/lld/em_asm_main_thread.wat.out @@ -23,7 +23,6 @@ (export "__data_end" (global $global$2)) (export "main" (func $main)) (func $__wasm_call_ctors - (nop) ) (func $__original_main (result i32) (local $0 i32) diff --git a/test/lld/em_asm_shared.wat.out b/test/lld/em_asm_shared.wat.out index 0962d350408..5b72ac8abee 100644 --- a/test/lld/em_asm_shared.wat.out +++ b/test/lld/em_asm_shared.wat.out @@ -28,10 +28,8 @@ (export "__start_em_asm" (global $global$3)) (export "__stop_em_asm" (global $global$4)) (func $__wasm_call_ctors - (nop) ) (func $__wasm_apply_data_relocs - (nop) ) (func $__original_main (result i32) (local $0 i32) diff --git a/test/lld/hello_world.wat.out b/test/lld/hello_world.wat.out index 8081048d867..0e105c4c024 100644 --- a/test/lld/hello_world.wat.out +++ b/test/lld/hello_world.wat.out @@ -12,7 +12,6 @@ (export "__wasm_call_ctors" (func $__wasm_call_ctors)) (export "main" (func $main)) (func $__wasm_call_ctors - (nop) ) (func $__original_main (result i32) (drop diff --git a/test/lld/longjmp.wat.out b/test/lld/longjmp.wat.out index a9a5ac76394..508ded29330 100644 --- a/test/lld/longjmp.wat.out +++ b/test/lld/longjmp.wat.out @@ -25,7 +25,6 @@ (export "main" (func $main)) (export "dynCall_vii" (func $dynCall_vii)) (func $__wasm_call_ctors - (nop) ) (func $__original_main (result i32) (local $0 i32) diff --git a/test/lld/main_module_table.wat.out b/test/lld/main_module_table.wat.out index 05675c911c1..b926e05e0a6 100644 --- a/test/lld/main_module_table.wat.out +++ b/test/lld/main_module_table.wat.out @@ -6,6 +6,5 @@ (export "__stdio_write" (func $__stdio_write)) (export "__data_end" (global $global)) (func $__stdio_write - (nop) ) ) diff --git a/test/lld/main_module_table_2.wat.out b/test/lld/main_module_table_2.wat.out index de7e5542a59..819ca321639 100644 --- a/test/lld/main_module_table_2.wat.out +++ b/test/lld/main_module_table_2.wat.out @@ -7,6 +7,5 @@ (export "__stdio_write" (func $__stdio_write)) (export "__data_end" (global $global)) (func $__stdio_write - (nop) ) ) diff --git a/test/lld/main_module_table_3.wat.out b/test/lld/main_module_table_3.wat.out index 0bd9dcd2ac1..06201f383dd 100644 --- a/test/lld/main_module_table_3.wat.out +++ b/test/lld/main_module_table_3.wat.out @@ -8,6 +8,5 @@ (export "__stdio_write" (func $__stdio_write)) (export "__data_end" (global $global)) (func $__stdio_write - (nop) ) ) diff --git a/test/lld/main_module_table_4.wat.out b/test/lld/main_module_table_4.wat.out index d144e688e2d..966ac588d2c 100644 --- a/test/lld/main_module_table_4.wat.out +++ b/test/lld/main_module_table_4.wat.out @@ -9,6 +9,5 @@ (export "__stdio_write" (func $__stdio_write)) (export "__data_end" (global $global)) (func $__stdio_write - (nop) ) ) diff --git a/test/lld/main_module_table_5.wat.out b/test/lld/main_module_table_5.wat.out index 04167c9b34a..38e4be98b87 100644 --- a/test/lld/main_module_table_5.wat.out +++ b/test/lld/main_module_table_5.wat.out @@ -11,13 +11,10 @@ (export "__data_end" (global $global)) (export "dynCall_v" (func $dynCall_v)) (func $__stdio_write - (nop) ) (func $other - (nop) ) (func $stuff - (nop) ) (func $dynCall_v (param $fptr i32) (call_indirect (type $0) diff --git a/test/lld/recursive.wat.out b/test/lld/recursive.wat.out index ae7945a2aae..47035fe7a06 100644 --- a/test/lld/recursive.wat.out +++ b/test/lld/recursive.wat.out @@ -11,7 +11,6 @@ (export "__wasm_call_ctors" (func $__wasm_call_ctors)) (export "main" (func $main)) (func $__wasm_call_ctors - (nop) ) (func $foo (param $0 i32) (param $1 i32) (result i32) (local $2 i32) diff --git a/test/lld/recursive_safe_stack.wat.out b/test/lld/recursive_safe_stack.wat.out index ad30dc776aa..f042d8e1522 100644 --- a/test/lld/recursive_safe_stack.wat.out +++ b/test/lld/recursive_safe_stack.wat.out @@ -21,7 +21,6 @@ (export "main" (func $main)) (export "__set_stack_limits" (func $__set_stack_limits)) (func $__wasm_call_ctors - (nop) ) (func $foo (param $0 i32) (param $1 i32) (result i32) (local $2 i32) diff --git a/test/lld/reserved_func_ptr.wat.out b/test/lld/reserved_func_ptr.wat.out index 5239ce510bb..554d6aa0606 100644 --- a/test/lld/reserved_func_ptr.wat.out +++ b/test/lld/reserved_func_ptr.wat.out @@ -17,13 +17,10 @@ (export "__main_argc_argv" (func $main)) (export "dynCall_viii" (func $dynCall_viii)) (func $__wasm_call_ctors - (nop) ) (func $address_taken_func\28int\2c\20int\2c\20int\29 (param $0 i32) (param $1 i32) (param $2 i32) - (nop) ) (func $address_taken_func2\28int\2c\20int\2c\20int\29 (param $0 i32) (param $1 i32) (param $2 i32) - (nop) ) (func $main (param $0 i32) (param $1 i32) (result i32) (local $2 i32) diff --git a/test/lld/safe_stack_standalone-wasm.wat.out b/test/lld/safe_stack_standalone-wasm.wat.out index 63d6c09d492..2820566b3ee 100644 --- a/test/lld/safe_stack_standalone-wasm.wat.out +++ b/test/lld/safe_stack_standalone-wasm.wat.out @@ -19,7 +19,6 @@ (export "main" (func $main)) (export "__set_stack_limits" (func $__set_stack_limits)) (func $__wasm_call_ctors - (nop) ) (func $foo (param $0 i32) (param $1 i32) (result i32) (local $2 i32) diff --git a/test/lld/shared.wat.out b/test/lld/shared.wat.out index 1a496e43650..6ce5b208e86 100644 --- a/test/lld/shared.wat.out +++ b/test/lld/shared.wat.out @@ -19,7 +19,6 @@ (export "ptr_puts" (global $global$0)) (export "ptr_local_func" (global $global$1)) (func $__wasm_call_ctors - (nop) ) (func $__wasm_apply_data_relocs (i32.store diff --git a/test/lld/shared_longjmp.wat.out b/test/lld/shared_longjmp.wat.out index 449714270ea..96ef571f779 100644 --- a/test/lld/shared_longjmp.wat.out +++ b/test/lld/shared_longjmp.wat.out @@ -32,10 +32,8 @@ (export "__threwValue" (global $global$1)) (export "dynCall_vii" (func $dynCall_vii)) (func $__wasm_call_ctors - (nop) ) (func $__wasm_apply_data_relocs - (nop) ) (func $_start (local $0 i32) diff --git a/test/metadce/name_collision.wast.dced b/test/metadce/name_collision.wast.dced index 50e7010dc45..6be2d6ecfab 100644 --- a/test/metadce/name_collision.wast.dced +++ b/test/metadce/name_collision.wast.dced @@ -2,6 +2,5 @@ (type $0 (func)) (export "test" (func $test)) (func $test (type $0) - (nop) ) ) diff --git a/test/metadce/outside.wast.dced b/test/metadce/outside.wast.dced index 6cc8b71d601..f5dc4e1e0b4 100644 --- a/test/metadce/outside.wast.dced +++ b/test/metadce/outside.wast.dced @@ -11,7 +11,6 @@ (elem $0 (global.get $from_segment_2) $table_func) (export "wasm_func" (func $a_wasm_func)) (func $table_func (type $0) - (nop) ) (func $a_wasm_func (type $0) (call $a_js_func) diff --git a/test/metadce/tag.wast.dced b/test/metadce/tag.wast.dced index 6f07a4e7571..f2d5cf6250b 100644 --- a/test/metadce/tag.wast.dced +++ b/test/metadce/tag.wast.dced @@ -9,7 +9,6 @@ (throw $t0) ) (catch $t1 - (nop) ) ) ) diff --git a/test/passes/duplicate-function-elimination_all-features.txt b/test/passes/duplicate-function-elimination_all-features.txt index 6867002bf0b..d46f4ed416f 100644 --- a/test/passes/duplicate-function-elimination_all-features.txt +++ b/test/passes/duplicate-function-elimination_all-features.txt @@ -16,7 +16,6 @@ (export "memory" (memory $foo)) (export "global" (global $bar)) (func $bar (type $0) - (nop) ) ) (module diff --git a/test/passes/func-metrics.txt b/test/passes/func-metrics.txt index b61e8146ec9..86eb8871f10 100644 --- a/test/passes/func-metrics.txt +++ b/test/passes/func-metrics.txt @@ -13,10 +13,10 @@ global Const : 3 RefFunc : 3 func: empty - [binary-bytes] : 3 + [binary-bytes] : 2 [total] : 1 [vars] : 0 - Nop : 1 + Block : 1 func: small [binary-bytes] : 9 [total] : 5 @@ -44,7 +44,6 @@ func: ifs (table $0 256 256 funcref) (elem $0 (i32.const 0) $ifs $ifs $ifs) (func $empty - (nop) ) (func $small (nop) diff --git a/test/passes/licm.txt b/test/passes/licm.txt index 83edd78c3d2..64d86c32efa 100644 --- a/test/passes/licm.txt +++ b/test/passes/licm.txt @@ -508,7 +508,6 @@ ) (func $after (loop $loop - (nop) ) (drop (i32.const 10) diff --git a/test/passes/minify-imports-and-exports_all-features.txt b/test/passes/minify-imports-and-exports_all-features.txt index 2d131416d48..87aa3c2f289 100644 --- a/test/passes/minify-imports-and-exports_all-features.txt +++ b/test/passes/minify-imports-and-exports_all-features.txt @@ -10020,9 +10020,7 @@ longname4880 => zza (export "OBa" (func $foo2)) (export "PBa" (tag $tag1)) (func $foo1 (type $0) - (nop) ) (func $foo2 (type $0) - (nop) ) ) diff --git a/test/passes/minify-imports_all-features.txt b/test/passes/minify-imports_all-features.txt index 536a6a042f6..ec25564396a 100644 --- a/test/passes/minify-imports_all-features.txt +++ b/test/passes/minify-imports_all-features.txt @@ -10014,9 +10014,7 @@ longname4880 => zza (export "foo2" (func $foo2)) (export "tag1" (tag $tag1)) (func $foo1 (type $0) - (nop) ) (func $foo2 (type $0) - (nop) ) ) diff --git a/test/passes/post-emscripten.txt b/test/passes/post-emscripten.txt index 186cdeb7675..6cefe366f8e 100644 --- a/test/passes/post-emscripten.txt +++ b/test/passes/post-emscripten.txt @@ -43,10 +43,8 @@ ) ) (func $other_safe (param $0 i32) (param $1 f32) - (nop) ) (func $other_unsafe (param $0 i32) (param $1 f32) - (nop) ) (func $deep_safe (param $0 i32) (param $1 f32) (call $other_safe @@ -84,7 +82,6 @@ ) ) (func $other_safe (param $0 i32) (param $1 f32) - (nop) ) ) (module diff --git a/test/passes/print-function-map.txt b/test/passes/print-function-map.txt index 228276e0635..5cf4c5843fb 100644 --- a/test/passes/print-function-map.txt +++ b/test/passes/print-function-map.txt @@ -5,9 +5,7 @@ (type $0 (func)) (import "env" "foo" (func $Foo)) (func $bar - (nop) ) (func $baz - (nop) ) ) diff --git a/test/passes/remove-unused-brs_enable-multivalue.txt b/test/passes/remove-unused-brs_enable-multivalue.txt index f3c1b7d7733..ee6efbf4dab 100644 --- a/test/passes/remove-unused-brs_enable-multivalue.txt +++ b/test/passes/remove-unused-brs_enable-multivalue.txt @@ -529,7 +529,6 @@ ) ) (loop $in2 - (nop) ) (loop $in3 (block $out2 diff --git a/test/passes/remove-unused-names_merge-blocks_all-features.txt b/test/passes/remove-unused-names_merge-blocks_all-features.txt index ec63edd5e01..2169d636b71 100644 --- a/test/passes/remove-unused-names_merge-blocks_all-features.txt +++ b/test/passes/remove-unused-names_merge-blocks_all-features.txt @@ -1657,7 +1657,6 @@ (type $1 (func (param i32))) (tag $e (param i32)) (func $foo (type $0) - (nop) ) (func $throw (type $0) (nop) diff --git a/test/passes/remove-unused-nonfunction-module-elements_all-features.txt b/test/passes/remove-unused-nonfunction-module-elements_all-features.txt index e125373d8d8..c56b99253d1 100644 --- a/test/passes/remove-unused-nonfunction-module-elements_all-features.txt +++ b/test/passes/remove-unused-nonfunction-module-elements_all-features.txt @@ -111,7 +111,6 @@ (data $0 (i32.const 1) "hello, world!") (elem $0 (i32.const 0) $waka) (func $waka (type $0) - (nop) ) ) (module @@ -228,7 +227,6 @@ (data $0 (global.get $memoryBase) "hello, world!") (elem $0 (global.get $tableBase) $waka) (func $waka (type $0) - (nop) ) ) (module @@ -347,6 +345,5 @@ (tag $e1 (param i64)) (export "e1" (tag $e1)) (func $f (type $0) (param $0 i32) - (nop) ) ) diff --git a/test/passes/spill-pointers.txt b/test/passes/spill-pointers.txt index 0c51683efdf..52d7b19a16b 100644 --- a/test/passes/spill-pointers.txt +++ b/test/passes/spill-pointers.txt @@ -12,7 +12,6 @@ (table $0 1 1 funcref) (elem $0 (i32.const 0)) (func $nothing - (nop) ) (func $not-alive (local $x i32) @@ -698,7 +697,6 @@ (global.get $stack_ptr) ) (func $nothing - (nop) ) (func $not-alive (local $x i32) From ce1a2b480ae89e65b4d94e1bb3332c5980e2479f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 15 Nov 2024 14:44:20 -0500 Subject: [PATCH 133/622] Reset function context when ending a function in IRBuilder (#7081) IRBuilder contains a pointer to the current function that is used to create scratch locals, look up the operand types for returns, etc. This pointer is nullable because IRBuilder can also be used in non-function contexts such as global initializers. Visiting the start of a function sets the function pointer, and after this change visiting the end of a function resets the pointer to null. This avoids potential problems where code outside a function would be able to incorrectly use scratch locals and returns if the IRBuilder had previously been used to build a function. This change requires some adjustments to Outlining, which visits code out of order, so ends up visiting code from inside a function after visiting the end of the function. To support this use case, add a `setFunction` method to IRBuilder that lets the user explicitly control its function context. Also remove the optional function pointer parameter to the IRBuilder constructor since it is less flexible and not used. --- src/parser/parsers.h | 2 +- src/passes/Outlining.cpp | 18 ++++++++++++------ src/wasm-ir-builder.h | 9 ++++++--- src/wasm/wasm-ir-builder.cpp | 1 + 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/parser/parsers.h b/src/parser/parsers.h index b6699aab6a9..4121788d75e 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -1143,7 +1143,7 @@ ifelse(Ctx& ctx, const std::vector& annotations, bool folded) { ctx.setSrcLoc(annotations); } - ctx.makeIf(pos, annotations, label, *type); + CHECK_ERR(ctx.makeIf(pos, annotations, label, *type)); if (folded && !ctx.in.takeSExprStart("then"sv)) { return ctx.in.err("expected 'then' before if instructions"); diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index 68c3d039707..429511557e1 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -42,8 +42,8 @@ namespace wasm { struct ReconstructStringifyWalker : public StringifyWalker { - ReconstructStringifyWalker(Module* wasm) - : existingBuilder(*wasm), outlinedBuilder(*wasm) { + ReconstructStringifyWalker(Module* wasm, Function* func) + : existingBuilder(*wasm), outlinedBuilder(*wasm), func(func) { this->setModule(wasm); DBG(std::cerr << "\nexistingBuilder: " << &existingBuilder << " outlinedBuilder: " << &outlinedBuilder << "\n"); @@ -77,6 +77,9 @@ struct ReconstructStringifyWalker // contain repeat sequences found in the program. IRBuilder outlinedBuilder; + // The function we are outlining from. + Function* func; + void addUniqueSymbol(SeparatorReason reason) { if (auto curr = reason.getFuncStart()) { startExistingFunction(curr->func); @@ -108,6 +111,8 @@ struct ReconstructStringifyWalker DBG(desc = "Loop Start at "); } else if (reason.getEnd()) { ASSERT_OK(existingBuilder.visitEnd()); + // Reset the function in case we just ended the function scope. + existingBuilder.setFunction(func); // Outlining performs an unnested walk of the Wasm module, visiting // each scope one at a time. IRBuilder, in contrast, expects to // visit several nested scopes at a time. Thus, calling end() finalizes @@ -346,15 +351,16 @@ struct Outlining : public Pass { void outline(Module* module, Sequences seqByFunc) { // TODO: Make this a function-parallel sub-pass. - ReconstructStringifyWalker reconstruct(module); std::vector keys(seqByFunc.size()); std::transform(seqByFunc.begin(), seqByFunc.end(), keys.begin(), [](auto pair) { return pair.first; }); - for (auto func : keys) { - reconstruct.sequences = std::move(seqByFunc[func]); - reconstruct.doWalkFunction(module->getFunction(func)); + for (auto funcName : keys) { + auto* func = module->getFunction(funcName); + ReconstructStringifyWalker reconstruct(module, func); + reconstruct.sequences = std::move(seqByFunc[funcName]); + reconstruct.doWalkFunction(func); } } diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 40689a879e6..0210c55e50b 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -40,8 +40,7 @@ namespace wasm { // globals, tables, functions, etc.) to already exist in the module. class IRBuilder : public UnifiedExpressionVisitor> { public: - IRBuilder(Module& wasm, Function* func = nullptr) - : wasm(wasm), func(func), builder(wasm) {} + IRBuilder(Module& wasm) : wasm(wasm), builder(wasm) {} // Get the valid Binaryen IR expression representing the sequence of visited // instructions. The IRBuilder is reset and can be used with a fresh sequence @@ -60,6 +59,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { // pushed instruction. void setDebugLocation(const std::optional&); + // Set the function used to add scratch locals when constructing an isolated + // sequence of IR. + void setFunction(Function* func) { this->func = func; } + // Handle the boundaries of control flow structures. Users may choose to use // the corresponding `makeXYZ` function below instead of `visitXYZStart`, but // either way must call `visitEnd` and friends at the appropriate times. @@ -234,7 +237,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { private: Module& wasm; - Function* func; + Function* func = nullptr; Builder builder; // The location lacks debug info as it was marked as not having it. diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 5e5decca42b..398db68ff4f 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -981,6 +981,7 @@ Result<> IRBuilder::visitEnd() { if (scope.needsPopFixup()) { EHUtils::handleBlockNestedPops(func, wasm); } + this->func = nullptr; } else if (auto* block = scope.getBlock()) { assert(*expr == block); block->name = scope.label; From 368c1edf2a6182dcde7a91e6facbf3dbdd6c7456 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 15 Nov 2024 16:18:29 -0500 Subject: [PATCH 134/622] Mark Result and MaybeResult [[nodiscard]] (#7083) Since these types may be carrying errors that need to be handled or propagated, it is always an error not to use them in some way. Adding the [[nodiscard]] attribute caused the compiler to find a few instances where we were incorrectly ignoring results. Fix these places. --- src/parser/parsers.h | 6 +++--- src/support/result.h | 4 ++-- test/gtest/local-graph.cpp | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 4121788d75e..2d3321dcdd4 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -1099,7 +1099,7 @@ block(Ctx& ctx, const std::vector& annotations, bool folded) { auto type = blocktype(ctx); CHECK_ERR(type); - ctx.makeBlock(pos, annotations, label, *type); + CHECK_ERR(ctx.makeBlock(pos, annotations, label, *type)); CHECK_ERR(instrs(ctx)); @@ -1162,7 +1162,7 @@ ifelse(Ctx& ctx, const std::vector& annotations, bool folded) { return ctx.in.err("else label does not match if label"); } - ctx.visitElse(); + CHECK_ERR(ctx.visitElse()); CHECK_ERR(instrs(ctx)); @@ -1205,7 +1205,7 @@ loop(Ctx& ctx, const std::vector& annotations, bool folded) { auto type = blocktype(ctx); CHECK_ERR(type); - ctx.makeLoop(pos, annotations, label, *type); + CHECK_ERR(ctx.makeLoop(pos, annotations, label, *type)); CHECK_ERR(instrs(ctx)); diff --git a/src/support/result.h b/src/support/result.h index ab71d3a533b..7cd360d533a 100644 --- a/src/support/result.h +++ b/src/support/result.h @@ -40,7 +40,7 @@ struct Err { } // Represent a result of type T or an error message. -template struct Result { +template struct [[nodiscard]] Result { std::variant val; Result(Result& other) = default; @@ -56,7 +56,7 @@ template struct Result { }; // Represent an optional result of type T or an error message. -template struct MaybeResult { +template struct [[nodiscard]] MaybeResult { std::variant val; MaybeResult() : val(None{}) {} diff --git a/test/gtest/local-graph.cpp b/test/gtest/local-graph.cpp index 5360813ceef..d9ec57bebab 100644 --- a/test/gtest/local-graph.cpp +++ b/test/gtest/local-graph.cpp @@ -25,7 +25,7 @@ TEST_F(LocalGraphTest, ObstacleBasics) { )wasm"; Module wasm; - WATParser::parseModule(wasm, moduleText); + ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); // Get access to the contents of the wasm. auto* func = wasm.functions[0].get(); @@ -79,7 +79,7 @@ TEST_F(LocalGraphTest, ObstacleMultiblock) { ) )wasm"; Module wasm; - WATParser::parseModule(wasm, moduleText); + ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); auto* func = wasm.functions[0].get(); auto* block = func->body->cast(); auto* set = block->list[0]->cast(); @@ -113,7 +113,7 @@ TEST_F(LocalGraphTest, ObstacleUnreachable) { ) )wasm"; Module wasm; - WATParser::parseModule(wasm, moduleText); + ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); auto* func = wasm.functions[0].get(); auto* block = func->body->cast(); auto* set = block->list[0]->cast(); @@ -149,7 +149,7 @@ TEST_F(LocalGraphTest, ObstacleMultiGet) { ) )wasm"; Module wasm; - WATParser::parseModule(wasm, moduleText); + ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); auto* func = wasm.functions[0].get(); auto* block = func->body->cast(); auto* set = block->list[0]->cast(); @@ -185,7 +185,7 @@ TEST_F(LocalGraphTest, ObstacleMultiSet) { ) )wasm"; Module wasm; - WATParser::parseModule(wasm, moduleText); + ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); auto* func = wasm.functions[0].get(); auto* block = func->body->cast(); auto* setA = block->list[0]->cast(); @@ -227,7 +227,7 @@ TEST_F(LocalGraphTest, ObstacleMultiSetIndexes) { ) )wasm"; Module wasm; - WATParser::parseModule(wasm, moduleText); + ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); auto* func = wasm.functions[0].get(); auto* block = func->body->cast(); auto* setA = block->list[0]->cast(); @@ -276,7 +276,7 @@ TEST_F(LocalGraphTest, ObstacleMultiSetIf) { ) )wasm"; Module wasm; - WATParser::parseModule(wasm, moduleText); + ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); auto* func = wasm.functions[0].get(); auto* block = func->body->cast(); auto* iff = block->list[0]->cast(); @@ -332,7 +332,7 @@ TEST_F(LocalGraphTest, ObstacleStructSet) { ) )wasm"; Module wasm; - WATParser::parseModule(wasm, moduleText); + ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); auto* func = wasm.functions[0].get(); auto* outerBlock = func->body->cast(); auto* block = outerBlock->list[0]->cast(); From f6f898b6b6f85b5e2c4f5bc0c61b8465e72dc35e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 15 Nov 2024 16:58:48 -0500 Subject: [PATCH 135/622] [NFC] Remove redundant [[nodiscard]] attributes (#7084) Now that Result and MaybeResult are annotated [[nodiscard]] at the type level, individual functions and methods that return these types do not need to be annotated [[nodiscard]] themselves. Remove the newly redundant annotations. --- src/wasm-ir-builder.h | 288 +++++++++++++++++------------------ src/wasm/wasm-ir-builder.cpp | 2 +- 2 files changed, 140 insertions(+), 150 deletions(-) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 0210c55e50b..d6e45314933 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -45,11 +45,11 @@ class IRBuilder : public UnifiedExpressionVisitor> { // Get the valid Binaryen IR expression representing the sequence of visited // instructions. The IRBuilder is reset and can be used with a fresh sequence // of instructions after this is called. - [[nodiscard]] Result build(); + Result build(); // Call visit() on an existing Expression with its non-child fields // initialized to initialize the child fields and refinalize it. - [[nodiscard]] Result<> visit(Expression*); + Result<> visit(Expression*); // Like visit, but pushes the expression onto the stack as-is without popping // any children or refinalization. @@ -66,27 +66,26 @@ class IRBuilder : public UnifiedExpressionVisitor> { // Handle the boundaries of control flow structures. Users may choose to use // the corresponding `makeXYZ` function below instead of `visitXYZStart`, but // either way must call `visitEnd` and friends at the appropriate times. - [[nodiscard]] Result<> visitFunctionStart(Function* func); - [[nodiscard]] Result<> visitBlockStart(Block* block); - [[nodiscard]] Result<> visitIfStart(If* iff, Name label = {}); - [[nodiscard]] Result<> visitElse(); - [[nodiscard]] Result<> visitLoopStart(Loop* iff); - [[nodiscard]] Result<> visitTryStart(Try* tryy, Name label = {}); - [[nodiscard]] Result<> visitCatch(Name tag); - [[nodiscard]] Result<> visitCatchAll(); - [[nodiscard]] Result<> visitDelegate(Index label); - [[nodiscard]] Result<> visitTryTableStart(TryTable* trytable, - Name label = {}); - [[nodiscard]] Result<> visitEnd(); + Result<> visitFunctionStart(Function* func); + Result<> visitBlockStart(Block* block); + Result<> visitIfStart(If* iff, Name label = {}); + Result<> visitElse(); + Result<> visitLoopStart(Loop* iff); + Result<> visitTryStart(Try* tryy, Name label = {}); + Result<> visitCatch(Name tag); + Result<> visitCatchAll(); + Result<> visitDelegate(Index label); + Result<> visitTryTableStart(TryTable* trytable, Name label = {}); + Result<> visitEnd(); // Used to visit break nodes when traversing a single block without its // context. The type indicates how many values the break carries to its // destination. - [[nodiscard]] Result<> visitBreakWithType(Break*, Type); + Result<> visitBreakWithType(Break*, Type); // Used to visit switch nodes when traversing a single block without its // context. The type indicates how many values the switch carries to its // destination. - [[nodiscard]] Result<> visitSwitchWithType(Switch*, Type); + Result<> visitSwitchWithType(Switch*, Type); // Binaryen IR uses names to refer to branch targets, but in general there may // be branches to constructs that do not yet have names, so in IRBuilder we @@ -95,145 +94,138 @@ class IRBuilder : public UnifiedExpressionVisitor> { // // Labels in delegates need special handling because the indexing needs to be // relative to the try's enclosing scope rather than the try itself. - [[nodiscard]] Result getLabelIndex(Name label, - bool inDelegate = false); + Result getLabelIndex(Name label, bool inDelegate = false); // Instead of calling visit, call makeXYZ to have the IRBuilder allocate the // nodes. This is generally safer than calling `visit` because the function // signatures ensure that there are no missing fields. - [[nodiscard]] Result<> makeNop(); - [[nodiscard]] Result<> makeBlock(Name label, Type type); - [[nodiscard]] Result<> makeIf(Name label, Type type); - [[nodiscard]] Result<> makeLoop(Name label, Type type); - [[nodiscard]] Result<> makeBreak(Index label, bool isConditional); - [[nodiscard]] Result<> makeSwitch(const std::vector& labels, - Index defaultLabel); + Result<> makeNop(); + Result<> makeBlock(Name label, Type type); + Result<> makeIf(Name label, Type type); + Result<> makeLoop(Name label, Type type); + Result<> makeBreak(Index label, bool isConditional); + Result<> makeSwitch(const std::vector& labels, Index defaultLabel); // Unlike Builder::makeCall, this assumes the function already exists. - [[nodiscard]] Result<> makeCall(Name func, bool isReturn); - [[nodiscard]] Result<> - makeCallIndirect(Name table, HeapType type, bool isReturn); - [[nodiscard]] Result<> makeLocalGet(Index local); - [[nodiscard]] Result<> makeLocalSet(Index local); - [[nodiscard]] Result<> makeLocalTee(Index local); - [[nodiscard]] Result<> makeGlobalGet(Name global); - [[nodiscard]] Result<> makeGlobalSet(Name global); - [[nodiscard]] Result<> makeLoad(unsigned bytes, - bool signed_, - Address offset, - unsigned align, - Type type, - Name mem); - [[nodiscard]] Result<> makeStore( + Result<> makeCall(Name func, bool isReturn); + Result<> makeCallIndirect(Name table, HeapType type, bool isReturn); + Result<> makeLocalGet(Index local); + Result<> makeLocalSet(Index local); + Result<> makeLocalTee(Index local); + Result<> makeGlobalGet(Name global); + Result<> makeGlobalSet(Name global); + Result<> makeLoad(unsigned bytes, + bool signed_, + Address offset, + unsigned align, + Type type, + Name mem); + Result<> makeStore( unsigned bytes, Address offset, unsigned align, Type type, Name mem); - [[nodiscard]] Result<> - makeAtomicLoad(unsigned bytes, Address offset, Type type, Name mem); - [[nodiscard]] Result<> - makeAtomicStore(unsigned bytes, Address offset, Type type, Name mem); - [[nodiscard]] Result<> makeAtomicRMW( + Result<> makeAtomicLoad(unsigned bytes, Address offset, Type type, Name mem); + Result<> makeAtomicStore(unsigned bytes, Address offset, Type type, Name mem); + Result<> makeAtomicRMW( AtomicRMWOp op, unsigned bytes, Address offset, Type type, Name mem); - [[nodiscard]] Result<> + Result<> makeAtomicCmpxchg(unsigned bytes, Address offset, Type type, Name mem); - [[nodiscard]] Result<> makeAtomicWait(Type type, Address offset, Name mem); - [[nodiscard]] Result<> makeAtomicNotify(Address offset, Name mem); - [[nodiscard]] Result<> makeAtomicFence(); - [[nodiscard]] Result<> makeSIMDExtract(SIMDExtractOp op, uint8_t lane); - [[nodiscard]] Result<> makeSIMDReplace(SIMDReplaceOp op, uint8_t lane); - [[nodiscard]] Result<> makeSIMDShuffle(const std::array& lanes); - [[nodiscard]] Result<> makeSIMDTernary(SIMDTernaryOp op); - [[nodiscard]] Result<> makeSIMDShift(SIMDShiftOp op); - [[nodiscard]] Result<> + Result<> makeAtomicWait(Type type, Address offset, Name mem); + Result<> makeAtomicNotify(Address offset, Name mem); + Result<> makeAtomicFence(); + Result<> makeSIMDExtract(SIMDExtractOp op, uint8_t lane); + Result<> makeSIMDReplace(SIMDReplaceOp op, uint8_t lane); + Result<> makeSIMDShuffle(const std::array& lanes); + Result<> makeSIMDTernary(SIMDTernaryOp op); + Result<> makeSIMDShift(SIMDShiftOp op); + Result<> makeSIMDLoad(SIMDLoadOp op, Address offset, unsigned align, Name mem); - [[nodiscard]] Result<> makeSIMDLoadStoreLane(SIMDLoadStoreLaneOp op, - Address offset, - unsigned align, - uint8_t lane, - Name mem); - [[nodiscard]] Result<> makeMemoryInit(Name data, Name mem); - [[nodiscard]] Result<> makeDataDrop(Name data); - [[nodiscard]] Result<> makeMemoryCopy(Name destMem, Name srcMem); - [[nodiscard]] Result<> makeMemoryFill(Name mem); - [[nodiscard]] Result<> makeConst(Literal val); - [[nodiscard]] Result<> makeUnary(UnaryOp op); - [[nodiscard]] Result<> makeBinary(BinaryOp op); - [[nodiscard]] Result<> makeSelect(std::optional type = std::nullopt); - [[nodiscard]] Result<> makeDrop(); - [[nodiscard]] Result<> makeReturn(); - [[nodiscard]] Result<> makeMemorySize(Name mem); - [[nodiscard]] Result<> makeMemoryGrow(Name mem); - [[nodiscard]] Result<> makeUnreachable(); - [[nodiscard]] Result<> makePop(Type type); - [[nodiscard]] Result<> makeRefNull(HeapType type); - [[nodiscard]] Result<> makeRefIsNull(); - [[nodiscard]] Result<> makeRefFunc(Name func); - [[nodiscard]] Result<> makeRefEq(); - [[nodiscard]] Result<> makeTableGet(Name table); - [[nodiscard]] Result<> makeTableSet(Name table); - [[nodiscard]] Result<> makeTableSize(Name table); - [[nodiscard]] Result<> makeTableGrow(Name table); - [[nodiscard]] Result<> makeTableFill(Name table); - [[nodiscard]] Result<> makeTableCopy(Name destTable, Name srcTable); - [[nodiscard]] Result<> makeTableInit(Name elem, Name table); - [[nodiscard]] Result<> makeTry(Name label, Type type); - [[nodiscard]] Result<> makeTryTable(Name label, - Type type, - const std::vector& tags, - const std::vector& labels, - const std::vector& isRefs); - [[nodiscard]] Result<> makeThrow(Name tag); - [[nodiscard]] Result<> makeRethrow(Index label); - [[nodiscard]] Result<> makeThrowRef(); - [[nodiscard]] Result<> makeTupleMake(uint32_t arity); - [[nodiscard]] Result<> makeTupleExtract(uint32_t arity, uint32_t index); - [[nodiscard]] Result<> makeTupleDrop(uint32_t arity); - [[nodiscard]] Result<> makeRefI31(Shareability share); - [[nodiscard]] Result<> makeI31Get(bool signed_); - [[nodiscard]] Result<> makeCallRef(HeapType type, bool isReturn); - [[nodiscard]] Result<> makeRefTest(Type type); - [[nodiscard]] Result<> makeRefCast(Type type); - [[nodiscard]] Result<> + Result<> makeSIMDLoadStoreLane(SIMDLoadStoreLaneOp op, + Address offset, + unsigned align, + uint8_t lane, + Name mem); + Result<> makeMemoryInit(Name data, Name mem); + Result<> makeDataDrop(Name data); + Result<> makeMemoryCopy(Name destMem, Name srcMem); + Result<> makeMemoryFill(Name mem); + Result<> makeConst(Literal val); + Result<> makeUnary(UnaryOp op); + Result<> makeBinary(BinaryOp op); + Result<> makeSelect(std::optional type = std::nullopt); + Result<> makeDrop(); + Result<> makeReturn(); + Result<> makeMemorySize(Name mem); + Result<> makeMemoryGrow(Name mem); + Result<> makeUnreachable(); + Result<> makePop(Type type); + Result<> makeRefNull(HeapType type); + Result<> makeRefIsNull(); + Result<> makeRefFunc(Name func); + Result<> makeRefEq(); + Result<> makeTableGet(Name table); + Result<> makeTableSet(Name table); + Result<> makeTableSize(Name table); + Result<> makeTableGrow(Name table); + Result<> makeTableFill(Name table); + Result<> makeTableCopy(Name destTable, Name srcTable); + Result<> makeTableInit(Name elem, Name table); + Result<> makeTry(Name label, Type type); + Result<> makeTryTable(Name label, + Type type, + const std::vector& tags, + const std::vector& labels, + const std::vector& isRefs); + Result<> makeThrow(Name tag); + Result<> makeRethrow(Index label); + Result<> makeThrowRef(); + Result<> makeTupleMake(uint32_t arity); + Result<> makeTupleExtract(uint32_t arity, uint32_t index); + Result<> makeTupleDrop(uint32_t arity); + Result<> makeRefI31(Shareability share); + Result<> makeI31Get(bool signed_); + Result<> makeCallRef(HeapType type, bool isReturn); + Result<> makeRefTest(Type type); + Result<> makeRefCast(Type type); + Result<> makeBrOn(Index label, BrOnOp op, Type in = Type::none, Type out = Type::none); - [[nodiscard]] Result<> makeStructNew(HeapType type); - [[nodiscard]] Result<> makeStructNewDefault(HeapType type); - [[nodiscard]] Result<> - makeStructGet(HeapType type, Index field, bool signed_); - [[nodiscard]] Result<> makeStructSet(HeapType type, Index field); - [[nodiscard]] Result<> makeArrayNew(HeapType type); - [[nodiscard]] Result<> makeArrayNewDefault(HeapType type); - [[nodiscard]] Result<> makeArrayNewData(HeapType type, Name data); - [[nodiscard]] Result<> makeArrayNewElem(HeapType type, Name elem); - [[nodiscard]] Result<> makeArrayNewFixed(HeapType type, uint32_t arity); - [[nodiscard]] Result<> makeArrayGet(HeapType type, bool signed_); - [[nodiscard]] Result<> makeArraySet(HeapType type); - [[nodiscard]] Result<> makeArrayLen(); - [[nodiscard]] Result<> makeArrayCopy(HeapType destType, HeapType srcType); - [[nodiscard]] Result<> makeArrayFill(HeapType type); - [[nodiscard]] Result<> makeArrayInitData(HeapType type, Name data); - [[nodiscard]] Result<> makeArrayInitElem(HeapType type, Name elem); - [[nodiscard]] Result<> makeRefAs(RefAsOp op); - [[nodiscard]] Result<> makeStringNew(StringNewOp op); - [[nodiscard]] Result<> makeStringConst(Name string); - [[nodiscard]] Result<> makeStringMeasure(StringMeasureOp op); - [[nodiscard]] Result<> makeStringEncode(StringEncodeOp op); - [[nodiscard]] Result<> makeStringConcat(); - [[nodiscard]] Result<> makeStringEq(StringEqOp op); - [[nodiscard]] Result<> makeStringWTF8Advance(); - [[nodiscard]] Result<> makeStringWTF16Get(); - [[nodiscard]] Result<> makeStringIterNext(); - [[nodiscard]] Result<> makeStringSliceWTF(); - [[nodiscard]] Result<> makeContBind(HeapType contTypeBefore, - HeapType contTypeAfter); - [[nodiscard]] Result<> makeContNew(HeapType ct); - [[nodiscard]] Result<> makeResume(HeapType ct, - const std::vector& tags, - const std::vector& labels); - [[nodiscard]] Result<> makeSuspend(Name tag); + Result<> makeStructNew(HeapType type); + Result<> makeStructNewDefault(HeapType type); + Result<> makeStructGet(HeapType type, Index field, bool signed_); + Result<> makeStructSet(HeapType type, Index field); + Result<> makeArrayNew(HeapType type); + Result<> makeArrayNewDefault(HeapType type); + Result<> makeArrayNewData(HeapType type, Name data); + Result<> makeArrayNewElem(HeapType type, Name elem); + Result<> makeArrayNewFixed(HeapType type, uint32_t arity); + Result<> makeArrayGet(HeapType type, bool signed_); + Result<> makeArraySet(HeapType type); + Result<> makeArrayLen(); + Result<> makeArrayCopy(HeapType destType, HeapType srcType); + Result<> makeArrayFill(HeapType type); + Result<> makeArrayInitData(HeapType type, Name data); + Result<> makeArrayInitElem(HeapType type, Name elem); + Result<> makeRefAs(RefAsOp op); + Result<> makeStringNew(StringNewOp op); + Result<> makeStringConst(Name string); + Result<> makeStringMeasure(StringMeasureOp op); + Result<> makeStringEncode(StringEncodeOp op); + Result<> makeStringConcat(); + Result<> makeStringEq(StringEqOp op); + Result<> makeStringWTF8Advance(); + Result<> makeStringWTF16Get(); + Result<> makeStringIterNext(); + Result<> makeStringSliceWTF(); + Result<> makeContBind(HeapType contTypeBefore, HeapType contTypeAfter); + Result<> makeContNew(HeapType ct); + Result<> makeResume(HeapType ct, + const std::vector& tags, + const std::vector& labels); + Result<> makeSuspend(Name tag); // Private functions that must be public for technical reasons. - [[nodiscard]] Result<> visitExpression(Expression*); + Result<> visitExpression(Expression*); // Do not push pops onto the stack since we generate our own pops as necessary // when visiting the beginnings of try blocks. - [[nodiscard]] Result<> visitPop(Pop*) { return Ok{}; } + Result<> visitPop(Pop*) { return Ok{}; } private: Module& wasm; @@ -562,12 +554,11 @@ class IRBuilder : public UnifiedExpressionVisitor> { // `block`, but otherwise we will have to allocate a new block. Result finishScope(Block* block = nullptr); - [[nodiscard]] Result getLabelName(Index label, - bool forDelegate = false); - [[nodiscard]] Result getDelegateLabelName(Index label) { + Result getLabelName(Index label, bool forDelegate = false); + Result getDelegateLabelName(Index label) { return getLabelName(label, true); } - [[nodiscard]] Result addScratchLocal(Type); + Result addScratchLocal(Type); struct HoistedVal { // The index in the stack of the original value-producing expression. @@ -578,7 +569,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { // Find the last value-producing expression, if any, and hoist its value to // the top of the stack using a scratch local if necessary. - [[nodiscard]] MaybeResult hoistLastValue(); + MaybeResult hoistLastValue(); // Transform the stack as necessary such that the original producer of the // hoisted value will be popped along with the final expression that produces // the value, if they are different. May only be called directly after @@ -586,11 +577,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { // consume, so if the hoisted value has `sizeHint` elements, it is left intact // even if it is a tuple. Otherwise, hoisted tuple values will be broken into // pieces. - [[nodiscard]] Result<> packageHoistedValue(const HoistedVal&, - size_t sizeHint = 1); + Result<> packageHoistedValue(const HoistedVal&, size_t sizeHint = 1); - [[nodiscard]] Result getLabelType(Index label); - [[nodiscard]] Result getLabelType(Name labelName); + Result getLabelType(Index label); + Result getLabelType(Name labelName); void dump(); }; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 398db68ff4f..0d46156be28 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -366,7 +366,7 @@ struct IRBuilder::ChildPopper ChildPopper(IRBuilder& builder) : builder(builder) {} private: - [[nodiscard]] Result<> popConstrainedChildren(std::vector& children) { + Result<> popConstrainedChildren(std::vector& children) { auto& scope = builder.getScope(); // Two-part indices into the stack of available expressions and the vector From 69591ded5acab404cba96af7ebc1afd54034c545 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Sat, 16 Nov 2024 00:40:01 -0800 Subject: [PATCH 136/622] Rename memory-copy-fill-lowering pass (#7082) Since the resulting code has the same undefined behavior as LLVM, make the pass name reflect that. --- src/passes/CMakeLists.txt | 2 +- ...opyFillLowering.cpp => LLVMMemoryCopyFillLowering.cpp} | 8 ++++---- src/passes/pass.cpp | 4 ++-- src/passes/passes.h | 2 +- test/lit/exec/memory-copy.wat | 2 +- test/lit/exec/memory-fill.wat | 2 +- test/lit/help/wasm-metadce.test | 8 ++++---- test/lit/help/wasm-opt.test | 8 ++++---- test/lit/help/wasm2js.test | 8 ++++---- test/lit/passes/memory-copy-fill-lowering.wast | 2 +- 10 files changed, 23 insertions(+), 23 deletions(-) rename src/passes/{MemoryCopyFillLowering.cpp => LLVMMemoryCopyFillLowering.cpp} (98%) diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 6053c55222a..c6e079079c0 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -58,12 +58,12 @@ set(passes_SOURCES JSPI.cpp LegalizeJSInterface.cpp LimitSegments.cpp + LLVMMemoryCopyFillLowering.cpp LocalCSE.cpp LocalSubtyping.cpp LogExecution.cpp LoopInvariantCodeMotion.cpp Memory64Lowering.cpp - MemoryCopyFillLowering.cpp MemoryPacking.cpp MergeBlocks.cpp MergeSimilarFunctions.cpp diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/LLVMMemoryCopyFillLowering.cpp similarity index 98% rename from src/passes/MemoryCopyFillLowering.cpp rename to src/passes/LLVMMemoryCopyFillLowering.cpp index 5855e545066..e5b940a5af2 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/LLVMMemoryCopyFillLowering.cpp @@ -25,8 +25,8 @@ // particular, pointer overflow is UB and not handled here). namespace wasm { -struct MemoryCopyFillLowering - : public WalkerPass> { +struct LLVMMemoryCopyFillLowering + : public WalkerPass> { bool needsMemoryCopy = false; bool needsMemoryFill = false; Name memCopyFuncName; @@ -253,8 +253,8 @@ struct MemoryCopyFillLowering } }; -Pass* createMemoryCopyFillLoweringPass() { - return new MemoryCopyFillLowering(); +Pass* createLLVMMemoryCopyFillLoweringPass() { + return new LLVMMemoryCopyFillLowering(); } } // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index fcefb89ad49..5cbf4a31fe9 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -272,10 +272,10 @@ void PassRegistry::registerPasses() { registerPass("table64-lowering", "lower 64-bit tables 32-bit ones", createTable64LoweringPass); - registerPass("memory-copy-fill-lowering", + registerPass("llvm-memory-copy-fill-lowering", "Lower memory.copy and memory.fill to wasm mvp and disable " "the bulk-memory feature.", - createMemoryCopyFillLoweringPass); + createLLVMMemoryCopyFillLoweringPass); registerPass("memory-packing", "packs memory into separate segments, skipping zeros", createMemoryPackingPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 121dbcd9de7..212a2b0e40a 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -80,6 +80,7 @@ Pass* createIntrinsicLoweringPass(); Pass* createTraceCallsPass(); Pass* createInstrumentLocalsPass(); Pass* createInstrumentMemoryPass(); +Pass* createLLVMMemoryCopyFillLoweringPass(); Pass* createLoopInvariantCodeMotionPass(); Pass* createMemory64LoweringPass(); Pass* createMemoryPackingPass(); @@ -116,7 +117,6 @@ Pass* createOptimizeForJSPass(); Pass* createOutliningPass(); #endif Pass* createPickLoadSignsPass(); -Pass* createMemoryCopyFillLoweringPass(); Pass* createModAsyncifyAlwaysOnlyUnwindPass(); Pass* createModAsyncifyNeverUnwindPass(); Pass* createPoppifyPass(); diff --git a/test/lit/exec/memory-copy.wat b/test/lit/exec/memory-copy.wat index 793f94656a2..3583b048e55 100644 --- a/test/lit/exec/memory-copy.wat +++ b/test/lit/exec/memory-copy.wat @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. -;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s +;; RUN: wasm-opt %s --enable-bulk-memory --llvm-memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s ;; Tests derived from bulk-memory.wast spec tests diff --git a/test/lit/exec/memory-fill.wat b/test/lit/exec/memory-fill.wat index dc9aead6da9..c63dcef0c92 100644 --- a/test/lit/exec/memory-fill.wat +++ b/test/lit/exec/memory-fill.wat @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. -;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s +;; RUN: wasm-opt %s --enable-bulk-memory --llvm-memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s ;; Tests derived from bulk-memory.wast spec tests diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 908b381e84f..50f71f8f52d 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -230,6 +230,10 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: @@ -239,10 +243,6 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: -;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and -;; CHECK-NEXT: memory.fill to wasm mvp and -;; CHECK-NEXT: disable the bulk-memory feature. -;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 7d173b08421..b30f62150c9 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -239,6 +239,10 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: @@ -248,10 +252,6 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: -;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and -;; CHECK-NEXT: memory.fill to wasm mvp and -;; CHECK-NEXT: disable the bulk-memory feature. -;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 514f638d912..69923a0649d 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -193,6 +193,10 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: @@ -202,10 +206,6 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: -;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and -;; CHECK-NEXT: memory.fill to wasm mvp and -;; CHECK-NEXT: disable the bulk-memory feature. -;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast index 540237ff7b5..551f5a4e6b5 100644 --- a/test/lit/passes/memory-copy-fill-lowering.wast +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: wasm-opt --enable-bulk-memory %s --memory-copy-fill-lowering -S -o - | filecheck %s +;; RUN: wasm-opt --enable-bulk-memory %s --llvm-memory-copy-fill-lowering -S -o - | filecheck %s (module (memory 0) From 08b7496306915dbe11030a7a4cf79207f9460d2f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 18 Nov 2024 15:28:29 -0800 Subject: [PATCH 137/622] [NFC] Finalize blocks with explicit breakability in IRBuilder (#7085) Since IRBuilder already knows what labels are used by branches, it is easy for it to pass that information when finalizing blocks. This avoids finalization having to walk the blocks looking for branches, speeding up a future version of the binary parser that uses IRBuilder by 10%. --- src/passes/Outlining.cpp | 8 ++++++++ src/wasm/wasm-ir-builder.cpp | 12 ++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index 429511557e1..b86e6cd17e1 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -304,6 +304,14 @@ struct Outlining : public Pass { // Position the outlined functions first in the functions vector to make // the outlining lit tests far more readable. moveOutlinedFunctions(module, substrings.size()); + + // Because we visit control flow in stringified order rather than normal + // postorder, IRBuilder is not able to properly track branches, so it may + // not have finalized blocks with the correct types. ReFinalize now to fix + // any issues. + PassRunner runner(getPassRunner()); + runner.add(std::make_unique()); + runner.run(); } Name addOutlinedFunction(Module* module, diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 0d46156be28..5253c91ee5d 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -972,7 +972,12 @@ Result<> IRBuilder::visitEnd() { block->type = blockType; return block; } - return builder.makeBlock(label, {curr}, blockType); + auto* block = builder.makeBlock(); + block->name = label; + block->list.push_back(curr); + block->finalize(blockType, + scope.labelUsed ? Block::HasBreak : Block::NoBreak); + return block; }; if (auto* func = scope.getFunction()) { @@ -985,9 +990,8 @@ Result<> IRBuilder::visitEnd() { } else if (auto* block = scope.getBlock()) { assert(*expr == block); block->name = scope.label; - // TODO: Track branches so we can know whether this block is a target and - // finalize more efficiently. - block->finalize(block->type); + block->finalize(block->type, + scope.labelUsed ? Block::HasBreak : Block::NoBreak); push(block); } else if (auto* loop = scope.getLoop()) { loop->body = *expr; From 25b8e6a714d2217e8735a925bc751900bce09d53 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 18 Nov 2024 15:43:11 -0800 Subject: [PATCH 138/622] Use hints when generating fresh labels in IRBuilder (#7086) IRBuilder often has to generate new label names for blocks and other scopes. Previously it would generate each new name by starting with "block" or "label" and incrementing a suffix until finding a fresh name, but this made name generation quadratic in the number of names to generate. To spend less time generating names, track a hint index at which to start looking for a fresh name and increment it every time a name is generated. This speeds up a version of the binary parser that uses IRBuilder by about 15%. --- src/wasm-ir-builder.h | 7 ++- src/wasm/wasm-ir-builder.cpp | 6 ++- test/lit/basic/reference-types.wast | 38 +++++++------- test/lit/passes/outlining.wast | 4 +- test/lit/wat-kitchen-sink.wast | 18 +++---- test/wasm2js/br_table_temp.2asm.js | 68 +++++++++++++------------- test/wasm2js/br_table_temp.2asm.js.opt | 20 ++++---- test/wasm2js/labels.2asm.js | 8 +-- 8 files changed, 87 insertions(+), 82 deletions(-) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index d6e45314933..30e770e28dd 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -507,16 +507,19 @@ class IRBuilder : public UnifiedExpressionVisitor> { // its stack. std::unordered_map> labelDepths; - Name makeFresh(Name label) { + Name makeFresh(Name label, Index hint = 0) { return Names::getValidName( label, [&](Name candidate) { return labelDepths.insert({candidate, {}}).second; }, - 0, + hint, ""); } + Index blockHint = 0; + Index labelHint = 0; + void pushScope(ScopeCtx scope) { if (auto label = scope.getOriginalLabel()) { // Assign a fresh label to the scope, if necessary. diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 5253c91ee5d..a73c7f2acb4 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -987,6 +987,8 @@ Result<> IRBuilder::visitEnd() { EHUtils::handleBlockNestedPops(func, wasm); } this->func = nullptr; + blockHint = 0; + labelHint = 0; } else if (auto* block = scope.getBlock()) { assert(*expr == block); block->name = scope.label; @@ -1073,9 +1075,9 @@ Result IRBuilder::getLabelName(Index label, bool forDelegate) { if (!scopeLabel) { // The scope does not already have a name, so we need to create one. if ((*scope)->getBlock()) { - scopeLabel = makeFresh("block"); + scopeLabel = makeFresh("block", blockHint++); } else { - scopeLabel = makeFresh("label"); + scopeLabel = makeFresh("label", labelHint++); } } if (!forDelegate) { diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 6ef51c23d39..44494c80d97 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -361,25 +361,17 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block $block0 (result eqref) - ;; CHECK-TEXT-NEXT: (br_if $block0 - ;; CHECK-TEXT-NEXT: (global.get $global_eqref) - ;; CHECK-TEXT-NEXT: (i32.const 1) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $block1 (result eqref) ;; CHECK-TEXT-NEXT: (br_if $block1 - ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: (global.get $global_eqref) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block $block2 (result funcref) + ;; CHECK-TEXT-NEXT: (block $block2 (result eqref) ;; CHECK-TEXT-NEXT: (br_if $block2 - ;; CHECK-TEXT-NEXT: (local.get $local_funcref) + ;; CHECK-TEXT-NEXT: (ref.null none) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -387,7 +379,7 @@ ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $block3 (result funcref) ;; CHECK-TEXT-NEXT: (br_if $block3 - ;; CHECK-TEXT-NEXT: (global.get $global_funcref) + ;; CHECK-TEXT-NEXT: (local.get $local_funcref) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -395,7 +387,7 @@ ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $block4 (result funcref) ;; CHECK-TEXT-NEXT: (br_if $block4 - ;; CHECK-TEXT-NEXT: (ref.null nofunc) + ;; CHECK-TEXT-NEXT: (global.get $global_funcref) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -403,15 +395,15 @@ ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $block5 (result funcref) ;; CHECK-TEXT-NEXT: (br_if $block5 - ;; CHECK-TEXT-NEXT: (ref.func $foo) + ;; CHECK-TEXT-NEXT: (ref.null nofunc) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block $block6 (result anyref) + ;; CHECK-TEXT-NEXT: (block $block6 (result funcref) ;; CHECK-TEXT-NEXT: (br_if $block6 - ;; CHECK-TEXT-NEXT: (local.get $local_anyref) + ;; CHECK-TEXT-NEXT: (ref.func $foo) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -419,7 +411,7 @@ ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $block7 (result anyref) ;; CHECK-TEXT-NEXT: (br_if $block7 - ;; CHECK-TEXT-NEXT: (global.get $global_anyref) + ;; CHECK-TEXT-NEXT: (local.get $local_anyref) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -427,7 +419,7 @@ ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $block8 (result anyref) ;; CHECK-TEXT-NEXT: (br_if $block8 - ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: (global.get $global_anyref) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -435,7 +427,7 @@ ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $block9 (result anyref) ;; CHECK-TEXT-NEXT: (br_if $block9 - ;; CHECK-TEXT-NEXT: (local.get $local_eqref) + ;; CHECK-TEXT-NEXT: (ref.null none) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -443,6 +435,14 @@ ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $block10 (result anyref) ;; CHECK-TEXT-NEXT: (br_if $block10 + ;; CHECK-TEXT-NEXT: (local.get $local_eqref) + ;; CHECK-TEXT-NEXT: (i32.const 1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $block11 (result anyref) + ;; CHECK-TEXT-NEXT: (br_if $block11 ;; CHECK-TEXT-NEXT: (ref.null none) ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) diff --git a/test/lit/passes/outlining.wast b/test/lit/passes/outlining.wast index 5119572b395..a02e6f2cd89 100644 --- a/test/lit/passes/outlining.wast +++ b/test/lit/passes/outlining.wast @@ -675,8 +675,8 @@ ;; CHECK: (func $a (type $1) (param $0 i32) (result i32) ;; CHECK-NEXT: (call $outline$) ;; CHECK-NEXT: (block $block - ;; CHECK-NEXT: (block $block0 - ;; CHECK-NEXT: (br_table $block $block0 + ;; CHECK-NEXT: (block $block1 + ;; CHECK-NEXT: (br_table $block $block1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index 97d9f57e835..8b77de74dac 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -2570,14 +2570,14 @@ ) ;; CHECK: (func $label-index (type $0) - ;; CHECK-NEXT: (block $block1 + ;; CHECK-NEXT: (block $block2 ;; CHECK-NEXT: (block $block - ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (block $block1 ;; CHECK-NEXT: (block $l ;; CHECK-NEXT: (br $block) - ;; CHECK-NEXT: (br $block0) - ;; CHECK-NEXT: (br $l) ;; CHECK-NEXT: (br $block1) + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: (br $block2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2844,9 +2844,9 @@ ;; CHECK: (func $br-table-index (type $0) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (block $block1 - ;; CHECK-NEXT: (block $block0 - ;; CHECK-NEXT: (br_table $block $l $block0 $block1 + ;; CHECK-NEXT: (block $block2 + ;; CHECK-NEXT: (block $block1 + ;; CHECK-NEXT: (br_table $block $l $block1 $block2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4821,9 +4821,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref $to-f32-cont)) ;; CHECK-NEXT: (tuple.drop 3 - ;; CHECK-NEXT: (block $block0 (type $34) (result i32 i64 (ref null $simple-cont)) + ;; CHECK-NEXT: (block $block1 (type $34) (result i32 i64 (ref null $simple-cont)) ;; CHECK-NEXT: (local.set $f - ;; CHECK-NEXT: (resume $simple-cont (on $empty $block) (on $tag-pair-to-pair $block0) + ;; CHECK-NEXT: (resume $simple-cont (on $empty $block) (on $tag-pair-to-pair $block1) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i64.const 1) ;; CHECK-NEXT: (local.get $ct) diff --git a/test/wasm2js/br_table_temp.2asm.js b/test/wasm2js/br_table_temp.2asm.js index 9bc6cafa4f3..a8592a186c4 100644 --- a/test/wasm2js/br_table_temp.2asm.js +++ b/test/wasm2js/br_table_temp.2asm.js @@ -115,7 +115,7 @@ function asmFunc(imports) { function $11($0_1) { $0_1 = $0_1 | 0; var $2_1 = 0, $4_1 = 0, $3_1 = 0; - block0 : { + block1 : { block : { $2_1 = 33; $3_1 = $2_1; @@ -124,7 +124,7 @@ function asmFunc(imports) { case 0: break block; default: - break block0; + break block1; }; } $4_1 = 32; @@ -134,7 +134,7 @@ function asmFunc(imports) { function $12($0_1) { $0_1 = $0_1 | 0; - block3 : { + block4 : { switch ($0_1 | 0) { case 3: return 100 | 0; @@ -145,7 +145,7 @@ function asmFunc(imports) { case 0: return 103 | 0; default: - break block3; + break block4; }; } return 104 | 0; @@ -154,11 +154,11 @@ function asmFunc(imports) { function $13($0_1) { $0_1 = $0_1 | 0; var $1_1 = 0, $3_1 = 0, $4_1 = 0, $5_1 = 0, $6_1 = 0, $7_1 = 0, $8_1 = 0; - block3 : { + block4 : { block : { - block0 : { - block1 : { - block2 : { + block1 : { + block2 : { + block3 : { $3_1 = 200; $4_1 = $3_1; $5_1 = $3_1; @@ -169,13 +169,13 @@ function asmFunc(imports) { case 0: break block; case 1: - break block0; - case 2: break block1; - case 3: + case 2: break block2; - default: + case 3: break block3; + default: + break block4; }; } $1_1 = $7_1; @@ -196,7 +196,7 @@ function asmFunc(imports) { function $14($0_1) { $0_1 = $0_1 | 0; - block0 : { + block1 : { switch ($0_1 | 0) { case 0: case 2: @@ -12508,7 +12508,7 @@ function asmFunc(imports) { case 24614: return 0 | 0; default: - break block0; + break block1; }; } return 1 | 0; @@ -13052,8 +13052,8 @@ function asmFunc(imports) { function $58($0_1) { $0_1 = $0_1 | 0; var $2_1 = 0, $4_1 = 0, $5_1 = 0, $3_1 = 0; - block1 : { - block0 : { + block2 : { + block1 : { block : { $2_1 = 16; $3_1 = $2_1; @@ -13063,9 +13063,9 @@ function asmFunc(imports) { case 0: break block; case 1: - break block0; - default: break block1; + default: + break block2; }; } $4_1 = 2 + $3_1 | 0; @@ -13079,8 +13079,8 @@ function asmFunc(imports) { $0_1 = $0_1 | 0; var $2_1 = 0, $3_1 = 0, $4_1 = 0, $5_1 = 0; block : { - block0 : { - block1 : { + block1 : { + block2 : { $2_1 = 8; $3_1 = $2_1; $4_1 = $2_1; @@ -13089,9 +13089,9 @@ function asmFunc(imports) { case 0: break block; case 1: - break block0; - default: break block1; + default: + break block2; }; } $4_1 = 16; @@ -13104,8 +13104,8 @@ function asmFunc(imports) { function $60($0_1) { $0_1 = $0_1 | 0; var $2_1 = 0, $3_1 = 0, $4_1 = 0, $5_1 = 0; - block1 : { - block0 : { + block2 : { + block1 : { block : { $2_1 = 8; $3_1 = $2_1; @@ -13115,9 +13115,9 @@ function asmFunc(imports) { case 0: break block; case 1: - break block0; - default: break block1; + default: + break block2; }; } $4_1 = 16; @@ -13130,14 +13130,14 @@ function asmFunc(imports) { function $61($0_1) { $0_1 = $0_1 | 0; var $3_1 = 0, $2_1 = 0, $4_1 = 0; - block0 : { + block1 : { block : { $2_1 = 8; $3_1 = $2_1; $4_1 = $2_1; switch ($0_1 | 0) { case 1: - break block0; + break block1; default: break block; }; @@ -13150,8 +13150,8 @@ function asmFunc(imports) { function $62($0_1) { $0_1 = $0_1 | 0; var $2_1 = 0, $3_1 = 0, $4_1 = 0, $5_1 = 0; - block1 : { - block0 : { + block2 : { + block1 : { block : { $2_1 = 8; $3_1 = $2_1; @@ -13161,9 +13161,9 @@ function asmFunc(imports) { case 0: break block; case 1: - break block0; - default: break block1; + default: + break block2; }; } $4_1 = 16; @@ -13176,14 +13176,14 @@ function asmFunc(imports) { function $63($0_1) { $0_1 = $0_1 | 0; var $3_1 = 0, $2_1 = 0, $4_1 = 0; - block0 : { + block1 : { block : { $2_1 = 8; $3_1 = $2_1; $4_1 = $2_1; switch ($0_1 | 0) { case 1: - break block0; + break block1; default: break block; }; diff --git a/test/wasm2js/br_table_temp.2asm.js.opt b/test/wasm2js/br_table_temp.2asm.js.opt index b64e46a77a3..f1dad0ab274 100644 --- a/test/wasm2js/br_table_temp.2asm.js.opt +++ b/test/wasm2js/br_table_temp.2asm.js.opt @@ -60,7 +60,7 @@ function asmFunc(imports) { function $12($0) { $0 = $0 | 0; - block3 : { + block4 : { switch ($0 | 0) { case 3: return 100; @@ -71,7 +71,7 @@ function asmFunc(imports) { case 0: return 103; default: - break block3; + break block4; }; } return 104; @@ -79,7 +79,7 @@ function asmFunc(imports) { function $13($0) { $0 = $0 | 0; - block3 : { + block4 : { switch ($0 | 0) { case 3: return 210; @@ -90,7 +90,7 @@ function asmFunc(imports) { case 0: return 213; default: - break block3; + break block4; }; } return 214; @@ -98,7 +98,7 @@ function asmFunc(imports) { function $14($0) { $0 = $0 | 0; - block0 : { + block1 : { switch ($0 | 0) { case 0: case 2: @@ -12410,7 +12410,7 @@ function asmFunc(imports) { case 24614: return 0; default: - break block0; + break block1; }; } return 1; @@ -12554,7 +12554,7 @@ function asmFunc(imports) { $0 = $0 | 0; var $1 = 0; $1 = 16; - block1 : { + block2 : { switch ($0 | 0) { case 0: $1 = 18; @@ -12562,7 +12562,7 @@ function asmFunc(imports) { $1 = $1 + 1 | 0; break; default: - break block1; + break block2; }; } return $1 | 0; @@ -12590,7 +12590,7 @@ function asmFunc(imports) { $0 = $0 | 0; var $1 = 0; $1 = 8; - block1 : { + block2 : { switch ($0 | 0) { case 0: $1 = 16; @@ -12598,7 +12598,7 @@ function asmFunc(imports) { $1 = $1 + 1 | 0; break; default: - break block1; + break block2; }; } return $1 | 0; diff --git a/test/wasm2js/labels.2asm.js b/test/wasm2js/labels.2asm.js index ac29783006e..431e172d9f0 100644 --- a/test/wasm2js/labels.2asm.js +++ b/test/wasm2js/labels.2asm.js @@ -143,10 +143,6 @@ function asmFunc(imports) { break label; } i = i + 1 | 0; - label0 : { - break label0; - } - i = i + 1 | 0; label1 : { break label1; } @@ -159,6 +155,10 @@ function asmFunc(imports) { break label3; } i = i + 1 | 0; + label4 : { + break label4; + } + i = i + 1 | 0; return i | 0; } From b0e999a2b8841d8be21cbcdc84cbc1d6469e36d7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 Nov 2024 09:28:01 -0800 Subject: [PATCH 139/622] Fuzzing: ClusterFuzz integration (#7079) The main addition here is a bundle_clusterfuzz.py script which will package up the exact files that should be uploaded to ClusterFuzz. It also documents the process and bundling and testing. You can do bundle.py OUTPUT_FILE.tgz That bundles wasm-opt from ./bin., which is enough for local testing. For actually uploading to ClusterFuzz, we need a portable build, and @dschuff had the idea to reuse the emsdk build, which works nicely. Doing bundle.py OUTPUT_FILE.tgz --build-dir=/path/to/emsdk/upstream/ will bundle wasm-opt (+libs) from the emsdk. I verified that those builds work on ClusterFuzz. I added several forms of testing here. First, our main fuzzer fuzz_opt.py now has a ClusterFuzz testcase handler, which simulates a ClusterFuzz environment. Second, there are smoke tests that run in the unit test suite, and can also be run separately: python -m unittest test/unit/test_cluster_fuzz.py Those unit tests can also run on a given bundle, e.g. one created from an emsdk build, for testing right before upload: BINARYEN_CLUSTER_FUZZ_BUNDLE=/path/to/bundle.tgz python -m unittest test/unit/test_cluster_fuzz.py A third piece of testing is to add a --fuzz-passes test. That is a mode for -ttf (translate random data into a valid wasm fuzz testcase) that uses random data to pick and run a set of passes, to further shape the wasm. (--fuzz-passes had no previous testing, and this PR fixes it and tidies it up a little, adding some newer passes too). Otherwise this PR includes the key run.py script that is bundled and then executed by ClusterFuzz, basically a python script that runs wasm-opt -ttf [..] to generate testcases, sets up their JS, and emits them. fuzz_shell.js, which is the JS to execute testcases, will now check if it is provided binary data of a wasm file. If so, it does not read a wasm file from argv[1]. (This is needed because ClusterFuzz expects a single file for the testcase, so we make a JS file with bundled wasm inside it.) --- scripts/bundle_clusterfuzz.py | 135 +++++++++ scripts/clusterfuzz/run.py | 163 +++++++++++ scripts/fuzz_opt.py | 82 +++++- scripts/fuzz_shell.js | 10 +- src/tools/fuzzing/fuzzing.cpp | 133 ++++++++- src/tools/wasm-opt.cpp | 4 +- test/lit/help/wasm-opt.test | 8 +- .../fuzz_metrics_passes_noprint.bin.txt | 35 +++ .../passes/fuzz_metrics_passes_noprint.passes | 1 + test/passes/fuzz_metrics_passes_noprint.wasm | Bin 0 -> 40960 bytes test/unit/test_cluster_fuzz.py | 259 ++++++++++++++++++ 11 files changed, 808 insertions(+), 22 deletions(-) create mode 100755 scripts/bundle_clusterfuzz.py create mode 100755 scripts/clusterfuzz/run.py create mode 100644 test/passes/fuzz_metrics_passes_noprint.bin.txt create mode 100644 test/passes/fuzz_metrics_passes_noprint.passes create mode 100644 test/passes/fuzz_metrics_passes_noprint.wasm create mode 100644 test/unit/test_cluster_fuzz.py diff --git a/scripts/bundle_clusterfuzz.py b/scripts/bundle_clusterfuzz.py new file mode 100755 index 00000000000..a035538377c --- /dev/null +++ b/scripts/bundle_clusterfuzz.py @@ -0,0 +1,135 @@ +#!/usr/bin/python3 + +''' +Bundle files for uploading to ClusterFuzz. + +Usage: + +bundle.py OUTPUT_FILE.tgz [--build-dir=BUILD_DIR] + +The output file will be a .tgz file. + +if a build directory is provided, we will look under there to find bin/wasm-opt +and lib/libbinaryen.so. A useful place to get builds from is the Emscripten SDK, +as you can do + + ./emsdk install tot + +after which ./upstream/ (from the emsdk dir) will contain builds of wasm-opt and +libbinaryen.so (that are designed to run on as many systems as possible, by not +depending on newer libc symbols, etc., as opposed to a normal local build). +Thus, the full workflow could be + + cd emsdk + ./emsdk install tot + cd ../binaryen + python3 scripts/bundle_clusterfuzz.py binaryen_wasm_fuzzer.tgz --build-dir=../emsdk/upstream + +When using --build-dir in this way, you are responsible for ensuring that the +wasm-opt in the build dir is compatible with the scripts in the current dir +(e.g., if run.py here passes a flag that is only in a new/older version of +wasm-opt, a problem can happen). + +Before uploading to ClusterFuzz, it is worth doing the following: + + 1. Run the local fuzzer (scripts/fuzz_opt.py). That includes a ClusterFuzz + testcase handler, which simulates what ClusterFuzz does. + + 2. Run the unit tests, which include smoke tests for our ClusterFuzz support: + + python -m unittest test/unit/test_cluster_fuzz.py + + Look at the logs, which will contain statistics on the wasm files the + fuzzer emits, and see that they look reasonable. + + You should run the unit tests on the bundle you are about to upload, by + setting the proper env var like this (using the same filename as above): + + BINARYEN_CLUSTER_FUZZ_BUNDLE=`pwd`/binaryen_wasm_fuzzer.tgz python -m unittest test/unit/test_cluster_fuzz.py + + Note that you must pass an absolute filename (e.g. using pwd as shown). + + The unittest logs should reflect that that bundle is being used at the + very start ("Using existing bundle: ..." rather than "Making a new + bundle"). Note that some of the unittests also create their own bundles, to + test the bundling script itself, so later down you will see logging of + bundle creation even if you provide a bundle. + +After uploading to ClusterFuzz, you can wait a while for it to run, and then: + + 1. Inspect the log to see that we generate all the testcases properly, and + their sizes look reasonably random, etc. + + 2. Inspect the sample testcase and run it locally, to see that + + d8 --wasm-staging testcase.js + + properly runs the testcase, emitting logging etc. + + 3. Check the stats and crashes page (known crashes should at least be showing + up). Note that these may take longer to show up than 1 and 2. +''' + +import os +import sys +import tarfile + +# Read the filenames first, as importing |shared| changes the directory. +output_file = os.path.abspath(sys.argv[1]) +print(f'Bundling to: {output_file}') +assert output_file.endswith('.tgz'), 'Can only generate a .tgz' + +build_dir = None +if len(sys.argv) >= 3: + assert sys.argv[2].startswith('--build-dir=') + build_dir = sys.argv[2].split('=')[1] + build_dir = os.path.abspath(build_dir) + # Delete the argument, as importing |shared| scans it. + sys.argv.pop() + +from test import shared # noqa + +# Pick where to get the builds +if build_dir: + binaryen_bin = os.path.join(build_dir, 'bin') + binaryen_lib = os.path.join(build_dir, 'lib') +else: + binaryen_bin = shared.options.binaryen_bin + binaryen_lib = shared.options.binaryen_lib + +with tarfile.open(output_file, "w:gz") as tar: + # run.py + run = os.path.join(shared.options.binaryen_root, 'scripts', 'clusterfuzz', 'run.py') + print(f' .. run: {run}') + tar.add(run, arcname='run.py') + + # fuzz_shell.js + fuzz_shell = os.path.join(shared.options.binaryen_root, 'scripts', 'fuzz_shell.js') + print(f' .. fuzz_shell: {fuzz_shell}') + tar.add(fuzz_shell, arcname='scripts/fuzz_shell.js') + + # wasm-opt binary + wasm_opt = os.path.join(binaryen_bin, 'wasm-opt') + print(f' .. wasm-opt: {wasm_opt}') + tar.add(wasm_opt, arcname='bin/wasm-opt') + + # For a dynamic build we also need libbinaryen.so and possibly other files. + # Try both .so and .dylib suffixes for more OS coverage. + for suffix in ['.so', '.dylib']: + libbinaryen = os.path.join(binaryen_lib, f'libbinaryen{suffix}') + if os.path.exists(libbinaryen): + print(f' .. libbinaryen: {libbinaryen}') + tar.add(libbinaryen, arcname=f'lib/libbinaryen{suffix}') + + # The emsdk build also includes some more necessary files. + for name in [f'libc++{suffix}', f'libc++{suffix}.2', f'libc++{suffix}.2.0']: + path = os.path.join(binaryen_lib, name) + if os.path.exists(path): + print(f' ......... : {path}') + tar.add(path, arcname=f'lib/{name}') + +print('Done.') +print('To run the tests on this bundle, do:') +print() +print(f'BINARYEN_CLUSTER_FUZZ_BUNDLE={output_file} python -m unittest test/unit/test_cluster_fuzz.py') +print() diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py new file mode 100755 index 00000000000..efddfc2d43b --- /dev/null +++ b/scripts/clusterfuzz/run.py @@ -0,0 +1,163 @@ +# +# Copyright 2024 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. + +''' +ClusterFuzz run.py script: when run by ClusterFuzz, it uses wasm-opt to generate +a fixed number of testcases. This is a "blackbox fuzzer", see + +https://google.github.io/clusterfuzz/setting-up-fuzzing/blackbox-fuzzing/ + +This file should be bundled up together with the other files it needs, see +bundle_clusterfuzz.py. +''' + +import os +import getopt +import random +import subprocess +import sys + +# The V8 flags we put in the "fuzzer flags" files, which tell ClusterFuzz how to +# run V8. By default we apply all staging flags. +FUZZER_FLAGS_FILE_CONTENTS = '--wasm-staging' + +# Maximum size of the random data that we feed into wasm-opt -ttf. This is +# smaller than fuzz_opt.py's INPUT_SIZE_MAX because that script is tuned for +# fuzzing large wasm files (to reduce the overhead we have of launching many +# processes per file), which is less of an issue on ClusterFuzz. +MAX_RANDOM_SIZE = 15 * 1024 + +# The prefix for fuzz files. +FUZZ_FILENAME_PREFIX = 'fuzz-' + +# The prefix for flags files. +FLAGS_FILENAME_PREFIX = 'flags-' + +# The name of the fuzzer (appears after FUZZ_FILENAME_PREFIX / +# FLAGS_FILENAME_PREFIX). +FUZZER_NAME_PREFIX = 'binaryen-' + +# The root directory of the bundle this will be in, which is the directory of +# this very file. +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) + +# The path to the wasm-opt binary that we run to generate testcases. +FUZZER_BINARY_PATH = os.path.join(ROOT_DIR, 'bin', 'wasm-opt') + +# The path to the fuzz_shell.js script that will execute the wasm in each +# testcase. +JS_SHELL_PATH = os.path.join(ROOT_DIR, 'scripts', 'fuzz_shell.js') + +# The arguments we provide to wasm-opt to generate wasm files. +FUZZER_ARGS = [ + # Generate a wasm from random data. + '--translate-to-fuzz', + # Run some random passes, to further shape the random wasm we emit. + '--fuzz-passes', + # Enable all features but disable ones not yet ready for fuzzing. This may + # be a smaller set than fuzz_opt.py, as that enables a few experimental + # flags, while here we just fuzz with d8's --wasm-staging. + '-all', + '--disable-shared-everything', + '--disable-fp16', +] + + +# Returns the file name for fuzz or flags files. +def get_file_name(prefix, index): + return f'{prefix}{FUZZER_NAME_PREFIX}{index}.js' + + +# Returns the contents of a .js fuzz file, given particular wasm contents that +# we want to be executed. +def get_js_file_contents(wasm_contents): + # Start with the standard JS shell. + with open(JS_SHELL_PATH) as file: + js = file.read() + + # Prepend the wasm contents, so they are used (rather than the normal + # mechanism where the wasm file's name is provided in argv). + wasm_contents = ','.join([str(c) for c in wasm_contents]) + js = f'var binary = new Uint8Array([{wasm_contents}]);\n\n' + js + return js + + +def main(argv): + # Parse the options. See + # https://google.github.io/clusterfuzz/setting-up-fuzzing/blackbox-fuzzing/#uploading-a-fuzzer + output_dir = '.' + num = 100 + expected_flags = ['input_dir=', 'output_dir=', 'no_of_files='] + optlist, _ = getopt.getopt(argv[1:], '', expected_flags) + for option, value in optlist: + if option == '--output_dir': + output_dir = value + elif option == '--no_of_files': + num = int(value) + + for i in range(1, num + 1): + input_data_file_path = os.path.join(output_dir, f'{i}.input') + wasm_file_path = os.path.join(output_dir, f'{i}.wasm') + + # wasm-opt may fail to run in rare cases (when the fuzzer emits code it + # detects as invalid). Just try again in such a case. + for attempt in range(0, 100): + # Generate random data. + random_size = random.SystemRandom().randint(1, MAX_RANDOM_SIZE) + with open(input_data_file_path, 'wb') as file: + file.write(os.urandom(random_size)) + + # Generate wasm from the random data. + cmd = [FUZZER_BINARY_PATH] + FUZZER_ARGS + cmd += ['-o', wasm_file_path, input_data_file_path] + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError: + # Try again. + print('(oops, retrying wasm-opt)') + attempt += 1 + if attempt == 99: + # Something is very wrong! + raise + continue + # Success, leave the loop. + break + + # Generate a testcase from the wasm + with open(wasm_file_path, 'rb') as file: + wasm_contents = file.read() + testcase_file_path = os.path.join(output_dir, + get_file_name(FUZZ_FILENAME_PREFIX, i)) + js_file_contents = get_js_file_contents(wasm_contents) + with open(testcase_file_path, 'w') as file: + file.write(js_file_contents) + + # Emit a corresponding flags file. + flags_file_path = os.path.join(output_dir, + get_file_name(FLAGS_FILENAME_PREFIX, i)) + with open(flags_file_path, 'w') as file: + file.write(FUZZER_FLAGS_FILE_CONTENTS) + + print(f'Created testcase: {testcase_file_path}, {len(wasm_contents)} bytes') + + # Remove temporary files. + os.remove(input_data_file_path) + os.remove(wasm_file_path) + + print(f'Created {num} testcases.') + + +if __name__ == '__main__': + main(sys.argv) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index bf712c82120..cd583e026ee 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -36,6 +36,7 @@ import random import re import sys +import tarfile import time import traceback from os.path import abspath @@ -1574,6 +1575,84 @@ def handle(self, wasm): run([in_bin('wasm-opt'), abspath('a.wast')] + FEATURE_OPTS) +# Fuzz in a near-identical manner to how we fuzz on ClusterFuzz. This is mainly +# to see that fuzzing that way works properly (it likely won't catch anything +# the other fuzzers here catch, though it is possible). That is, running this +# script continuously will give continuous cover that ClusterFuzz should be +# running ok. +# +# Note that this is *not* deterministic like the other fuzzers: it runs run.py +# like ClusterFuzz does, and that generates its own random data. If a bug is +# caught here, it must be reduced manually. +class ClusterFuzz(TestCaseHandler): + frequency = 0.1 + + def handle(self, wasm): + self.ensure() + + # run.py() should emit these two files. Delete them to make sure they + # are created by run.py() in the next step. + fuzz_file = 'fuzz-binaryen-1.js' + flags_file = 'flags-binaryen-1.js' + for f in [fuzz_file, flags_file]: + if os.path.exists(f): + os.unlink(f) + + # Call run.py(), similarly to how ClusterFuzz does. + run([sys.executable, + os.path.join(self.clusterfuzz_dir, 'run.py'), + '--output_dir=' + os.getcwd(), + '--no_of_files=1']) + + # We should see the two files. + assert os.path.exists(fuzz_file) + assert os.path.exists(flags_file) + + # Run the testcase in V8, similarly to how ClusterFuzz does. + cmd = [shared.V8] + # The flags are given in the flags file - we do *not* use our normal + # flags here! + with open(flags_file, 'r') as f: + flags = f.read() + cmd.append(flags) + # Run the fuzz file, which contains a modified fuzz_shell.js - we do + # *not* run fuzz_shell.js normally. + cmd.append(os.path.abspath(fuzz_file)) + # No wasm file needs to be provided: it is hardcoded into the JS. Note + # that we use run_vm(), which will ignore known issues in our output and + # in V8. Those issues may cause V8 to e.g. reject a binary we emit that + # is invalid, but that should not be a problem for ClusterFuzz (it isn't + # a crash). + output = run_vm(cmd) + + # Verify that we called something. The fuzzer should always emit at + # least one exported function (unless we've decided to ignore the entire + # run). + if output != IGNORE: + assert FUZZ_EXEC_CALL_PREFIX in output + + def ensure(self): + # The first time we actually run, set things up: make a bundle like the + # one ClusterFuzz receives, and unpack it for execution into a dir. The + # existence of that dir shows we've ensured all we need. + if hasattr(self, 'clusterfuzz_dir'): + return + + self.clusterfuzz_dir = 'clusterfuzz' + if os.path.exists(self.clusterfuzz_dir): + shutil.rmtree(self.clusterfuzz_dir) + os.mkdir(self.clusterfuzz_dir) + + print('Bundling for ClusterFuzz') + bundle = 'fuzz_opt_clusterfuzz_bundle.tgz' + run([in_binaryen('scripts', 'bundle_clusterfuzz.py'), bundle]) + + print('Unpacking for ClusterFuzz') + tar = tarfile.open(bundle, "r:gz") + tar.extractall(path=self.clusterfuzz_dir) + tar.close() + + # The global list of all test case handlers testcase_handlers = [ FuzzExec(), @@ -1585,7 +1664,8 @@ def handle(self, wasm): Merge(), # TODO: enable when stable enough, and adjust |frequency| (see above) # Split(), - RoundtripText() + RoundtripText(), + ClusterFuzz(), ] diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index d9a994896ba..ce817646e27 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -25,14 +25,18 @@ if (typeof process === 'object' && typeof require === 'function') { }; } -// We are given the binary to run as a parameter. -var binary = readBinary(argv[0]); +// The binary to be run. This may be set already (by code that runs before this +// script), and if not, we get the filename from argv. +var binary; +if (!binary) { + binary = readBinary(argv[0]); +} // Normally we call all the exports of the given wasm file. But, if we are // passed a final parameter in the form of "exports:X,Y,Z" then we call // specifically the exports X, Y, and Z. var exportsToCall; -if (argv[argv.length - 1].startsWith('exports:')) { +if (argv.length > 0 && argv[argv.length - 1].startsWith('exports:')) { exportsToCall = argv[argv.length - 1].substr('exports:'.length).split(','); argv.pop(); } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index cbdbff3cabf..ed653ef6b96 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -55,16 +55,23 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, wasm, read_file>(filename, Flags::Binary)) {} void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { + // Pick random passes to further shape the wasm. This is similar to how we + // pick random passes in fuzz_opt.py, but the goal there is to find problems + // in the passes, while the goal here is more to shape the wasm, so that + // translate-to-fuzz emits interesting outputs (the latter is important for + // things like ClusterFuzz, where we are using Binaryen to fuzz other things + // than itself). As a result, the list of passes here is different from + // fuzz_opt.py. while (options.passes.size() < 20 && !random.finished() && !oneIn(3)) { - switch (upTo(32)) { + switch (upTo(42)) { case 0: case 1: case 2: case 3: case 4: { - options.passes.push_back("O"); options.passOptions.optimizeLevel = upTo(4); - options.passOptions.shrinkLevel = upTo(4); + options.passOptions.shrinkLevel = upTo(3); + options.addDefaultOptPasses(); break; } case 5: @@ -83,7 +90,14 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { options.passes.push_back("duplicate-function-elimination"); break; case 10: - options.passes.push_back("flatten"); + // Some features do not support flatten yet. + if (!wasm.features.hasReferenceTypes() && + !wasm.features.hasExceptionHandling() && !wasm.features.hasGC()) { + options.passes.push_back("flatten"); + if (oneIn(2)) { + options.passes.push_back("rereloop"); + } + } break; case 11: options.passes.push_back("inlining"); @@ -127,11 +141,9 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { case 24: options.passes.push_back("reorder-locals"); break; - case 25: { - options.passes.push_back("flatten"); - options.passes.push_back("rereloop"); + case 25: + options.passes.push_back("directize"); break; - } case 26: options.passes.push_back("simplify-locals"); break; @@ -150,18 +162,115 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { case 31: options.passes.push_back("vacuum"); break; + case 32: + options.passes.push_back("merge-locals"); + break; + case 33: + options.passes.push_back("licm"); + break; + case 34: + options.passes.push_back("tuple-optimization"); + break; + case 35: + options.passes.push_back("rse"); + break; + case 36: + options.passes.push_back("monomorphize"); + break; + case 37: + options.passes.push_back("monomorphize-always"); + break; + case 38: + case 39: + case 40: + case 41: + // GC specific passes. + if (wasm.features.hasGC()) { + // Most of these depend on closed world, so just set that. + options.passOptions.closedWorld = true; + + switch (upTo(16)) { + case 0: + options.passes.push_back("abstract-type-refining"); + break; + case 1: + options.passes.push_back("cfp"); + break; + case 2: + options.passes.push_back("gsi"); + break; + case 3: + options.passes.push_back("gto"); + break; + case 4: + options.passes.push_back("heap2local"); + break; + case 5: + options.passes.push_back("heap-store-optimization"); + break; + case 6: + options.passes.push_back("minimize-rec-groups"); + break; + case 7: + options.passes.push_back("remove-unused-types"); + break; + case 8: + options.passes.push_back("signature-pruning"); + break; + case 9: + options.passes.push_back("signature-refining"); + break; + case 10: + options.passes.push_back("type-finalizing"); + break; + case 11: + options.passes.push_back("type-refining"); + break; + case 12: + options.passes.push_back("type-merging"); + break; + case 13: + options.passes.push_back("type-ssa"); + break; + case 14: + options.passes.push_back("type-unfinalizing"); + break; + case 15: + options.passes.push_back("unsubtyping"); + break; + default: + WASM_UNREACHABLE("unexpected value"); + } + } + break; default: WASM_UNREACHABLE("unexpected value"); } } + if (oneIn(2)) { + // We randomize these when we pick -O?, but sometimes do so even without, as + // they affect some passes. options.passOptions.optimizeLevel = upTo(4); + options.passOptions.shrinkLevel = upTo(3); } - if (oneIn(2)) { - options.passOptions.shrinkLevel = upTo(4); + + if (!options.passOptions.closedWorld && oneIn(2)) { + options.passOptions.closedWorld = true; + } + + // Usually DCE at the very end, to ensure that our binaries validate in other + // VMs, due to how non-nullable local validation and unreachable code + // interact. See fuzz_opt.py and + // https://github.com/WebAssembly/binaryen/pull/5665 + // https://github.com/WebAssembly/binaryen/issues/5599 + if (wasm.features.hasGC() && !oneIn(10)) { + options.passes.push_back("dce"); } - std::cout << "opt level: " << options.passOptions.optimizeLevel << '\n'; - std::cout << "shrink level: " << options.passOptions.shrinkLevel << '\n'; + + // TODO: We could in theory run some function-level passes on particular + // functions, but then we'd need to do this after generation, not + // before (and random data no longer remains then). } void TranslateToFuzzReader::build() { diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 3e11521790d..3e429a976fd 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -161,8 +161,8 @@ int main(int argc, const char* argv[]) { }) .add("--fuzz-passes", "-fp", - "Pick a random set of passes to run, useful for fuzzing. this depends " - "on translate-to-fuzz (it picks the passes from the input)", + "When doing translate-to-fuzz, pick a set of random passes from the " + "input to further shape the wasm", WasmOptOption, Options::Arguments::Zero, [&](Options* o, const std::string& arguments) { fuzzPasses = true; }) diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index b30f62150c9..1ac823fa7fd 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -41,10 +41,10 @@ ;; CHECK-NEXT: --initial-fuzz,-if Initial wasm content in ;; CHECK-NEXT: translate-to-fuzz (-ttf) mode ;; CHECK-NEXT: -;; CHECK-NEXT: --fuzz-passes,-fp Pick a random set of passes to -;; CHECK-NEXT: run, useful for fuzzing. this -;; CHECK-NEXT: depends on translate-to-fuzz (it -;; CHECK-NEXT: picks the passes from the input) +;; CHECK-NEXT: --fuzz-passes,-fp When doing translate-to-fuzz, +;; CHECK-NEXT: pick a set of random passes from +;; CHECK-NEXT: the input to further shape the +;; CHECK-NEXT: wasm ;; CHECK-NEXT: ;; CHECK-NEXT: --no-fuzz-memory don't emit memory ops when ;; CHECK-NEXT: fuzzing diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt new file mode 100644 index 00000000000..b4d67bab08e --- /dev/null +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -0,0 +1,35 @@ +Metrics +total + [exports] : 23 + [funcs] : 34 + [globals] : 30 + [imports] : 5 + [memories] : 1 + [memory-data] : 17 + [table-data] : 6 + [tables] : 1 + [tags] : 0 + [total] : 9415 + [vars] : 105 + Binary : 726 + Block : 1537 + Break : 331 + Call : 306 + CallIndirect : 10 + Const : 1479 + Drop : 83 + GlobalGet : 778 + GlobalSet : 584 + If : 531 + Load : 164 + LocalGet : 774 + LocalSet : 570 + Loop : 244 + Nop : 105 + RefFunc : 6 + Return : 94 + Select : 70 + Store : 86 + Switch : 2 + Unary : 654 + Unreachable : 281 diff --git a/test/passes/fuzz_metrics_passes_noprint.passes b/test/passes/fuzz_metrics_passes_noprint.passes new file mode 100644 index 00000000000..1d1a109be0b --- /dev/null +++ b/test/passes/fuzz_metrics_passes_noprint.passes @@ -0,0 +1 @@ +translate-to-fuzz_fuzz-passes_metrics diff --git a/test/passes/fuzz_metrics_passes_noprint.wasm b/test/passes/fuzz_metrics_passes_noprint.wasm new file mode 100644 index 0000000000000000000000000000000000000000..24c4a2e2ed5d642cb69045b8752d9395d5ddf209 GIT binary patch literal 40960 zcmV(lK=i*vAc8#o=JCR7F!>J0-2vo?bu^+63k?LrZrBcS;Qe>7c5v&I3)M4Ump!W3 zVVjZv$r1H@&eT}DzbxQ4Q^~#h5AYV9$yZi%24;58u(a!m0>naT+8p+}X4Ck#)Ug1m z>JD{UTrPqTBq|8!2akV=I9{-nBUMp?R*RPCr2g_c44=iX*In*a zaOQFk%B0#Jm_Te8oFpfnm{HY%LIe|U>sJ%OX0bA9-DCVU$Cv*Irp=HEP-dq^t=nyg zF~YF;cp(%kL{?R8iK>FUt*$zic=|;(q!iF4!b?D|I>9G$DTun4I_ix#7ncSHF4H;` zV0zb5JLXk;Q8eDcmVxx4Giss~4F#8@=E$JTARwaYIe>2PwuD`3on?NwDUU7XF0FLO zofW5FqMLoIGcSE`3Vh8ai6kZq1(dh^@J=(QCs1bemyUnI0%70^4}67bSdmL+ zi=TkxR<`@sK5E2hkv9xH2A4%9zn|Z>r2s$T=Chp4t^7H`!)O7HW$e+MSLS870ms_U ztRnn=lnT&K%Zghi1+s_In@#M5_*U;1iZa+EwIbpQR8lX^jhkv}d8&ycCcN(%R+x&m zcF4?F_k^f%gnbXrqTPRqVI@@KONQ%HMn>Vdp3QqZX`CW9bjzgtGhY`R3vLnp;;)Q6 zSTJ$Scm3wUR(5!i?8j{9gsJ{GZ73H90`;TBlVr?MO%DggN*%(2LMZH;2NEAb6^uOH zE@2M;LXKkP$Eb;ge0?5Sv)doE(`MNth$VnpF-;z5+ZG!x@Vzpi6hIK-LCWL|iY|br zE1D6+9fhDmg~B_VFxh=+sjbNNHA3YNl~88A$EGpSus|UenV{8}qqK{r1U^inrl%w4 zkM-!f!YCX>82f;It+~CL^)p488V1AXoVsuC2v^W3%vP}aAy2?+2^}p z_9cVRX9U9eC(hXvwCV$4nF2kUp^(zio#&cE<40401lbb2*UgX#E-nJZiS3KN_N>O~ zJNLCp)Jq9ig}gzN(}a`PjNPmFZu`yIE+9f_Omz)3flu`^%wGX5+8S@zu^2SgJco{3 z^%!j|$3Nwk{>eM_HcZ85RfE-&{B5lGGz77T9O0QWb&QIB(n~^vW6&K;(~6JvoQV5v z|Ge;fty)~>OdY00ZK3@2s60VOgL5qzSUYoOo!F-W3Y>5&a*wWw!V5mAu}3<9Nfogt zFEp8=e~B5n4=6{>zI!mJj0B`Pv_Oi_F7wvOnsAy#8#q~eZr+RvfQp9lK+ZM4Be@Tq zy;NN)G5x|LB+!{N_7N{W{R1Vh3z>kr-7jlI#&>>D!i695%BN4AQ#@_l+Xw^2J3 zpxCj29NyHxx0bHqc}X0uI#~IxJ`440#97O9Gk>e7eeChQ{A1nW%su&tKuiX})He+# zUFyM;Pfj+13GK^t$O3=ZQCqKiCQ@0z3vb$Z6k#WR%kDfF#w{@Q7kUN?pYD#-RYaoY z)m6&JX8n%1O@xnGD!8mkgo1&x#E$O>i*UXFj zGD2Sn=ob2I$*g=ZCl%P@U|3s68P;meH7`l30N zjoSsEEc-fehu^+-8#Gfwl^7y#0Uy{3Ashq|t*QwGZ184?mw&^-uV zFJ5nRTAMN+ph_U(Om-HMupKSpW^`UX9cLRmEO3&6*nbd!W2UpV=)=V=+I{-3TRM6r z-;JBAz0v8qw4uuJ2)#j?R+>OeRq9PeQYe9VwxL&z@}4K#PPFDXPoX1U^${0hw!5{z zuRUJC5Q=Lbew(MxeYhERlmlcjMRL4|NKryNJMH1Q~0@F8MrEW zY%JkquT%+dq4g3PyZ^F9`68pAqAm8V)Fg%=r3qZ8bD)NSocYuSmQ$1%JKWrFj<)@f zgIJ|Wv@pxIV#?-B_Iwk5C?pf{_or9>%Lc{fRKFU$wBUK0E#kIgQbghxx~y3H^aOVv z<5OzP?mwZ1MpmdpT%C8?@9Ljco@tOz1Fk&T!dv0y;<{-YD>gC4M&rv}^W_p_=K9B! zvRI2;4j&NY>4q5ZC-?4|h0Z&iC{eHx@6sMZg#xVh15lX9TT#90K#vvGGJqr_;I zG2Q~RFVIdoTdqmK-=>XfV^LI6OFiO3+(fFPMhdrCb9nyx5iLaUNo%-9kFp`e^AGG~ zdualj6^LLYHaN_wHRSh?N!-4&#M0lqX=EWvd3p+a$?4%R8(0Gt+>X>kc)!%I80bQ} z96cxk%hSvBaGVGrOZ+>ygr1v5Hb&_aZe|-BpTmHA*%-0OeNZCOjmB!pKXNvXM zhc>28gbXpg=!YBZp}HLaFpb%DkCIzJ?-iRRqEY1Vh4PkZI+#f zvWSxy4h7*AX(yrQn>v0tVh>|Q4dD)S^H>COMj!}Aq$`DE%LI?KgvHM8jxAjE$wX=m z8lp;YqstW0^C1yK(#MMyZ)2OnnG1+vQ*|wC^;8o|Alvo5O=jjKXc+@Ion`os=`J(7eBE2dm<$`o0n0_al{*l~m4U=+d9%-W0MtPCje z)>7P3Scud1lCkN_z><2mbB78ghR0Hef%^zi_c(A(Im8`UA~5go3>}IZ@oKizIwBBEMKI%xEFw^C@CS4Pa)`m+%x47`6&ZOY?|+O77kDI7=e zS}p`33|)?6>&=RX#5Kz8dnBMl4Z$3gun6Fy1)<0q}HS>cl*9oInh_aCm!oBBb$h_E zg8=(S+V?JvGjKQB&qzAqZc=WGyQi^$Ob>B`_6V;CS!RTFCEC2bVKxHDt*FwkgU*e# zCf{=Lv5*cn8&`ASme|Y!BFj!{^2z)%(Nz3yo)VarM}V92MBl2UYmOA3mj0QCQIv+O zOoYbb{Z_;jFKMD0oD|eIr9uU;d$rzsDiEs);|*4iYul7m78Cx*Il`0Q8Fi}G`DiVy z&s%u-B-YArf&h>Zg)3Zv<=&h#m>cw2f@(k&%mtVM!>%!dQf4Jt!3%KD2?Eqv8_LvS zQE@KktAJvH#(tH9ONhCRa9|5HZ;e)@a8nR)#)gAzg}@aMPX6DTyU(^|l8=Y?PPRzL zA0&k){A%c(6p5??{oLHxyZB?>$doZDQmnh%!#}_p-ex&<^u$<1a51r^;Z?!+UK3@1 zLp_WJ0=|-M8=p%-B2dtlH+792x zc?1?_p%O*lTjxqifWt38B*gc-YZK9M_GMOiWUZL=cx{kB1rnhxTj?p$M;1q;x94HZ z?JLQ<1(9sFzXLGj&z)GAaHy(m6dDO#?4}Zkiy?moLesH|{}aVHoYsGJ--iVPP4i5% zCw@;u%}1$YLPk455sr(r14;xS>D|koM9A8><+GLZUx@^tbsPU411RkQeM88`agAMX zQZA{nEIxKD+H1B7msKyrn~qA51Y+`Q11jx1yaAoaYd*k!*&HR+6nP5&}?TSQt1q*mYz=M8WxjL45185fi`!8{r_So zLy`}8P+5HIHvwe3SBfwxnRdXKMVd#LJ()9V;mk4*%Jc)4@bJ7`YxHJwM%`t)`*aw6 zI=LbhY~88Q=Hlz+z7%MLLO0ZAr!)d`WyE8fp1*eve~z47ItQ{($NT9ybF&{Rdsk)J zN5RdsDii6olfJ0G1viyifi8vCN#>2OyS&4Xg0XE{~Z*MUMWgnEx&=-CKLoJL5} z67T&M#x|DI?Kj%e4Tc|;gr*Kh9B-j9Q<~FBDE%PXyG-lPr;z_AMF}lwy7fz0L)jN5 zwx58wy`Ip_^WeOu(@jJ4fPvca7?%97{X^tR@=bTDBf3}D(VL2K1@*TnhWN?r7utdO zOr%R`!#+5qN$}-Q{7)ZX%&Ua?loH=5Z4IN(m42Dsv*MXg7oigsV8r>z0qc zN&Fo(!Q-Fa`@1yQ1{Es?A z)h|lJG1zZQ0XZ`%sXxEf$|lq1ncv~dMC$a$HmZ(DhZZnZ5(1~K6;jvW2;9v+${h|x zdicknL6b*ENJ@ES4{ZACy71co*LuQ;4W&1I+k-;yynsF~v(MtX*Wmxa4jB2Rb~FmJ zCtLGAc1r6eRgUtyUw0Ojrk8sxWExk#&W9-4@u_%6bFnBGWy(vDpf03R)RnTnhMPw-6CBsg9nuGPlJU6)L z_R4IkOo^ss7tV;JH$!N@@TL|V>O|=OZH|LakvyVuuApQ-)NI#8EyfPTN6E3vN-uV1 z`SQV{fdqvN!^uilgfh@N^v)akDJXHQ#jm!2d(lR5%@=-PORZlwYS$X7xw7AMv%(~E z??JY_FYVUBh9&CnOoj)!*m<+=so@1mdopv2x^iCQx4aWw$F#B0nuY2r8#RfZY}#gh zjxW{7la=@{({g6^KDR0mDW!elE@BYjLo4wi8A<&>{onPL1%Ny*yAgLy3&Ryds>CJc zou_Hq_aMT70yUa&!3M}CE_@)?b=V<3vP0LvE4B$ z4tJ?~W>YItADMuvve>?90RKZFBcX3bHKPA=L!|E_+9>{8J-SNLQz?QtATGxJICUh%; z*K+)W5y`O4?bT=-l6Fl=te5B6n!ligbm+;b6KgOIF7IqP zM#4G8eed@vQUbIb>qY;>XJ8+$Wc80*{?!;yVg;SnBwh@_luMFESD1I5hFJ&36 zk|A?eO5LDl?p^?A83-rFx!+(?0@@ILAIUkrzeK_?qWS^vc+M!@zzMLHcFX?Gy%`_S z+fYiWVMzPapaZ+Uxhcd$5=a@IhpH4PK}LF_Yf$55(Y`~UUF!^6J`hOMm24d(*@^Js z?kTS}nROOg_vimezo-vs%JEY;0;gjI(Oq+@)k`c;6`lW_Gl;3fD$0f}` zffa#xtto`VHA8#&rR>lt?-Jf+$%R2g!5j%2;Q_Jar(bK~ZDz-*;*N|`nJgsrd@%n# z=2bbS!E!O?bvZ2afSL-b#C+UNz2w(@O7Am=u|n5T}+9`7Be!{DqPcHn`c@=k;s!S(H0> z2iB8NN<^n&KCAp^pA=zQsbON+IH!qQ0h`0c&LE|e-Yl%hI3Jog&Jwj4I?h#jZkK%eRuK$AK?DcjsD*tS;u|J0W3?|W)uLm!O?;^kRt zl$uH_UM^Kv&{l2Nfvrw<)Tjk^SCt((a}U}A5m^c0*h{+2bN!D#lMI$0k{mC+wmRmF z5gd!HvVD*Ghaz>+!thhm>fUkbDayiNW<(Q7>9e#jfqSE#34-3}hps9HiP=wfzQqD~ zS#dpLFk*HU)2~Se?rt9uGZJ95Ai!^<$dy->o{qV6a6Q3DxRJEB8XYd)t+sodlEtM} zOfyY!n%>xkyPo3Z9O%*jKbTx4IeNL}yOd0hG(!5RcC-^5JYN3jFV7!LC7T86e0FX^ z&qGX5wH&?={^u_d0S?G+_);7B;xc_ zdH@e)Yo{*9(!sC6;E=XqN0>1Av&&u(EBqeGPexrM$zsU0)LGDscfSOclZi@c?h}eS zEE*#IMfvQ(n&uz?50&C#R}s9MVF789`o}bOaT7U5_~(x4xIxFd&Q2r0&UxE=NUqZ5i!az z82$Q8Ir@NI9yjeIU99d<`M-mh_gI<^T6{IfCq9=lfpJ*5Hs&&=A>`gHIq&gO&?jixqWjy(4>VWLPl9g!Mz1tb{0W~EF^T+8(txy< zrGZ~`%wRYM`xo{qM{I?zHx-9OB6H`I8>(b}yP!=4^;l zn#coS^#_%QgeS)r6J+vk%B7k4gN9Pd&i`|G(Bvk@8~|aoWHuA=VH;ba6@qDD&>>rb z7fJ^nZds>CSk3?ests-^_U$ECiV4$|W?Z8(m78Wc29IiPcxC^h%=R_DChmdr9tx`T z1%5yo$}x5unY}bIjgL-vPxKtp;SUC>-kA9s;iB0MR*$)RB1)smo~66_NNd$@H7QpI z3M%L&U$^s%9VOi@ON{7)$5Q$LJ&eu1c6x*vG!kfre17V{C+@c z7ps2H>W5uB7Az8y{~0}>y|H?1gJDyxj-|O0Cv8~C>T3q=%fYg}`~-m~9na9e&0d>i zEt?P76Y2uaXY+#$YjQ`adWNYI#gqu|4cQ1cSbqu6<4z}SVb!<1AaGsWNxPQ-~t$#K*uDOL8YX{^Gbtoe!;vMn(eRxO!NN< z5Xj#*XxYBp=ET_`|6OfgGugOsT*bA$NKto#ek3So-}@7!a3Z z<=sJ86rlH*f{ws_a<83@qr!!JD*aTxQ7Pi|x}qj-aZ0tXS5#tXX%N(4>(Z$Xp<6(9 z*7;aGKQajJTPiI_2GEXSu;BXb{O^S{tgayG#*fLVBAP~o3QJsH!#{q`SCz3r%R0ij zbOtnR!zd2BwZ%I|RBvnUa9r$z_jAYz20vICccY;;)$vCZi?(a8{YH?K3%dOdc6J$- zT=_IdU9kB=X}|9O1mAl-VwJ1aP`h^@Q+r! zl{N#-$v8q&j<^?*W5g!w7)}{aN9b5gSVy3EKe>T87+O6WdKj5V16k@2+y*{eC=?g0 z^yZ58Gk%}CuHnZfAJJ^%<-RntU zzhKNqqYzd$Cu;}zpzzmNseBNYD-00=*q%lL2hbPUV@T6Iv6_D5nY(_EYhO06) zw%6m`#S*OpI$_3mmh|4Z%HiqXE0^|__#sa(G|OAQT_f8FblzPq>KSgls5nS>1@~0@ zkY|O2=ogu%Cidg^7`pEHxe2gjP9!Iv(9)oXia*73D)L5Kw0e*)QsKPfZps*xgaN8v zk;0T{n6~?ogX3;&Xqm$k0Xe_Mv2!93b-&Z2shAy(gXqA=5snjhf*!(et>})!m@!~f z5UW$88(`J)b@VE--Bzp9SekzVq+&KqKN@g8AN0IdbodT(pKjq$B-Ge0XlOb*Mk3M2bn^vWm?X>xSN>5xALi7#L)SYBjj|F$ia2v(GYZ7WUG7_L~pV0CjtvVwEytETQ-C8+}(oO(m*4mt?-%y4> zNEjC6Acd4$^m|tTor5?YTne@z1Gh^JP};)p1kxxj1zxAcmQ;QK=LceQP7^DVY9O35 zqWT~a2i*l)PCk*rNmiW9P(zZX$@h$r?Vbt(w2M&8(9rYv8Gd@;c^*FR)!w&>9JJf2 z!pjE!7)Gw24#+6e#3N#=CMg*gmZTwvC_MyUh!q~HQ)ml-(A3Y>YE=GPEM#*DYjojI z^LOegS-7k0U?uG|6c@ZZmqe+)epERwuVpJvyH-1U@aR+YI3w{jwA9q6vJttdn7SO8}p zi19Fms+f+sbYkftiNQxnFswb8N*YZE&`@|UW{BxS@>RA4TH)onLzn`%#0&Sx)Z05|wW`Ez>L9~syKNrHk`(C3d z=zX2l%*;Y>VhB;OM3|&03>hDUVqkcyz;7v&K5jRKzd?vCWTFVO&@5~e3FVx53C8JJ z;SmHvoAmb)=YC`)2HZ&k9g3wQaREdzvSyCm&q)w3kzo1MzD4v|7RrFnd>M;EeHfXp6qnXIoB0n?EKGDeU$6^k15 z{~Bpx7_yJPbEKvG5kx^?cpEJd8ezB7P-AMpe02@iqs2n6L@?)*&CN4vqqE7!?wlj` z9QZn?U09;gOITgG&Mal1D(T#^VH~#eFS}gBdntqv(MpKkaHrjGG;X;6QlP^*cFf!D%922ArVJz~qSzex zI|uB1@RY){glZo@G(Hz8T9nWqDmr3H=LBA4Zf#QmxeRv~NL4y{(MoEu8YI7woEUEL z4k;Hb|7|)t2@ufZIu{54gEN4fQob8AuB=VdE{-hVGCUd?Bojzx)SGaTDI|xT=KYJ@ z*nSVYHdjqe=v4y-)WN6okq8C=D?@(xW;H8}Gh#xTMXX9eHOlmJ8t8EIZ<%Vmln(b- z$&W~i;=or9DAmTxF}ki|b6wd`+%seZc|rlSOW>u7ns3 zC40|a5@mgQuim8_0^qHwQgiJWvNOBXyzpeGXw{-B*?RhgwWleKHtkUS@?=)Zm>CXRW z=O$O}nHj;5SXbbjn+f&umbmw!FDdp}>e^zck41*WtOP6K;<5OV$C)|+7z;=bhEWvz z-_oMXZcPl=uFZCMY(x2BtP@=+S_0jl6yIrM`wX3RFsKCm*K2g|Qc+4$=(aI>l-bZ`+$X7pTSYHYGpqtC7VZ zyl49%DCfGVnmxoEZ|(Culec|2^-td$77P+FjWudAg$OH@RqRAxXJN|dE+awpu!RtX zVOmn{P~Fu`cmS0V9$3xeNpJ6EH9$9mmkqX|kr1Z|0>$fD!fpd)OZgkS$I@8zQCV8; zJ8TMs4!SI$*0S>4f+>*l`s#M?nwLYxA24PAd=J-}SwyB0dAPnlfUMUXICWFW2XD81 zD`jr_LDLMr*fvD{+@v_^+=2n`y9SV>PRp$SFR8#92I>4ve>JZ=WkPemj87cVpOuf* zC<=@SpywuotjSK5~wEDjMieGfMmP^WZZzkc96I-bb~we*2=%P?ugcSyd|8 zJ2}|G#UjX0S|Q~iVphV7NJy@$hfO}Pa91CLTt;bF2IUr%t7oiS`_*X}-gLKs3bQqL z9Q%?R_shV-x)+a>%BoRU3a5xF5Aab9S288Kyuxb9*DOOL2$#k^n-i2)9u zpAm*Ab?35@nThw0DzT%3zCq&5Qt#CASNfxSf$vfVp9fLY!PZQ37*HJ-;3AVW< z=UvByj-~HwF0}BzkVBaT)iWdI=VptI;AMpIE=65>u8Tthcy#kmRI^Ho?vhR#ik6miFse){kdcl3sjJrH0}`1P#MwrcJJt=fq|`06zCb#TXwO8OqDCTXyz(#@9l=WnaC+tUVS1w(if#L1{8~>H2PxXR!<*^mXosr3{`*h`PgtK-|cVGEIkwdsx zm)8v0uBPFUkCK@n9X9tSt^HKCOXoo#hC~!ZK3IVOL>V4dYkr8qN~?L^>Kpi+Ycf=& zKQParxgtPVB)<(?ha;uZMOg4Z@Oxj%z-T^1t^#E+nL=*D%Lv~)25^i+i$IE|r`uDFd- zl)=hmu_56L?w%*kjD~6L<#y!*+Lw?Vp`Wp>ElAP zI~EyCxx^`|qq-{?ibVN|g(*$1M0MfXFMopZzX;|e{%0}s=({{WGO+t2U%$wG;#yO~aJ2Zqbvu;s-- z#%AUnk%xp7WWw!tc7a2z76tn~O-K=sngaxG&N#N(L2M{8Z#NPHSo!yd@|@b3 z2+q3ugL9*^SqLt}xr*+`cA zH~aauWY=eMSbFGb7Gt@5WJzEQiYN%R00t$FTQk{Wl*peJ(A8S*jzSNCBJXeC%!V>y zU8pPzUnh1vk-R!6pbTMTwFktIiSSAj%<=F8WIB%MN?h}EJV2>up4`*dR_1;T#SUv# zL*;Chs?C}d1(5lbY|0p}&_zl`x+oji>F3`o2Lf|xIj6t1mnFYW z8Y1t9vuay^Ozz+Uqr=uM;n3F@V^7Nx(H}w6_K(>U=N-)`B-)@(FqD#pcLG4yk}R;G zM==;6@6TlQM+;gCt;D6ux4?x{?Zuy$yLS}8gDy;dyN&t2J-!nIXt2NrLGT!vGJ?di zfw1&G>FPPSUgLh&t2y;#wabh=o`Yw>=?%?G|ubx3Q zMnIKw+QM4WSoRz#-qJIX5^ow3S;(h)`g^+VsBc+CI^U%oHuF`-h_K25(f#zbe_;BZ zJXP_4DKo;N3M;M?XDQNUG@$GDGT*K)ZN^%+P#WqcS#)8C0N`?~vh=_lbXPD4MBX7ND0XN z4|(fYw-Ph)u|&SNwQeI-tmI9F0s23S=VB@WY_V0p{^i<5p#R8hF&DMeFNmQ6vI{a% zb`wF>P1(AeTZSE?%fA#J0jAhEUGfX9G_eze@h6;IaT;6gJ*W)uxeDQTW&!UqiP!$k&mrS;~m> z&~$Lgd_yC%*8E|dcegHJ5kcBg{p>a8T5zFZJd^);1eK$hVO{dEaOYd~)7>N67C`o zUu)Af#&2oe>8X0Q6z>p{>|&ygp9-(Erv7rJXpyME^ip{G)298T9!=@h9Y(O-hj9+v zU0itoZA4c5O9>J6N?_c4=uL#de?m(!`ib}aP2(X(s}C;R!vVgXs25suNaqH5;=3dp zx&(H%Kjp@p^6yW!Y93e0!pk_e#6$t^J=lQ|!q$=-*}sVngvEHtdjnb-dApu!Au82I_Il6Ye{U(pFoZur(-o^;_} z%1_V}@+faW!hLUC>xATlt$b(SF31!cPJ7O3is@$IerIPFxxYqmXIy;QSP@uyh+H=(dA0H^G8>QZN^NH zTRIDTSzmnZ^jkK6XS$`w&ad`Gk;fs~Rfu%L;9Kf?cJ)%juA43Q9D_}Qqs?ys%rWfw zHI}qD%CnRATugi6N!@-SM%}&~MOw*f*2l?SYr)y8N^t(8)KmpEWX8}0Xdh$!I@dC0 zqqMf0>i51&PT~Z=u3#05JI6tw4em=vTe?tOwpil%wJ{RhnaO5{d@b+)8|#VMD*@Zz z&lVMNj5_n9j&MrvBKryA)6nti^Vk;w$|nsNS2+OdW?hZs1on=%Khg-+)5SQlhT$~m zl55q4GSK)UTe_9f^`FL(C+Zy4ZNq|Q1i0bSn53~+nfO--+jg%ryQ+AyH3op|%l`(f zh>AavqpYliYTNKFY+28vLt}MAA`Cj{@mmV$hyOeRf=6-fd?KGv9l=RKx7d^2EgOZ`+se|pJKV4hMQKEgS+WG!&8&+jC2GouykJ5g7bJ_q%4R+V z0L3hmX1Y{Kr>CM*TbYFmbSU?*?5n#SGs@9LIFl63wTH@q-vS0H^PH8bopSoVs74|x zCLLVb*8Rs?HO+IrB*hDvZi6)2p;2x@0D|U(GJ_da)d7b%b8eUYUAkhM+#_kKdWqCf zi+Gr?#4gK3u$DS>%c@aOKP^5{j;n+7TgL;=3GOpDn}K#&Z%>{2Vm?>)@YY`2F&>1b z-G*W>&bV2k3AsQu5@aBQJ0cCzqe`W}=DF71wP)t&d>+U|R`fWjKnH!A=It0u3-thJfyuxA{G)lkE|c)rA?&V&3hTO zTYKKz1!Vex1!r2}y4!H?eiu#XPOo`dy^J?IS_(^m;*Ukk*?V7IsG(#Aku0`OPb?To z5H;`18YxMA;PBfzZp$$st($<%tPvGGeHsZe;w={4QQ9Hpbum9-g?EUo2TWw_#>%mQ zJoQnGg5w%cK)qL?(fI_vepd4R^yA8{pucOrQFIDolDT zw|+=90Sg^cIUhB>AUJ>gART=O{*`=3s>wEg^*U=LZ8KJaNM~#Rc-$pMIh-moQY$|X zzWm$GqO_>u4=-_(fyZ4^GOmOMFNivA-1z?d5lWzp9>^gjvk0YbQ?k%~m)pw>g!eQy zg}F$_jwzM=vUJ~tYvsB1c%5M@?i$xBB>IoC*Tw9x6Xw&jhBj7vhKSH?0FNCj1FK)V&itAwvWU&d_Scm3o zBU@uXE`}Ng?lVIsEO})Sz@1nA&a3#+l`dKNw1`^KJnr*wxRld1DK4~^`fp{)mB@ir zY3LWywP|{?sj1D#dtDOg2ecX@2;$%o&cNhHRsCMdS|_SyRZzj>U1a^~lt9< z3OAFwSVyqb>d1>jxr( z+)NFpW5-xd*Y5oVYPqfR3?*fCE=6~Evb8rtOY-)5?od!dLKt}xx>qK89!YZY*>oZz zPr~{yH?$gFNxJoQ&)X+P8ci1gzn5WjNiVgNGxBOjIjpPV?2((^CcbC2~ldjun2Q*{VJx-5D{x-AK%w7G-xnI~@d6Q4tW z)hvIX-P@htKX=;{V{Z5U`Oi3!$ncW8s4?-^Di{6gH+y8m?r}Z50EZT7IpbXL=I3@i z#vHg#JP0}}75Sav){Sqgy*4uG|1^9Q^>?c*x)6x?d7LwIBcJnn-}>8I4#Ocx@U#ly z^Zrst|3~Iv-^e3nWTbGD^OBj|+moU3>)FEUXJKA!uxjPf2GgH&EbHWuU2U@P)rpSr z)Ph4BM8mOidXI3&atJK@?y$+)^?bUk9RI#rZ=~+)7H8ats;5$4ywc|Y!r$BF%o|3| zgrl|urE>`f9Z1iW5JUDb`h7&_Xl-GUpt&&`n#|Y}#`h{1iB1RGs3TuWwr79|K;6GB z2aRVnGC0BDC>6WFHBgGr#L1Flng$;th>~KW_m>SzGu2IJckQMT4Bz2qFAg2fg)u@S z|Nk-)aUW4qO7*ux0;dN17C{C+wh3MTepdK1T`*~M4#zR{A2!fe$zuBK=pvy3?ODgoEU|r_Qij zP!zyWp!%%c3U$@)IyNfv6`DKjLi_lha0&11{Ibt~zl6p)Hr1_r` zuV8260bz~B2rTzXF&M})^DYorjPTeRDN|Ne0O3Jz1gYW&ojeZYGwmde2V+HfY_!Dp%qcb z6|S14CC=71S9d2Hph22DQ%(|alau_-dv(ENxV7cTY!Tsb8|?yjo>$VSLa%w_pPiJ4 zff(e8e(nh4>^Y;{Z#mf~pFxB0dma_3AD|xQB;Q5^wx>1voNzI@$$LoBf=*H+*On19M4t zfp@e9E*uwZaV?qgh13i5Z`H&G;E$j`F--@HizO~EQ5(8#3&S^nqq4|V@dISBO`h?> z(CIJ1qL#}u(>*epPyFzL7UiEdwI>zpe&~s7)HUgZ>t%qNUByKIYc3Jx&<*2YkXXII z>iM`Fhk6&IW8retI^5JCB`6&Eyi@=?K*YbjpLvdOK3>g|Gvtyl(MOtCbHq3$Le!%F z)e8ZCWuYRWu{h#{WBS>9^9%q^|UZR30@I8und6KF`D)%MqI`!2+cjLnrNg zp|C;#9a5l>7h4SV{n`)IFlJ|`+GbEHO+gsWEb?Lv=?IRasS9!FaOEX!N}n6NWz<|V zt`RA+niocb;f5ZF&ex*?clucomx6m^Hx<_5Q@Q=LsViGr(un<9-IIC4f@QM>HsLaY_COw|ug25W@cL!S0g5&+6l~P=fF`F90jE>wZLS&@0u%%DCt_-Cl2N@z5VbIVkK*FhAvd5 zQ4Dx4PyMg|9vU-OU;N^ z0}m3C%aKFbjl|ixocW%1*P4~h0laOYrP;w7ky*+a3eyyxn$hDHvPN?Z5D8=qiaVP6 z0c+pwZZ_HCdhdjlij@pqgczZdS7AI!?dJBUf-TdZnj*_}@_(sm=#pVC@2Iqh>Qf3r z+0@V{%TPzJW%n+hlzP5$M^mo%Af=YCrDYq(H#oFosVj^%h;Pj0?Z{J%+V#kwGV2LqVF*Kh)?rLNe@g>6!8;RJ(yN#ZA)$k8#dK z-zwfymes-%II*9lDs~f5^<#m+>XR?&wuFp(H#mHXUj7*^9~*;3#z3B-txGTUs?~+@ zrR}l1RA`b%x*{CV$Jze9m3tvOQWYqeH0I;K(6iZArqNjjcPobmrJ|;ae{YoQ&mr2 zK@s_M9?krYIDoLkt{O2N_M-rgG<=or2K^3Ikqfu^Fb1@h)Zx>6NH zQRmC2UWx{kw_~NWZ+4q7ScltKxTR)oGXHU%k75YYhuE8RYb5Hhp}S6*1h`j5aU5hk zzAr}rI}6qet4ZsBl?sZsS!TGt(wHvRcGx1VcNt23CGPg!bC6Yk$RRFm%7Znj~^R%cE z0l|msfHHZD%Ew;WO-l}X5Q}R2HUIwEXEsuX4i-ir2hgi-8G{=deQ3F=JsC=zQ{)3J zPy_%O+w88K_ox7>ptgyU%6zOiv6}k(u~xRK-hsq2r2=c3BHc_cb3Oij9f> z_s=%l_>yqNVTJOn6Foe8Y>SBWr?D^Mz=Epzob;0Dd8wyvv-Dt58?&8viD#DPh8#lN zFpvCroocm=gCQ9_)uu)cF(9k;cbn;B`vO51wqYZ3dF&-32^aW4E12aCex3sA@ACiz zYHXX@yoGbt{({;WG9!9bk!b_a3s{x1n@{DeEVi4ltr&bBIIPSMC_Nt6k|;gzNZqet=)0 z?;ZnOvWp^?5PUGUfv@47mAUyOM8B|EE?&1_UI*W$^ul?r>HYy``aD~riKaT*Oi`Q2ZcfXldChbW~}yA__MGpccOrot=J?}UoLgh zk*Vx+7-y!Fx1l};;`;8l%}=A%e-gPcQf|npTuE4oN+5U28PAI};h*}Qd2$#KKKgCs z1F@?GAP4{EtlsKpmuB2i&!s|7?kP$RHwW;VdZK_X0($+cYzlKwPQ%&MzKk|&It9kq zhmjFKr9gX-jTEU9r~_R(R93$Lp6J(v?z9A6qh!bB=AnzZWorDNU)6KYG7L3+eoft@ z|GP~r2Pa|#bV+-SJU%*rt-Ne!q-vh^ZGyd9qdaIM_n;oJ>t2Yfi$WMCaK4Sg#^>il zY%V+G|I1Bf6gGvkKbQSs7w@uKafhu?|_7PJoT*zRJ-fQ0r)r4R=Gm&fG_< z)rrh=s9^QmxfpSBYuCh0#v+c&A*c&PV^Tf`kW z)~Qpga7XXF?f{E&JGm*9a}ly!(Aa#R!U7O;H?LjplGtFf2LP?cI9BeG=P;eg{B@2M8iW3Z!ln8?>#08tVhU`wBg z6B>>y9j<02`x>c^UacL)VXEjayZGFEe`7}#NsNXU#_3g(o-Tx|mw>;YJL{>n5 z%=OO5$hTYP4?+RvZob~}U>HSH5E9%_B6v1_dz*SDx8{^l7QdszxL^S(*zaMk;ucAJ z0YVi-XY2}2#RP*TH;g8!)*#C3RRXltt}v8JkuViq_Nj3C{e`uAy$#C{Z22LDF`MFTRTVUdytD2s19DE1P% zo^kt3%O_%UTWpH;)m+kB&KAy;R)F>O&o!h&tx&D+^> zz2G<5|63I@Ed&0vCN$r6EgTdz6G*$;8fhGW>Aug&dpr}RSpx4@BR|%~w%|{waFyZH z(!O4gN2`1-IyZJ&soCT+@IZ*~$Bf?FIsO#fce`cTP2hFVyz1zHAXiju_Wbc`%wI%+ z59HlQrvq8c%Ke2sX#^w3KY+^5ZJk(&U_tjElvg^38{_aDJGf5kC4$M40KG`x7N$S= zUXse~HlP~3Ga`Z%;-5-VBPaH4{r(1pI2mE5w;%MfL57;L0Tfuo1nQ}o zW3e1?m@PtQ5-UP$PSS735a@0ET$#O`sYk19>LJ|Z6)dk*KskArqh+@e>DMUt#}iTf zT&5viQGV|DRrw;nix-Wogo=W^A>TS^Ib{klcYlcUhoC_>b%&KpCV_QL1pvvEctY-f zHy2B$)5?Zuq)CHr?%qbNBLJQ~dGXsYqjm)IjDlTx zKK%E(h$#38agjdJga|rOI=a(SALG1_ODgu zhy=1c8K*_!R-$yFVAi{%J~}31+!fj^OnKQtOX^EbazM#@VG-b_2M{b8?{A+H1Vy?; z`A;?nM#QFb9RU=<=9Dmc@t*GU4A7N&>^5R%Utbat(BYNOl%qomb@pXKs#J=_&!Z&% zZRxW8$6rjLDh}en9F@XMyhm!8;p9Zjs1C2Cb+M1HmQPb5?_Yk*Pz!YAFL){B7&|ro z>!g_<10QMa5NfrRNcX^8GW@`=TRn$4fH{KoBVflH8{oH(Cz_mTrn-yAt*P^~?7&`O zc#f3qN`R>^N=jvl%=Tj7f9{F&qJ=Z(p`p1AA*tY@@&TvWX2)o|9Z_L~%FR+8dGbMHHIF4gcvI z!=zBc0Q5y z3w=Yh^BqV#39k~ePYw}HOfoJ)QNXpTOAprc%{^RLFCuA_G~+Sy^Cn+Ghv0e7bo>tt zqkV=X#xkXAGA}86JnUY@Lo$B;zxd~^yq`U<%TQ3my~mQ-W|{+rZI{Ra(>}wFMU%)2 zQJov5mAGss;wJVVoca@JY_~}v3D$= zb3%D}i!FvzloE7zhB%NdO~+9v?vGa|7hi%$Y_TNY^O)Fx((<2`Phh=_{?5n+#QtaF zq6qE))(5@Tp02~m^Jhwx#MBdC9tJ#m&A|AKSVxQ1sf(Sal7A#MrryE=l0uRqfh@d9 z+$_sa&_Vxy(heb2{bCQ)bk}X_kva&*x(iV*`%ysnBgXg~7Ov>zp-+hraTb0VXd1$!_qrp zTLYg+T$ke`41ivKB>bm48LMQ?2x90$3V-^%quj@`(^>vW9(zosY695KuCNR^9%FF7 zaUPdYk{7&!#JU}5cY>9LY)*t_9tVv4d0eKxmQ>e~@R8HHUXks*;sc(|mLa<3e)*iQ z%1grt;Ycp6x2?B{fsc#z%g3}_6kkHOCl~gKhDAWQXg2gMdO@;`ffdoW1*TtrTkb3h zt^pd&h?mOhlDhiViHKL~$he9HrGH?r@UVYNUA2u9lJS1>T+Bk{r9L=b3F>dW75$LS zk4nOR3RQ=T>7Lz70Bwb108$kTTQ~%Y_skkSWh9!SQ<4e=5)Tvr=W0KfB zzpeMwpbMZhw*~BwhPSj3E(E8z!0%~ot`uxm>B?h{yuG8-YybyH#vU`ko*4k${U6Pf zG0-S3DsP-TAQX*}jFHM=oFZGV48ULYf-+S&r%20EG&NAMHcFw#l27oqDxIK(JZ_() z#RjhG>~Bq~0mm`XW|xrKYKxVQwoZ)^o1WK=re{$p6Da<|9B526S7epGg-Le& zC!5kt_M7upRpYPM!I-kL<6*z!CgXcE%#?&vjDbS5Pn6LK zUtH>fy!3wcD|9kwDBIy)BNjr4TB?y#f&RgN@vS;@CwZ`(9B%9YBltGN2WQW!wd{P9 zrOjG+|6Fv_Ushk&&WF&X({PSN(OMZRQrSo{S{mZ=Ol1}VH$ZWM6YxPpF)0|cHB=EK z?b#8~V{M!qj5H%?x zxzqr_y5>7tj=0(?G*dLH9sz3X89C(Qi@Y}R3C9*F^RjHDIKd6Nj)l=)gzyscx&A&? zt8H78Y%LcIzSRVv$GmyzeIw7c5MNa1x9jMs(=R>F3YGRci6j(dQ`2%-?}bPUz$w!F zr)T-M66k$8+o?hD&2w143tcuuR-0#C?<}>gR@ZX%ezkbhq!pa(IzP!o#+dLffs#<{5bD$t+#w|Uif ztI#fqGgJ`dGKkuL*ro`>g@&2EHvNSDNj%f?p9VVEfT8G74V|>?A%#o9O#qTJ4WKu^ ztp&UwNCWrFa9X1T34-^;bqvZSK}y>b1-4eKj8x6Tt`HKJ{m8K;xuwi)X{cyq@SObf zxv~68d}r&a5YoH^b)_`Z$2w*nsV-M}zPuzb9RrZYz=&Ni-ll|hps1vW!~?Gp)Zi{) zwwqUYs)jz#|H)qTtGoq#u6~RCd1+f{XW)xFL*~S0(n3J7bgvIm5Ao}0Gn4A99_!F=WqU!P0nY#5ZR+id(Tje^t zxaYm;5`y7RjUajuZ9&vE)>SRY(gsIfL^vMapJ_!g1!TB$Cf z0F_2lla}-c2rL~gsP9E~Q7lPKfiJOmD=~b(l5DD!GJz3qz%&)=aERDn1fg9G%O$8h z5|O(wGCOa55ey{)Lv|*KL5w%On*^%fr24mMfkOY}odkxS(AURLc9nHT9Gzmzv*Si4 zkp0?Dlh(^bj8QM%?^9T z99PF~FuMk~9ZvHi3pmUNrH~(B7Zjb{M2P@PgkPlSrMC;)cJT-Jn3kCzrcbM(lD`L4 z_Hup#{gwANQDW*Brya;%FmUepbql?pW*i983G!IQbG*L5 zeBw(`y+E&N$h>*uCDOhBSG_)hVv@WiKrfPrvXt_nLhst#1LxQTjD{}Hj#QH4k1YTQ zGKu!dRr6t=sL)9ytgutQvJvnI;i92^$3BPRYd3Zo2)YHc7MFSvzv_P^I49*uCX`EN zYHs19`@AKj?lfl~DB~e4*x0MrA1-8@V3c_*9EA0#J{kuH3I?$Izj)8v=1#TX$!7`N zF3_D@w;(471AHfzu~w=ZNN}mG0vD*?r>ieP_b2LA6fG-fOmu&jm#t4tfT0Q;<4YOL z4~+FZFi)?IgfkegFFUc=MZ5s$-ARWZ@m3&@N^DF3O6bE~%(_-E{1XbzmONaFEdwQ_ z%^Qz`PQWelng2;`WTk1d);sI+YIH`t=5+rTwH%MGocy^?PLT4eTfJitwnsVeY63c& zuSSLt>7zShA8&$%hGH(O^~f&AQLJveEv#J2=mDvHhRr5a#*8wZq+x^0= zz|Ve(3!f{ar;c74HVcfsbf7r!alSzPu0Z^r-0{N|@6Gi&_}mtX>*xXT)XCfwTA5Ec z14osg-}ez2R7x)Iby-+FezWzwsK~Dl$KT%GciVYiH>w5738c?}+Xt`!2%+}qhZ05p%KoMXC}Th1x{f3- zXdLc&H$;({R|-Yt+rlv)!`?0`7~g)A4S%-seaMs*ULil=(%Ew-{ zp2hN9`siVwm+SXI;BU>SnphC%q@1#E?T-tyZS~_%NiwH3vaUFQrA4_?wM}lUADFlf zwzIl@V)Kqc19@g;WXK}}aVpvz_iZA`p0Oov5lp$*H0V4>QU}(<^iAnAuXQ*PhA`Rq zOc|2%ZQu=n8$bk;(pEFt$a=!_R^Ftp%Ur>XkAbn0Mfa?{J|%!` z=wQd?!|KTee*oR}*zJ??qOR;glm04!Z8QszJR52frj9Y!87WtP4P44rF-H>gK7*JD>CmH@|?|*rVV2IQ_=u z#s4{Ir_<$f?v&hFGcO7rg6BZt$e?^FFQ6Olo)9rKY|!3e(4r?48SddWf*Geb0+DKP zO=QDNYu=xey66T&Cd0#Z!Iwb=9^5IQtkAm1Yj4$2BU8Bv9S;Yv6T{HY(CJbdK@YFk zVn_95+IVMF#S0imN$nb>S5w#T_X~w-^xS%*aGWuVUJTU;O4dWlSLA@HC)m4Ubl?x+ zv(2JuQb#@q8!WcUe%*_iMvpv~oojiQiGjl{Sca3Ogu-}J9Ix!@MdMyJt{P$#D@1ED z8akeayXyX~gS$N-r1t{@V=7;rzg_mXb$f|IFXj%gYX5Z+AuE!dTap>ehTMp6#Z59G zXNznEiG;=HAcqB5J_|=Ejb#X)u)hwSw0h2h4X7^x|JF#~xP&QytI(w5B!?eEdc{jO`f2?`_wMNDv-+ zI{91N(Fp(+p9$@G0sjaH9ZA?2&2CYiT&_v|V0cpNv+4kec?X4~QoQYTb$VvHVwQK; z1t2~rZ6e+7Z>oQ!y2t+K^n|#0RGr+si$nH_ zA@#3*?f{P#8UZMKL*Qk1Yd7&-bH$RWWG$(U*bay{h0@;6i)y_2pr2@`91qz^Y03Jt z$`2x-uD{9&QTk}SRBYhd49yHly}!Dla#aaeWH>2xZ1e z?kJl9xeVU3;e`m#wa1BaiO5b*Alc-9OAP>J9V|H<9|Y(%y=$yfCY2aUD$`kr(FuQpPTKnV6Y5schp>J_J(~xF-)t-u^oO;YX(*PM@VrtRH^eEo9P#Db^BFA0zzMjGIG77wv<#45 zIcegKZIsK&=nRF&s*=7o&hWShAx+7nbVbuTc*?;NQCL~Zp zz{w>|bVv;>`mwAZpC&14YFYR1?h9OfgdIQ-Sb+a}O#rs}t%xC54Ccz*k2^$RiVNG3 zSTGcJP2!BfUn96qy14Em<4QTUDr51h})XauuS6 zXG8WOp{?^Rk9Fl1k%yMrp|gK*wnR+8IZ|etGYatZgH-a>m+m@^lD1)vShrCoEMZQC zLnComDS^B~;z>coJ0d%IVuGO@d?4z}U7#7pOU;_o7DJC8bz?kqW1=b1vw#P@K%R0S zyWf04drU|wji7IeqReP(LK{=~9^1b{Rm(a{?U@e}nzMmczx*9EasIh?qTDu~k@i$a zcoi6p>ra}zq>m^##Q%isa9&`I|JRyk-QKc)9m1>fo^y*78^Dt+StqHcEwxODrH4!a z5#0|@h>t9&Oti7igEogS`zP?mtEsr_mlx*{URy{gH{(8}q;;~gdC|eN_%zB*h$Yk! z{w(hgKsQ4NksGuVWhEL22L)mt!CzBGC=r5moeStCa5Y!^3_A6%nTFrUtuaNh@7*&N z`H?m3s0yGrAnRN=k=3)aPnka}%9te+*Z70!4$+}L#;5SOA(x|WFrJ1f{$%EqrA|i^DGjmNor>g7f-Kzz^68f?qqm7eD(~H8JzL9}b&uIP@`B?MI zVy|^wh7BG;M!-Kz5yVZg4ecJ>ikJ`wJB&1NOu(9BGeZV%e$Qu58OE>4kkc4&ju9^X zokHaf$@^iKVBr;)QUnO&CR{|VHsfFP(TP8SU#jX2UNckkmwT-IEpiy=IEISCg2Fy@ z8BltOI{Rs-Pl$ck^t*OXaz{i{f4HCO$H5yj#)ZmwuB0Vgha2d!>w@E4lDqSk=8P zoC{EdCHAXaQat?h*)7y&lQoi^cl zHl_If?Ao`PjFT^u6ZH%rcnL;qI=|JB8hod_0$47N1>5b$cyrOxt_xHm^z=@jyb{cO z$J6zS5q8(2i=0YI12AEFbPo4d8X2T35|=%nxCRYk50jz~%P_DdZ8TNff%Mi|f6|wC zdu7gVrS9{~$EbTtG-RFx^NvCHo_;+OX`x06?{i6m^Af9=JHUtk^_ty^y8(O4PD$u- ze~KWVIPdPsj*b{7ne9pYhN=btpXE__PWRat^HW%tF5d}7yY*?I19)kUbuUs-ZH(e@ zpp(QJuECArgM}zD7K-B9UjQdeG+I1`um88>;FU(k8AFRs98Tv%ymHfqfWCQUh{~nC zu?ZIl1uuE2XYI8%ECr{OPc>9wh5UDX=T!!8czJa0+LgMJL)OXuSshGK6Zum?52Db% z`*AK{*vdi79z*x0a)4Q=W%M^qx@vf8@_WqBODpS8-ScUyds>lL=AxffRKUw`0w?=L zk&cDEyGD_-4O%9ShB7$C-9E3Z4MwU|RChgj|q4Sfy zIWO6)t`FjnSwC>q&(QD=6)x6K0a%S@?>G~_2=DF!0{prQx zz5|DyH$FW7L61`GQVIKGbC;a<$hzO`&^a>LKTGux&R8x~{HI6jY?@lkfTp0CJwFol zyfF>v5%WBlm*Itw1D^zY(Y(;F!`5T;E?7L?q-?;uQZTgmj16*$*rq4KQ{=oEwx&qI zawlv4-%bY5tR=+w640Ie3*9@tV&=B@Fk)k>KWX{|*u2T&iYnG0BgEPkJWfPf;IWp*d|aYaYK38KPS>~d>7xs zC1mNkHi_WLsh33j`^EZylkbtPwg88yL`bb5{wd$Gj%G$xH=~EmA}o2VPC%Aikyb4a z^xP48C#X-N*r$Ny;)%l8u_(n@4~In2`QxM;Y~7@)Zz7s)m*a^#L36Vb z-}|GNQf&OUVSOjT_0V2Qk#CH8vyHi2$+uX+`u~X33z&>6)*FldK|{U#kE@a!Fwzcs zK@Sek#YZup7-b6M=tPYe#06s0<4}+GRDysFL=W>XMi2S(Bw~0tj&$*`_2DDUK-13n zFin-%SyrJ_C3B;f#n5(kMW@6vCBoG(cU11?Iqu->Z4Vf~BJ91$5e?T|ZW`r`w_z!j znL$x1NGn>NX;W&iUIG#fk50|jjW@b#;Ut$yeQ2Jzr`6O;77+s`12} z{K=C5q?+0jK~2eVAU(^OVAWvYP*Xn?7c^pr-;fS+DRaE{doGocY?ldz6s4d+b+ z2G7jMw2!bhn&%y-a!9R(;=5hNsF;zJ)1YR zj1J%X4)}dbw|9(}u<63@;q4E)8m1G0_-rywNoNmOK;g2ee--K0*zUKnIx9#S=>XZr z?MnBfor7@(MPg!wkMOE^z237InD9oOXFwBp5Esbi)_jx0H9G@|Wd>+0JU>4j?KjE| z6e*rwIQ?3mj)3pR!v6Pv3{%ssi;~kenRymq=hLpsZ$T|?qQmGaOA4h&7Sc(9>Ro2! zbP2Ej^yC6Zh}3@{*_t(ezQ#}_af@si?-80lC@MV~7^d15Wj@-z?@b+}@0$7GMbPF8 z!4OAQ{ITa)uA6wKHT_4teElgtQm%CT!>DX3;P)=-f3&k() zk;%f?VW#I^We3Wqe^egqBvmVuR-yH!Qll3GY>l!H zq_#y2K|Aw&(If^@c$n02&G*i2ZZu^0df* zC=Lj0TkT5y*ppDfg9I_%-}B7NKZWN5se#38hs(_22Zj)vG8gkGUuLAZmwAR%%8@<) zP@5@gh={06+0+{EJQCy(e~&{j5<2!;eVui+s;of-%J50VVmU5l|zT5n8bk}qw-4Z`ya2(l@sCH&*a@9qDY!urQQ8}y$6o2+K3(li!s)^+6 zfMeTF-u+rc@D#n5?96KFYZ2m*96+&RR8do8`~3LdoiDNZO^U;3^;{z2Tcrhjg9kQ( zgSHZ-zTDDO(NoT2*}_?cTEC~;m&>PqjqNG_417`;hseM{H#%d4Hpdz8iRxJ~hr;_{ zj|RJi$g%PSvNvC$H=ruOK+d-(RZAgJr0@JOFYWf(`JmF!Wr#r4-VYdEThDj^EH_Ix z+Ja`#Q{_cM(>al)u!a#a_sWtBOHgPg;9?@X+>)nAx(Go;{73AP>wd|jp%)=VhgMRe zk2XbAug)yB!i4Sz;!yN}d1<3U;>d02r9SALQNWDI<>)n%23z{gLIZIh-H*GuIZX+@^9Qo`A z3{)V90#Fyo5qstpfO^~x z59*P*tMZibv_PX?!!GH3K=Mv@oNzSGPXFfKQ-e2O=q61-`7y-h}nqt( zZ6`eJc|wqC!9}#PwRJbfSqXW)1a%zOttCjeK#5 z=*Ed*w%)CPvbU5}?i-t!L}|vX7?4~}VAzJgD!EBHHFg3a^ByFqzcQK{{%-gV#{ll3 zy*1I+732-jhuE@^Q@3daDyh)=tqjlIrSF`qCrB|qd(nJFk$u7+ei@OtSUN5&`d?r# ztm6ITh{l*`bt(D<$uWK1hufjI|GWbaNpBu)7WG`nEOV3-8}z)M%hqy0Q90lRI3c-xZ!%^3hi}$gg{Q1mk)N` zMG27YBzz$@$S)?g7*s8tti}HUmxJ>@H3TRIafpfh^Ert8ZeK~Q5^M7AM|xbnh6U^_I>`-CT!n+7kG&L zr0f4Y`)IP%*V`iNhY5C1_Mnrl%-2*j~^QwWqrK1mH2&m zh=iMjV@gF1QJwto2*XOsOKAcy$Kf3g#Q(DE@2m8Mr-$|&N-vuO2&Y2maTT2S1{GW; zH>b}#Zcaj(h-N%$;{-$;K6+!5F^tP=$J;iDuFqV5{mv_C>{rdz$9|b;-w?j4hRNXt z>s|^s+IQ%VHb?3BU!nSD23}kS+8gdAN zRChB}o&H*rAm>WBE7UGIq}#2laxA=fwca*pNPE*~g+}6%T4Y6relaUAHn8juxwi*e zzyL8>VM(O2E~qK$aKaaap(Aj4#tpX6K49I@TyW{fYo)l{ZIMq~$>@@idUi8~Ba6z} ztXTQk1#;V?BhZ}L-3=MO7o;m?LFKI`6Om`C7eZ2NxxM=RRZ!xJRXH>+JnxMum^3bc zBA@C_i6G|ogyS@dvz>&}04*;OVHgsnML|AmQ|>Oq{gXs{VX1G?WMNaT(t2X)=LQ3^ zcG@BR3;hO-|2RT2%G#~XO;55&I_EDAxBqMM&ki{6(yyxGBTxvNK3d$m?HRS5!B0dK z{Bps%==VYnrPcEdxh4WvlW)a|cWr{;QAe-~F!@e1mcYn^(WU~`;(r06d!%~tN%s_S zabM?h4}TZTOSX)SSj6p)wDxW720YH_Y3no@WBRORX$R%GoEYXC+$VD2rB|ml)Hh5d zzMF~pqG_Pmb3{<(8Wbn>oPeVyDclx~C-xSr`0u@{70Bv6X^F*K^s<_4rGiG{(KRy} z4MwUJgy|0CxgB2!O7Q!_>gl}_H?lX^wXOh` z6hauqA&0ILt7<_LQU_=im{}8=!fzx4oXbx~A(MB}9VDp&7@mXcVOf zi&`5T`^6w=U<0A>FABQz-Se+b5r;PCA3X`yF%MvOKkuqRbX4#+dKI{O7vFtB+)}$& zbFpE{^5^$nb@N@L(Ri1nrP=H#Q#n$^Wj1&A96F?44hKv!U@p1&w_^Wk^{j6%oXMqd zWRp+rIWu5rgp77Vbhn+dU}@43G;Qcv*weE6@^qAJ9HfKpvjtDyILoTEYOV)Y)7Iye z!QGBfV;}vywVI$(epu1Zo^tSx09X){pcDt^Bb)@wQx#8$D)wF=f68+=SZYDPAX%CO z-6xL!5m#XUgU)|ZN#MwP6ISM9_3Nc#^(cgry{#3=tk@Gp&ynOp$IdDYo&W@=p09q> zrzLY9o~pzrw=vEaD)*lm6zBScEfxI2_QSjW!Y~wjo)<*5nv1Ybv-9!08u>M&zF;$( zg$0Bq87NKKU70k@iIFgrXtYBhgtK?Qh%rrsCP703=-kK-4@t87BFvgrW^n&UGI#8e zed&T!4aDkj{~0ELErc4$u-I6WVCFpLfH`sG)?E&5-A!h z;>g^&hC{rfz5Y%VPJ9J<#@WoaV<9Jk%%owK$J39*Yolh9#d0$MJMWv^-L9}t1>ob4 zKeFi`W6*U)9gZX*g6R~u>VzrS0EkYYbVcd~na6*^bK55@Vz}`#S8;LBec^T^=#2Wd zsI=?@2gg@8`f3HO#qe`LF2+>t`fOXQu$c5OaSOVL6FF+dt6Z5Kerb_U(xRd#Y#-*{ zHMltAM5fnK0cA*O?zYUAg${1Wv;Mf%Q`nETb$|d8`5|kLgQP;9Oc?@(cT{_Q-gTGU zYrtV$MZhw@?pZp8M&JnT^YP_!uinDM!l&iNv!lRSs#ieJ#luA0urYz&O%x12mguzG zVtdn&O3invhm12^Y?w}CDb$Xa8e4~7J8l#HzCPn%wrKzly3h8^ziKhAKx#i!>pbos zSE$miL{E0=tonk4pRVCQu(eR~P+Nv4SCx);$&6Xm3p#)+j=i2U0(*ihV9l1hE{MKN zxS`YKpnKBPn{K|cHNb_F0eZe;*39n5$Jwsj3jKfOMKOTr3k`Ezk_FRP0Ru%W6uA0# z=#fej=zuBGS2(i%T^Ni8;6dlNLOkY@1nVT8lx`nqjXWv6xNfn^-F5_RV}Xf4vo<1i zrtORO9=co1EBU-lT-h1IPpDuQ(9fk9s8Ovj_cVKIRdOs2J2xuG>kt&@>6;&N9lSMc zBp)K1+@xS=Jk@1_%oQPkL+@x@8*BF1jDM%fu*1DeU}mCzN|~B4$p7lKVx=|_-|zq$ z@^KgIhx#?TB|S{vL>$3o9PFq|QWv;yJpo)f5C&h}LQdU+n%#U4BZl${dvvpcN!_yB zsd0fYq`Z2itvem@*GQbbw+yx;GWg>-8(pQU+YMgC0f82ohCWFr+sc}Kghr(H&CfFL zX1gxV&JGAnxFc2kaEF z)KU&0sUiefhIwUfT6&X#P-}xcOg|t(cD>0~WKY zAqcIsb9Lx0j(+^wE22gQxeS6xVAA)|uF477Ap_23dNaDcLg4mea3c>WQQDg3}} z`q`tG)mCZZk0; z3I{P@G;l^))95$NT8kozpFBf3K1ek#j3<9}JqS1|e;dOhFx1Gp=12OD^6UGJxKZDW zKqyW#zL7JJfZ|^(R>X03PDw3zNVCH`Mm}v$$&GW~;ATsqy}>J2Pxg3B2;URb1QM*# z3gbxsA$r)I+%D^f?PycY@L3KgLHz<8w&d$uU+-!>E(YC@!V=*{;d_EQ2x?Rl+hmzZ z@(w*N*bqf~>Z+q%Mw<||>!7R-));ep{(cfR$s3{cf*`qGy-yk3zYk{b#UkE zk9Z=SAIT)b5-MDo>J2C-fVawCdYWc9j45LAJCpk~@%w9f7&IRfpey3pa$7mx1;}v1 zJ)*70Gz*(coo;q@C(ybB%@VW0y=9aJ!j2Yfb6W%qy;BeCYW46j?da{&LQyx8;k%|Q zbsJS7Y8R(+Cqg(bT3Z5R5B@!3{w7->z&RP%NsAQL3AgK#gBrZEjWXw!nF9$G&x&#C z%eAtIt!N2qDZqBhCONOFN}dXXRb*vG49FU`_*}p;;5! z^beLCrpb{8brP&8m=KDXG_3>1B@~u90|VGiVH!NJ+8~FIk~cEod^Gd3;q3+K);*R` zv=t1q2PP*z;da9C;ne+$3NJp3k!F8?ssi z262xNFPauH*UN}rBXLj__+MCK{^}O^H@t&jBRo_;;3Ku;eK=-ZcRN6h9}h~!=mcu8 z*H)SXTc7M>NzdvnhJEyy2SD!VOXB{l+gOn`y7YNR6)RQ((Nz|!AlisX4jHo4-g)3$ zBk0hIwz+|y2eD4P3}MjmnY?|_Kz<#7cJNq^U>P3NeeWiX9GOxUi#C}t6yoFTPPuAC z0r)n@#J#BOO^GJUe-gJcRn$N^=^>WgUtcDA4TBIwmJ4<4`_uu z`T!5($^q?^=GBhJ-!Y|bX}{+J6{aZA7j#cbgCq<6D+~DP5*~aZ&`i_uhR-&s^#!U1 z7W+lDAxLoABR9Q&IknW#BQ-~?nF$2tY5XEU<1@(L+-^ROE|pjsC_w9Qv;2-u&R!9T z(JZH!D)g#k7b54)LB-l40H@wr+CZAy00EWW?kCC~fVS5}IXCq2yFi>2CDvmZg=#nI zBp2FrCOld1!-lnd6&8((d^4Oo&F6ypAh_wB=mvth!ifa?j3T_Jyn{GJW(KMPv8}v) z1zYTcJ@KUYVoF=`-!1oSnxVm%DZ z4jlD@o)A`o!SBy$HA3Qk;S*bUPvT;EZFkoSptVv;Py|$qMP|n7-Qi85oml|0h8U&I zfL#(7pSI7cy3YI>8$ELtYjVWCt4mNgd1uz{G-gP&)Z58r&i5zL-mA{*lOZ!GA3xG1 zTu0B1KtVRbD-Vpl1ChYzKYLx-l^1ZppBQ#)*8Jq%}H!# z0TNBDyp=V|G42rymVq)(!C!SB4pW`e^Ck}S!6QBU{}K*w*my*Yo2^i~tJpvLr7ii}72MbJ6Tq0DSc)g2gL7LJd)w03Yco2~wQ3A>l z_D@ILh~~vhiv$nsR}Ja=$a2@$#3&&gceQzdJXmMF@^^lCTx1pepBsm3{|V2BpUYzj zRUy!zyXwO5CuFYD3GHcVQYfzc?0``5tDoozyh>v6gVLPA{BTW(c{`S*2bw8P7zP#E zEGT_DP5YvZGwS;F8Zxf|e>q&E_ij2r-$Dssw^z52Ba#sl0HJ+*q^x}cc!Mq;M+_H5 zVzgdk_MFuUq5v}rVzPfUV-0^1f@@W;jNAQ+$-b(SZTynpXt8VNwgVoLZtmPOUBzuT zmBLvA{Vz82JxaBr3)tLI)0`LA9SyiRs%jY^?T-en>FyOl1^pJW!4$2uH|<%%ytMgF zS>2Lv(_ql`f{4H`!~H_&V-{Zt5;9Rcf|p*VskOb$1C&Gi-gK&?LiUYp=V1ey?dba3 zPxImc;|%vJFs}L**(CxhzXlH(zMX!PU!Ehha{T@C*iYesO-zNv{AQs$rSr6NXg~ta z(2+TSF8?^;UtPyh9p>#PO6w{OPA|PvcoC18QrK*`LL;$ZQz(=E(#vuaKjhPaiRFoQ z#;NGRdS(Pu6L~_Ej4qct3MFru$xjQ4>CC85GT--!GRJ@2c+-=p zD4kE-)#M6p6!LV$e*De^HtIzmj6Zwu*|^QR#b{b=Y-hJ>a_jq^1xS=IzOKr91C7qh zTmWgW5|b{nn7uDb^{+WS$gk^;Y&jd|?S4%M&-LfWfKdF;?V>+8JUmGvXUZUlL_I z2CI82gE@vz4IwF0-b_23heiWo!*?n*&^z3KA4sZoga}V?j7eS6ng+O|EE`q9{cX9) zw%K!sB@CqFA;*0drV+)@rkp$TTt2y}Mg>xvtM?|6u@A~D)sJkV=pIJ36%HuxD`mkF z5T(EYa&QY5=}|{Aev8mhu@0{DF(jb;K46R8`E_a47>q>E^ZRZqzEomeL5An|{E;nE zTS-0D^Wox+Vn#-TC9UX#59dZD3IwIOc3CMhx+v&$(Y**ckH_hf^ z_WqP%fMM9`t`X>!#VC;FO;-`)*Y{XnJ5h;k>6pHf^%NSf+MbLLOJE*MBFABBbo0pM zvzogas5{NFNkDikKsXRo!`2`TFsP!*|6RH|8cPa^Nz#G{Oc-F4i6+K9MZWCDsnJX#zlLrLm-STH-5Y>yL&Kx_U^Xsf zlyR!o42LylJI-Q2QoTq%uMl*8nTXxbcjCZ!u$3ZT;vUFx&r=i0I*)zX7JVhwP{5Vj z9L+E6hEogFXfRx?tpM>`S5VdxaRVRs4v#v0orURJ$e>{Ene2jNv#g-~16{S|l^F0W zSh0;4-pb9vJ+2K^NKwigOtY!+9ht`6=h@~jAC1XxnD zo@b8r8#p|mMEu`tMg2H}Iz;T4a@Hn=T+t?7h??U~SN!v3>0k=Mzp3HY6NG^$imBdA zf5geZ0st0>;?_4Ab9hjH+#F&)AXb$?J8CDVY!o)EYhdv1Z-&)rtP+$m(h+D^gIid~ zes*)Tw}W9DTNeRSy*FoE?}5APmlX!5?c4{{0I!J^RqQ7tCFoF*T%E~LTXn#4eJc?@rz*&eO)D8mY=Jk{!|9863I7HyU zgPe(fiv2Uu-Rwy0U9H43X4-Nv6-OFaF>a!89mkr!wl0~qs*RxK^U3Oao!H63k4 zO&6EMRDjrv*qNRj{wvHEr<30%zn)7LL5`2I8wU79N^M>jijV+AAlvb!R9Gz}B9M1m|{Ud%RsgPU!FirLf~yi zzzWf>{oJ zX>zl?Q32ww-xI^PldSrvvadLbN4b1^O8HkJ1@$hUW?she>|KNJ4!0x z5cbjch^NC7`N`wMif8uu@_98*`#zIB$VxIhk~Zw|m}Tz4`%_r0N9@nEdTg0JI{G1@ z`7pJnWUw5ygI`S^M-sdi|&ENVHv95UttF8`1)Mv=cTV z@~Rp=Zh$Z4vr`hBxcm}P9H64iQX(c3ad)1gy8U__$@<2}IA zJ%+jl+^zY>h)fM&PuTP4tMuR+3}{@@4$eV0e81H6kjJkr+zn@;O`PEw=p9f-*X1c_ zdfgBP!bP<17Xv%|FRI_wuNV+qi_0aH!epBGmTP)<`d=lbJRvkR&w4x}ydDQ5vYcGI zc+gh4lU42_i5or-zGWsf-JDMGWwFPnm{l#0TiFF}!IFPkt|z3=#`IMP0qztaG#SX^ z#?&ECDc{j0KTzHw-vD2xzeyHkT%47h4LS#G?Xwq(5x~QU$5JK~aeIYTPYmBW7j^y| zCmzgz@ha$Lw9(868t1CKrt+9V=v^yyj7YVg1Z&ac9W}w-y>t}2tCYpcp4LabE;WND zDBvnI3gQYEwXs9eW7)k$cxKV!O86M({}PU_#?`x-*!i;jrB(&{zrv=0X5n%+%N$}Q zVnQmlz&X(>2&wdR(X~eL!Q>|p79ecb4&Qi6pXR!#jeFQBe-VP%dsU#=e0x@6c;%q& z30e=ytEXO>jDE)AI_;br90v`XTypdO6yfnIC-Wuv4^o{mySD@6Y2fjZqrAWWJqG-Yp z;lME3TJi+Nbm9I`_N&dNfn5`z@`Jh|EJN9gQ;hg-K*>8CZss6q1j;VHD?pL$_%9J1 zfpNg`t<7QzssnuQ{VFVtn-kM$^OE)K3zRA1cs|<>DL6E5zlI9{2alV% zRZ$b8@8e-D$kRcc#&&KqYv4)|M!zu8Gd>g|43K6;G?=92H3+FBuvqXm3imJT1ttMD zKeeY5s0H3rGR8@;)D(3x5&2RAxHSOMaXWW}QUwCfiRN$;hexp@{kY$!U{88G(k-%L zE%xqQFYoO=P~K=cZ0kvWB5Azl77C>R8Ja}Py-qYhpFF-f4quGsbI!)k8i%Yf(J?=A zvAye^A&cE|d;!bz;%`2@eftwFt7Dz6kII_;0~jC(f}RJ~{@CrrBpv3Euvbf#-6!Hj zjD(wP>)EMPMs>TchL+OZ?4bi7|-zia!jkgN=X~I{4V5Bpqj# zP46NP54xR&rtfB^rs-F{NV2y?*P21lPbk_27Mx*pOxPjxFP!@~o-!7HJKw#-Wf~z% zl8t=Rxl1BFfq4;X(XuXk(#TK9fo$*_E_#%hs>OM%mRTs`pa&pC@>Y*H*wCP8z~RXC z5&TS&;@JB)v@10eUtY5KgfjUEo-oT8KzrM|NE|W z&J{bS0{plyeKmCneFcawi&W+9`wp!6&>#Oh6UT&l*D}Y66aS#((0leVf|et!XEFS3 za_mUTryYXm=!S8L08F7WF4o%kbjXpR8PCioFs^=fGd8D!_>ncjc?lGN$@L{4NZXY2j)mJye)1jkNo;&dF?1Lx0degOFGSAdr)`K1Mut)YuM-a+asCh z-wo0DCN(*&_a@_W^)FAP5pM+1d&N{bYe19=YLMVxhyDTw#T2J{7zS0z>A~FQ;{6S3 z(KO2kLv9s7;1FI9x*$c`h()xG`j;qq>nPbTR~P*Q&?GL!Lbpa)Xjml6*#$44}IrMpNQ z&ogK+lSiC2Jw<<)OskRBhR6|<RDX1(+)=p${euR-Z?E;W9zi7orlrx#p^BbCyw9^HYCs;_HvafTVL1E!I<^SsDPF z*yy3l4;H{q&S2!W$0k8LBmqV7h)j0bduU=#ATZ-dz>-OZ_upufHr4~g`Ei-vvK_q9 z?@ypJAbBuX6%UPM_)k-+69`^p{CS+l{Ds9Wi+bBBIFD_Wi-9 z{~hqj`(J^>!=G~|{7Gsto72q*;f82X5RyEDov)}WTBd1v4=7}N+5}JvQ<0Vp(!26< z>+C4o59Z;aR?%4%U)A6LXT@HB5BmGUUoY=!)h<6Yp=yvhi6Ag{GP_&|E%EV~#EGSx z$4>*olfj+1dLL*!3CcE9?!$$)wN^-kD zGK~H>ezg7RHSUt}DWlOVSa06Xadw-F@?zSWmG z@Y9&qYz;xl0{P~QV&yo0*my6(U^lc@K&=>|ts#ux*g5$|@~uG};yugK4QPDNhaSh{ z*=@*A1UFuWGk8Bc-wQ9fu6MsLl`yA67p1}lv3jq%qv#xM-E4Mf(H+`mx}lR%SG^Q6iaZ{M6U-L=24CSPJ1n~%s;XvBT z`Ig}wszrZLUh@A$Q?tf`3C;UX6KOs&PbZH){eqC;$2)vCadI0S z)1E@UTDvCl3_PX+08o^i=Rc3$?#-MMAV{tLvqRz7X4YPH?KV(Z`ktP9%G)P{xeqi5h>a%hEz6eAQTUU(hHyW2;Q6h=WU) zOiS*9qclHfl9*O9eejsq)vZPll7A=|I67?+%6h}w;&Time^J4Mxf4x@d~>w7p=55+ z4;fs^;S02*J38@-ac7|xE0(iLXx34L_b6sGUEEdbgNyR=iT)`qrWcEUo&7o zzz8IiILb>N-Ec)N)BQwH9$04%lnC9!<`#Cki1s{5@)%&KPzP5>^n@%9mWM+(03zku z54H_1YgL1H6Y+lz7?6#*(s1?0nT`gDuD_lH!nghc<~2#LVT@E~uyU@l8%Uy|Nu1b5 zaz!tlmJxx~6Je1}&El{ZgZBybn_1fHW_p*2_w1g5Y^MoxnjP3k1n)V*k^n9$m{t#H z!mzyNOCY1pCBLkoT2&6&j9bGM=g}%eGto2_1}$I^W^+E$O0PJeo~;UoL^uIaYCyq%r>fI87UeT{3=+w^I9JHQN}8F^$Tr8@|~`(bU) zF0Ppp+%xcvC`;rPKf0?@$I<6dQ8&nM2nbHzdIU0t5(dxUJ{|%?6K6`=$ky0^J?B?!uZXJSw=4j zB2%n$L|Q7mK9XT_xWRZu29VW1f*|l+0VXb!HHYzhG3lyY0nNNN_J%SR8G*-gGo7D= z%T0AN+_lOI$pAqPs2S02s52_=d~y0$Vy`toQRiO*?M-trGIy`T%>~qxz1Ig1c-te+ zLYAvI@AbY>0q;FPhnVFz$@Q}CVbRQF@QZ*`s<*Dew}WX7qmjJJpBuk*02T2%cph`M z{7_;y^+{!Xm&$Gduz`~tUkSlfWX3)=b8dtxZI=n87pkojGrtgIqbn^tQeqN<8cQBn zhwTfyQCb&W)wBp}fx`fLV5;=Ob29Xk^<#Jp(5=yIxz8h?VXW=+lwdh~lu4eyz7tDhU?pOW(0bLd0 ziLiGk2iCK-BXe9>aVU?Tw<)hyxn{FY#3YTVu_=1>%mbC9`_t1~1r! z4CI>qwjDax7^><=IKmx&^(Qm>1xC;oF}?7OBuzFo2c+s7La4CW-R)3beYhzi_Kcam z{0`lf0C|g16YCQ0rN~NBJLi5UwQBPVypo$FdUI0y3?sBlDKmJL4wk99zRMXnqbiA; zB$ZI)wcc|RnoAgpjbb{n1bycA2h_ukIlqCI^XaN;9;dkWyK>OKxp|B@-a}tgt*fR~ zSmb6<_{e9~;pO{;=&+R9D%YfNWJ6D3FGtWw>0`k$rnJjJCB}>|Yvq?=fCTrLPYW>a z@7!7xhQOG5`Infc2M=VPy!N_*jejD=nE|d3ftU;V(XNH|J7i?b8!2jQUrdx`R@e|# zE%_6=Be}m+pwI!!Le7_u2ax-~)rS_F%BP+UzM2}u1+S`ncRTTt`K}?Q2g8z%AAzAZ zP*TbfcU{0|4M`&NBe}f-I+x($9w1ALyKdbrpPDCI?UPjZ<{C>0+fqIiAPrr3MP&}L zbHL|Mt?gIJgdmR5#k^)_QKvcNFv81k!wHU(Yb3h*Hf2P|7s(=3ktY!5>b;T0&X$#S z;4^DBp$56#xGk=n$LBm5TaMyUHjsQ?nIos6OIX7*u3%S}0<=-L{eC}(q}itcetX@I zB&`iVolxmjBPNiGxp+b$BFlcIiLcDku|)U#KCG3gTPk9x8_< z8HM+yQKhvV!pgeRVDkxsH5a4|C4374E1{c{U&|8)FVl~v^FtV3>%#}>+PSCq4?ZY1 zk^5ora%@%WK6o3J!0lQTMg)_i@3mvHi?uE)srUoyB=nmb!S6Vz567O4u%@n($hg$Y z;sxpP4$VYt`R?^70vEob0h@epJp*9phrtuf zTuFr+>6+$5xZxXh^*W+vwtI}nZnEL5WY$UGYwVWka7dOF6X#t`@qRX@QYv=&(DJl$OFuK@*eWwoW`z&qe_cd2Mj)E54 z{h^@V`|?9(N8+UfTGXi18qnngkCm1E+}6JW@do0=DkKxE+mjO{2?h?Ka?z1sUBw%d zt6uM02ItvAxr52g6j9kp-fe}!T!GrPFI%JY$Jfn*v8ov&PEIlkE`zjh4v@N2uy=FX z)2rUkLge|AxPExYC`MwZgmb-TjQpwQwC%sGo>1bD9YB4y0p0#b4iXWH5E6TZO&5hF zA)xQZ$sSe&17IxHerC~ip((xpX(99;zPiO3ZDdGgI_r2iFFL(qD_?JG!qVyf-|T-T zEHiVsE~t!D0SDXix?t5LBRGtX)e%biP46GM2R30F=$@8{Plf4gNbcVFUn&aOk`(wY E`lLRa3IG5A literal 0 HcmV?d00001 diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py new file mode 100644 index 00000000000..293cfa339f7 --- /dev/null +++ b/test/unit/test_cluster_fuzz.py @@ -0,0 +1,259 @@ +import os +import platform +import re +import statistics +import subprocess +import sys +import tarfile +import tempfile +import unittest + +from scripts.test import shared +from . import utils + + +def get_build_dir(): + # wasm-opt is in the bin/ dir, and the build dir is one above it, + # and contains bin/ and lib/. + return os.path.dirname(os.path.dirname(shared.WASM_OPT[0])) + + +# Windows is not yet supported. +@unittest.skipIf(platform.system() == 'Windows', "showing class skipping") +class ClusterFuzz(utils.BinaryenTestCase): + @classmethod + def setUpClass(cls): + # Bundle up our ClusterFuzz package, and unbundle it to a directory. + # Keep the directory alive in a class var. + cls.temp_dir = tempfile.TemporaryDirectory() + cls.clusterfuzz_dir = cls.temp_dir.name + + bundle = os.environ.get('BINARYEN_CLUSTER_FUZZ_BUNDLE') + if bundle: + print(f'Using existing bundle: {bundle}') + else: + print('Making a new bundle') + bundle = os.path.join(cls.clusterfuzz_dir, 'bundle.tgz') + cmd = [shared.in_binaryen('scripts', 'bundle_clusterfuzz.py')] + cmd.append(bundle) + cmd.append(f'--build-dir={get_build_dir()}') + shared.run_process(cmd) + + print('Unpacking bundle') + tar = tarfile.open(bundle, "r:gz") + tar.extractall(path=cls.clusterfuzz_dir) + tar.close() + + print('Ready') + + # Test our bundler for ClusterFuzz. + def test_bundle(self): + # The bundle should contain certain files: + # 1. run.py, the main entry point. + self.assertTrue(os.path.exists(os.path.join(self.clusterfuzz_dir, 'run.py'))) + # 2. scripts/fuzz_shell.js, the js testcase shell + self.assertTrue(os.path.exists(os.path.join(self.clusterfuzz_dir, 'scripts', 'fuzz_shell.js'))) + # 3. bin/wasm-opt, the wasm-opt binary in a static build + wasm_opt = os.path.join(self.clusterfuzz_dir, 'bin', 'wasm-opt') + self.assertTrue(os.path.exists(wasm_opt)) + + # See that we can execute the bundled wasm-opt. It should be able to + # print out its version. + out = subprocess.check_output([wasm_opt, '--version'], text=True) + self.assertIn('wasm-opt version ', out) + + # Generate N testcases, using run.py from a temp dir, and outputting to a + # testcase dir. + def generate_testcases(self, N, testcase_dir): + proc = subprocess.run([sys.executable, + os.path.join(self.clusterfuzz_dir, 'run.py'), + f'--output_dir={testcase_dir}', + f'--no_of_files={N}'], + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + self.assertEqual(proc.returncode, 0) + return proc + + # Test the bundled run.py script. + def test_run_py(self): + temp_dir = tempfile.TemporaryDirectory() + + N = 10 + proc = self.generate_testcases(N, temp_dir.name) + + # We should have logged the creation of N testcases. + self.assertEqual(proc.stdout.count('Created testcase:'), N) + + # We should have actually created them. + for i in range(0, N + 2): + fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') + flags_file = os.path.join(temp_dir.name, f'flags-binaryen-{i}.js') + # We actually emit the range [1, N], so 0 or N+1 should not exist. + if i >= 1 and i <= N: + self.assertTrue(os.path.exists(fuzz_file)) + self.assertTrue(os.path.exists(flags_file)) + else: + self.assertTrue(not os.path.exists(fuzz_file)) + self.assertTrue(not os.path.exists(flags_file)) + + def test_fuzz_passes(self): + # We should see interesting passes being run in run.py. This is *NOT* a + # deterministic test, since the number of passes run is random (we just + # let run.py run normally, to simulate the real environment), so flakes + # are possible here. However, we do the check in a way that the + # statistical likelihood of a flake is insignificant. Specifically, we + # just check that we see a different number of passes run in two + # different invocations, which is enough to prove that we are running + # different passes each time. And the number of passes is on average + # over 100 here (10 testcases, and each runs 0-20 passes or so). + temp_dir = tempfile.TemporaryDirectory() + N = 10 + + # Try many times to see a different number, to make flakes even less + # likely. In the worst case if there were two possible numbers of + # passes run, with equal probability, then if we failed 100 iterations + # every second, we could go for billions of billions of years without a + # flake. (And, if there are only two numbers with *non*-equal + # probability then something is very wrong, and we'd like to see + # errors.) + seen_num_passes = set() + for i in range(100): + os.environ['BINARYEN_PASS_DEBUG'] = '1' + try: + proc = self.generate_testcases(N, temp_dir.name) + finally: + del os.environ['BINARYEN_PASS_DEBUG'] + + num_passes = proc.stderr.count('running pass') + print(f'num passes: {num_passes}') + seen_num_passes.add(num_passes) + if len(seen_num_passes) > 1: + return + raise Exception(f'We always only saw {seen_num_passes} passes run') + + def test_file_contents(self): + # As test_fuzz_passes, this is nondeterministic, but statistically it is + # almost impossible to get a flake here. + temp_dir = tempfile.TemporaryDirectory() + N = 100 + self.generate_testcases(N, temp_dir.name) + + # To check for interesting wasm file contents, we'll note how many + # struct.news appear (a signal that we are emitting WasmGC, and also a + # non-trivial number of them), the sizes of the wasm files, and the + # exports. + seen_struct_news = [] + seen_sizes = [] + seen_exports = [] + + # The number of struct.news appears in the metrics report like this: + # + # StructNew : 18 + # + struct_news_regex = re.compile(r'StructNew\s+:\s+(\d+)') + + # The number of exports appears in the metrics report like this: + # + # [exports] : 1 + # + exports_regex = re.compile(r'\[exports\]\s+:\s+(\d+)') + + for i in range(1, N + 1): + fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') + flags_file = os.path.join(temp_dir.name, f'flags-binaryen-{i}.js') + + # The flags file must contain --wasm-staging + with open(flags_file) as f: + self.assertEqual(f.read(), '--wasm-staging') + + # The fuzz files begin with + # + # var binary = new Uint8Array([..binary data as numbers..]); + # + with open(fuzz_file) as f: + first_line = f.readline().strip() + start = 'var binary = new Uint8Array([' + end = ']);' + self.assertTrue(first_line.startswith(start)) + self.assertTrue(first_line.endswith(end)) + numbers = first_line[len(start):-len(end)] + + # Convert to binary, and see that it is a valid file. + numbers_array = [int(x) for x in numbers.split(',')] + binary_file = os.path.join(temp_dir.name, 'file.wasm') + with open(binary_file, 'wb') as f: + f.write(bytes(numbers_array)) + metrics = subprocess.check_output( + shared.WASM_OPT + ['-all', '--metrics', binary_file, '-q'], text=True) + + # Update with what we see. + struct_news = re.findall(struct_news_regex, metrics) + if not struct_news: + # No line is emitted when --metrics sees no struct.news. + struct_news = ['0'] + # Metrics should contain one line for StructNews. + self.assertEqual(len(struct_news), 1) + seen_struct_news.append(int(struct_news[0])) + + seen_sizes.append(os.path.getsize(binary_file)) + + exports = re.findall(exports_regex, metrics) + # Metrics should contain one line for exports. + self.assertEqual(len(exports), 1) + seen_exports.append(int(exports[0])) + + print() + + # struct.news appear to be distributed as mean 15, stddev 24, median 10, + # so over 100 samples we are incredibly likely to see an interesting + # number at least once. It is also incredibly unlikely for the stdev to + # be zero. + print(f'mean struct.news: {statistics.mean(seen_struct_news)}') + print(f'stdev struct.news: {statistics.stdev(seen_struct_news)}') + print(f'median struct.news: {statistics.median(seen_struct_news)}') + self.assertGreaterEqual(max(seen_struct_news), 10) + self.assertGreater(statistics.stdev(seen_struct_news), 0) + + print() + + # sizes appear to be distributed as mean 2933, stddev 2011, median 2510. + print(f'mean sizes: {statistics.mean(seen_sizes)}') + print(f'stdev sizes: {statistics.stdev(seen_sizes)}') + print(f'median sizes: {statistics.median(seen_sizes)}') + self.assertGreaterEqual(max(seen_sizes), 1000) + self.assertGreater(statistics.stdev(seen_sizes), 0) + + print() + + # exports appear to be distributed as mean 9, stddev 6, median 8. + print(f'mean exports: {statistics.mean(seen_exports)}') + print(f'stdev exports: {statistics.stdev(seen_exports)}') + print(f'median exports: {statistics.median(seen_exports)}') + self.assertGreaterEqual(max(seen_exports), 8) + self.assertGreater(statistics.stdev(seen_exports), 0) + + print() + + # "zzz" in test name so that this runs last. If it runs first, it can be + # confusing as it appears next to the logging of which bundle we use (see + # setUpClass). + def test_zzz_bundle_build_dir(self): + cmd = [shared.in_binaryen('scripts', 'bundle_clusterfuzz.py')] + cmd.append('bundle.tgz') + # Test that we notice the --build-dir flag. Here we pass an invalid + # value, so we should error. + cmd.append('--build-dir=foo_bar') + + failed = False + try: + subprocess.check_call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except subprocess.CalledProcessError: + # Expected error. + failed = True + self.assertTrue(failed) + + # Test with a valid --build-dir. + cmd.pop() + cmd.append(f'--build-dir={get_build_dir()}') + subprocess.check_call(cmd) From 206ad2906c9e0af92ec4c4da223c96755243aa2e Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Tue, 19 Nov 2024 14:23:48 -0800 Subject: [PATCH 140/622] Add nontrapping-fptoint lowering pass (#7016) This pass lowers nontrapping FP to int instructions to implement LLVM's conversion behavior. This means that they are not fully complete lowerings according to the wasm spec, but have the same undefined behavior that LLM does. This keeps the pass simpler and preserves existing behavior when compiling without nontrapping-ft. This will be used in emscripten, so that we can build libraries with nontrapping-fp and lower them away after link if desired. --- src/passes/CMakeLists.txt | 1 + src/passes/LLVMNontrappingFPToIntLowering.cpp | 180 ++++++++++++ src/passes/pass.cpp | 4 + src/passes/passes.h | 1 + .../lit/exec/nontrapping-fptoint-lowering.wat | 261 ++++++++++++++++++ test/lit/help/wasm-metadce.test | 5 + test/lit/help/wasm-opt.test | 5 + test/lit/help/wasm2js.test | 5 + .../passes/nontrapping-fptoint-lowering.wast | 208 ++++++++++++++ 9 files changed, 670 insertions(+) create mode 100644 src/passes/LLVMNontrappingFPToIntLowering.cpp create mode 100644 test/lit/exec/nontrapping-fptoint-lowering.wat create mode 100644 test/lit/passes/nontrapping-fptoint-lowering.wast diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index c6e079079c0..e461634066e 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -76,6 +76,7 @@ set(passes_SOURCES NameList.cpp NameTypes.cpp NoInline.cpp + LLVMNontrappingFPToIntLowering.cpp OnceReduction.cpp OptimizeAddedConstants.cpp OptimizeCasts.cpp diff --git a/src/passes/LLVMNontrappingFPToIntLowering.cpp b/src/passes/LLVMNontrappingFPToIntLowering.cpp new file mode 100644 index 00000000000..d14e58af806 --- /dev/null +++ b/src/passes/LLVMNontrappingFPToIntLowering.cpp @@ -0,0 +1,180 @@ +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" +#include +#include + +// By default LLVM emits nontrapping float-to-int instructions to implement its +// fptoui/fptosi conversion instructions. This pass replaces these instructions +// with code sequences which also implement LLVM's fptoui/fptosi, but which are +// not semantically equivalent in wasm. This is because out-of-range inputs to +// these instructions produce poison values. So we need only ensure that there +// is no trap, but need not ensure any particular result. The transformation +// in this pass is the same as the one used by LLVM to lower fptoui/fptosi +// to wasm trapping instructions. + +// For example, if a conversion is guarded by a range check in the source, LLVM +// can move the conversion before the check (and instead guard the use of the +// result, which may be poison). This is valid in LLVM and for the nontrapping +// wasm fptoint instructions but not for the trapping conversions. The +// transformation in this pass is valid only if the nontrapping conversions +// in the wasm were generated from LLVM and implement LLVM's conversion +// semantics. + +namespace wasm { +struct LLVMNonTrappingFPToIntLoweringImpl + : public WalkerPass> { + bool isFunctionParallel() override { return true; } + + std::unique_ptr create() override { + return std::make_unique(); + } + + UnaryOp getReplacementOp(UnaryOp op) { + switch (op) { + case TruncSatSFloat32ToInt32: + return TruncSFloat32ToInt32; + case TruncSatUFloat32ToInt32: + return TruncUFloat32ToInt32; + case TruncSatSFloat64ToInt32: + return TruncSFloat64ToInt32; + case TruncSatUFloat64ToInt32: + return TruncUFloat64ToInt32; + case TruncSatSFloat32ToInt64: + return TruncSFloat32ToInt64; + case TruncSatUFloat32ToInt64: + return TruncUFloat32ToInt64; + case TruncSatSFloat64ToInt64: + return TruncSFloat64ToInt64; + case TruncSatUFloat64ToInt64: + return TruncUFloat64ToInt64; + default: + WASM_UNREACHABLE("Unexpected opcode"); + } + } + + template void replaceSigned(Unary* curr) { + BinaryOp ltOp; + UnaryOp absOp; + switch (curr->op) { + case TruncSatSFloat32ToInt32: + case TruncSatSFloat32ToInt64: + ltOp = LtFloat32; + absOp = AbsFloat32; + break; + case TruncSatSFloat64ToInt32: + case TruncSatSFloat64ToInt64: + ltOp = LtFloat64; + absOp = AbsFloat64; + break; + default: + WASM_UNREACHABLE("Unexpected opcode"); + } + + Builder builder(*getModule()); + Index v = Builder::addVar(getFunction(), curr->value->type); + // if fabs(operand) < INT_MAX then use the trapping operation, else return + // INT_MIN. The altnernate value is correct for the case where the input is + // INT_MIN itself; otherwise it's UB so any value will do. + replaceCurrent(builder.makeIf( + builder.makeBinary( + ltOp, + builder.makeUnary( + absOp, builder.makeLocalTee(v, curr->value, curr->value->type)), + builder.makeConst(static_cast(std::numeric_limits::max()))), + builder.makeUnary(getReplacementOp(curr->op), + builder.makeLocalGet(v, curr->value->type)), + builder.makeConst(std::numeric_limits::min()))); + } + + template void replaceUnsigned(Unary* curr) { + BinaryOp ltOp, geOp; + + switch (curr->op) { + case TruncSatUFloat32ToInt32: + case TruncSatUFloat32ToInt64: + ltOp = LtFloat32; + geOp = GeFloat32; + break; + case TruncSatUFloat64ToInt32: + case TruncSatUFloat64ToInt64: + ltOp = LtFloat64; + geOp = GeFloat64; + break; + default: + WASM_UNREACHABLE("Unexpected opcode"); + } + + Builder builder(*getModule()); + Index v = Builder::addVar(getFunction(), curr->value->type); + // if op < INT_MAX and op >= 0 then use the trapping operation, else return + // 0 + replaceCurrent(builder.makeIf( + builder.makeBinary( + AndInt32, + builder.makeBinary( + ltOp, + builder.makeLocalTee(v, curr->value, curr->value->type), + builder.makeConst(static_cast(std::numeric_limits::max()))), + builder.makeBinary(geOp, + builder.makeLocalGet(v, curr->value->type), + builder.makeConst(static_cast(0.0)))), + builder.makeUnary(getReplacementOp(curr->op), + builder.makeLocalGet(v, curr->value->type)), + builder.makeConst(static_cast(0)))); + } + + void visitUnary(Unary* curr) { + switch (curr->op) { + case TruncSatSFloat32ToInt32: + replaceSigned(curr); + break; + case TruncSatSFloat64ToInt32: + replaceSigned(curr); + break; + case TruncSatSFloat32ToInt64: + replaceSigned(curr); + break; + case TruncSatSFloat64ToInt64: + replaceSigned(curr); + break; + case TruncSatUFloat32ToInt32: + replaceUnsigned(curr); + break; + case TruncSatUFloat64ToInt32: + replaceUnsigned(curr); + break; + case TruncSatUFloat32ToInt64: + replaceUnsigned(curr); + break; + case TruncSatUFloat64ToInt64: + replaceUnsigned(curr); + break; + default: + break; + } + } + + void doWalkFunction(Function* func) { Super::doWalkFunction(func); } +}; + +struct LLVMNonTrappingFPToIntLowering : public Pass { + void run(Module* module) override { + if (!module->features.hasTruncSat()) { + return; + } + PassRunner runner(module); + // Run the Impl pass as an inner pass in parallel. This pass updates the + // module features, so it can't be parallel. + runner.add(std::make_unique()); + runner.setIsNested(true); + runner.run(); + module->features.disable(FeatureSet::TruncSat); + } +}; + +Pass* createLLVMNonTrappingFPToIntLoweringPass() { + return new LLVMNonTrappingFPToIntLowering(); +} + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 5cbf4a31fe9..4be24cebffc 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -334,6 +334,10 @@ void PassRegistry::registerPasses() { registerPass("no-partial-inline", "mark functions as no-inline (for partial inlining only)", createNoPartialInlinePass); + registerPass("llvm-nontrapping-fptoint-lowering", + "lower nontrapping float-to-int operations to wasm mvp and " + "disable the nontrapping fptoint feature", + createLLVMNonTrappingFPToIntLoweringPass); registerPass("once-reduction", "reduces calls to code that only runs once", createOnceReductionPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 212a2b0e40a..aadd26d41b2 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -119,6 +119,7 @@ Pass* createOutliningPass(); Pass* createPickLoadSignsPass(); Pass* createModAsyncifyAlwaysOnlyUnwindPass(); Pass* createModAsyncifyNeverUnwindPass(); +Pass* createLLVMNonTrappingFPToIntLoweringPass(); Pass* createPoppifyPass(); Pass* createPostEmscriptenPass(); Pass* createPrecomputePass(); diff --git a/test/lit/exec/nontrapping-fptoint-lowering.wat b/test/lit/exec/nontrapping-fptoint-lowering.wat new file mode 100644 index 00000000000..72a86bf9cdc --- /dev/null +++ b/test/lit/exec/nontrapping-fptoint-lowering.wat @@ -0,0 +1,261 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt --enable-nontrapping-float-to-int %s --llvm-nontrapping-fptoint-lowering --fuzz-exec -q | filecheck %s + +(module + + (func $assert_i32 (param i32 i32) + (if (i32.ne (local.get 1) (local.get 0)) + (then (unreachable))) + ) + (func $assert_i64 (param i64 i64) + (if (i64.ne (local.get 1) (local.get 0)) + (then (unreachable))) + ) + + (func $i32.trunc_sat_f32_s (param $x f32) (result i32) (i32.trunc_sat_f32_s (local.get $x))) + (func $i32.trunc_sat_f32_u (param $x f32) (result i32) (i32.trunc_sat_f32_u (local.get $x))) + (func $i32.trunc_sat_f64_s (param $x f64) (result i32) (i32.trunc_sat_f64_s (local.get $x))) + (func $i32.trunc_sat_f64_u (param $x f64) (result i32) (i32.trunc_sat_f64_u (local.get $x))) + (func $i64.trunc_sat_f32_s (param $x f32) (result i64) (i64.trunc_sat_f32_s (local.get $x))) + (func $i64.trunc_sat_f32_u (param $x f32) (result i64) (i64.trunc_sat_f32_u (local.get $x))) + (func $i64.trunc_sat_f64_s (param $x f64) (result i64) (i64.trunc_sat_f64_s (local.get $x))) + (func $i64.trunc_sat_f64_u (param $x f64) (result i64) (i64.trunc_sat_f64_u (local.get $x))) + + ;; CHECK: [fuzz-exec] calling f32_i32 + (func $f32_i32 (export "f32_i32") + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const 0.0)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -0.0)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const 0x1p-149)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -0x1p-149)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const 1.0)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const 0x1.19999ap+0)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const 1.5)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -1.0)) (i32.const -1)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -0x1.19999ap+0)) (i32.const -1)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -1.5)) (i32.const -1)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -1.9)) (i32.const -1)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -2.0)) (i32.const -2)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const 2147483520.0)) (i32.const 2147483520)) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -2147483648.0)) (i32.const -2147483648)) + ;; For out-of-range inputs, we ensure there is no trap, but do not check the value + (drop (call $i32.trunc_sat_f32_s (f32.const 2147483648.0))) + (call $assert_i32 (call $i32.trunc_sat_f32_s (f32.const -2147483904.0)) (i32.const 0x80000000)) + (drop (call $i32.trunc_sat_f32_s (f32.const inf))) + (drop (call $i32.trunc_sat_f32_s (f32.const -inf))) + (drop (call $i32.trunc_sat_f32_s (f32.const nan))) + (drop (call $i32.trunc_sat_f32_s (f32.const nan:0x200000))) + (drop (call $i32.trunc_sat_f32_s (f32.const -nan))) + (drop (call $i32.trunc_sat_f32_s (f32.const -nan:0x200000))) + ) + + ;; CHECK: [fuzz-exec] calling f32_i32_u + (func $f32_i32_u (export "f32_i32_u") + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 0.0)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const -0.0)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 0x1p-149)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const -0x1p-149)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 1.0)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 0x1.19999ap+0)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 1.5)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 1.9)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 2.0)) (i32.const 2)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 2147483648)) (i32.const -2147483648)) ;; 0x1.00000p+31 -> 8000 0000 + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const 4294967040.0)) (i32.const -256)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const -0x1.ccccccp-1)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f32_u (f32.const -0x1.fffffep-1)) (i32.const 0)) + (drop (call $i32.trunc_sat_f32_u (f32.const 4294967296.0))) + (drop (call $i32.trunc_sat_f32_u (f32.const -1.0))) + (drop (call $i32.trunc_sat_f32_u (f32.const inf))) + (drop (call $i32.trunc_sat_f32_u (f32.const -inf))) + (drop (call $i32.trunc_sat_f32_u (f32.const nan))) + (drop (call $i32.trunc_sat_f32_u (f32.const nan:0x200000))) + (drop (call $i32.trunc_sat_f32_u (f32.const -nan))) + (drop (call $i32.trunc_sat_f32_u (f32.const -nan:0x200000))) + ) + + ;; CHECK: [fuzz-exec] calling f64_i32 + (func $f64_i32 (export "f64_i32") + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const 0.0)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const -0.0)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const 0x0.0000000000001p-1022)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const -0x0.0000000000001p-1022)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const 1.0)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const 0x1.199999999999ap+0)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const 1.5)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const -1.0)) (i32.const -1)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const -0x1.199999999999ap+0)) (i32.const -1)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const -1.5)) (i32.const -1)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const -1.9)) (i32.const -1)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const -2.0)) (i32.const -2)) + (call $assert_i32 (call $i32.trunc_sat_f64_s (f64.const -2147483648.0)) (i32.const -2147483648)) + (drop (call $i32.trunc_sat_f64_s (f64.const 2147483647.0))) + (drop (call $i32.trunc_sat_f64_s (f64.const 2147483648.0))) + (drop (call $i32.trunc_sat_f64_s (f64.const -2147483649.0))) + (drop (call $i32.trunc_sat_f64_s (f64.const inf))) + (drop (call $i32.trunc_sat_f64_s (f64.const -inf))) + (drop (call $i32.trunc_sat_f64_s (f64.const nan))) + (drop (call $i32.trunc_sat_f64_s (f64.const nan:0x4000000000000))) + (drop (call $i32.trunc_sat_f64_s (f64.const -nan))) + (drop (call $i32.trunc_sat_f64_s (f64.const -nan:0x4000000000000))) + ) + + ;; CHECK: [fuzz-exec] calling f64_i32_s + (func $f64_i32_s (export "f64_i32_s") + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 0.0)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const -0.0)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 0x0.0000000000001p-1022)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const -0x0.0000000000001p-1022)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 1.0)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 0x1.199999999999ap+0)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 1.5)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 1.9)) (i32.const 1)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 2.0)) (i32.const 2)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 2147483648)) (i32.const -2147483648)) ;; 0x1.00000p+31 -> 8000 0000 + + (drop (call $i32.trunc_sat_f64_u (f64.const 4294967295.0))) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const -0x1.ccccccccccccdp-1)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const -0x1.fffffffffffffp-1)) (i32.const 0)) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const 1e8)) (i32.const 100000000)) + (drop (call $i32.trunc_sat_f64_u (f64.const 4294967296.0))) + (call $assert_i32 (call $i32.trunc_sat_f64_u (f64.const -1.0)) (i32.const 0x00000000)) + (drop (call $i32.trunc_sat_f64_u (f64.const 1e16))) + (drop (call $i32.trunc_sat_f64_u (f64.const 1e30))) + (drop (call $i32.trunc_sat_f64_u (f64.const 9223372036854775808))) + (drop (call $i32.trunc_sat_f64_u (f64.const inf))) + (drop (call $i32.trunc_sat_f64_u (f64.const -inf))) + (drop (call $i32.trunc_sat_f64_u (f64.const nan))) + (drop (call $i32.trunc_sat_f64_u (f64.const nan:0x4000000000000))) + (drop (call $i32.trunc_sat_f64_u (f64.const -nan))) + (drop (call $i32.trunc_sat_f64_u (f64.const -nan:0x4000000000000))) + ) + + ;; CHECK: [fuzz-exec] calling f32_i64 + (func $f32_i64 (export "f32_i64") + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const 0.0)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -0.0)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const 0x1p-149)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -0x1p-149)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const 1.0)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const 0x1.19999ap+0)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const 1.5)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -1.0)) (i64.const -1)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -0x1.19999ap+0)) (i64.const -1)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -1.5)) (i64.const -1)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -1.9)) (i64.const -1)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -2.0)) (i64.const -2)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const 4294967296)) (i64.const 4294967296)) ;; 0x1.00000p+32 -> 1 0000 0000 + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -4294967296)) (i64.const -4294967296)) ;; -0x1.00000p+32 -> ffff ffff 0000 0000 + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const 9223371487098961920.0)) (i64.const 9223371487098961920)) + (call $assert_i64 (call $i64.trunc_sat_f32_s (f32.const -9223372036854775808.0)) (i64.const -9223372036854775808)) + (drop (call $i64.trunc_sat_f32_s (f32.const 9223372036854775808.0))) + (drop (call $i64.trunc_sat_f32_s (f32.const -9223373136366403584.0))) + (drop (call $i64.trunc_sat_f32_s (f32.const inf))) + (drop (call $i64.trunc_sat_f32_s (f32.const -inf))) + (drop (call $i64.trunc_sat_f32_s (f32.const nan))) + (drop (call $i64.trunc_sat_f32_s (f32.const nan:0x200000))) + (drop (call $i64.trunc_sat_f32_s (f32.const -nan))) + (drop (call $i64.trunc_sat_f32_s (f32.const -nan:0x200000))) + ) + + ;; CHECK: [fuzz-exec] calling f32_i64_u + (func $f32_i64_u (export "f32_i64_u") + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const 0.0)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const -0.0)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const 0x1p-149)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const -0x1p-149)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const 1.0)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const 0x1.19999ap+0)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const 1.5)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const 4294967296)) (i64.const 4294967296)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const 18446742974197923840.0)) (i64.const -1099511627776)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const -0x1.ccccccp-1)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const -0x1.fffffep-1)) (i64.const 0)) + (drop (call $i64.trunc_sat_f32_u (f32.const 18446744073709551616.0))) + (call $assert_i64 (call $i64.trunc_sat_f32_u (f32.const -1.0)) (i64.const 0x0000000000000000)) + (drop (call $i64.trunc_sat_f32_u (f32.const inf))) + (drop (call $i64.trunc_sat_f32_u (f32.const -inf))) + (drop (call $i64.trunc_sat_f32_u (f32.const nan))) + (drop (call $i64.trunc_sat_f32_u (f32.const nan:0x200000))) + (drop (call $i64.trunc_sat_f32_u (f32.const -nan))) + (drop (call $i64.trunc_sat_f32_u (f32.const -nan:0x200000))) + ) + + ;; CHECK: [fuzz-exec] calling f64_i64 + (func $f64_i64 (export "f64_i64") + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const 0.0)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -0.0)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const 0x0.0000000000001p-1022)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -0x0.0000000000001p-1022)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const 1.0)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const 0x1.199999999999ap+0)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const 1.5)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -1.0)) (i64.const -1)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -0x1.199999999999ap+0)) (i64.const -1)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -1.5)) (i64.const -1)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -1.9)) (i64.const -1)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -2.0)) (i64.const -2)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const 4294967296)) (i64.const 4294967296)) ;; 0x1.00000p+32 -> 1 0000 0000 + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -4294967296)) (i64.const -4294967296)) ;; -0x1.00000p+32 -> ffff ffff 0000 0000 + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const 9223372036854774784.0)) (i64.const 9223372036854774784)) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -9223372036854775808.0)) (i64.const -9223372036854775808)) + (drop (call $i64.trunc_sat_f64_s (f64.const 9223372036854775808.0))) + (call $assert_i64 (call $i64.trunc_sat_f64_s (f64.const -9223372036854777856.0)) (i64.const 0x8000000000000000)) + (drop (call $i64.trunc_sat_f64_s (f64.const inf))) + (drop (call $i64.trunc_sat_f64_s (f64.const -inf))) + (drop (call $i64.trunc_sat_f64_s (f64.const nan))) + (drop (call $i64.trunc_sat_f64_s (f64.const nan:0x4000000000000))) + (drop (call $i64.trunc_sat_f64_s (f64.const -nan))) + (drop (call $i64.trunc_sat_f64_s (f64.const -nan:0x4000000000000))) + ) + + ;; CHECK: [fuzz-exec] calling f64_i64_u + (func $f64_i64_u (export "f64_i64_u") + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 0.0)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const -0.0)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 0x0.0000000000001p-1022)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const -0x0.0000000000001p-1022)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 1.0)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 0x1.199999999999ap+0)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 1.5)) (i64.const 1)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 4294967295)) (i64.const 0xffffffff)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 4294967296)) (i64.const 0x100000000)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 18446744073709549568.0)) (i64.const -2048)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const -0x1.ccccccccccccdp-1)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const -0x1.fffffffffffffp-1)) (i64.const 0)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 1e8)) (i64.const 100000000)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 1e16)) (i64.const 10000000000000000)) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const 9223372036854775808)) (i64.const -9223372036854775808)) + (drop (call $i64.trunc_sat_f64_u (f64.const 18446744073709551616.0))) + (call $assert_i64 (call $i64.trunc_sat_f64_u (f64.const -1.0)) (i64.const 0x0000000000000000)) + (drop (call $i64.trunc_sat_f64_u (f64.const inf))) + (drop (call $i64.trunc_sat_f64_u (f64.const -inf))) + (drop (call $i64.trunc_sat_f64_u (f64.const nan))) + (drop (call $i64.trunc_sat_f64_u (f64.const nan:0x4000000000000))) + (drop (call $i64.trunc_sat_f64_u (f64.const -nan))) + (drop (call $i64.trunc_sat_f64_u (f64.const -nan:0x4000000000000))) + ) +) +;; CHECK: [fuzz-exec] calling f32_i32 + +;; CHECK: [fuzz-exec] calling f32_i32_u + +;; CHECK: [fuzz-exec] calling f64_i32 + +;; CHECK: [fuzz-exec] calling f64_i32_s + +;; CHECK: [fuzz-exec] calling f32_i64 + +;; CHECK: [fuzz-exec] calling f32_i64_u + +;; CHECK: [fuzz-exec] calling f64_i64 + +;; CHECK: [fuzz-exec] calling f64_i64_u +;; CHECK-NEXT: [fuzz-exec] comparing f32_i32 +;; CHECK-NEXT: [fuzz-exec] comparing f32_i32_u +;; CHECK-NEXT: [fuzz-exec] comparing f32_i64 +;; CHECK-NEXT: [fuzz-exec] comparing f32_i64_u +;; CHECK-NEXT: [fuzz-exec] comparing f64_i32 +;; CHECK-NEXT: [fuzz-exec] comparing f64_i32_s +;; CHECK-NEXT: [fuzz-exec] comparing f64_i64 +;; CHECK-NEXT: [fuzz-exec] comparing f64_i64_u diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 50f71f8f52d..4dc03883a60 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -234,6 +234,11 @@ ;; CHECK-NEXT: memory.fill to wasm mvp and ;; CHECK-NEXT: disable the bulk-memory feature. ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int +;; CHECK-NEXT: operations to wasm mvp and +;; CHECK-NEXT: disable the nontrapping fptoint +;; CHECK-NEXT: feature +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 1ac823fa7fd..62a30a770d3 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -243,6 +243,11 @@ ;; CHECK-NEXT: memory.fill to wasm mvp and ;; CHECK-NEXT: disable the bulk-memory feature. ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int +;; CHECK-NEXT: operations to wasm mvp and +;; CHECK-NEXT: disable the nontrapping fptoint +;; CHECK-NEXT: feature +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 69923a0649d..e2a450bc85f 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -197,6 +197,11 @@ ;; CHECK-NEXT: memory.fill to wasm mvp and ;; CHECK-NEXT: disable the bulk-memory feature. ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int +;; CHECK-NEXT: operations to wasm mvp and +;; CHECK-NEXT: disable the nontrapping fptoint +;; CHECK-NEXT: feature +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: diff --git a/test/lit/passes/nontrapping-fptoint-lowering.wast b/test/lit/passes/nontrapping-fptoint-lowering.wast new file mode 100644 index 00000000000..e4598dcf9ff --- /dev/null +++ b/test/lit/passes/nontrapping-fptoint-lowering.wast @@ -0,0 +1,208 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt --enable-nontrapping-float-to-int %s --llvm-nontrapping-fptoint-lowering -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (func $truncsat + ;; CHECK-NEXT: (local $0 f32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (local $2 f32) + ;; CHECK-NEXT: (local $3 f32) + ;; CHECK-NEXT: (local $4 f64) + ;; CHECK-NEXT: (local $5 f64) + ;; CHECK-NEXT: (local $6 f32) + ;; CHECK-NEXT: (local $7 f32) + ;; CHECK-NEXT: (local $8 f64) + ;; CHECK-NEXT: (local $9 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (f32.lt + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 2147483648) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.trunc_f32_s + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const -2147483648) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i64) + ;; CHECK-NEXT: (f32.lt + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (local.tee $3 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 9223372036854775808) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i64.trunc_f32_s + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i64.const -9223372036854775808) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (f64.lt + ;; CHECK-NEXT: (f64.abs + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 2147483647) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.trunc_f64_s + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const -2147483648) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i64) + ;; CHECK-NEXT: (f64.lt + ;; CHECK-NEXT: (f64.abs + ;; CHECK-NEXT: (local.tee $5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 9223372036854775808) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i64.trunc_f64_s + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i64.const -9223372036854775808) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (f32.lt + ;; CHECK-NEXT: (local.tee $6 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 4294967296) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.ge + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.trunc_f32_u + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i64) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (f32.lt + ;; CHECK-NEXT: (local.tee $7 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 18446744073709551615) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.ge + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i64.trunc_f32_u + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (f64.lt + ;; CHECK-NEXT: (local.tee $8 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.ge + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.trunc_f64_u + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i64) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (f64.lt + ;; CHECK-NEXT: (local.tee $9 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 18446744073709551615) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.ge + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i64.trunc_f64_u + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $truncsat (type $0) + (local $0 f32) + (local $1 f64) + (drop (i32.trunc_sat_f32_s (local.get $0))) + (drop (i64.trunc_sat_f32_s (local.get $0))) + (drop (i32.trunc_sat_f64_s (local.get $1))) + (drop (i64.trunc_sat_f64_s (local.get $1))) + (drop (i32.trunc_sat_f32_u (local.get $0))) + (drop (i64.trunc_sat_f32_u (local.get $0))) + (drop (i32.trunc_sat_f64_u (local.get $1))) + (drop (i64.trunc_sat_f64_u (local.get $1))) + ) +) From e13bf0fb72fca160f457570b930c4ba3c35ead3a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 Nov 2024 15:26:09 -0800 Subject: [PATCH 141/622] Improve fuzzing of both closed and open world styles of modules (#7090) Before, we would simply not export a function that had an e.g. anyref param. As a result, the modules were effectively "closed", which was good for testing full closed-world mode, but not for testing degrees of open world. To improve that, this PR allows the fuzzer to export such functions, and an "enclose world" pass is added that "closes" the wasm (makes it more compatible with closed-world) that is run 50% of the time, giving us coverage of both styles. --- scripts/fuzz_opt.py | 6 + src/passes/CMakeLists.txt | 1 + src/passes/EncloseWorld.cpp | 155 ++++++++++++ src/passes/pass.cpp | 3 + src/passes/passes.h | 1 + src/tools/fuzzing/fuzzing.cpp | 37 ++- src/tools/wasm-reduce.cpp | 1 + test/lit/help/wasm-metadce.test | 3 + test/lit/help/wasm-opt.test | 3 + test/lit/help/wasm2js.test | 3 + test/lit/passes/enclose-world.wast | 237 ++++++++++++++++++ .../fuzz_metrics_passes_noprint.bin.txt | 58 ++--- ...e-to-fuzz_all-features_metrics_noprint.txt | 72 +++--- 13 files changed, 497 insertions(+), 83 deletions(-) create mode 100644 src/passes/EncloseWorld.cpp create mode 100644 test/lit/passes/enclose-world.wast diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index cd583e026ee..4fc92f367b3 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -225,6 +225,11 @@ def randomize_fuzz_settings(): # optimizations we use to create any other wasm file. FUZZ_OPTS += ['--dce'] + # Enclose the world much of the time when fuzzing closed-world, so that many + # types are private and hence optimizable. + if CLOSED_WORLD and random.random() < 0.5: + GEN_ARGS += ['--enclose-world'] + print('randomized settings (NaNs, OOB, legalize):', NANS, OOB, LEGALIZE) @@ -1790,6 +1795,7 @@ def write_commands(commands, filename): ("--dce",), ("--directize",), ("--discard-global-effects",), + ("--enclose-world",), ("--flatten", "--dfo",), ("--duplicate-function-elimination",), ("--flatten",), diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index e461634066e..6b78e487dea 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -35,6 +35,7 @@ set(passes_SOURCES DuplicateImportElimination.cpp DuplicateFunctionElimination.cpp DWARF.cpp + EncloseWorld.cpp ExtractFunction.cpp Flatten.cpp FuncCastEmulation.cpp diff --git a/src/passes/EncloseWorld.cpp b/src/passes/EncloseWorld.cpp new file mode 100644 index 00000000000..5c6b70546a8 --- /dev/null +++ b/src/passes/EncloseWorld.cpp @@ -0,0 +1,155 @@ +/* + * Copyright 2024 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. + */ + +// +// "Closes" the world, in the sense of making it more compatible with the +// --closed-world flag, in a potentially destructive manner. This is mainly +// useful for fuzzing (in that a random module is usually very incomptable with +// closed world, with most types being public and hence unoptimizable, but +// running this pass makes as many as we can fully private). +// +// The fixup we do is to find references sent out/received in, and to +// externalize / internalize them. For example, this export: +// +// (func $refs (export "refs") (param $x (ref $X)) (result (ref $Y)) +// +// would have the following function exported in its place: +// +// (func $refs-closed (export "refs") (param $x externref) (result externref) +// (extern.convert_any +// (call $refs +// (ref.cast (ref $X) +// (any.convert_extern +// (local.get $x)))))) +// + +#include "ir/names.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" + +namespace wasm { + +namespace { + +struct EncloseWorld : public Pass { + void run(Module* module) override { + // Handle exports. + // TODO: Non-function exports. + std::vector> newExports; + for (auto& ex : module->exports) { + if (ex->kind == ExternalKind::Function) { + auto* func = module->getFunction(ex->value); + // If this opens up types, replace it with an enclosed stub. + if (opensTypes(func)) { + auto stubName = makeStubStubForExport(func, module); + ex->value = stubName; + } + } + } + for (auto& ex : newExports) { + module->addExport(std::move(ex)); + } + + // TODO: Handle imports. + } + +private: + // Whether a type is an "open" ref, that is, a type that closed-world would + // consider to keep things public and prevent some amount of closed-world + // optimizations. + bool isOpenRef(Type t) { + // Only externref keeps things closed, and we must ignore things that + // cannot be converted to/from it (like funcrefs), so we can just check for + // the top type being any. + return t.isRef() && t.getHeapType().getTop() == HeapType::any; + } + + // Whether a function causes types to be open. + bool opensTypes(Function* func) { + for (const auto& param : func->getParams()) { + if (isOpenRef(param)) { + return true; + } + } + // TODO: Handle tuple results. + return isOpenRef(func->getResults()); + } + + // Make an enclosed stub function for an exported function, and return its + // name. + Name makeStubStubForExport(Function* func, Module* module) { + // Pick a valid name for the stub we are about to create. + auto stubName = Names::getValidFunctionName( + *module, std::string("stub$") + func->name.toString()); + + // Create the stub. + Builder builder(*module); + + // The stub's body is just a call to the original function, but with some + // conversions to/from externref. + std::vector params; + + auto externref = Type(HeapType::ext, Nullable); + + // Handle params. + std::vector stubParams; + for (const auto& param : func->getParams()) { + if (!isOpenRef(param)) { + // A normal parameter. Just pass it to the original function. + auto* get = builder.makeLocalGet(stubParams.size(), param); + params.push_back(get); + stubParams.push_back(param); + } else { + // A type we must fix up: receive as an externref and then internalize + // and cast before sending to the original function. + auto* get = builder.makeLocalGet(stubParams.size(), externref); + auto* interned = builder.makeRefAs(AnyConvertExtern, get); + // This cast may be trivial, but we leave it to the optimizer to remove. + auto* cast = builder.makeRefCast(interned, param); + params.push_back(cast); + stubParams.push_back(externref); + } + } + + auto* call = builder.makeCall(func->name, params, func->getResults()); + + // Generate the stub's type. + auto oldResults = func->getResults(); + Type resultsType = isOpenRef(oldResults) ? externref : oldResults; + auto type = Signature(Type(stubParams), resultsType); + + // Handle the results and make the body. + Expression* body; + if (!isOpenRef(oldResults)) { + // Just use the call. + body = call; + } else { + // Fix up the call's result. + body = builder.makeRefAs(ExternConvertAny, call); + } + + module->addFunction(builder.makeFunction(stubName, type, {}, body)); + + return stubName; + } +}; + +} // anonymous namespace + +Pass* createEncloseWorldPass() { return new EncloseWorld(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 4be24cebffc..7f6985e0fd1 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -159,6 +159,9 @@ void PassRegistry::registerPasses() { registerPass("emit-target-features", "emit the target features section in the output", createEmitTargetFeaturesPass); + registerPass("enclose-world", + "modify the wasm (destructively) for closed-world", + createEncloseWorldPass); registerPass("extract-function", "leaves just one function (useful for debugging)", createExtractFunctionPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index aadd26d41b2..b313b343165 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -46,6 +46,7 @@ Pass* createDWARFDumpPass(); Pass* createDuplicateImportEliminationPass(); Pass* createDuplicateFunctionEliminationPass(); Pass* createEmitTargetFeaturesPass(); +Pass* createEncloseWorldPass(); Pass* createExtractFunctionPass(); Pass* createExtractFunctionIndexPass(); Pass* createFlattenPass(); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index ed653ef6b96..135e5039384 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -62,6 +62,17 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { // things like ClusterFuzz, where we are using Binaryen to fuzz other things // than itself). As a result, the list of passes here is different from // fuzz_opt.py. + + // Enclose the world, some of the time. We do this before picking any other + // passes so that we make the initial fuzz contents more optimizable by + // closed-world passes later. Note that we do this regardless of whether we + // are in closed-world mode or not, as it is good to get this variety + // regardless. + if (oneIn(2)) { + options.passes.push_back("enclose-world"); + } + + // Main selection of passes. while (options.passes.size() < 20 && !random.finished() && !oneIn(3)) { switch (upTo(42)) { case 0: @@ -1075,30 +1086,14 @@ Function* TranslateToFuzzReader::addFunction() { // Add hang limit checks after all other operations on the function body. wasm.addFunction(std::move(allocation)); // Export some functions, but not all (to allow inlining etc.). Try to export - // at least one, though, to keep each testcase interesting. Only functions - // with valid params and returns can be exported because the trap fuzzer - // depends on that (TODO: fix this). - auto validExportType = [](Type t) { - if (!t.isRef()) { - return true; - } - auto heapType = t.getHeapType(); - return heapType == HeapType::ext || heapType == HeapType::func || - heapType == HeapType::string; - }; + // at least one, though, to keep each testcase interesting. Avoid non- + // nullable params, as those cannot be constructed by the fuzzer on the + // outside. bool validExportParams = std::all_of(paramType.begin(), paramType.end(), [&](Type t) { - return validExportType(t) && t.isDefaultable(); + return t.isDefaultable(); }); - // Note: spec discussions around JS API integration are still ongoing, and it - // is not clear if we should allow nondefaultable types in exports or not - // (in imports, we cannot allow them in the fuzzer anyhow, since it can't - // construct such values in JS to send over to the wasm from the fuzzer - // harness). - bool validExportResults = - std::all_of(resultType.begin(), resultType.end(), validExportType); - if (validExportParams && validExportResults && - (numAddedFunctions == 0 || oneIn(2)) && + if (validExportParams && (numAddedFunctions == 0 || oneIn(2)) && !wasm.getExportOrNull(func->name)) { auto* export_ = new Export; export_->name = func->name; diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 8d9858b7829..026825118f3 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -275,6 +275,7 @@ struct Reducer "--dae-optimizing", "--dce", "--duplicate-function-elimination", + "--enclose-world", "--gto", "--inlining", "--inlining-optimizing", diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 4dc03883a60..e44e51a167b 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -143,6 +143,9 @@ ;; CHECK-NEXT: --emit-target-features emit the target features section ;; CHECK-NEXT: in the output ;; CHECK-NEXT: +;; CHECK-NEXT: --enclose-world modify the wasm (destructively) +;; CHECK-NEXT: for closed-world +;; CHECK-NEXT: ;; CHECK-NEXT: --extract-function leaves just one function (useful ;; CHECK-NEXT: for debugging) ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 62a30a770d3..5c978ee81e7 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -152,6 +152,9 @@ ;; CHECK-NEXT: --emit-target-features emit the target features section ;; CHECK-NEXT: in the output ;; CHECK-NEXT: +;; CHECK-NEXT: --enclose-world modify the wasm (destructively) +;; CHECK-NEXT: for closed-world +;; CHECK-NEXT: ;; CHECK-NEXT: --extract-function leaves just one function (useful ;; CHECK-NEXT: for debugging) ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index e2a450bc85f..c1dd0401d9e 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -106,6 +106,9 @@ ;; CHECK-NEXT: --emit-target-features emit the target features section ;; CHECK-NEXT: in the output ;; CHECK-NEXT: +;; CHECK-NEXT: --enclose-world modify the wasm (destructively) +;; CHECK-NEXT: for closed-world +;; CHECK-NEXT: ;; CHECK-NEXT: --extract-function leaves just one function (useful ;; CHECK-NEXT: for debugging) ;; CHECK-NEXT: diff --git a/test/lit/passes/enclose-world.wast b/test/lit/passes/enclose-world.wast new file mode 100644 index 00000000000..0077c27bef3 --- /dev/null +++ b/test/lit/passes/enclose-world.wast @@ -0,0 +1,237 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --enclose-world -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $A (struct)) + (type $A (struct)) + ;; CHECK: (type $B (struct (field i32))) + (type $B (struct (field i32))) + ;; CHECK: (type $2 (func (param externref))) + + ;; CHECK: (type $3 (func (result externref))) + + ;; CHECK: (type $4 (func (param anyref) (result anyref))) + + ;; CHECK: (type $5 (func (param externref) (result externref))) + + ;; CHECK: (type $C (struct (field i32) (field f64))) + (type $C (struct (field i32 f64))) + + ;; CHECK: (type $7 (func (param i32) (result f64))) + + ;; CHECK: (type $8 (func (param anyref))) + + ;; CHECK: (type $9 (func (result anyref))) + + ;; CHECK: (type $10 (func (param (ref $A) (ref null $B)) (result (ref $C)))) + + ;; CHECK: (type $11 (func (param i32 (ref $A) funcref))) + + ;; CHECK: (type $12 (func (param externref externref) (result externref))) + + ;; CHECK: (type $13 (func (param i32 externref funcref))) + + ;; CHECK: (export "normal" (func $normal)) + + ;; CHECK: (export "externref-param" (func $externref-param)) + + ;; CHECK: (export "externref-result" (func $externref-result)) + + ;; CHECK: (export "anyref-param" (func $stub$anyref-param)) + + ;; CHECK: (export "anyref-result" (func $stub$anyref-result)) + + ;; CHECK: (export "anyref-both" (func $stub$anyref-both)) + + ;; CHECK: (export "anyref-both-dupe" (func $stub$anyref-both-dupe)) + + ;; CHECK: (export "many" (func $stub$many)) + + ;; CHECK: (export "mixed" (func $stub$mixed)) + + ;; CHECK: (func $normal (type $7) (param $x i32) (result f64) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + (func $normal (export "normal") (param $x i32) (result f64) + ;; A normal function which we do not need to do anything with. + (f64.const 3.14159) + ) + + ;; CHECK: (func $externref-param (type $2) (param $x externref) + ;; CHECK-NEXT: ) + (func $externref-param (export "externref-param") (param $x externref) + ;; An externref param is fine, we don't need to do anything. + ) + + ;; CHECK: (func $externref-result (type $3) (result externref) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $externref-result (export "externref-result") (result externref) + ;; An externref result is also fine. + (unreachable) + ) + + ;; CHECK: (func $anyref-param (type $8) (param $x anyref) + ;; CHECK-NEXT: ) + (func $anyref-param (export "anyref-param") (param $x anyref) + ;; An anyref requires a fixup. We will call this from a stub that has an + ;; externref param, which calls this. + ) + + ;; CHECK: (func $anyref-result (type $9) (result anyref) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $anyref-result (export "anyref-result") (result anyref) + ;; An anyref result also requires a fixup. + (unreachable) + ) + + ;; CHECK: (func $anyref-both (type $4) (param $x anyref) (result anyref) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $anyref-both (export "anyref-both") (param $x anyref) (result anyref) + ;; Here we must fix up both the param and the result. + (unreachable) + ) + + ;; CHECK: (func $anyref-both-dupe (type $4) (param $x anyref) (result anyref) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $anyref-both-dupe (export "anyref-both-dupe") (param $x anyref) (result anyref) + ;; Identical to the above function, and should be fixed up in the same + ;; manner. In theory we could use the same stub for both, but we leave that + ;; for the optimizer. + (unreachable) + ) + + ;; CHECK: (func $many (type $10) (param $a (ref $A)) (param $b (ref null $B)) (result (ref $C)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $many (export "many") (param $a (ref $A)) (param $b (ref null $B)) (result (ref $C)) + ;; Various declared types are used, and must be fixed up. + (unreachable) + ) + + ;; CHECK: (func $mixed (type $11) (param $a i32) (param $b (ref $A)) (param $c funcref) + ;; CHECK-NEXT: ) + (func $mixed (export "mixed") (param $a i32) (param $b (ref $A)) (param $c funcref) + ;; One param needs to be fixed, two others do not: we can ignore i32 and + ;; funcref. + ) +) + +;; CHECK: (func $stub$anyref-param (type $2) (param $0 externref) +;; CHECK-NEXT: (call $anyref-param +;; CHECK-NEXT: (ref.cast anyref +;; CHECK-NEXT: (any.convert_extern +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $stub$anyref-result (type $3) (result externref) +;; CHECK-NEXT: (extern.convert_any +;; CHECK-NEXT: (call $anyref-result) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $stub$anyref-both (type $5) (param $0 externref) (result externref) +;; CHECK-NEXT: (extern.convert_any +;; CHECK-NEXT: (call $anyref-both +;; CHECK-NEXT: (ref.cast anyref +;; CHECK-NEXT: (any.convert_extern +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $stub$anyref-both-dupe (type $5) (param $0 externref) (result externref) +;; CHECK-NEXT: (extern.convert_any +;; CHECK-NEXT: (call $anyref-both-dupe +;; CHECK-NEXT: (ref.cast anyref +;; CHECK-NEXT: (any.convert_extern +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $stub$many (type $12) (param $0 externref) (param $1 externref) (result externref) +;; CHECK-NEXT: (extern.convert_any +;; CHECK-NEXT: (call $many +;; CHECK-NEXT: (ref.cast (ref $A) +;; CHECK-NEXT: (any.convert_extern +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (ref.cast (ref null $B) +;; CHECK-NEXT: (any.convert_extern +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $stub$mixed (type $13) (param $0 i32) (param $1 externref) (param $2 funcref) +;; CHECK-NEXT: (call $mixed +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (ref.cast (ref $A) +;; CHECK-NEXT: (any.convert_extern +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +(module + ;; Two exports of a single function that needs fixups. We could reuse a + ;; single stub, but we leave that for the optimizer. + + (export "a" (func $anyref-both)) + + (export "b" (func $anyref-both)) + + ;; CHECK: (type $0 (func (param externref) (result externref))) + + ;; CHECK: (type $1 (func (param anyref) (result anyref))) + + ;; CHECK: (export "a" (func $stub$anyref-both)) + + ;; CHECK: (export "b" (func $stub$anyref-both_2)) + + ;; CHECK: (func $anyref-both (type $1) (param $x anyref) (result anyref) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $anyref-both (param $x anyref) (result anyref) + (unreachable) + ) +) +;; CHECK: (func $stub$anyref-both (type $0) (param $0 externref) (result externref) +;; CHECK-NEXT: (extern.convert_any +;; CHECK-NEXT: (call $anyref-both +;; CHECK-NEXT: (ref.cast anyref +;; CHECK-NEXT: (any.convert_extern +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $stub$anyref-both_2 (type $0) (param $0 externref) (result externref) +;; CHECK-NEXT: (extern.convert_any +;; CHECK-NEXT: (call $anyref-both +;; CHECK-NEXT: (ref.cast anyref +;; CHECK-NEXT: (any.convert_extern +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index b4d67bab08e..5e0dbfe0774 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 23 - [funcs] : 34 - [globals] : 30 + [exports] : 50 + [funcs] : 64 + [globals] : 24 [imports] : 5 [memories] : 1 - [memory-data] : 17 - [table-data] : 6 + [memory-data] : 15 + [table-data] : 16 [tables] : 1 [tags] : 0 - [total] : 9415 - [vars] : 105 - Binary : 726 - Block : 1537 - Break : 331 - Call : 306 - CallIndirect : 10 - Const : 1479 - Drop : 83 - GlobalGet : 778 - GlobalSet : 584 - If : 531 - Load : 164 - LocalGet : 774 - LocalSet : 570 - Loop : 244 - Nop : 105 - RefFunc : 6 - Return : 94 - Select : 70 - Store : 86 - Switch : 2 - Unary : 654 - Unreachable : 281 + [total] : 6973 + [vars] : 223 + Binary : 534 + Block : 1168 + Break : 228 + Call : 282 + CallIndirect : 38 + Const : 1083 + Drop : 115 + GlobalGet : 580 + GlobalSet : 444 + If : 354 + Load : 113 + LocalGet : 501 + LocalSet : 367 + Loop : 148 + Nop : 99 + RefFunc : 16 + Return : 91 + Select : 53 + Store : 70 + Switch : 1 + Unary : 466 + Unreachable : 222 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index a77708b2e06..8df9d033cc0 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,48 +1,54 @@ Metrics total - [exports] : 6 - [funcs] : 12 + [exports] : 9 + [funcs] : 10 [globals] : 4 [imports] : 8 [memories] : 1 [memory-data] : 112 - [table-data] : 3 + [table-data] : 2 [tables] : 1 - [tags] : 0 - [total] : 608 - [vars] : 48 - ArrayNew : 5 + [tags] : 1 + [total] : 682 + [vars] : 37 + ArrayLen : 1 + ArrayNew : 7 ArrayNewFixed : 5 - Binary : 75 - Block : 80 - BrOn : 5 - Break : 4 - Call : 21 - CallRef : 1 - Const : 113 + ArraySet : 1 + AtomicNotify : 1 + Binary : 79 + Block : 72 + BrOn : 4 + Break : 7 + Call : 19 + Const : 149 Drop : 15 - GlobalGet : 39 - GlobalSet : 36 - If : 21 - Load : 17 - LocalGet : 45 - LocalSet : 20 + GlobalGet : 35 + GlobalSet : 32 + If : 20 + Load : 20 + LocalGet : 55 + LocalSet : 26 Loop : 7 - Nop : 7 - RefAs : 2 + MemoryFill : 1 + Nop : 9 + Pop : 1 + RefAs : 1 + RefCast : 1 RefEq : 1 - RefFunc : 9 - RefI31 : 1 - RefIsNull : 1 + RefFunc : 17 + RefI31 : 2 + RefIsNull : 2 RefNull : 8 Return : 5 - Select : 2 + SIMDExtract : 3 Store : 1 - StringConst : 4 - StringEq : 1 - StringMeasure : 2 - StructNew : 13 - TupleExtract : 1 + StringConst : 3 + StringMeasure : 1 + StringWTF16Get : 1 + StructNew : 23 + Try : 1 + TupleExtract : 3 TupleMake : 4 - Unary : 19 - Unreachable : 18 + Unary : 23 + Unreachable : 16 From 81dbc52c446680469a5e00e4e26b091bfc266a59 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 20 Nov 2024 08:23:14 -0800 Subject: [PATCH 142/622] Fuzzer: Legalize and prune the JS interface in pickPasses (#7092) Also add a test that the ClusterFuzz run.py does not warn, which was helpful when debugging this. --- src/tools/fuzzing/fuzzing.cpp | 7 +++ .../fuzz_metrics_passes_noprint.bin.txt | 60 +++++++++---------- test/unit/test_cluster_fuzz.py | 12 ++++ 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 135e5039384..a283aae918f 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -270,6 +270,13 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { options.passOptions.closedWorld = true; } + // Prune things that error in JS if we call them (like SIMD), some of the + // time. This alters the wasm/JS boundary quite a lot, so testing both forms + // is useful. + if (oneIn(2)) { + options.passes.push_back("legalize-and-prune-js-interface"); + } + // Usually DCE at the very end, to ensure that our binaries validate in other // VMs, due to how non-nullable local validation and unreachable code // interact. See fuzz_opt.py and diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index 5e0dbfe0774..9c8c25c1279 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 50 - [funcs] : 64 - [globals] : 24 - [imports] : 5 + [exports] : 54 + [funcs] : 84 + [globals] : 17 + [imports] : 4 [memories] : 1 - [memory-data] : 15 - [table-data] : 16 + [memory-data] : 11 + [table-data] : 22 [tables] : 1 [tags] : 0 - [total] : 6973 - [vars] : 223 - Binary : 534 - Block : 1168 - Break : 228 - Call : 282 - CallIndirect : 38 - Const : 1083 - Drop : 115 - GlobalGet : 580 - GlobalSet : 444 - If : 354 - Load : 113 - LocalGet : 501 - LocalSet : 367 - Loop : 148 - Nop : 99 - RefFunc : 16 - Return : 91 - Select : 53 - Store : 70 - Switch : 1 - Unary : 466 - Unreachable : 222 + [total] : 8343 + [vars] : 264 + Binary : 597 + Block : 1335 + Break : 226 + Call : 346 + CallIndirect : 65 + Const : 1375 + Drop : 107 + GlobalGet : 719 + GlobalSet : 522 + If : 458 + Load : 139 + LocalGet : 650 + LocalSet : 441 + Loop : 165 + Nop : 97 + RefFunc : 22 + Return : 120 + Select : 71 + Store : 56 + Switch : 2 + Unary : 574 + Unreachable : 256 diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 293cfa339f7..1d275c712c2 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -97,6 +97,18 @@ def test_run_py(self): self.assertTrue(not os.path.exists(fuzz_file)) self.assertTrue(not os.path.exists(flags_file)) + # Run.py should report no errors or warnings to stderr, except from + # those we know are safe. + SAFE_WARNINGS = [ + # When we randomly pick no passes to run, this is shown. + 'warning: no passes specified, not doing any work', + ] + stderr = proc.stderr + for safe in SAFE_WARNINGS: + stderr = stderr.replace(safe, '') + stderr = stderr.strip() + self.assertEqual(stderr, '') + def test_fuzz_passes(self): # We should see interesting passes being run in run.py. This is *NOT* a # deterministic test, since the number of passes run is random (we just From 53f1c5f5f049498f3006247bca07b81868a7d543 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 20 Nov 2024 09:28:28 -0800 Subject: [PATCH 143/622] Fuzzer: Use V8's --future flag (#7091) --- scripts/fuzz_opt.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 4fc92f367b3..f272ddf83d9 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -729,15 +729,25 @@ def run_bynterp(wasm, args): del os.environ['BINARYEN_MAX_INTERPRETER_DEPTH'] +# Enable even more staged things than V8_OPTS. V8_OPTS are the flags we want to +# use when testing, and enable all features we test against, while --future may +# also enable non-feature things like new JITs and such (which are never needed +# for our normal tests, but do make sense to fuzz for V8's sake). We do this +# randomly for more variety. +def get_v8_extra_flags(): + return ['--future'] if random.random() < 0.5 else [] + + V8_LIFTOFF_ARGS = ['--liftoff', '--no-wasm-tier-up'] -# default to running with liftoff enabled, because we need to pick either +# Default to running with liftoff enabled, because we need to pick either # liftoff or turbo* for consistency (otherwise running the same command twice # may have different results due to NaN nondeterminism), and liftoff is faster -# for small things +# for small things. def run_d8_js(js, args=[], liftoff=True): cmd = [shared.V8] + shared.V8_OPTS + cmd += get_v8_extra_flags() if liftoff: cmd += V8_LIFTOFF_ARGS cmd += [js] @@ -838,7 +848,7 @@ class D8: name = 'd8' def run(self, wasm, extra_d8_flags=[]): - return run_vm([shared.V8, FUZZ_SHELL_JS] + shared.V8_OPTS + extra_d8_flags + ['--', wasm]) + return run_vm([shared.V8, FUZZ_SHELL_JS] + shared.V8_OPTS + get_v8_extra_flags() + extra_d8_flags + ['--', wasm]) def can_run(self, wasm): # V8 does not support shared memories when running with From c18159e35aef7a202ceb7c80b2a354101a2830d7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 20 Nov 2024 09:30:08 -0800 Subject: [PATCH 144/622] Fuzzer: Remove --enclose-world from list of fuzz-exec passes (#7093) It is ok to use this pass to shape the wasm, but not to test for changes using fuzz-exec, as the pass is destructive: it can alter observable behavior. Specifically, it adds casts which can trap in the wasm, which can replace a JS exception which happens outside, and that difference is noticeable. --- scripts/fuzz_opt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index f272ddf83d9..d6a3921296b 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1805,7 +1805,6 @@ def write_commands(commands, filename): ("--dce",), ("--directize",), ("--discard-global-effects",), - ("--enclose-world",), ("--flatten", "--dfo",), ("--duplicate-function-elimination",), ("--flatten",), From 45d8f24ad36562939bed14b2157fd5bb51c396bc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 20 Nov 2024 16:40:29 -0800 Subject: [PATCH 145/622] [NFC] Refactor nice methods in fuzz_shell.js (#7096) This mostly moves the code around and avoids some duplication. It also tracks the list of exports with both names and values, so that if we compile more than one module, we can still access exports from the previous. Also add a first test of running fuzz_shell.js in node. This does make build() append the exports, which was done before on the main module but not the second one. That only affects the wasm-split fuzzer, which is not active yet, so this is still NFC. --- scripts/fuzz_shell.js | 125 +++++++++++++++++++--------------- test/lit/node/fuzz_shell.wast | 20 ++++++ 2 files changed, 90 insertions(+), 55 deletions(-) create mode 100644 test/lit/node/fuzz_shell.wast diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index ce817646e27..782040dac84 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -138,17 +138,16 @@ function logValue(x, y) { console.log('[LoggingExternalInterface logging ' + printed(x, y) + ']'); } -// Some imports need to access exports by index. -var exportsList; -function getExportByIndex(index) { - if (!exportsList) { - exportsList = []; - for (var e in exports) { - exportsList.push(e); - } - } - return exports[exportsList[index]]; -} +// Track the exports in a map (similar to the Exports object from wasm, i.e., +// whose keys are strings and whose values are the corresponding exports). +var exports = {}; + +// Also track exports in a list, to allow access by index. Each entry here will +// be in the form of { name: .., value: .. }. That allows us to log the name of +// the function and also to call it. This is important because different +// functions may have the same name, if they were exported by different +// Instances under the same export names. +var exportList = []; // Given a wasm function, call it as best we can from JS, and return the result. function callFunc(func) { @@ -202,11 +201,11 @@ var imports = { // Export operations. 'call-export': (index) => { - callFunc(getExportByIndex(index)); + callFunc(exportList[index].value); }, 'call-export-catch': (index) => { try { - callFunc(getExportByIndex(index)); + callFunc(exportList[index].value); return 0; } catch (e) { // We only want to catch exceptions, not wasm traps: traps should still @@ -278,57 +277,73 @@ if (secondBinary) { }); } -// Create the wasm. -var module = new WebAssembly.Module(binary); - -var instance; -try { - instance = new WebAssembly.Instance(module, imports); -} catch (e) { - console.log('exception thrown: failed to instantiate module'); - quit(); -} - -// Handle the exports. -var exports = instance.exports; +// Compile and instantiate a wasm file. +function build(binary) { + var module = new WebAssembly.Module(binary); -// Link in a second module, if one was provided. -if (secondBinary) { - var secondModule = new WebAssembly.Module(secondBinary); - - // The secondary module just needs to import the primary one: all original - // imports it might have needed were exported from there. - var secondImports = {'primary': exports}; - var secondInstance; + var instance; try { - secondInstance = new WebAssembly.Instance(secondModule, secondImports); + instance = new WebAssembly.Instance(module, imports); } catch (e) { - console.log('exception thrown: failed to instantiate second module'); + console.log('exception thrown: failed to instantiate module'); quit(); } -} -// Run the wasm. -if (!exportsToCall) { - // We were not told specific exports, so call them all. - exportsToCall = []; - for (var e in exports) { - exportsToCall.push(e); + // Update the exports. Note that this adds onto |exports|, |exportList|, + // which is intentional: if we build another wasm, or build this one more + // than once, we want to be able to call them all, so we unify all their + // exports. (We do trample in |exports| when keys are equal - basically this + // is a single global namespace - but |exportList| is appended to, so we do + // keep the ability to call anything that was ever exported.) + for (var key in instance.exports) { + var value = instance.exports[key]; + exports[key] = value; + exportList.push({ name: key, value: value }); } } -for (var e of exportsToCall) { - if (typeof exports[e] !== 'function') { - continue; - } - var func = exports[e]; - try { - console.log('[fuzz-exec] calling ' + e); - var result = callFunc(func); - if (typeof result !== 'undefined') { - console.log('[fuzz-exec] note result: ' + e + ' => ' + printed(result)); +// Run the code by calling exports. +function callExports() { + // Call the exports we were told, or if we were not given an explicit list, + // call them all. + var relevantExports = exportsToCall || exportList; + + for (var e of relevantExports) { + var name, value; + if (typeof e === 'string') { + // We are given a string name to call. Look it up in the global namespace. + name = e; + value = exports[e]; + } else { + // We are given an object form exportList, which bas both a name and a + // value. + name = e.name; + value = e.value; + } + + if (typeof value !== 'function') { + continue; + } + + try { + console.log('[fuzz-exec] calling ' + name); + var result = callFunc(value); + if (typeof result !== 'undefined') { + console.log('[fuzz-exec] note result: ' + name + ' => ' + printed(result)); + } + } catch (e) { + console.log('exception thrown: ' + e); } - } catch (e) { - console.log('exception thrown: ' + e); } } + +// Build the main wasm. +build(binary); + +// Build the second wasm, if one was provided. +if (secondBinary) { + build(secondBinary); +} + +// Run. +callExports(); diff --git a/test/lit/node/fuzz_shell.wast b/test/lit/node/fuzz_shell.wast new file mode 100644 index 00000000000..deb157ec873 --- /dev/null +++ b/test/lit/node/fuzz_shell.wast @@ -0,0 +1,20 @@ +;; Test running a wasm file in fuzz_shell.js. + +(module + (func $test (export "test") (result i32) + (i32.const 42) + ) +) + +;; Build to a binary wasm. +;; +;; RUN: wasm-opt %s -o %t.wasm -q + +;; Run in node. +;; +;; RUN: node %S/../../../scripts/fuzz_shell.js %t.wasm | filecheck %s +;; +;; CHECK: [fuzz-exec] calling test +;; CHECK: [fuzz-exec] note result: test => 42 + + From af5f74aeb3c53081ffaedbde18a77bdede0a697e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 21 Nov 2024 11:11:48 -0800 Subject: [PATCH 146/622] Fuzzing: Append more JS operations in run.py (#7098) The main fuzz_shell.js code builds and runs the given wasm. After the refactoring in #7096, it is simple to append to that file and add more build and run operations, adding more variety to the code, including cross-module interactions. Add logic to run.py to do that for ClusterFuzz. To test this, add a node test that builds a module with internal state that can actually show which module is being executed. The test appends a build+run operation, whose output prove that we are calling from the first module to the second and vice versa. Also add a ClusterFuzz test for run.py that verifies that we add a variety of build/run operations. --- scripts/clusterfuzz/run.py | 44 ++++++++- test/lit/node/fuzz_shell_append.wast | 135 +++++++++++++++++++++++++++ test/unit/test_cluster_fuzz.py | 37 ++++++++ 3 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 test/lit/node/fuzz_shell_append.wast diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index efddfc2d43b..4b5e67fdeff 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -25,10 +25,12 @@ import os import getopt +import math import random import subprocess import sys + # The V8 flags we put in the "fuzzer flags" files, which tell ClusterFuzz how to # run V8. By default we apply all staging flags. FUZZER_FLAGS_FILE_CONTENTS = '--wasm-staging' @@ -39,6 +41,12 @@ # processes per file), which is less of an issue on ClusterFuzz. MAX_RANDOM_SIZE = 15 * 1024 +# Max and median amount of extra JS operations we append, like extra compiles or +# runs of the wasm. We allow a high max, but the median is far lower, so that +# typical testcases are not long-running. +MAX_EXTRA_JS_OPERATIONS = 40 +MEDIAN_EXTRA_JS_OPERATIONS = 2 + # The prefix for fuzz files. FUZZ_FILENAME_PREFIX = 'fuzz-' @@ -80,6 +88,11 @@ def get_file_name(prefix, index): return f'{prefix}{FUZZER_NAME_PREFIX}{index}.js' +# We should only use the system's random number generation, which is the best. +# (We also use urandom below, which uses this under the hood.) +system_random = random.SystemRandom() + + # Returns the contents of a .js fuzz file, given particular wasm contents that # we want to be executed. def get_js_file_contents(wasm_contents): @@ -91,6 +104,35 @@ def get_js_file_contents(wasm_contents): # mechanism where the wasm file's name is provided in argv). wasm_contents = ','.join([str(c) for c in wasm_contents]) js = f'var binary = new Uint8Array([{wasm_contents}]);\n\n' + js + + # The default JS builds and runs the wasm. Append some random additional + # operations as well, as more compiles and executions can find things. To + # approximate a number in the range [0, MAX_EXTRA_JS_OPERATIONS) but with a + # median of MEDIAN_EXTRA_JS_OPERATIONS, start in the range [0, 1) and then + # raise it to the proper power, as multiplying by itself keeps the range + # unchanged, but lowers the median. Specifically, the median begins at 0.5, + # so + # + # 0.5^power = MEDIAN_EXTRA_JS_OPERATIONS / MAX_EXTRA_JS_OPERATIONS + # + # is what we want, and if we take log2 of each side, gives us + # + # power = log2(MEDIAN_EXTRA_JS_OPERATIONS / MAX_EXTRA_JS_OPERATIONS) / log2(0.5) + # = -log2(MEDIAN_EXTRA_JS_OPERATIONS / MAX_EXTRA_JS_OPERATIONS) + power = -math.log2(float(MEDIAN_EXTRA_JS_OPERATIONS) / MAX_EXTRA_JS_OPERATIONS) + x = system_random.random() + x = math.pow(x, power) + num = math.floor(x * MAX_EXTRA_JS_OPERATIONS) + assert num >= 0 and num <= MAX_EXTRA_JS_OPERATIONS + for i in range(num): + js += system_random.choice([ + # Compile and link the wasm again. Each link adds more to the total + # exports that we can call. + 'build(binary);\n', + # Run all the exports we've accumulated. + 'callExports();\n', + ]) + return js @@ -115,7 +157,7 @@ def main(argv): # detects as invalid). Just try again in such a case. for attempt in range(0, 100): # Generate random data. - random_size = random.SystemRandom().randint(1, MAX_RANDOM_SIZE) + random_size = system_random.randint(1, MAX_RANDOM_SIZE) with open(input_data_file_path, 'wb') as file: file.write(os.urandom(random_size)) diff --git a/test/lit/node/fuzz_shell_append.wast b/test/lit/node/fuzz_shell_append.wast new file mode 100644 index 00000000000..4aa61d5361f --- /dev/null +++ b/test/lit/node/fuzz_shell_append.wast @@ -0,0 +1,135 @@ +;; Test that appending more build and run operations, as the ClusterFuzz run.py +;; does, works properly. + +(module + (import "fuzzing-support" "log-i32" (func $log (param i32))) + (import "fuzzing-support" "call-export-catch" (func $call.export.catch (param i32) (result i32))) + + (global $errors (mut i32) + (i32.const 0) + ) + + (func $errors (export "errors") + ;; Log the number of errors we've seen. + (call $log + (global.get $errors) + ) + ) + + (func $do-call (param $x i32) + ;; Given an index $x, call the export of that index, and note an error if + ;; we see one. + (if + (call $call.export.catch + (local.get $x) + ) + (then + ;; Log that we errored right now, and then increment the total. + (call $log + (i32.const -1) + ) + (global.set $errors + (i32.add + (global.get $errors) + (i32.const 1) + ) + ) + ) + ) + ;; Log the total number of errors so far. + (call $log + (global.get $errors) + ) + ) + + (func $call-0 (export "call0") + ;; This calls "errors". + (call $do-call + (i32.const 0) + ) + ) + + (func $call-3 (export "call3") + ;; The first time we try this, there is no export at index 3, since we just + ;; have ["errors", "call0", "call3"]. After we build the module a second + ;; time, we will have "errors" from the second module there. + (call $do-call + (i32.const 3) + ) + ) +) + +;; Run normally. +;; +;; RUN: wasm-opt %s -o %t.wasm -q +;; RUN: node %S/../../../scripts/fuzz_shell.js %t.wasm | filecheck %s +;; +;; "errors" reports we've seen no errors. +;; CHECK: [fuzz-exec] calling errors +;; CHECK: [LoggingExternalInterface logging 0] + +;; "call0" calls "errors", which logs 0 twice. +;; CHECK: [fuzz-exec] calling call0 +;; CHECK: [LoggingExternalInterface logging 0] +;; CHECK: [LoggingExternalInterface logging 0] + +;; "call3" calls an invalid index, and logs -1 as an error, and 1 as the total +;; errors so far. +;; CHECK: [fuzz-exec] calling call3 +;; CHECK: [LoggingExternalInterface logging -1] +;; CHECK: [LoggingExternalInterface logging 1] + +;; Append another build + run. +;; +;; RUN: cp %S/../../../scripts/fuzz_shell.js %t.js +;; RUN: echo "build(binary);" >> %t.js +;; RUN: echo "callExports();" >> %t.js +;; RUN: node %t.js %t.wasm | filecheck %s --check-prefix=APPENDED +;; +;; The first part is unchanged from before. +;; APPENDED: [fuzz-exec] calling errors +;; APPENDED: [LoggingExternalInterface logging 0] +;; APPENDED: [fuzz-exec] calling call0 +;; APPENDED: [LoggingExternalInterface logging 0] +;; APPENDED: [LoggingExternalInterface logging 0] +;; APPENDED: [fuzz-exec] calling call3 +;; APPENDED: [LoggingExternalInterface logging -1] +;; APPENDED: [LoggingExternalInterface logging 1] + +;; Next, we build the module again, append its exports, and call them all. + +;; "errors" from the first module recalls that we errored before. +;; APPENDED: [fuzz-exec] calling errors +;; APPENDED: [LoggingExternalInterface logging 1] + +;; "call0" calls "errors", and they both log 1. +;; APPENDED: [fuzz-exec] calling call0 +;; APPENDED: [LoggingExternalInterface logging 1] +;; APPENDED: [LoggingExternalInterface logging 1] + +;; "call3" does *not* error like before, as the later exports provide something +;; at index 3: the second module's "errors". That reports that the second module +;; has seen no errors, and then call3 from the first module reports that that +;; module has seen 1 error. +;; APPENDED: [fuzz-exec] calling call3 +;; APPENDED: [LoggingExternalInterface logging 0] +;; APPENDED: [LoggingExternalInterface logging 1] + +;; "errors" from the second module reports no errors. +;; APPENDED: [fuzz-exec] calling errors +;; APPENDED: [LoggingExternalInterface logging 0] + +;; "call0" from the second module to the first makes the first module's "errors" +;; report 1, and then we report 0 from the second module. +;; APPENDED: [fuzz-exec] calling call0 +;; APPENDED: [LoggingExternalInterface logging 1] +;; APPENDED: [LoggingExternalInterface logging 0] + +;; "call3" from the second module calls "errors" in the second module, and they +;; both report 0 errors. +;; APPENDED: [fuzz-exec] calling call3 +;; APPENDED: [LoggingExternalInterface logging 0] +;; APPENDED: [LoggingExternalInterface logging 0] + +;; Overall, we have seen each module call the other, showing calls work both +;; ways. diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 1d275c712c2..8ec1d892819 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -247,6 +247,43 @@ def test_file_contents(self): print() + # To check for interesting JS file contents, we'll note how many times + # we build and run the wasm. + seen_builds = [] + seen_calls = [] + + for i in range(1, N + 1): + fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') + with open(fuzz_file) as f: + js = f.read() + seen_builds.append(js.count('build(binary);')) + seen_calls.append(js.count('callExports();')) + + # There is always one build and one call (those are in the default + # fuzz_shell.js), and we add a couple of operations, each with equal + # probability to be a build or a call, so over the 100 testcases here we + # have an overwhelming probability to see at least one extra build and + # one extra call. + # + # builds and calls are distributed as mean 4, stddev 5, median 2. + print(f'mean JS builds: {statistics.mean(seen_builds)}') + print(f'stdev JS builds: {statistics.stdev(seen_builds)}') + print(f'median JS builds: {statistics.median(seen_builds)}') + # Assert on at least 2, which means we added at least one to the default + # one that always exists, as mentioned before. + self.assertGreaterEqual(max(seen_builds), 2) + self.assertGreater(statistics.stdev(seen_builds), 0) + + print() + + print(f'mean JS calls: {statistics.mean(seen_calls)}') + print(f'stdev JS calls: {statistics.stdev(seen_calls)}') + print(f'median JS calls: {statistics.median(seen_calls)}') + self.assertGreaterEqual(max(seen_calls), 2) + self.assertGreater(statistics.stdev(seen_calls), 0) + + print() + # "zzz" in test name so that this runs last. If it runs first, it can be # confusing as it appears next to the logging of which bundle we use (see # setUpClass). From 3342d56e4a13170c094a29138b32ff17cad4c01d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 21 Nov 2024 11:26:33 -0800 Subject: [PATCH 147/622] [wasm2js] Properly handle loops without labels (#7100) When a loop has no name, the name does not matter, but we also cannot emit the same name for all such loops, as that is invalid JS. Just do not emit a while(){} at all in that case, as no continue can exist anyhow. Fixes #7099 Also fix two missing * in error reporting logic, that was printing pointers rather than the expression we wanted to print. I think we changed how iostream prints things years ago, and forgot to update these. --- src/wasm2js.h | 9 ++++++-- test/wasm2js/br_table_temp.2asm.js | 34 ++++++++++++----------------- test/wasm2js/empty_loop.2asm.js | 28 ++++++++++++++++++++++++ test/wasm2js/empty_loop.2asm.js.opt | 26 ++++++++++++++++++++++ test/wasm2js/empty_loop.wast | 18 +++++++++++++++ 5 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 test/wasm2js/empty_loop.2asm.js create mode 100644 test/wasm2js/empty_loop.2asm.js.opt create mode 100644 test/wasm2js/empty_loop.wast diff --git a/src/wasm2js.h b/src/wasm2js.h index 15ae019ea22..d965dcc0cca 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -1181,6 +1181,11 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, Ref visitLoop(Loop* curr) { Name asmLabel = curr->name; + if (!asmLabel) { + // This loop has no label, so it cannot be continued to. We can just + // emit the body. + return visit(curr->body, result); + } continueLabels.insert(asmLabel); Ref body = visit(curr->body, result); // if we can reach the end of the block, we must leave the while (1) loop @@ -1779,7 +1784,7 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, return ret; } default: { - Fatal() << "Unhandled type in unary: " << curr; + Fatal() << "Unhandled type in unary: " << *curr; } } } @@ -1949,7 +1954,7 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, } return ret; default: - Fatal() << "Unhandled type in binary: " << curr; + Fatal() << "Unhandled type in binary: " << *curr; } return makeJsCoercion(ret, wasmToJsType(curr->type)); } diff --git a/test/wasm2js/br_table_temp.2asm.js b/test/wasm2js/br_table_temp.2asm.js index a8592a186c4..01a2238e146 100644 --- a/test/wasm2js/br_table_temp.2asm.js +++ b/test/wasm2js/br_table_temp.2asm.js @@ -12554,12 +12554,10 @@ function asmFunc(imports) { function $19() { var $1_1 = 0, $2_1 = 0, $4_1 = 0; label : { - $null_Name_ : while (1) { - $1_1 = 3; - switch (0 | 0) { - default: - break label; - }; + $1_1 = 3; + switch (0 | 0) { + default: + break label; }; } return $1_1 | 0; @@ -12568,13 +12566,11 @@ function asmFunc(imports) { function $20() { var $1_1 = 0, $2_1 = 0, $4_1 = 0; label : { - $null_Name_ : while (1) { - dummy(); - $1_1 = 4; - switch (-1 | 0) { - default: - break label; - }; + dummy(); + $1_1 = 4; + switch (-1 | 0) { + default: + break label; }; } return $1_1 | 0; @@ -12583,13 +12579,11 @@ function asmFunc(imports) { function $21() { var $1_1 = 0; label : { - $null_Name_ : while (1) { - dummy(); - $1_1 = 5; - switch (1 | 0) { - default: - break label; - }; + dummy(); + $1_1 = 5; + switch (1 | 0) { + default: + break label; }; } return $1_1 | 0; diff --git a/test/wasm2js/empty_loop.2asm.js b/test/wasm2js/empty_loop.2asm.js new file mode 100644 index 00000000000..7e81b464377 --- /dev/null +++ b/test/wasm2js/empty_loop.2asm.js @@ -0,0 +1,28 @@ + +function wasm2js_trap() { throw new Error('abort'); } + +function asmFunc(imports) { + var Math_imul = Math.imul; + var Math_fround = Math.fround; + var Math_abs = Math.abs; + var Math_clz32 = Math.clz32; + var Math_min = Math.min; + var Math_max = Math.max; + var Math_floor = Math.floor; + var Math_ceil = Math.ceil; + var Math_trunc = Math.trunc; + var Math_sqrt = Math.sqrt; + var g = 0; + function test() { + g = 0; + wasm2js_trap(); + } + + return { + "test": test + }; +} + +var retasmFunc = asmFunc({ +}); +export var test = retasmFunc.test; diff --git a/test/wasm2js/empty_loop.2asm.js.opt b/test/wasm2js/empty_loop.2asm.js.opt new file mode 100644 index 00000000000..ff37d056fc3 --- /dev/null +++ b/test/wasm2js/empty_loop.2asm.js.opt @@ -0,0 +1,26 @@ + +function wasm2js_trap() { throw new Error('abort'); } + +function asmFunc(imports) { + var Math_imul = Math.imul; + var Math_fround = Math.fround; + var Math_abs = Math.abs; + var Math_clz32 = Math.clz32; + var Math_min = Math.min; + var Math_max = Math.max; + var Math_floor = Math.floor; + var Math_ceil = Math.ceil; + var Math_trunc = Math.trunc; + var Math_sqrt = Math.sqrt; + function test() { + wasm2js_trap(); + } + + return { + "test": test + }; +} + +var retasmFunc = asmFunc({ +}); +export var test = retasmFunc.test; diff --git a/test/wasm2js/empty_loop.wast b/test/wasm2js/empty_loop.wast new file mode 100644 index 00000000000..8f22c033d08 --- /dev/null +++ b/test/wasm2js/empty_loop.wast @@ -0,0 +1,18 @@ +(module + (global $g (mut i32) (i32.const 0)) + + (func $test (export "test") + ;; Loops without labels. We should not emit anything invalid here (like a + ;; null name, which would end up identical between the two, which is wrong). + (loop + (loop + ;; Some content, so the loop is not trivial. + (global.set $g + (i32.const 0) + ) + (unreachable) + ) + (unreachable) + ) + ) +) From 901ba6024f3ca9117c5720be3cf19ab75034070a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 21 Nov 2024 11:49:08 -0800 Subject: [PATCH 148/622] Make validation of stale types stricter (#7097) We previously allowed valid expressions to have stale types as long as those stale types were supertypes of the most precise possible types for the expressions. Allowing stale types like this could mask bugs where we failed to propagate precise type information, though. Make validation stricter by requiring all expressions except for control flow structures to have the most precise possible types. Control flow structures are exempt because many passes that can refine types wrap the refined expressions in blocks with the old type to avoid the need for refinalization. This pattern would be broken and we would need to refinalize more frequently without this exception for control flow structures. Now that all non-control flow expressions must have precise types, remove functionality relating to building select instructions with non-precise types. Since finalization of selects now always calculates a LUB rather than using a provided type, remove the type parameter from BinaryenSelect in the C and JS APIs. Now that stale types are no longer valid, fix a bug in TypeSSA where it failed to refinalize module-level code. This bug previously would not have caused problems on its own, but the stale types could cause problems for later runs of Unsubtyping. Now the stale types would cause TypeSSA output to fail validation. Also fix a bug where Builder::replaceWithIdenticalType was in fact replacing with refined types. Fixes #7087. --- CHANGELOG.md | 2 + src/binaryen-c.cpp | 9 +-- src/binaryen-c.h | 3 +- src/js/binaryen.js-post.js | 4 +- src/passes/OptimizeInstructions.cpp | 2 +- src/passes/RemoveUnusedBrs.cpp | 2 +- src/passes/SSAify.cpp | 2 +- src/passes/TypeSSA.cpp | 1 + src/tools/fuzzing.h | 2 +- src/tools/fuzzing/fuzzing.cpp | 7 +-- src/wasm-builder.h | 33 ++++------- src/wasm.h | 1 - src/wasm/wasm-binary.cpp | 11 ++-- src/wasm/wasm-ir-builder.cpp | 4 +- src/wasm/wasm-validator.cpp | 8 +-- src/wasm/wasm.cpp | 10 ---- test/binaryen.js/kitchen-sink.js | 2 +- test/example/c-api-kitchen-sink.c | 5 +- test/lit/basic/reference-types.wast | 6 +- test/lit/passes/cfp.wast | 18 +++--- test/lit/passes/coalesce-locals-gc.wast | 56 +++++++++++++------ test/lit/passes/issue-7087.wast | 31 ++++++++++ .../passes/optimize-instructions-gc-tnh.wast | 10 +--- .../remove-unused-brs_all-features.wast | 2 +- test/lit/passes/ssa.wast | 16 ++++++ test/lit/select-gc.wat | 26 --------- 26 files changed, 144 insertions(+), 129 deletions(-) create mode 100644 test/lit/passes/issue-7087.wast delete mode 100644 test/lit/select-gc.wat diff --git a/CHANGELOG.md b/CHANGELOG.md index a1dbd45919f..9c45eac79da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ full changeset diff at the end of each section. Current Trunk ------------- + - BinaryenSelect no longer takes a type parameter. + v120 ---- diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 854b0c9953b..a278f77780a 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1276,17 +1276,12 @@ BinaryenExpressionRef BinaryenBinary(BinaryenModuleRef module, BinaryenExpressionRef BinaryenSelect(BinaryenModuleRef module, BinaryenExpressionRef condition, BinaryenExpressionRef ifTrue, - BinaryenExpressionRef ifFalse, - BinaryenType type) { + BinaryenExpressionRef ifFalse) { auto* ret = ((Module*)module)->allocator.alloc(); - ret->condition = condition; - ret->ifTrue = ifTrue; - ret->ifFalse = ifFalse; - ret->finalize(type); - return ret; - } Return* makeReturn(Expression* value = nullptr) { auto* ret = wasm.allocator.alloc(); ret->value = value; @@ -1381,21 +1370,23 @@ class Builder { // Returns a replacement with the precise same type, and with minimal contents // as best we can. As a replacement, this may reuse the input node. template Expression* replaceWithIdenticalType(T* curr) { + auto type = curr->type; + // Anything that would otherwise have a more refined type than the original + // expression needs to be wrapped in a block with the original type. + auto maybeWrap = [&](Expression* expr) -> Expression* { + return expr->type == type ? expr : makeBlock({expr}, type); + }; if (curr->type.isTuple() && curr->type.isDefaultable()) { - return makeConstantExpression(Literal::makeZeros(curr->type)); + return maybeWrap(makeConstantExpression(Literal::makeZeros(curr->type))); } - if (curr->type.isNullable() && curr->type.isNull()) { - return ExpressionManipulator::refNull(curr, curr->type); + if (curr->type.isNullable()) { + return maybeWrap(ExpressionManipulator::refNull( + curr, Type(curr->type.getHeapType().getBottom(), Nullable))); } if (curr->type.isRef() && curr->type.getHeapType().isMaybeShared(HeapType::i31)) { - Expression* ret = - makeRefI31(makeConst(0), curr->type.getHeapType().getShared()); - if (curr->type.isNullable()) { - // To keep the type identical, wrap it in a block that adds nullability. - ret = makeBlock({ret}, curr->type); - } - return ret; + return maybeWrap( + makeRefI31(makeConst(0), curr->type.getHeapType().getShared())); } if (!curr->type.isBasic()) { // We can't do any better, keep the original. diff --git a/src/wasm.h b/src/wasm.h index 22e71560f4a..a5fc070e611 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1271,7 +1271,6 @@ class Select : public SpecificExpression { Expression* condition; void finalize(); - void finalize(Type type_); }; class Drop : public SpecificExpression { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 73718e636a8..21842e19bbb 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -7042,6 +7042,7 @@ bool WasmBinaryReader::maybeVisitSIMDLoadStoreLane(Expression*& out, } void WasmBinaryReader::visitSelect(Select* curr, uint8_t code) { + Type annotated = Type::none; if (code == BinaryConsts::SelectWithType) { size_t numTypes = getU32LEB(); std::vector types; @@ -7052,15 +7053,15 @@ void WasmBinaryReader::visitSelect(Select* curr, uint8_t code) { } types.push_back(t); } - curr->type = Type(types); + annotated = Type(types); } curr->condition = popNonVoidExpression(); curr->ifFalse = popNonVoidExpression(); curr->ifTrue = popNonVoidExpression(); - if (code == BinaryConsts::SelectWithType) { - curr->finalize(curr->type); - } else { - curr->finalize(); + curr->finalize(); + if (code == BinaryConsts::SelectWithType && + !Type::isSubType(curr->type, annotated)) { + throwError("select type does not match annotation"); } } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index a73c7f2acb4..54cd0149e5c 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1412,9 +1412,7 @@ Result<> IRBuilder::makeBinary(BinaryOp op) { Result<> IRBuilder::makeSelect(std::optional type) { Select curr; CHECK_ERR(visitSelect(&curr)); - auto* built = - type ? builder.makeSelect(curr.condition, curr.ifTrue, curr.ifFalse, *type) - : builder.makeSelect(curr.condition, curr.ifTrue, curr.ifFalse); + auto* built = builder.makeSelect(curr.condition, curr.ifTrue, curr.ifFalse); if (type && !Type::isSubType(built->type, *type)) { return Err{"select type does not match expected type"}; } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 339a3c7a193..516bb86e15e 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3671,10 +3671,10 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { auto oldType = curr->type; ReFinalizeNode().visit(curr); auto newType = curr->type; - // It's ok for types to be further refinable, but they must admit a - // superset of the values allowed by the most precise possible type, i.e. - // they must not be strict subtypes of or unrelated to the refined type. - if (!Type::isSubType(newType, oldType)) { + // It's ok for control flow structures to be further refinable, but all + // other instructions must have the most-precise possible types. + if (oldType != newType && !(Properties::isControlFlowStructure(curr) && + Type::isSubType(newType, oldType))) { std::ostringstream ss; ss << "stale type found in " << scope << " on " << curr << "\n(marked as " << oldType << ", should be " << newType << ")\n"; diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index a1ac076e08f..f89ef80c21c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -777,16 +777,6 @@ void Binary::finalize() { } } -void Select::finalize(Type type_) { - assert(ifTrue && ifFalse); - if (ifTrue->type == Type::unreachable || ifFalse->type == Type::unreachable || - condition->type == Type::unreachable) { - type = Type::unreachable; - } else { - type = type_; - } -} - void Select::finalize() { assert(ifTrue && ifFalse); if (ifTrue->type == Type::unreachable || ifFalse->type == Type::unreachable || diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index da281910f14..6e6808ab8eb 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -598,7 +598,7 @@ function test_core() { module.ref.is_null(module.ref.null(binaryen.externref)), module.ref.is_null(module.ref.null(binaryen.funcref)), module.ref.is_null(module.ref.func("kitchen()sinker", binaryen.funcref)), - module.select(temp10, module.ref.null(binaryen.funcref), module.ref.func("kitchen()sinker", binaryen.funcref), binaryen.funcref), + module.select(temp10, module.ref.null(binaryen.funcref), module.ref.func("kitchen()sinker", binaryen.funcref)), // GC module.ref.eq(module.ref.null(binaryen.eqref), module.ref.null(binaryen.eqref)), diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index d3f20e888f2..a6a196ae6df 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -1021,7 +1021,7 @@ void test_core() { module, 8, 0, 2, 8, BinaryenTypeFloat64(), makeInt32(module, 9), "0"), BinaryenStore(module, 4, 0, 0, temp13, temp14, BinaryenTypeInt32(), "0"), BinaryenStore(module, 8, 2, 4, temp15, temp16, BinaryenTypeInt64(), "0"), - BinaryenSelect(module, temp10, temp11, temp12, BinaryenTypeAuto()), + BinaryenSelect(module, temp10, temp11, temp12), BinaryenReturn(module, makeInt32(module, 1337)), // Tail call BinaryenReturnCall( @@ -1040,8 +1040,7 @@ void test_core() { module, temp10, BinaryenRefNull(module, BinaryenTypeNullFuncref()), - BinaryenRefFunc(module, "kitchen()sinker", BinaryenTypeFuncref()), - BinaryenTypeFuncref()), + BinaryenRefFunc(module, "kitchen()sinker", BinaryenTypeFuncref())), // GC BinaryenRefEq(module, BinaryenRefNull(module, BinaryenTypeNullref()), diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 44494c80d97..22250c9a0b4 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -711,7 +711,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (select (result anyref) + ;; CHECK-TEXT-NEXT: (select (result eqref) ;; CHECK-TEXT-NEXT: (local.get $local_eqref) ;; CHECK-TEXT-NEXT: (ref.i31 ;; CHECK-TEXT-NEXT: (i32.const 0) @@ -1314,7 +1314,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (select (result anyref) + ;; CHECK-BIN-NEXT: (select (result eqref) ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: (ref.i31 ;; CHECK-BIN-NEXT: (i32.const 0) @@ -2651,7 +2651,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (select (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (select (result eqref) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (ref.i31 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 461baa37317..e70cfc639e5 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2332,9 +2332,11 @@ ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (select (result (ref null $A)) ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (local.tee $B - ;; CHECK-NEXT: (struct.new $B - ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (block (result (ref null $A)) + ;; CHECK-NEXT: (local.tee $B + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) @@ -2361,10 +2363,12 @@ ;; This select is used to keep the type that reaches the struct.set $A, ;; and not $B, so it looks like a perfect copy of $A->$A. (select (result (ref null $A)) - (ref.null $A) - (local.tee $B - (struct.new $B - (i32.const 20) + (ref.null none) + (block (result (ref null $A)) + (local.tee $B + (struct.new $B + (i32.const 20) + ) ) ) (i32.const 0) diff --git a/test/lit/passes/coalesce-locals-gc.wast b/test/lit/passes/coalesce-locals-gc.wast index d2b6fcaeb13..59232d1b4a5 100644 --- a/test/lit/passes/coalesce-locals-gc.wast +++ b/test/lit/passes/coalesce-locals-gc.wast @@ -25,7 +25,7 @@ (global $nn-tuple-global (mut (tuple (ref any) i32)) (tuple.make 2 (ref.i31 (i32.const 0)) (i32.const 1))) - ;; CHECK: (func $test-dead-get-non-nullable (type $6) (param $0 (ref struct)) + ;; CHECK: (func $test-dead-get-non-nullable (type $7) (param $0 (ref struct)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref struct)) @@ -43,7 +43,7 @@ ) ) - ;; CHECK: (func $br_on_null (type $7) (param $0 (ref null $array)) (result (ref null $array)) + ;; CHECK: (func $br_on_null (type $8) (param $0 (ref null $array)) (result (ref null $array)) ;; CHECK-NEXT: (block $label$1 (result (ref null $array)) ;; CHECK-NEXT: (block $label$2 ;; CHECK-NEXT: (br $label$1 @@ -79,7 +79,7 @@ ) ) - ;; CHECK: (func $nn-dead (type $4) + ;; CHECK: (func $nn-dead (type $3) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $nn-dead) @@ -118,7 +118,7 @@ ) ) - ;; CHECK: (func $nn-dead-nameless (type $4) + ;; CHECK: (func $nn-dead-nameless (type $3) ;; CHECK-NEXT: (local $0 (ref func)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $nn-dead) @@ -149,26 +149,24 @@ ) ) - ;; CHECK: (func $unreachable-get-null (type $4) + ;; CHECK: (func $unreachable-get-null (type $3) ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (local $1 i31ref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result anyref) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i31ref) - ;; CHECK-NEXT: (ref.i31 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $unreachable-get-null - ;; Check that we don't replace the local.get $null with a ref.null, which - ;; would have a more precise type. + ;; Check that we don't replace the local.get $null with just a ref.null, which + ;; would have a more precise type. We wrap the ref.null in a block instead. (local $null-any anyref) (local $null-i31 i31ref) (unreachable) @@ -180,6 +178,32 @@ ) ) + ;; CHECK: (func $unreachable-get-tuple (type $3) + ;; CHECK-NEXT: (local $0 (tuple anyref i32)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (block (type $6) (result anyref i32) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-get-tuple + (local $tuple (tuple anyref i32)) + (unreachable) + (drop + ;; If we replaced the get with something with a more refined type, this + ;; extract would end up with a stale type. + (tuple.extract 2 0 + (local.get $tuple) + ) + ) + ) + ;; CHECK: (func $remove-tee-refinalize (type $5) (param $0 (ref null $A)) (param $1 (ref null $B)) (result structref) ;; CHECK-NEXT: (struct.get $B 0 ;; CHECK-NEXT: (local.get $1) @@ -218,16 +242,14 @@ ) ) - ;; CHECK: (func $replace-i31-local (type $8) (result i32) + ;; CHECK: (func $replace-i31-local (type $9) (result i32) ;; CHECK-NEXT: (local $0 i31ref) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (ref.test (ref i31) ;; CHECK-NEXT: (ref.cast i31ref ;; CHECK-NEXT: (block (result i31ref) - ;; CHECK-NEXT: (ref.i31 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -250,7 +272,7 @@ ) ) - ;; CHECK: (func $replace-struct-param (type $9) (param $0 f64) (param $1 (ref null $A)) (result f32) + ;; CHECK: (func $replace-struct-param (type $10) (param $0 f64) (param $1 (ref null $A)) (result f32) ;; CHECK-NEXT: (call $replace-struct-param ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (unreachable) @@ -278,7 +300,7 @@ ) ) - ;; CHECK: (func $test (type $10) (param $0 (ref any)) (result (ref any) i32) + ;; CHECK: (func $test (type $11) (param $0 (ref any)) (result (ref any) i32) ;; CHECK-NEXT: (local $1 (tuple anyref i32)) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (tuple.make 2 diff --git a/test/lit/passes/issue-7087.wast b/test/lit/passes/issue-7087.wast new file mode 100644 index 00000000000..096c88e5d45 --- /dev/null +++ b/test/lit/passes/issue-7087.wast @@ -0,0 +1,31 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Regression test for a bug in TypeSSA. TypeSSA creates a new subtype, $t_1, +;; for use in the struct.new in the global initializer, but ran ReFinalize only +;; on function code, not on module-level code. As a result, the tuple.make +;; result type still used $t instead of $t_1 after TypeSSA. This stale type +;; caused Unsubtyping to incorrectly break the subtype relationship between $t +;; and $t_1, leading to a validation error. The fix was to refinalize +;; module-level code in TypeSSA and fix the validator so it would have caught +;; the stale type. + +;; RUN: wasm-opt %s -all --type-ssa --unsubtyping -S -o - | filecheck %s + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $t (sub (struct))) + (type $t (sub (struct))) + + ;; CHECK: (type $t_1 (sub $t (struct))) + + ;; CHECK: (global $g (tuple i32 (ref null $t)) (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (struct.new_default $t_1) + ;; CHECK-NEXT: )) + (global $g (tuple i32 (ref null $t)) + (tuple.make 2 + (i32.const 0) + (struct.new $t) + ) + ) +) diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index ff2975766de..c930f2fc19a 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -497,7 +497,7 @@ ) ;; TNH: (func $null.arm.null.effects (type $void) - ;; TNH-NEXT: (block ;; (replaces unreachable StructSet we can't emit) + ;; TNH-NEXT: (block ;; TNH-NEXT: (drop ;; TNH-NEXT: (select ;; TNH-NEXT: (unreachable) @@ -505,9 +505,6 @@ ;; TNH-NEXT: (call $get-i32) ;; TNH-NEXT: ) ;; TNH-NEXT: ) - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (i32.const 1) - ;; TNH-NEXT: ) ;; TNH-NEXT: (unreachable) ;; TNH-NEXT: ) ;; TNH-NEXT: (block @@ -523,7 +520,7 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; NO_TNH: (func $null.arm.null.effects (type $void) - ;; NO_TNH-NEXT: (block ;; (replaces unreachable StructSet we can't emit) + ;; NO_TNH-NEXT: (block ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (select ;; NO_TNH-NEXT: (unreachable) @@ -531,9 +528,6 @@ ;; NO_TNH-NEXT: (call $get-i32) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (i32.const 1) - ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (block diff --git a/test/lit/passes/remove-unused-brs_all-features.wast b/test/lit/passes/remove-unused-brs_all-features.wast index 4e722a04380..e2d89a5ead5 100644 --- a/test/lit/passes/remove-unused-brs_all-features.wast +++ b/test/lit/passes/remove-unused-brs_all-features.wast @@ -119,7 +119,7 @@ (func $i32_=>_none (param i32) ) ;; CHECK: (func $selectify (type $6) (param $x i32) (result funcref) - ;; CHECK-NEXT: (select (result funcref) + ;; CHECK-NEXT: (select (result (ref func)) ;; CHECK-NEXT: (ref.func $none_=>_i32) ;; CHECK-NEXT: (ref.func $i32_=>_none) ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast index b082cd88884..0d696f75a0d 100644 --- a/test/lit/passes/ssa.wast +++ b/test/lit/passes/ssa.wast @@ -58,4 +58,20 @@ (unreachable) ) ) + + ;; CHECK: (func $null-tuple (type $4) (result funcref) + ;; CHECK-NEXT: (local $tuple (tuple i32 funcref)) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $null-tuple (result funcref) + (local $tuple (tuple i32 funcref)) + (tuple.extract 2 1 + (local.get $tuple) + ) + ) ) diff --git a/test/lit/select-gc.wat b/test/lit/select-gc.wat deleted file mode 100644 index bd1394950f6..00000000000 --- a/test/lit/select-gc.wat +++ /dev/null @@ -1,26 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s - -;; Check that annotated select is propery roundtripped, even if the type is -;; only used in that one place in the whole module. - -(module - ;; CHECK: (type $struct (struct)) - (type $struct (struct)) - - ;; CHECK: (func $foo (type $0) (result anyref) - ;; CHECK-NEXT: (select (result (ref null $struct)) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $foo (result anyref) - (select (result (ref null $struct)) - (ref.null any) - (ref.null eq) - (i32.const 1) - ) - ) -) From 4488a3e351214e038600f58e5806c31ad0bfae46 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 21 Nov 2024 15:04:29 -0800 Subject: [PATCH 149/622] [NFC] Refactor ClusterFuzz run.py (#7101) This just moves code around. It will allow more code reuse in a later PR. Also add a bit of test logging. --- scripts/clusterfuzz/run.py | 99 ++++++++++++++++++++-------------- test/unit/test_cluster_fuzz.py | 16 +++--- 2 files changed, 66 insertions(+), 49 deletions(-) diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index 4b5e67fdeff..6bbb74ef886 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -93,17 +93,64 @@ def get_file_name(prefix, index): system_random = random.SystemRandom() -# Returns the contents of a .js fuzz file, given particular wasm contents that -# we want to be executed. -def get_js_file_contents(wasm_contents): +# Generate a random wasm file, and return a string that creates a typed array of +# those bytes, suitable for use in a JS file, in the form +# +# new Uint8Array([..wasm_contents..]) +# +# Receives the testcase index and the output dir. +def get_wasm_contents(i, output_dir): + input_data_file_path = os.path.join(output_dir, f'{i}.input') + wasm_file_path = os.path.join(output_dir, f'{i}.wasm') + + # wasm-opt may fail to run in rare cases (when the fuzzer emits code it + # detects as invalid). Just try again in such a case. + for attempt in range(0, 100): + # Generate random data. + random_size = system_random.randint(1, MAX_RANDOM_SIZE) + with open(input_data_file_path, 'wb') as file: + file.write(os.urandom(random_size)) + + # Generate wasm from the random data. + cmd = [FUZZER_BINARY_PATH] + FUZZER_ARGS + cmd += ['-o', wasm_file_path, input_data_file_path] + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError: + # Try again. + print('(oops, retrying wasm-opt)') + attempt += 1 + if attempt == 99: + # Something is very wrong! + raise + continue + # Success, leave the loop. + break + + # Generate a testcase from the wasm + with open(wasm_file_path, 'rb') as file: + wasm_contents = file.read() + + # Clean up temp files. + os.remove(wasm_file_path) + os.remove(input_data_file_path) + + # Convert to a string, and wrap into a typed array. + wasm_contents = ','.join([str(c) for c in wasm_contents]) + return f'new Uint8Array([{wasm_contents}])' + + +# Returns the contents of a .js fuzz file, given the index of the testcase and +# the output dir. +def get_js_file_contents(i, output_dir): # Start with the standard JS shell. with open(JS_SHELL_PATH) as file: js = file.read() # Prepend the wasm contents, so they are used (rather than the normal # mechanism where the wasm file's name is provided in argv). - wasm_contents = ','.join([str(c) for c in wasm_contents]) - js = f'var binary = new Uint8Array([{wasm_contents}]);\n\n' + js + wasm_contents = get_wasm_contents(i, output_dir) + js = f'var binary = {wasm_contents};\n\n' + js # The default JS builds and runs the wasm. Append some random additional # operations as well, as more compiles and executions can find things. To @@ -133,6 +180,8 @@ def get_js_file_contents(wasm_contents): 'callExports();\n', ]) + print(f'Created {wasm_contents.count(",")} wasm bytes') + return js @@ -150,39 +199,11 @@ def main(argv): num = int(value) for i in range(1, num + 1): - input_data_file_path = os.path.join(output_dir, f'{i}.input') - wasm_file_path = os.path.join(output_dir, f'{i}.wasm') - - # wasm-opt may fail to run in rare cases (when the fuzzer emits code it - # detects as invalid). Just try again in such a case. - for attempt in range(0, 100): - # Generate random data. - random_size = system_random.randint(1, MAX_RANDOM_SIZE) - with open(input_data_file_path, 'wb') as file: - file.write(os.urandom(random_size)) - - # Generate wasm from the random data. - cmd = [FUZZER_BINARY_PATH] + FUZZER_ARGS - cmd += ['-o', wasm_file_path, input_data_file_path] - try: - subprocess.check_call(cmd) - except subprocess.CalledProcessError: - # Try again. - print('(oops, retrying wasm-opt)') - attempt += 1 - if attempt == 99: - # Something is very wrong! - raise - continue - # Success, leave the loop. - break - - # Generate a testcase from the wasm - with open(wasm_file_path, 'rb') as file: - wasm_contents = file.read() testcase_file_path = os.path.join(output_dir, get_file_name(FUZZ_FILENAME_PREFIX, i)) - js_file_contents = get_js_file_contents(wasm_contents) + + # Emit the JS file. + js_file_contents = get_js_file_contents(i, output_dir) with open(testcase_file_path, 'w') as file: file.write(js_file_contents) @@ -192,11 +213,7 @@ def main(argv): with open(flags_file_path, 'w') as file: file.write(FUZZER_FLAGS_FILE_CONTENTS) - print(f'Created testcase: {testcase_file_path}, {len(wasm_contents)} bytes') - - # Remove temporary files. - os.remove(input_data_file_path) - os.remove(wasm_file_path) + print(f'Created testcase: {testcase_file_path}') print(f'Created {num} testcases.') diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 8ec1d892819..387f65fd1d5 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -217,10 +217,10 @@ def test_file_contents(self): print() - # struct.news appear to be distributed as mean 15, stddev 24, median 10, - # so over 100 samples we are incredibly likely to see an interesting - # number at least once. It is also incredibly unlikely for the stdev to - # be zero. + print('struct.news are distributed as ~ mean 15, stddev 24, median 10') + # Given that, with 100 samples we are incredibly likely to see an + # interesting number at least once. It is also incredibly unlikely for + # the stdev to be zero. print(f'mean struct.news: {statistics.mean(seen_struct_news)}') print(f'stdev struct.news: {statistics.stdev(seen_struct_news)}') print(f'median struct.news: {statistics.median(seen_struct_news)}') @@ -229,7 +229,7 @@ def test_file_contents(self): print() - # sizes appear to be distributed as mean 2933, stddev 2011, median 2510. + print('sizes are distributed as ~ mean 2933, stddev 2011, median 2510') print(f'mean sizes: {statistics.mean(seen_sizes)}') print(f'stdev sizes: {statistics.stdev(seen_sizes)}') print(f'median sizes: {statistics.median(seen_sizes)}') @@ -238,7 +238,7 @@ def test_file_contents(self): print() - # exports appear to be distributed as mean 9, stddev 6, median 8. + print('exports are distributed as ~ mean 9, stddev 6, median 8') print(f'mean exports: {statistics.mean(seen_exports)}') print(f'stdev exports: {statistics.stdev(seen_exports)}') print(f'median exports: {statistics.median(seen_exports)}') @@ -264,8 +264,7 @@ def test_file_contents(self): # probability to be a build or a call, so over the 100 testcases here we # have an overwhelming probability to see at least one extra build and # one extra call. - # - # builds and calls are distributed as mean 4, stddev 5, median 2. + print('JS builds are distributed as ~ mean 4, stddev 5, median 2') print(f'mean JS builds: {statistics.mean(seen_builds)}') print(f'stdev JS builds: {statistics.stdev(seen_builds)}') print(f'median JS builds: {statistics.median(seen_builds)}') @@ -276,6 +275,7 @@ def test_file_contents(self): print() + print('JS calls are distributed as ~ mean 4, stddev 5, median 2') print(f'mean JS calls: {statistics.mean(seen_calls)}') print(f'stdev JS calls: {statistics.stdev(seen_calls)}') print(f'median JS calls: {statistics.median(seen_calls)}') From 7b12353fb2ef0240dd8bb3e6aaa764bc8408f9ad Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 21 Nov 2024 15:55:52 -0800 Subject: [PATCH 150/622] Fix printing of unreachable br_on_cast{_fail} (#7102) br_on_cast and br_on_cast_fail have two type annotations: one for their input type and one for their cast type. In cases where their operands were unreachable, we were previously printing "unreachable" for the input type annotation. This is not valid wat because "unreachable" is not a reference type. To fix the problem, print the bottom type of the cast type's hierarchy as the input type for br_on_cast and br_on_cast_fail when the operand is unreachable. This ensures that the instructions have the most precise possible output type according to Wasm typing rules, so it maximizes the number of contexts in which the printed instructions are valid. --- src/passes/Print.cpp | 17 ++++++++++-- test/lit/wat-kitchen-sink.wast | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 5a5d0129996..5c8463f78fa 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2219,7 +2219,15 @@ struct PrintExpressionContents printMedium(o, "br_on_cast "); curr->name.print(o); o << ' '; - printType(curr->ref->type); + if (curr->ref->type == Type::unreachable) { + // Need to print some reference type in the correct hierarchy rather + // than unreachable, and the bottom type is valid in the most + // contexts. + printType( + Type(curr->castType.getHeapType().getBottom(), NonNullable)); + } else { + printType(curr->ref->type); + } o << ' '; printType(curr->castType); return; @@ -2227,7 +2235,12 @@ struct PrintExpressionContents printMedium(o, "br_on_cast_fail "); curr->name.print(o); o << ' '; - printType(curr->ref->type); + if (curr->ref->type == Type::unreachable) { + printType( + Type(curr->castType.getHeapType().getBottom(), NonNullable)); + } else { + printType(curr->ref->type); + } o << ' '; printType(curr->castType); return; diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index 8b77de74dac..719cf2db383 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -4064,6 +4064,31 @@ drop ) + ;; CHECK: (func $br-on-cast-unreachable (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result i31ref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref any)) + ;; CHECK-NEXT: (br_on_cast $block (ref none) i31ref + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast-unreachable + block (result i31ref) + block (result (ref any)) + unreachable + br_on_cast 1 anyref i31ref + end + unreachable + end + drop + ) + ;; CHECK: (func $br-on-cast-fail (type $9) (param $0 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref any)) @@ -4089,6 +4114,31 @@ drop ) + ;; CHECK: (func $br-on-cast-fail-unreachable (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref any)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i31ref) + ;; CHECK-NEXT: (br_on_cast_fail $block (ref none) i31ref + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast-fail-unreachable + block (result (ref any)) + block (result i31ref) + unreachable + br_on_cast_fail 1 anyref i31ref + end + unreachable + end + drop + ) + ;; CHECK: (func $struct-new (type $63) (param $0 i32) (param $1 i64) (result (ref $pair)) ;; CHECK-NEXT: (struct.new $pair ;; CHECK-NEXT: (local.get $0) From 4bf9fca02c8f84c6283a4a9b17eca3f7c0144c5e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 21 Nov 2024 19:49:55 -0800 Subject: [PATCH 151/622] Propagate public visibility through all types (#7105) Previously the classification of public types propagated public visibility only through types that had previously been collected by `collectHeapTypes`. Since there are settings that cause `collectHeapTypes` to collect fewer types, it was possible for public types to be missed if they were only public because they were reached by an uncollected types. Ensure that all public heap types are properly classified by propagating public visibility even through types that are not part of the collected output. Fixes #7103. --- src/ir/module-utils.cpp | 19 +++++------ test/lit/passes/type-refining.wast | 55 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 2fd129a9c21..5ebd6edefe5 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -598,26 +598,23 @@ void classifyTypeVisibility(Module& wasm, // We will need to traverse the types used by public types and mark them // public as well. std::vector workList; + std::unordered_set publicGroups; auto notePublic = [&](HeapType type) { if (type.isBasic()) { - return false; + return; + } + auto group = type.getRecGroup(); + if (!publicGroups.insert(group).second) { + // The groups in this type have already been marked public. + return; } - // All the rec group members are public as well. - bool inserted = false; for (auto member : type.getRecGroup()) { if (auto it = types.find(member); it != types.end()) { - if (it->second.visibility == Visibility::Public) { - // Since we mark all elements of a group public at once, if there is a - // member that is already public, all members must already be public. - break; - } it->second.visibility = Visibility::Public; - workList.push_back(member); - inserted = true; } + workList.push_back(member); } - return inserted; }; // TODO: Consider Tags as well, but they should store HeapTypes instead of diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index d045dbc1a62..91f61bc151e 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1519,3 +1519,58 @@ ) ) ) + +;; Regression test for a bug (#7103) in which the set of public types was +;; computed incorrectly, leading to an assertion failure. +(module + ;; CHECK: (type $1 (sub (struct (field (mut (ref null $1)))))) + (type $1 (sub (struct (field (mut (ref null $1)))))) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $7 (sub (struct (field (ref $1))))) + + ;; CHECK: (type $8 (sub (func))) + + ;; CHECK: (type $3 (func (result (ref null $8)))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $5 (sub (struct (field (ref $3))))) + (type $5 (sub (struct (field (ref func))))) + ;; CHECK: (type $6 (sub $1 (struct (field (mut (ref null $1)))))) + (type $6 (sub $1 (struct (field (mut (ref null $1)))))) + ) + (rec + (type $7 (sub (struct (field (ref $1))))) + (type $8 (sub (func))) + ) + ;; CHECK: (type $9 (sub $6 (struct (field (mut (ref null $1)))))) + (type $9 (sub $6 (struct (field (mut (ref null $1)))))) + + ;; CHECK: (elem declare func $1) + + ;; CHECK: (export "func" (func $1)) + (export "func" (func $1)) + + ;; CHECK: (func $1 (type $3) (result (ref null $8)) + ;; CHECK-NEXT: (local $l (ref $9)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $5 0 + ;; CHECK-NEXT: (struct.new $5 + ;; CHECK-NEXT: (ref.func $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + (func $1 (result (ref null $8)) + (local $l (ref $9)) + (drop + (struct.get $5 0 + (struct.new $5 + (ref.func $1) + ) + ) + ) + (ref.null $8) + ) +) From ad3735ae7def0923c0f04738c6cba3f86634e865 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 22 Nov 2024 14:21:15 -0800 Subject: [PATCH 152/622] Print unreachable loads with valid types (#7108) Since Load expressions use their `type` field to encode the type of the loaded value, unreachable loads need to come up with some other valid type to print. Previously we always chose i32 as that type, but that's not valid when the load was originally a v128 load with an alignment of 8, since 8 is greater than the maximum valid alignment of 4 for an i32. Fix the problem by taking alignment into account when choosing a type for the unreachable load. --- src/passes/Print.cpp | 10 +++++++++- test/lit/wat-kitchen-sink.wast | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 5c8463f78fa..0b15477ee9f 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -106,6 +106,14 @@ static Type forceConcrete(Type type) { return type.isConcrete() ? type : Type::i32; } +// Whatever type we print must be valid for the alignment. +static Type forceConcrete(Type type, Index align) { + return type.isConcrete() ? type + : align >= 16 ? Type::v128 + : align >= 8 ? Type::i64 + : Type::i32; +} + struct PrintSExpression : public UnifiedExpressionVisitor { std::ostream& o; unsigned indent = 0; @@ -538,7 +546,7 @@ struct PrintExpressionContents curr->name.print(o); } void visitLoad(Load* curr) { - prepareColor(o) << forceConcrete(curr->type); + prepareColor(o) << forceConcrete(curr->type, curr->align); if (curr->isAtomic) { o << ".atomic"; } diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index 719cf2db383..52cb2ca1d5c 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -3171,6 +3171,23 @@ drop ) + ;; CHECK: (func $load-v128-unreachable (type $0) + ;; CHECK-NEXT: (v128.load $mimport$0 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.load $mimport$0 align=8 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $load-v128-unreachable + unreachable + v128.load align=16 + unreachable + v128.load align=8 + unreachable + ) + ;; CHECK: (func $store (type $7) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (i32.store $mimport$0 offset=42 align=1 ;; CHECK-NEXT: (local.get $0) @@ -3660,7 +3677,7 @@ (func $ref-func ref.func $ref-func drop - ref.func 161 + ref.func 162 drop ) From 013a8d346807da751fec283eddf86aee9ea28382 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 22 Nov 2024 14:21:54 -0800 Subject: [PATCH 153/622] Print castType for unreachable br_on_cast{_fail} (#7107) I forgot that there is a validation rule that the output type for br_on_cast and br_on_cast_fail must be a subtype of the input type. We were previously printing bottom input types in cases where the cast operand was unreachable, but that's only valid if the cast type is the same bottom type. Instead print the most precise valid input type, which is the cast type itself. --- src/passes/Print.cpp | 10 ++++------ test/lit/wat-kitchen-sink.wast | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 0b15477ee9f..bbf5f2a6bea 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2229,10 +2229,9 @@ struct PrintExpressionContents o << ' '; if (curr->ref->type == Type::unreachable) { // Need to print some reference type in the correct hierarchy rather - // than unreachable, and the bottom type is valid in the most - // contexts. - printType( - Type(curr->castType.getHeapType().getBottom(), NonNullable)); + // than unreachable, and the cast type itself is the best possible + // option. + printType(curr->castType); } else { printType(curr->ref->type); } @@ -2244,8 +2243,7 @@ struct PrintExpressionContents curr->name.print(o); o << ' '; if (curr->ref->type == Type::unreachable) { - printType( - Type(curr->castType.getHeapType().getBottom(), NonNullable)); + printType(curr->castType); } else { printType(curr->ref->type); } diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index 52cb2ca1d5c..33d2e1d621b 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -4086,7 +4086,7 @@ ;; CHECK-NEXT: (block $block (result i31ref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref any)) - ;; CHECK-NEXT: (br_on_cast $block (ref none) i31ref + ;; CHECK-NEXT: (br_on_cast $block i31ref i31ref ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4136,7 +4136,7 @@ ;; CHECK-NEXT: (block $block (result (ref any)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i31ref) - ;; CHECK-NEXT: (br_on_cast_fail $block (ref none) i31ref + ;; CHECK-NEXT: (br_on_cast_fail $block i31ref i31ref ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From ca61aeeb87e330635548ce6368ac053576ee994c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 22 Nov 2024 14:44:36 -0800 Subject: [PATCH 154/622] Remove AutoDrop (#7106) The only internal use was in wasm2js, which doesn't need it. Fix API tests to explicitly drop expressions as necessary. --- CHANGELOG.md | 1 + src/binaryen-c.cpp | 6 - src/binaryen-c.h | 5 - src/ir/utils.h | 88 - src/js/binaryen.js-post.js | 3 - src/wasm/wasm-validator.cpp | 3 +- src/wasm2js.h | 1 - test/binaryen.js/kitchen-sink.js | 18 +- test/binaryen.js/kitchen-sink.js.txt | 2109 +---------------- test/example/c-api-kitchen-sink.c | 13 +- test/example/c-api-kitchen-sink.txt | 12 +- .../example/c-api-relooper-unreachable-if.cpp | 1 - test/example/c-api-unused-mem.cpp | 1 - test/wasm2js/br_table_temp.2asm.js | 3 +- test/wasm2js/emscripten.2asm.js | 1 + test/wasm2js/labels.2asm.js | 6 +- test/wasm2js/refs.2asm.js | 4 +- test/wasm2js/switch.2asm.js | 4 +- test/wasm2js/unreachable-later.2asm.js | 2 +- 19 files changed, 41 insertions(+), 2240 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c45eac79da..324c29526f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Current Trunk ------------- - BinaryenSelect no longer takes a type parameter. + - AutoDrop APIs have been removed. v120 ---- diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index a278f77780a..4294d56ea64 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -5496,12 +5496,6 @@ void BinaryenModuleRunPasses(BinaryenModuleRef module, passRunner.run(); } -void BinaryenModuleAutoDrop(BinaryenModuleRef module) { - auto* wasm = (Module*)module; - PassRunner runner(wasm, globalPassOptions); - AutoDrop().run(&runner, wasm); -} - static BinaryenBufferSizes writeModule(BinaryenModuleRef module, char* output, size_t outputSize, diff --git a/src/binaryen-c.h b/src/binaryen-c.h index d24b2bb56df..8616571db59 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -3045,11 +3045,6 @@ BINARYEN_API void BinaryenModuleRunPasses(BinaryenModuleRef module, const char** passes, BinaryenIndex numPasses); -// Auto-generate drop() operations where needed. This lets you generate code -// without worrying about where they are needed. (It is more efficient to do it -// yourself, but simpler to use autodrop). -BINARYEN_API void BinaryenModuleAutoDrop(BinaryenModuleRef module); - // Serialize a module into binary form. Uses the currently set global debugInfo // option. // @return how many bytes were written. This will be less than or equal to diff --git a/src/ir/utils.h b/src/ir/utils.h index 72aa701bb77..8051cb1a348 100644 --- a/src/ir/utils.h +++ b/src/ir/utils.h @@ -181,94 +181,6 @@ struct ReFinalizeNode : public OverriddenVisitor { } }; -// 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> { - bool isFunctionParallel() override { return true; } - - std::unique_ptr create() override { - return std::make_unique(); - } - - AutoDrop() { name = "autodrop"; } - - bool maybeDrop(Expression*& child) { - bool acted = false; - if (child->type.isConcrete()) { - expressionStack.push_back(child); - if (!ExpressionAnalyzer::isResultUsed(expressionStack, getFunction()) && - !ExpressionAnalyzer::isResultDropped(expressionStack)) { - child = Builder(*getModule()).makeDrop(child); - acted = true; - } - expressionStack.pop_back(); - } - return acted; - } - - void reFinalize() { ReFinalizeNode::updateStack(expressionStack); } - - void visitBlock(Block* curr) { - if (curr->list.size() == 0) { - return; - } - for (Index i = 0; i < curr->list.size() - 1; i++) { - auto* child = curr->list[i]; - if (child->type.isConcrete()) { - curr->list[i] = Builder(*getModule()).makeDrop(child); - } - } - if (maybeDrop(curr->list.back())) { - reFinalize(); - assert(curr->type == Type::none || curr->type == Type::unreachable); - } - } - - void visitIf(If* curr) { - bool acted = false; - if (maybeDrop(curr->ifTrue)) { - acted = true; - } - if (curr->ifFalse) { - if (maybeDrop(curr->ifFalse)) { - acted = true; - } - } - if (acted) { - reFinalize(); - assert(curr->type == Type::none); - } - } - - void visitTry(Try* curr) { - bool acted = false; - if (maybeDrop(curr->body)) { - acted = true; - } - for (auto* catchBody : curr->catchBodies) { - if (maybeDrop(catchBody)) { - acted = true; - } - } - if (acted) { - reFinalize(); - assert(curr->type == Type::none); - } - } - - void doWalkFunction(Function* curr) { - ReFinalize().walkFunctionInModule(curr, getModule()); - walk(curr->body); - if (curr->getResults() == Type::none && curr->body->type.isConcrete()) { - curr->body = Builder(*getModule()).makeDrop(curr->body); - } - ReFinalize().walkFunctionInModule(curr, getModule()); - } -}; - struct I64Utilities { static Expression* recreateI64(Builder& builder, Expression* low, Expression* high) { diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index a90b79d4916..c8d8e31ba58 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -2674,9 +2674,6 @@ function wrapModule(module, self = {}) { Module['_BinaryenFunctionRunPasses'](func, module, i32sToStack(passes.map(strToStack)), passes.length) ); }; - self['autoDrop'] = function() { - return Module['_BinaryenModuleAutoDrop'](module); - }; self['dispose'] = function() { Module['_BinaryenModuleDispose'](module); }; diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 516bb86e15e..64c7fda02ab 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -720,8 +720,7 @@ void FunctionValidator::validateNormalBlockElements(Block* curr) { if (!shouldBeTrue( !curr->list[i]->type.isConcrete(), curr, - "non-final block elements returning a value must be drop()ed " - "(binaryen's autodrop option might help you)") && + "non-final block elements returning a value must be dropped") && !info.quiet) { getStream() << "(on index " << i << ":\n" << curr->list[i] << "\n), type: " << curr->list[i]->type diff --git a/src/wasm2js.h b/src/wasm2js.h index d965dcc0cca..f819089482f 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -361,7 +361,6 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { // First, do the lowering to a JS-friendly subset. { PassRunner runner(wasm, options); - runner.add(std::make_unique()); // TODO: only legalize if necessary - emscripten would already do so, and // likely other toolchains. but spec test suite needs that. runner.add("legalize-js-interface"); diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index 6e6808ab8eb..acef392c377 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -559,7 +559,7 @@ function test_core() { // All the rest module.block('', []), // block with no name module.if(temp1, temp2, temp3), - module.if(temp4, temp5), + module.if(temp4, module.drop(temp5)), module.loop("in", makeInt32(0)), module.loop(null, makeInt32(0)), module.break("the-value", temp6, temp7), @@ -686,6 +686,14 @@ function test_core() { console.log("getExpressionInfo=" + JSON.stringify(cleanInfo(binaryen.getExpressionInfo(valueList[3])))); console.log(binaryen.emitText(valueList[3])); // test printing a standalone expression + // Add drops of concrete expressions, except the last. + for (var i = 0; i < valueList.length - 1; i++) { + var type = binaryen.Expression.getType(valueList[i]); + if (type != binaryen.none && type != binaryen.unreachable) { + valueList[i] = module.drop(valueList[i]); + } + } + console.log("getExpressionInfo(i32.const)=" + JSON.stringify(binaryen.getExpressionInfo(module.i32.const(5)))); console.log("getExpressionInfo(i64.const)=" + JSON.stringify(binaryen.getExpressionInfo(module.i64.const(6, 7)))); console.log("getExpressionInfo(f32.const)=" + JSON.stringify(binaryen.getExpressionInfo(module.f32.const(8.5)))); @@ -698,10 +706,10 @@ function test_core() { } // Make the main body of the function. and one block with a return value, one without - var value = module.block("the-value", valueList); + var value = module.block("the-value", valueList, binaryen.i32); var droppedValue = module.drop(value); var nothing = module.block("the-nothing", [ droppedValue ]); - var body = module.block("the-body", [ nothing, makeInt32(42) ]); + var body = module.block("the-body", [ nothing, makeInt32(42) ], binaryen.i32); // Create the function var sinker = module.addFunction("kitchen()sinker", iIfF, binaryen.i32, [ binaryen.i32 ], body); @@ -749,13 +757,9 @@ function test_core() { var starter = module.addFunction("starter", binaryen.none, binaryen.none, [], module.nop()); module.setStart(starter); - // A bunch of our code needs drop, auto-add it - module.autoDrop(); - var features = binaryen.Features.All; module.setFeatures(features); assert(module.getFeatures() == features); - console.log(module.emitText()); // Verify it validates assert(module.validate()); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index d41301751d5..d38c168571e 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -1927,2116 +1927,13 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} ) (block ) - (if - (i32.const 1) - (then - (drop - (i32.const 2) - ) - ) - (else - (drop - (i32.const 3) - ) - ) - ) - (if - (i32.const 4) - (then - (drop - (i32.const 5) - ) - ) - ) - (drop - (loop $in (result i32) - (i32.const 0) - ) - ) - (drop - (loop (result i32) - (i32.const 0) - ) - ) - (drop - (br_if $the-value - (i32.const 1) - (i32.const 0) - ) - ) - (br_if $the-nothing - (i32.const 2) - ) - (br $the-value - (i32.const 3) - ) - (br $the-nothing) - (br_table $the-value $the-value - (i32.const 1) - (i32.const 0) - ) - (br_table $the-nothing $the-nothing - (i32.const 2) - ) - (drop - (i32.eqz - (call $"kitchen()sinker" - (i32.const 13) - (i64.const 37) - (f32.const 1.2999999523162842) - (f64.const 3.7) - ) - ) - ) - (drop - (i32.eqz - (i32.trunc_f32_s - (call $an-imported - (i32.const 13) - (f64.const 3.7) - ) - ) - ) - ) - (drop - (i32.eqz - (call_indirect $t0 (type $0) - (i32.const 13) - (i64.const 37) - (f32.const 1.2999999523162842) - (f64.const 3.7) - (i32.const 2449) - ) - ) - ) - (drop - (local.get $0) - ) - (local.set $0 - (i32.const 101) - ) - (drop - (local.tee $0 - (i32.const 102) - ) - ) - (drop - (i32.load - (i32.const 1) - ) - ) - (drop - (i64.load16_s offset=2 align=1 - (i32.const 8) - ) - ) - (drop - (f32.load - (i32.const 2) - ) - ) - (drop - (f64.load offset=2 - (i32.const 9) - ) - ) - (i32.store - (i32.const 10) - (i32.const 11) - ) - (i64.store offset=2 align=4 - (i32.const 110) - (i64.const 111) - ) - (drop - (select - (i32.const 3) - (i32.const 5) - (i32.const 1) - ) - ) - (return - (i32.const 1337) - ) - (return_call $"kitchen()sinker" - (i32.const 13) - (i64.const 37) - (f32.const 1.2999999523162842) - (f64.const 3.7) - ) - (return_call_indirect $t0 (type $0) - (i32.const 13) - (i64.const 37) - (f32.const 1.2999999523162842) - (f64.const 3.7) - (i32.const 2449) - ) - (drop - (ref.is_null - (ref.null noextern) - ) - ) - (drop - (ref.is_null - (ref.null nofunc) - ) - ) - (drop - (ref.is_null - (ref.func $"kitchen()sinker") - ) - ) - (drop - (select (result funcref) - (ref.null nofunc) - (ref.func $"kitchen()sinker") - (i32.const 1) - ) - ) - (drop - (ref.eq - (ref.null none) - (ref.null none) - ) - ) - (try - (do - (throw $a-tag - (i32.const 0) - ) - ) - (catch $a-tag - (drop - (pop i32) - ) - ) - ) - (i32.atomic.store - (i32.const 0) - (i32.atomic.load - (i32.const 0) - ) - ) - (drop - (memory.atomic.wait32 - (i32.const 0) - (i32.const 0) - (i64.const 0) - ) - ) - (drop - (memory.atomic.notify - (i32.const 0) - (i32.const 0) - ) - ) - (atomic.fence) - (tuple.drop 4 - (tuple.make 4 - (i32.const 13) - (i64.const 37) - (f32.const 1.2999999523162842) - (f64.const 3.7) - ) - ) - (drop - (tuple.extract 4 2 - (tuple.make 4 - (i32.const 13) - (i64.const 37) - (f32.const 1.2999999523162842) - (f64.const 3.7) - ) - ) - ) - (drop - (pop i32) - ) - (drop - (pop i64) - ) - (drop - (pop f32) - ) - (drop - (pop f64) - ) - (drop - (pop v128) - ) - (drop - (pop funcref) - ) - (drop - (pop externref) - ) - (drop - (pop anyref) - ) - (drop - (pop eqref) - ) - (drop - (pop i31ref) - ) - (drop - (pop structref) - ) - (drop - (pop stringref) - ) - (drop - (memory.size) - ) - (drop - (memory.grow - (i32.const 0) - ) - ) - (drop - (ref.i31 - (i32.const 0) - ) - ) - (drop - (i31.get_s - (ref.i31 - (i32.const 1) - ) - ) - ) - (drop - (i31.get_u - (ref.i31 - (i32.const 2) - ) - ) - ) - (nop) - (unreachable) - ) - ) - ) - (i32.const 42) - ) - ) - (func $starter (type $3) - (nop) - ) -) - -(module - (type $0 (func (param i32 i64 f32 f64) (result i32))) - (type $1 (func (param i32))) - (type $2 (func (param i32 f64) (result f32))) - (type $3 (func)) - (import "module" "base" (global $a-global-imp i32)) - (import "module" "base" (global $a-mut-global-imp (mut i32))) - (import "module" "base" (func $an-imported (type $2) (param i32 f64) (result f32))) - (import "module" "base" (tag $a-tag-imp (param i32))) - (global $a-global i32 (i32.const 1)) - (memory $0 1 256 shared) - (data $x0 (i32.const 10) "hello, world") - (data $y1 "I am passive") - (table $t0 1 funcref) - (elem $e0 (i32.const 0) $"kitchen()sinker") - (tag $a-tag (param i32)) - (export "mem" (memory $0)) - (export "kitchen_sinker" (func $"kitchen()sinker")) - (export "a-global-exp" (global $a-global)) - (export "a-tag-exp" (tag $a-tag)) - (start $starter) - (func $"kitchen()sinker" (type $0) (param $0 i32) (param $1 i64) (param $2 f32) (param $3 f64) (result i32) - (local $4 i32) - (block $the-body (result i32) - (block $the-nothing - (drop - (block $the-value (result i32) - (drop - (i32.clz - (i32.const -10) - ) - ) - (drop - (i64.ctz - (i64.const -22) - ) - ) - (drop - (i32.popcnt - (i32.const -10) - ) - ) - (drop - (f32.neg - (f32.const -33.61199951171875) - ) - ) - (drop - (f64.abs - (f64.const -9005.841) - ) - ) - (drop - (f32.ceil - (f32.const -33.61199951171875) - ) - ) - (drop - (f64.floor - (f64.const -9005.841) - ) - ) - (drop - (f32.trunc - (f32.const -33.61199951171875) - ) - ) - (drop - (f32.nearest - (f32.const -33.61199951171875) - ) - ) - (drop - (f64.sqrt - (f64.const -9005.841) - ) - ) - (drop - (i32.eqz - (i32.const -10) - ) - ) - (drop - (i64.extend_i32_s - (i32.const -10) - ) - ) - (drop - (i64.extend_i32_u - (i32.const -10) - ) - ) - (drop - (i32.wrap_i64 - (i64.const -22) - ) - ) - (drop - (i32.trunc_f32_s - (f32.const -33.61199951171875) - ) - ) - (drop - (i64.trunc_f32_s - (f32.const -33.61199951171875) - ) - ) - (drop - (i32.trunc_f32_u - (f32.const -33.61199951171875) - ) - ) - (drop - (i64.trunc_f32_u - (f32.const -33.61199951171875) - ) - ) - (drop - (i32.trunc_f64_s - (f64.const -9005.841) - ) - ) - (drop - (i64.trunc_f64_s - (f64.const -9005.841) - ) - ) - (drop - (i32.trunc_f64_u - (f64.const -9005.841) - ) - ) - (drop - (i64.trunc_f64_u - (f64.const -9005.841) - ) - ) - (drop - (i32.trunc_sat_f32_s - (f32.const -33.61199951171875) - ) - ) - (drop - (i64.trunc_sat_f32_s - (f32.const -33.61199951171875) - ) - ) - (drop - (i32.trunc_sat_f32_u - (f32.const -33.61199951171875) - ) - ) - (drop - (i64.trunc_sat_f32_u - (f32.const -33.61199951171875) - ) - ) - (drop - (i32.trunc_sat_f64_s - (f64.const -9005.841) - ) - ) - (drop - (i64.trunc_sat_f64_s - (f64.const -9005.841) - ) - ) - (drop - (i32.trunc_sat_f64_u - (f64.const -9005.841) - ) - ) - (drop - (i64.trunc_sat_f64_u - (f64.const -9005.841) - ) - ) - (drop - (i32.reinterpret_f32 - (f32.const -33.61199951171875) - ) - ) - (drop - (i64.reinterpret_f64 - (f64.const -9005.841) - ) - ) - (drop - (f32.convert_i32_s - (i32.const -10) - ) - ) - (drop - (f64.convert_i32_s - (i32.const -10) - ) - ) - (drop - (f32.convert_i32_u - (i32.const -10) - ) - ) - (drop - (f64.convert_i32_u - (i32.const -10) - ) - ) - (drop - (f32.convert_i64_s - (i64.const -22) - ) - ) - (drop - (f64.convert_i64_s - (i64.const -22) - ) - ) - (drop - (f32.convert_i64_u - (i64.const -22) - ) - ) - (drop - (f64.convert_i64_u - (i64.const -22) - ) - ) - (drop - (f64.promote_f32 - (f32.const -33.61199951171875) - ) - ) - (drop - (f32.demote_f64 - (f64.const -9005.841) - ) - ) - (drop - (f32.reinterpret_i32 - (i32.const -10) - ) - ) - (drop - (f64.reinterpret_i64 - (i64.const -22) - ) - ) - (drop - (i8x16.splat - (i32.const 42) - ) - ) - (drop - (i16x8.splat - (i32.const 42) - ) - ) - (drop - (i32x4.splat - (i32.const 42) - ) - ) - (drop - (i64x2.splat - (i64.const 1958505087099) - ) - ) - (drop - (f32x4.splat - (f32.const 42) - ) - ) - (drop - (f64x2.splat - (f64.const 42) - ) - ) - (drop - (v128.not - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.any_true - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.popcnt - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.abs - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.neg - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.all_true - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.bitmask - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.abs - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.neg - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.all_true - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.bitmask - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extadd_pairwise_i8x16_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extadd_pairwise_i8x16_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.abs - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.neg - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.all_true - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.bitmask - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extadd_pairwise_i16x8_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extadd_pairwise_i16x8_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.abs - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.neg - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.all_true - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.bitmask - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.abs - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.neg - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.sqrt - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.abs - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.neg - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.sqrt - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.convert_low_i32x4_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.convert_low_i32x4_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.promote_low_f32x4 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.trunc_sat_f32x4_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.trunc_sat_f32x4_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.convert_i32x4_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.convert_i32x4_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.demote_f64x2_zero - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extend_low_i8x16_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extend_high_i8x16_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extend_low_i8x16_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extend_high_i8x16_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extend_low_i16x8_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extend_high_i16x8_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extend_low_i16x8_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extend_high_i16x8_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.trunc_sat_f64x2_s_zero - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.trunc_sat_f64x2_u_zero - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extend_low_i32x4_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extend_high_i32x4_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extend_low_i32x4_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extend_high_i32x4_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32.add - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (f64.sub - (f64.const -9005.841) - (f64.const -9007.333) - ) - ) - (drop - (i32.div_s - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i64.div_u - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (i64.rem_s - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (i32.rem_u - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i32.and - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i64.or - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (i32.xor - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i64.shl - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (i64.shr_u - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (i32.shr_s - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i32.rotl - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i64.rotr - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (f32.div - (f32.const -33.61199951171875) - (f32.const -62.5) - ) - ) - (drop - (f64.copysign - (f64.const -9005.841) - (f64.const -9007.333) - ) - ) - (drop - (f32.min - (f32.const -33.61199951171875) - (f32.const -62.5) - ) - ) - (drop - (f64.max - (f64.const -9005.841) - (f64.const -9007.333) - ) - ) - (drop - (i32.eq - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (f32.ne - (f32.const -33.61199951171875) - (f32.const -62.5) - ) - ) - (drop - (i32.lt_s - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i64.lt_u - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (i64.le_s - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (i32.le_u - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i64.gt_s - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (i32.gt_u - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i32.ge_s - (i32.const -10) - (i32.const -11) - ) - ) - (drop - (i64.ge_u - (i64.const 4294967274) - (i64.const 4294967273) - ) - ) - (drop - (f32.lt - (f32.const -33.61199951171875) - (f32.const -62.5) - ) - ) - (drop - (f64.le - (f64.const -9005.841) - (f64.const -9007.333) - ) - ) - (drop - (f64.gt - (f64.const -9005.841) - (f64.const -9007.333) - ) - ) - (drop - (f32.ge - (f32.const -33.61199951171875) - (f32.const -62.5) - ) - ) - (drop - (i8x16.eq - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.ne - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.lt_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.lt_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.gt_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.gt_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.le_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.le_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.ge_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.ge_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.eq - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.ne - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.lt_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.lt_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.gt_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.gt_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.le_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.le_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.ge_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.ge_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.eq - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.ne - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.lt_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.lt_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.gt_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.gt_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.le_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.le_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.ge_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.ge_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.eq - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.ne - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.lt_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.gt_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.le_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.ge_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.eq - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.ne - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.lt - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.gt - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.le - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.ge - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.eq - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.ne - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.lt - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.gt - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.le - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.ge - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.and - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.or - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.xor - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.andnot - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.add - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.add_sat_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.add_sat_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.sub - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.sub_sat_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.sub_sat_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.min_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.min_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.max_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.max_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.avgr_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.add - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.add_sat_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.add_sat_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.sub - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.sub_sat_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.sub_sat_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.mul - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.min_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.min_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.max_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.max_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.avgr_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.q15mulr_sat_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extmul_low_i8x16_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extmul_high_i8x16_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extmul_low_i8x16_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extmul_high_i8x16_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.add - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.sub - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.mul - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.min_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.min_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.max_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.max_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.dot_i16x8_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extmul_low_i16x8_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extmul_high_i16x8_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extmul_low_i16x8_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extmul_high_i16x8_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.add - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.sub - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.mul - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extmul_low_i32x4_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extmul_high_i32x4_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extmul_low_i32x4_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extmul_high_i32x4_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.add - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.sub - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.mul - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.div - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.min - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.max - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.pmin - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.pmax - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.ceil - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.floor - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.trunc - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.nearest - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.add - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.sub - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.mul - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.div - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.min - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.max - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.pmin - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.pmax - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.ceil - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.floor - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.trunc - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.nearest - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.narrow_i16x8_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.narrow_i16x8_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.narrow_i32x4_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.narrow_i32x4_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.swizzle - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.extract_lane_s 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i8x16.extract_lane_u 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extract_lane_s 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.extract_lane_u 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i32x4.extract_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i64x2.extract_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f32x4.extract_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (f64x2.extract_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (i16x8.replace_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 42) - ) - ) - (drop - (i8x16.replace_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 42) - ) - ) - (drop - (i32x4.replace_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 42) - ) - ) - (drop - (i64x2.replace_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i64.const 184683593770) - ) - ) - (drop - (f32x4.replace_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (f32.const 42) - ) - ) (drop - (f64x2.replace_lane 1 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (f64.const 42) - ) - ) - (drop - (i8x16.shl - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i8x16.shr_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i8x16.shr_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i16x8.shl - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i16x8.shr_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i16x8.shr_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i32x4.shl - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i32x4.shr_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i32x4.shr_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i64x2.shl - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i64x2.shr_s - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (i32.const 1) - ) - ) - (drop - (i64x2.shr_u - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) + (if (result i32) (i32.const 1) - ) - ) - (drop - (v128.load8_splat - (i32.const 128) - ) - ) - (drop - (v128.load16_splat offset=16 align=1 - (i32.const 128) - ) - ) - (drop - (v128.load32_splat offset=16 - (i32.const 128) - ) - ) - (drop - (v128.load64_splat align=4 - (i32.const 128) - ) - ) - (drop - (v128.load8x8_s - (i32.const 128) - ) - ) - (drop - (v128.load8x8_u - (i32.const 128) - ) - ) - (drop - (v128.load16x4_s - (i32.const 128) - ) - ) - (drop - (v128.load16x4_u - (i32.const 128) - ) - ) - (drop - (v128.load32x2_s - (i32.const 128) - ) - ) - (drop - (v128.load32x2_u - (i32.const 128) - ) - ) - (drop - (v128.load32_zero - (i32.const 128) - ) - ) - (drop - (v128.load64_zero - (i32.const 128) - ) - ) - (drop - (v128.load8_lane 0 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.load8_lane offset=1 15 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.load16_lane 0 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.load16_lane offset=2 align=1 7 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.load32_lane 0 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.load32_lane offset=4 align=2 3 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.load64_lane 0 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.load64_lane offset=8 align=4 1 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (v128.store8_lane 0 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - (v128.store8_lane offset=1 15 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - (v128.store16_lane 0 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - (v128.store16_lane offset=2 align=1 7 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - (v128.store32_lane 0 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - (v128.store32_lane offset=4 align=2 3 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - (v128.store64_lane 0 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - (v128.store64_lane offset=8 align=4 1 - (i32.const 128) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - (drop - (i8x16.shuffle 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (drop - (v128.bitselect - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - (v128.const i32x4 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d) - ) - ) - (memory.init $x0 - (i32.const 1024) - (i32.const 0) - (i32.const 12) - ) - (data.drop $x0) - (memory.copy - (i32.const 2048) - (i32.const 1024) - (i32.const 12) - ) - (memory.fill - (i32.const 0) - (i32.const 42) - (i32.const 1024) - ) - (block - ) - (if - (i32.const 1) - (then - (drop + (then (i32.const 2) ) - ) - (else - (drop + (else (i32.const 3) ) ) diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index a6a196ae6df..93a6281f885 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -973,7 +973,7 @@ void test_core() { // All the rest BinaryenBlock(module, NULL, NULL, 0, -1), // block with no name and no type BinaryenIf(module, temp1, temp2, temp3), - BinaryenIf(module, temp4, temp5, NULL), + BinaryenIf(module, temp4, BinaryenDrop(module, temp5), NULL), BinaryenLoop(module, "in", makeInt32(module, 0)), BinaryenLoop(module, NULL, makeInt32(module, 0)), BinaryenBreak(module, "the-value", temp6, temp7), @@ -1232,6 +1232,14 @@ void test_core() { BinaryenExpressionPrint( valueList[3]); // test printing a standalone expression + // Add drops of concrete expressions + for (int i = 0; i < sizeof(valueList) / sizeof(valueList[0]); ++i) { + BinaryenType type = BinaryenExpressionGetType(valueList[i]); + if (type != BinaryenTypeNone() && type != BinaryenTypeUnreachable()) { + valueList[i] = BinaryenDrop(module, valueList[i]); + } + } + // Make the main body of the function. and one block with a return value, one // without BinaryenExpressionRef value = @@ -1360,9 +1368,6 @@ void test_core() { BinaryenNop(module)); BinaryenSetStart(module, starter); - // A bunch of our code needs drop(), auto-add it - BinaryenModuleAutoDrop(module); - BinaryenFeatures features = BinaryenFeatureAll(); BinaryenModuleSetFeatures(module, features); assert(BinaryenModuleGetFeatures(module) == features); diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 657999a55d1..8a5b6dc87b7 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -1964,15 +1964,13 @@ BinaryenFeatureAll: 524287 ) (block ) - (if - (i32.const 1) - (then - (drop + (drop + (if (result i32) + (i32.const 1) + (then (i32.const 2) ) - ) - (else - (drop + (else (i32.const 3) ) ) diff --git a/test/example/c-api-relooper-unreachable-if.cpp b/test/example/c-api-relooper-unreachable-if.cpp index 96c6c114976..8723aea83f9 100644 --- a/test/example/c-api-relooper-unreachable-if.cpp +++ b/test/example/c-api-relooper-unreachable-if.cpp @@ -13,7 +13,6 @@ int main() { RelooperRef the_relooper = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); - BinaryenModuleAutoDrop(the_module); { const char* segmentNames[] = {"0"}; const char* segmentDatas[] = {0}; diff --git a/test/example/c-api-unused-mem.cpp b/test/example/c-api-unused-mem.cpp index 750f3b703bc..f8bce00743a 100644 --- a/test/example/c-api-unused-mem.cpp +++ b/test/example/c-api-unused-mem.cpp @@ -14,7 +14,6 @@ int main() { RelooperRef the_relooper = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); - BinaryenModuleAutoDrop(the_module); { const char* segmentNames[] = {"0"}; const char* segmentDatas[] = {0}; diff --git a/test/wasm2js/br_table_temp.2asm.js b/test/wasm2js/br_table_temp.2asm.js index 01a2238e146..245792eaf68 100644 --- a/test/wasm2js/br_table_temp.2asm.js +++ b/test/wasm2js/br_table_temp.2asm.js @@ -196,6 +196,7 @@ function asmFunc(imports) { function $14($0_1) { $0_1 = $0_1 | 0; + var $2_1 = 0; block1 : { switch ($0_1 | 0) { case 0: @@ -12577,7 +12578,7 @@ function asmFunc(imports) { } function $21() { - var $1_1 = 0; + var $1_1 = 0, $4_1 = 0, $2_1 = 0; label : { dummy(); $1_1 = 5; diff --git a/test/wasm2js/emscripten.2asm.js b/test/wasm2js/emscripten.2asm.js index 8ba8564ceea..24a556b25d7 100644 --- a/test/wasm2js/emscripten.2asm.js +++ b/test/wasm2js/emscripten.2asm.js @@ -192,6 +192,7 @@ function asmFunc(imports) { function bools(x) { x = x | 0; + var $32 = 0; bools((HEAPU8[0 >> 0] | 0) & 1 | 0 | 0) | 0; bools((HEAP8[0 >> 0] | 0) & 1 | 0 | 0) | 0; bools((HEAPU16[0 >> 1] | 0) & 1 | 0 | 0) | 0; diff --git a/test/wasm2js/labels.2asm.js b/test/wasm2js/labels.2asm.js index 431e172d9f0..37eb9fccb5e 100644 --- a/test/wasm2js/labels.2asm.js +++ b/test/wasm2js/labels.2asm.js @@ -20,7 +20,7 @@ function asmFunc(imports) { } function $1() { - var i = 0, $6_1 = 0; + var i = 0, $6_1 = 0, $9_1 = 0, $7_1 = 0; i = 0; exit : { cont : while (1) { @@ -36,7 +36,7 @@ function asmFunc(imports) { } function $2() { - var i = 0, $8_1 = 0; + var i = 0, $8_1 = 0, $13_1 = 0, $11_1 = 0; i = 0; exit : { cont : while (1) { @@ -74,7 +74,7 @@ function asmFunc(imports) { function $4(max) { max = max | 0; - var i = 0, $9_1 = 0; + var i = 0, $9_1 = 0, $12_1 = 0, $10_1 = 0; i = 1; exit : { cont : while (1) { diff --git a/test/wasm2js/refs.2asm.js b/test/wasm2js/refs.2asm.js index 45e510e3759..c105b1c1cbf 100644 --- a/test/wasm2js/refs.2asm.js +++ b/test/wasm2js/refs.2asm.js @@ -52,13 +52,13 @@ function asmFunc(imports) { function funcref_temps($0, $1) { $1 = +$1; - var $2 = null, $3 = null, wasm2js_funcref$0 = null, wasm2js_funcref$1 = null, wasm2js_i32$0 = 0; + var $2 = null, $3 = null; $2 = $0; loop : while (1) { $3 = funcref_temps; break loop; }; - funcref_temps(funcref_temps, +(+((wasm2js_funcref$0 = $2, wasm2js_funcref$1 = $3 || wasm2js_trap(), wasm2js_i32$0 = 0, wasm2js_i32$0 ? wasm2js_funcref$0 : wasm2js_funcref$1) == null | 0))); + funcref_temps(funcref_temps, +(+((0 ? $2 : $3) == null | 0))); } function named_type_temps() { diff --git a/test/wasm2js/switch.2asm.js b/test/wasm2js/switch.2asm.js index 8c4a1f0dd93..2d3dcdcec45 100644 --- a/test/wasm2js/switch.2asm.js +++ b/test/wasm2js/switch.2asm.js @@ -16,7 +16,7 @@ function asmFunc(imports) { var i64toi32_i32$HIGH_BITS = 0; function $0(i) { i = i | 0; - var j = 0; + var j = 0, $7 = 0; j = 100; switch_ : { $7 : { @@ -49,7 +49,7 @@ function asmFunc(imports) { function $1(i, i$hi) { i = i | 0; i$hi = i$hi | 0; - var i64toi32_i32$5 = 0, i64toi32_i32$2 = 0, $7 = 0, $7$hi = 0, j = 0, j$hi = 0; + var i64toi32_i32$5 = 0, i64toi32_i32$2 = 0, $7 = 0, $7$hi = 0, j = 0, j$hi = 0, $10$hi = 0, $10 = 0; j = 100; j$hi = 0; switch_ : { diff --git a/test/wasm2js/unreachable-later.2asm.js b/test/wasm2js/unreachable-later.2asm.js index e5bc7d51661..bf1ed59f10c 100644 --- a/test/wasm2js/unreachable-later.2asm.js +++ b/test/wasm2js/unreachable-later.2asm.js @@ -15,7 +15,7 @@ function asmFunc(imports) { var global$0 = 10; function $0($0_1) { $0_1 = $0_1 | 0; - var $15 = Math_fround(0), $21 = 0, $29 = 0, $26 = 0; + var $15 = Math_fround(0), $21 = 0, $29 = 0, $26 = 0, $32 = 0; if (global$0) { return $0_1 | 0 } From 3d394018fe30e5d7ea153c975a158b95c1720393 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 25 Nov 2024 09:33:10 -0800 Subject: [PATCH 155/622] [GC] Refinalize after selectify in RemoveUnusedBrs (#7104) Replacing an if with a select may have refined the type. Without this fix, the sharper stale type checks complain. --- src/passes/RemoveUnusedBrs.cpp | 14 +++++++++-- test/lit/passes/remove-unused-brs-gc.wast | 29 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 96db281d87f..44549f68dff 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -1146,6 +1146,7 @@ struct RemoveUnusedBrs : public WalkerPass> { PassOptions& passOptions; bool needUniqify = false; + bool refinalize = false; FinalOptimizer(PassOptions& passOptions) : passOptions(passOptions) {} @@ -1419,8 +1420,14 @@ struct RemoveUnusedBrs : public WalkerPass> { if (condition.invalidates(ifTrue) || condition.invalidates(ifFalse)) { return nullptr; } - return Builder(*getModule()) - .makeSelect(iff->condition, iff->ifTrue, iff->ifFalse); + auto* select = Builder(*getModule()) + .makeSelect(iff->condition, iff->ifTrue, iff->ifFalse); + if (select->type != iff->type) { + // If the select is more refined than the if it replaces, we must + // propagate that outwards. + refinalize = true; + } + return select; } void visitLocalSet(LocalSet* curr) { @@ -1793,6 +1800,9 @@ struct RemoveUnusedBrs : public WalkerPass> { if (finalOptimizer.needUniqify) { wasm::UniqueNameMapper::uniquify(func->body); } + if (finalOptimizer.refinalize) { + ReFinalize().walkFunctionInModule(func, getModule()); + } } }; diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 0a9e0885873..fa7a6d72739 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -864,4 +864,33 @@ ) ) ) + + ;; CHECK: (func $select-refinalize (type $13) (param $param (ref $struct)) (result (ref struct)) + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $select-refinalize (param $param (ref $struct)) (result (ref struct)) + ;; The inner if can turn into a select. The type then changes, allowing the + ;; outer select to be refined, which will error if we do not refinalize. + (select (result (ref struct)) + (if (result (ref struct)) + (i32.const 0) + (then + (struct.new_default $struct) + ) + (else + (struct.new_default $struct) + ) + ) + (local.get $param) + (i32.const 0) + ) + ) ) From a2a8d2a3a067a23c547b51b4544a933f77a1c03c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 25 Nov 2024 12:05:15 -0800 Subject: [PATCH 156/622] Handle unoptimized branches in CodeFolding (#7111) CodeFolding previously did not consider br_on_* instructions at all, so it would happily merge tails even if there were br_on_* branches to the same label with non-matching tails. Fix the bug by making any label targeted by any instruction not explicitly handled by CodeFolding unoptimizable. This will gracefully handle other branching instructions like `resume` and `resume_throw` as well. Folding these branches properly is left as future work. Also rename the test file from code-folding_enable-threads.wast to just code-folding.wast and enable all features instead of just threads. The old name was left over from when the test was originally ported to lit, and the new feature is necessary because the new test uses GC instructions. --- src/passes/CodeFolding.cpp | 22 ++++--- ..._enable-threads.wast => code-folding.wast} | 63 +++++++++++++++---- 2 files changed, 65 insertions(+), 20 deletions(-) rename test/lit/passes/{code-folding_enable-threads.wast => code-folding.wast} (87%) diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp index 21527da6b18..0cddec4ca3f 100644 --- a/src/passes/CodeFolding.cpp +++ b/src/passes/CodeFolding.cpp @@ -84,7 +84,9 @@ struct ExpressionMarker void visitExpression(Expression* expr) { marked.insert(expr); } }; -struct CodeFolding : public WalkerPass> { +struct CodeFolding + : public WalkerPass< + ControlFlowWalker>> { bool isFunctionParallel() override { return true; } std::unique_ptr create() override { @@ -138,6 +140,17 @@ struct CodeFolding : public WalkerPass> { // walking + void visitExpression(Expression* curr) { + // For any branching instruction not explicitly handled by this pass, mark + // the labels it branches to unoptimizable. + // TODO: Handle folding br_on* instructions. br_on_null could be folded with + // other kinds of branches and br_on_non_null, br_on_cast, and + // br_on_cast_fail instructions could be folded with other copies of + // themselves. + BranchUtils::operateOnScopeNameUses( + curr, [&](Name label) { unoptimizables.insert(label); }); + } + void visitBreak(Break* curr) { if (curr->condition || curr->value) { unoptimizables.insert(curr->name); @@ -155,13 +168,6 @@ struct CodeFolding : public WalkerPass> { } } - void visitSwitch(Switch* curr) { - for (auto target : curr->targets) { - unoptimizables.insert(target); - } - unoptimizables.insert(curr->default_); - } - void visitUnreachable(Unreachable* curr) { // we can only optimize if we are at the end of the parent block if (!controlFlowStack.empty()) { diff --git a/test/lit/passes/code-folding_enable-threads.wast b/test/lit/passes/code-folding.wast similarity index 87% rename from test/lit/passes/code-folding_enable-threads.wast rename to test/lit/passes/code-folding.wast index b07000278ed..35816748149 100644 --- a/test/lit/passes/code-folding_enable-threads.wast +++ b/test/lit/passes/code-folding.wast @@ -1,7 +1,7 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. -;; RUN: foreach %s %t wasm-opt --code-folding --enable-threads -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --code-folding -S -o - | filecheck %s (module ;; CHECK: (type $0 (func)) @@ -15,13 +15,13 @@ (memory $0 1 1) ;; CHECK: (table $0 282 282 funcref) - ;; CHECK: (func $0 + ;; CHECK: (func $0 (type $0) ;; CHECK-NEXT: (block $label$1 ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (block $label$3 - ;; CHECK-NEXT: (call_indirect (type $13) + ;; CHECK-NEXT: (call_indirect $0 (type $13) ;; CHECK-NEXT: (block $label$4 ;; CHECK-NEXT: (br $label$3) ;; CHECK-NEXT: ) @@ -52,7 +52,7 @@ ) ) ) - ;; CHECK: (func $negative-zero (result f32) + ;; CHECK: (func $negative-zero (type $1) (result f32) ;; CHECK-NEXT: (if (result f32) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -82,7 +82,7 @@ ) ) ) - ;; CHECK: (func $negative-zero-b (result f32) + ;; CHECK: (func $negative-zero-b (type $1) (result f32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -105,7 +105,7 @@ ) ) ) - ;; CHECK: (func $negative-zero-c (result f32) + ;; CHECK: (func $negative-zero-c (type $1) (result f32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -128,7 +128,7 @@ ) ) ) - ;; CHECK: (func $break-target-outside-of-return-merged-code + ;; CHECK: (func $break-target-outside-of-return-merged-code (type $0) ;; CHECK-NEXT: (block $label$A ;; CHECK-NEXT: (if ;; CHECK-NEXT: (unreachable) @@ -202,7 +202,7 @@ ) ) ) - ;; CHECK: (func $break-target-inside-all-good + ;; CHECK: (func $break-target-inside-all-good (type $0) ;; CHECK-NEXT: (block $folding-inner0 ;; CHECK-NEXT: (block $label$A ;; CHECK-NEXT: (if @@ -269,7 +269,7 @@ ) ) ) - ;; CHECK: (func $leave-inner-block-type + ;; CHECK: (func $leave-inner-block-type (type $0) ;; CHECK-NEXT: (block $label$1 ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $label$2 @@ -312,7 +312,7 @@ (memory $0 1 1 shared) ;; CHECK: (export "func_2224" (func $0)) (export "func_2224" (func $0)) - ;; CHECK: (func $0 (result i32) + ;; CHECK: (func $0 (type $0) (result i32) ;; CHECK-NEXT: (local $var$0 i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (i32.const 0) @@ -352,7 +352,7 @@ ;; CHECK: (global $global$0 (mut i32) (i32.const 10)) (global $global$0 (mut i32) (i32.const 10)) - ;; CHECK: (func $determinism + ;; CHECK: (func $determinism (type $0) ;; CHECK-NEXT: (block $folding-inner0 ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block $label$1 @@ -439,7 +439,7 @@ ) (unreachable) ) - ;; CHECK: (func $careful-of-the-switch (param $0 i32) + ;; CHECK: (func $careful-of-the-switch (type $1) (param $0 i32) ;; CHECK-NEXT: (block $label$1 ;; CHECK-NEXT: (block $label$3 ;; CHECK-NEXT: (block $label$5 @@ -482,3 +482,42 @@ ) ) ) + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (func $br-on-null (type $0) + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_null $block + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $br-on-null) + ;; CHECK-NEXT: (br $block) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $br-on-null) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-null + (block $block + (drop + ;; The other two tails are the same, but this br_on_null should inhibit code + ;; folding. + (br_on_null $block + (ref.null none) + ) + ) + (drop + (block (result i32) + (call $br-on-null) + (br $block) + ) + ) + (call $br-on-null) + ) + ) +) From 8265573e14182ee7fd95d78e9c04c435486be9dc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 25 Nov 2024 12:28:35 -0800 Subject: [PATCH 157/622] [Fuzzing] Maximize fuzzing of CompareVMs (#7113) This increases our testing of V8, and helps find bugs where we are different than V8. --- scripts/fuzz_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index d6a3921296b..29940383751 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -801,7 +801,7 @@ def handle_pair(self, input, before_wasm, after_wasm, opts): class CompareVMs(TestCaseHandler): - frequency = 0.66 + frequency = 1 def __init__(self): super(CompareVMs, self).__init__() From 7cee02592033b830a05eeeb9990d15a1f33e6792 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 25 Nov 2024 12:55:34 -0800 Subject: [PATCH 158/622] Fix memory.grow bounds and overflow checks for mem64 (#7112) Previously the interpreter only executed overflow and bounds checks for memory.grow on 32-bit memories. Run the checks on 64-bit memories as well. --- src/wasm-interpreter.h | 9 +++++++-- test/lit/exec/memory64.wast | 12 ++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f3471cfa85c..81531e27c13 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3836,10 +3836,15 @@ class ModuleRunnerBase : public ExpressionRunner { auto fail = Literal::makeFromInt64(-1, memory->addressType); Flow ret = Literal::makeFromInt64(memorySize, addressType); uint64_t delta = flow.getSingleValue().getUnsigned(); - if (delta > uint32_t(-1) / Memory::kPageSize && addressType == Type::i32) { + uint64_t maxAddr = addressType == Type::i32 + ? std::numeric_limits::max() + : std::numeric_limits::max(); + if (delta > maxAddr / Memory::kPageSize) { + // Impossible to grow this much. return fail; } - if (memorySize >= uint32_t(-1) - delta && addressType == Type::i32) { + if (memorySize >= maxAddr - delta) { + // Overflow. return fail; } auto newSize = memorySize + delta; diff --git a/test/lit/exec/memory64.wast b/test/lit/exec/memory64.wast index 273f2679aad..0d28e719fc4 100644 --- a/test/lit/exec/memory64.wast +++ b/test/lit/exec/memory64.wast @@ -28,6 +28,14 @@ (i32.const 10) ) ) + + ;; CHECK: [fuzz-exec] calling memory.grow.fail + ;; CHECK-NEXT: [fuzz-exec] note result: memory.grow.fail => -1 + (func $memory.grow.fail (export "memory.grow.fail") (result i64) + (memory.grow + (i64.const -1) + ) + ) ) ;; CHECK: [fuzz-exec] calling memory.init.trap @@ -35,5 +43,9 @@ ;; CHECK: [fuzz-exec] calling memory.init.trap2 ;; CHECK-NEXT: [trap out of bounds segment access in memory.init] + +;; CHECK: [fuzz-exec] calling memory.grow.fail +;; CHECK-NEXT: [fuzz-exec] note result: memory.grow.fail => -1 +;; CHECK-NEXT: [fuzz-exec] comparing memory.grow.fail ;; CHECK-NEXT: [fuzz-exec] comparing memory.init.trap ;; CHECK-NEXT: [fuzz-exec] comparing memory.init.trap2 From f7afec956917c51071867f5b1b487111f1a1ef60 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Mon, 25 Nov 2024 15:58:58 -0800 Subject: [PATCH 159/622] [tests] Fix wasm2js test command line generation (#7114) --emscripten is added to the command line if the filename contains the string 'emscripten', but the current logic includes the cwd, so it fails if the tests are run from a directory with 'emscripten' in the name. Fix it so the test only includes the basename. --- scripts/test/wasm2js.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/test/wasm2js.py b/scripts/test/wasm2js.py index d716e5fe665..1f6ca4f8ae6 100644 --- a/scripts/test/wasm2js.py +++ b/scripts/test/wasm2js.py @@ -81,9 +81,9 @@ def test_wasm2js_output(): '--disable-exception-handling'] if opt: cmd += ['-O'] - if 'emscripten' in t: + if 'emscripten' in basename: cmd += ['--emscripten'] - if 'deterministic' in t: + if 'deterministic' in basename: cmd += ['--deterministic'] js = support.run_command(cmd) all_js.append(js) From cc97853b5f03e8c8441159e68ecb2262beee166f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 26 Nov 2024 09:05:48 -0800 Subject: [PATCH 160/622] Fix ArenaVector::swap (#7115) This was never right for over a decade, and just never used I suppose... it should have been called "take" since it grabbed data from the other item and then set that other item to empty. Fix it so it swaps properly. --- src/mixed_arena.h | 9 +++------ test/gtest/CMakeLists.txt | 1 + test/gtest/arena.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 test/gtest/arena.cpp diff --git a/src/mixed_arena.h b/src/mixed_arena.h index 530fa9daa6a..057887b828d 100644 --- a/src/mixed_arena.h +++ b/src/mixed_arena.h @@ -263,12 +263,9 @@ template class ArenaVectorBase { void operator=(SubType& other) { set(other); } void swap(SubType& other) { - data = other.data; - usedElements = other.usedElements; - allocatedElements = other.allocatedElements; - - other.data = nullptr; - other.usedElements = other.allocatedElements = 0; + std::swap(data, other.data); + std::swap(usedElements, other.usedElements); + std::swap(allocatedElements, other.allocatedElements); } // iteration diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 9b648fb654c..c3d281f1c0e 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -2,6 +2,7 @@ include_directories(../../third_party/googletest/googletest/include) include_directories(../../src/wasm) set(unittest_SOURCES + arena.cpp binary-reader.cpp cfg.cpp dfa_minimization.cpp diff --git a/test/gtest/arena.cpp b/test/gtest/arena.cpp new file mode 100644 index 00000000000..7453ea9f615 --- /dev/null +++ b/test/gtest/arena.cpp @@ -0,0 +1,39 @@ +#include "mixed_arena.h" +#include "gtest/gtest.h" + +using ArenaTest = ::testing::Test; + +TEST_F(ArenaTest, Swap) { + MixedArena arena; + + ArenaVector a(arena); + a.push_back(10); + a.push_back(20); + + ArenaVector b(arena); + + EXPECT_EQ(a.size(), 2U); + EXPECT_EQ(b.size(), 0U); + + a.swap(b); + + EXPECT_EQ(a.size(), 0U); + EXPECT_EQ(b.size(), 2U); + + a.swap(b); + + EXPECT_EQ(a.size(), 2U); + EXPECT_EQ(b.size(), 0U); + + // Now reverse a and b. The swap should be the same. + + b.swap(a); + + EXPECT_EQ(a.size(), 0U); + EXPECT_EQ(b.size(), 2U); + + b.swap(a); + + EXPECT_EQ(a.size(), 2U); + EXPECT_EQ(b.size(), 0U); +} From acb12584c277d9bfb0b1c78e65101fa54e9a7361 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Tue, 26 Nov 2024 14:11:06 -0800 Subject: [PATCH 161/622] [wasm2js] Run LLVM nontrapping fptoint lowering when running for emscripten (#7116) Lower away saturating fptoint operations when we know we are using emscripten. --- scripts/test/wasm2js.py | 9 +- src/wasm2js.h | 3 + .../conversions-emscripten-modified.2asm.js | 289 ++++++++++++++++++ ...onversions-emscripten-modified.2asm.js.opt | 121 ++++++++ .../conversions-emscripten-modified.wast | 10 + 5 files changed, 428 insertions(+), 4 deletions(-) create mode 100644 test/wasm2js/conversions-emscripten-modified.2asm.js create mode 100644 test/wasm2js/conversions-emscripten-modified.2asm.js.opt create mode 100644 test/wasm2js/conversions-emscripten-modified.wast diff --git a/scripts/test/wasm2js.py b/scripts/test/wasm2js.py index 1f6ca4f8ae6..224dc01cb3a 100644 --- a/scripts/test/wasm2js.py +++ b/scripts/test/wasm2js.py @@ -162,10 +162,11 @@ def update_wasm2js_tests(): if not wasm.endswith('.wast'): continue - if os.path.basename(wasm) in wasm2js_skipped_tests: + basename = os.path.basename(wasm) + if basename in wasm2js_skipped_tests: continue - asm = os.path.basename(wasm).replace('.wast', '.2asm.js') + asm = basename.replace('.wast', '.2asm.js') expected_file = os.path.join(shared.get_test_dir('wasm2js'), asm) if opt: expected_file += '.opt' @@ -192,9 +193,9 @@ def update_wasm2js_tests(): '--disable-exception-handling'] if opt: cmd += ['-O'] - if 'emscripten' in wasm: + if 'emscripten' in basename: cmd += ['--emscripten'] - if 'deterministic' in t: + if 'deterministic' in basename: cmd += ['--deterministic'] out = support.run_command(cmd) all_out.append(out) diff --git a/src/wasm2js.h b/src/wasm2js.h index f819089482f..7ed089448e2 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -361,6 +361,9 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { // First, do the lowering to a JS-friendly subset. { PassRunner runner(wasm, options); + if (flags.emscripten) { + runner.add("llvm-nontrapping-fptoint-lowering"); + } // TODO: only legalize if necessary - emscripten would already do so, and // likely other toolchains. but spec test suite needs that. runner.add("legalize-js-interface"); diff --git a/test/wasm2js/conversions-emscripten-modified.2asm.js b/test/wasm2js/conversions-emscripten-modified.2asm.js new file mode 100644 index 00000000000..0bb42c14220 --- /dev/null +++ b/test/wasm2js/conversions-emscripten-modified.2asm.js @@ -0,0 +1,289 @@ +function instantiate(info) { +function asmFunc(imports) { + var Math_imul = Math.imul; + var Math_fround = Math.fround; + var Math_abs = Math.abs; + var Math_clz32 = Math.clz32; + var Math_min = Math.min; + var Math_max = Math.max; + var Math_floor = Math.floor; + var Math_ceil = Math.ceil; + var Math_trunc = Math.trunc; + var Math_sqrt = Math.sqrt; + var env = imports.env; + var setTempRet0 = env.setTempRet0; + var i64toi32_i32$HIGH_BITS = 0; + // EMSCRIPTEN_START_FUNCS +; + function $0(x) { + x = Math_fround(x); + var $1_1 = Math_fround(0), $8 = 0; + $1_1 = x; + if (Math_fround(Math_abs($1_1)) < Math_fround(2147483648.0)) { + $8 = ~~$1_1 + } else { + $8 = -2147483648 + } + return $8 | 0; + } + + function $1(x) { + x = Math_fround(x); + var $1_1 = Math_fround(0), $10 = 0; + $1_1 = x; + if ($1_1 < Math_fround(4294967296.0) & $1_1 >= Math_fround(0.0) | 0) { + $10 = ~~$1_1 >>> 0 + } else { + $10 = 0 + } + return $10 | 0; + } + + function $2(x) { + x = +x; + var $1_1 = 0.0, $8 = 0; + $1_1 = x; + if (Math_abs($1_1) < 2147483647.0) { + $8 = ~~$1_1 + } else { + $8 = -2147483648 + } + return $8 | 0; + } + + function $3(x) { + x = +x; + var $1_1 = 0.0, $10 = 0; + $1_1 = x; + if ($1_1 < 4294967295.0 & $1_1 >= 0.0 | 0) { + $10 = ~~$1_1 >>> 0 + } else { + $10 = 0 + } + return $10 | 0; + } + + function $4(x) { + x = Math_fround(x); + var i64toi32_i32$0 = Math_fround(0), i64toi32_i32$1 = 0, $1_1 = Math_fround(0), $6_1 = 0, $7_1 = 0, $8 = 0, $8$hi = 0; + $1_1 = x; + if (Math_fround(Math_abs($1_1)) < Math_fround(9223372036854775808.0)) { + i64toi32_i32$0 = $1_1; + if (Math_fround(Math_abs(i64toi32_i32$0)) >= Math_fround(1.0)) { + if (i64toi32_i32$0 > Math_fround(0.0)) { + $6_1 = ~~Math_fround(Math_min(Math_fround(Math_floor(Math_fround(i64toi32_i32$0 / Math_fround(4294967296.0)))), Math_fround(Math_fround(4294967296.0) - Math_fround(1.0)))) >>> 0 + } else { + $6_1 = ~~Math_fround(Math_ceil(Math_fround(Math_fround(i64toi32_i32$0 - Math_fround(~~i64toi32_i32$0 >>> 0 >>> 0)) / Math_fround(4294967296.0)))) >>> 0 + } + $7_1 = $6_1; + } else { + $7_1 = 0 + } + i64toi32_i32$1 = $7_1; + $8 = ~~i64toi32_i32$0 >>> 0; + $8$hi = i64toi32_i32$1; + } else { + i64toi32_i32$1 = -2147483648; + $8 = 0; + $8$hi = i64toi32_i32$1; + } + i64toi32_i32$1 = $8$hi; + i64toi32_i32$HIGH_BITS = i64toi32_i32$1; + return $8 | 0; + } + + function $5(x) { + x = Math_fround(x); + var i64toi32_i32$0 = Math_fround(0), i64toi32_i32$1 = 0, $1_1 = Math_fround(0), $6_1 = 0, $7_1 = 0, $10 = 0, $10$hi = 0; + $1_1 = x; + if ($1_1 < Math_fround(18446744073709551615.0) & $1_1 >= Math_fround(0.0) | 0) { + i64toi32_i32$0 = $1_1; + if (Math_fround(Math_abs(i64toi32_i32$0)) >= Math_fround(1.0)) { + if (i64toi32_i32$0 > Math_fround(0.0)) { + $6_1 = ~~Math_fround(Math_min(Math_fround(Math_floor(Math_fround(i64toi32_i32$0 / Math_fround(4294967296.0)))), Math_fround(Math_fround(4294967296.0) - Math_fround(1.0)))) >>> 0 + } else { + $6_1 = ~~Math_fround(Math_ceil(Math_fround(Math_fround(i64toi32_i32$0 - Math_fround(~~i64toi32_i32$0 >>> 0 >>> 0)) / Math_fround(4294967296.0)))) >>> 0 + } + $7_1 = $6_1; + } else { + $7_1 = 0 + } + i64toi32_i32$1 = $7_1; + $10 = ~~i64toi32_i32$0 >>> 0; + $10$hi = i64toi32_i32$1; + } else { + i64toi32_i32$1 = 0; + $10 = 0; + $10$hi = i64toi32_i32$1; + } + i64toi32_i32$1 = $10$hi; + i64toi32_i32$HIGH_BITS = i64toi32_i32$1; + return $10 | 0; + } + + function $6(x) { + x = +x; + var i64toi32_i32$0 = 0.0, i64toi32_i32$1 = 0, $1_1 = 0.0, $6_1 = 0, $7_1 = 0, $8 = 0, $8$hi = 0; + $1_1 = x; + if (Math_abs($1_1) < 9223372036854775808.0) { + i64toi32_i32$0 = $1_1; + if (Math_abs(i64toi32_i32$0) >= 1.0) { + if (i64toi32_i32$0 > 0.0) { + $6_1 = ~~Math_min(Math_floor(i64toi32_i32$0 / 4294967296.0), 4294967296.0 - 1.0) >>> 0 + } else { + $6_1 = ~~Math_ceil((i64toi32_i32$0 - +(~~i64toi32_i32$0 >>> 0 >>> 0)) / 4294967296.0) >>> 0 + } + $7_1 = $6_1; + } else { + $7_1 = 0 + } + i64toi32_i32$1 = $7_1; + $8 = ~~i64toi32_i32$0 >>> 0; + $8$hi = i64toi32_i32$1; + } else { + i64toi32_i32$1 = -2147483648; + $8 = 0; + $8$hi = i64toi32_i32$1; + } + i64toi32_i32$1 = $8$hi; + i64toi32_i32$HIGH_BITS = i64toi32_i32$1; + return $8 | 0; + } + + function $7(x) { + x = +x; + var i64toi32_i32$0 = 0.0, i64toi32_i32$1 = 0, $1_1 = 0.0, $6_1 = 0, $7_1 = 0, $10 = 0, $10$hi = 0; + $1_1 = x; + if ($1_1 < 18446744073709551615.0 & $1_1 >= 0.0 | 0) { + i64toi32_i32$0 = $1_1; + if (Math_abs(i64toi32_i32$0) >= 1.0) { + if (i64toi32_i32$0 > 0.0) { + $6_1 = ~~Math_min(Math_floor(i64toi32_i32$0 / 4294967296.0), 4294967296.0 - 1.0) >>> 0 + } else { + $6_1 = ~~Math_ceil((i64toi32_i32$0 - +(~~i64toi32_i32$0 >>> 0 >>> 0)) / 4294967296.0) >>> 0 + } + $7_1 = $6_1; + } else { + $7_1 = 0 + } + i64toi32_i32$1 = $7_1; + $10 = ~~i64toi32_i32$0 >>> 0; + $10$hi = i64toi32_i32$1; + } else { + i64toi32_i32$1 = 0; + $10 = 0; + $10$hi = i64toi32_i32$1; + } + i64toi32_i32$1 = $10$hi; + i64toi32_i32$HIGH_BITS = i64toi32_i32$1; + return $10 | 0; + } + + function legalstub$4($0_1) { + $0_1 = Math_fround($0_1); + var i64toi32_i32$0 = 0, i64toi32_i32$4 = 0, i64toi32_i32$1 = 0, i64toi32_i32$3 = 0, $8 = 0, $1_1 = 0, $1$hi = 0, i64toi32_i32$2 = 0; + i64toi32_i32$0 = $4(Math_fround($0_1)) | 0; + i64toi32_i32$1 = i64toi32_i32$HIGH_BITS; + $1_1 = i64toi32_i32$0; + $1$hi = i64toi32_i32$1; + i64toi32_i32$2 = i64toi32_i32$0; + i64toi32_i32$0 = 0; + i64toi32_i32$3 = 32; + i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0; + if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) { + i64toi32_i32$0 = 0; + $8 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0; + } else { + i64toi32_i32$0 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0; + $8 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$2 >>> i64toi32_i32$4 | 0) | 0; + } + setTempRet0($8 | 0); + i64toi32_i32$0 = $1$hi; + return $1_1 | 0; + } + + function legalstub$5($0_1) { + $0_1 = Math_fround($0_1); + var i64toi32_i32$0 = 0, i64toi32_i32$4 = 0, i64toi32_i32$1 = 0, i64toi32_i32$3 = 0, $8 = 0, $1_1 = 0, $1$hi = 0, i64toi32_i32$2 = 0; + i64toi32_i32$0 = $5(Math_fround($0_1)) | 0; + i64toi32_i32$1 = i64toi32_i32$HIGH_BITS; + $1_1 = i64toi32_i32$0; + $1$hi = i64toi32_i32$1; + i64toi32_i32$2 = i64toi32_i32$0; + i64toi32_i32$0 = 0; + i64toi32_i32$3 = 32; + i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0; + if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) { + i64toi32_i32$0 = 0; + $8 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0; + } else { + i64toi32_i32$0 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0; + $8 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$2 >>> i64toi32_i32$4 | 0) | 0; + } + setTempRet0($8 | 0); + i64toi32_i32$0 = $1$hi; + return $1_1 | 0; + } + + function legalstub$6($0_1) { + $0_1 = +$0_1; + var i64toi32_i32$0 = 0, i64toi32_i32$4 = 0, i64toi32_i32$1 = 0, i64toi32_i32$3 = 0, $8 = 0, $1_1 = 0, $1$hi = 0, i64toi32_i32$2 = 0; + i64toi32_i32$0 = $6(+$0_1) | 0; + i64toi32_i32$1 = i64toi32_i32$HIGH_BITS; + $1_1 = i64toi32_i32$0; + $1$hi = i64toi32_i32$1; + i64toi32_i32$2 = i64toi32_i32$0; + i64toi32_i32$0 = 0; + i64toi32_i32$3 = 32; + i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0; + if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) { + i64toi32_i32$0 = 0; + $8 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0; + } else { + i64toi32_i32$0 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0; + $8 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$2 >>> i64toi32_i32$4 | 0) | 0; + } + setTempRet0($8 | 0); + i64toi32_i32$0 = $1$hi; + return $1_1 | 0; + } + + function legalstub$7($0_1) { + $0_1 = +$0_1; + var i64toi32_i32$0 = 0, i64toi32_i32$4 = 0, i64toi32_i32$1 = 0, i64toi32_i32$3 = 0, $8 = 0, $1_1 = 0, $1$hi = 0, i64toi32_i32$2 = 0; + i64toi32_i32$0 = $7(+$0_1) | 0; + i64toi32_i32$1 = i64toi32_i32$HIGH_BITS; + $1_1 = i64toi32_i32$0; + $1$hi = i64toi32_i32$1; + i64toi32_i32$2 = i64toi32_i32$0; + i64toi32_i32$0 = 0; + i64toi32_i32$3 = 32; + i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0; + if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) { + i64toi32_i32$0 = 0; + $8 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0; + } else { + i64toi32_i32$0 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0; + $8 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$2 >>> i64toi32_i32$4 | 0) | 0; + } + setTempRet0($8 | 0); + i64toi32_i32$0 = $1$hi; + return $1_1 | 0; + } + + // EMSCRIPTEN_END_FUNCS +; + return { + "i32_trunc_sat_f32_s": $0, + "i32_trunc_sat_f32_u": $1, + "i32_trunc_sat_f64_s": $2, + "i32_trunc_sat_f64_u": $3, + "i64_trunc_sat_f32_s": legalstub$4, + "i64_trunc_sat_f32_u": legalstub$5, + "i64_trunc_sat_f64_s": legalstub$6, + "i64_trunc_sat_f64_u": legalstub$7 + }; +} + + return asmFunc(info); +} diff --git a/test/wasm2js/conversions-emscripten-modified.2asm.js.opt b/test/wasm2js/conversions-emscripten-modified.2asm.js.opt new file mode 100644 index 00000000000..d29f8f21f2e --- /dev/null +++ b/test/wasm2js/conversions-emscripten-modified.2asm.js.opt @@ -0,0 +1,121 @@ +function instantiate(info) { +function asmFunc(imports) { + var Math_imul = Math.imul; + var Math_fround = Math.fround; + var Math_abs = Math.abs; + var Math_clz32 = Math.clz32; + var Math_min = Math.min; + var Math_max = Math.max; + var Math_floor = Math.floor; + var Math_ceil = Math.ceil; + var Math_trunc = Math.trunc; + var Math_sqrt = Math.sqrt; + var env = imports.env; + var setTempRet0 = env.setTempRet0; + var i64toi32_i32$HIGH_BITS = 0; + // EMSCRIPTEN_START_FUNCS +; + function $0($0_1) { + $0_1 = Math_fround($0_1); + return (Math_fround(Math_abs($0_1)) < Math_fround(2147483648.0) ? ~~$0_1 : -2147483648) | 0; + } + + function $1($0_1) { + $0_1 = Math_fround($0_1); + return ($0_1 < Math_fround(4294967296.0) & $0_1 >= Math_fround(0.0) ? ~~$0_1 >>> 0 : 0) | 0; + } + + function $2($0_1) { + $0_1 = +$0_1; + return (Math_abs($0_1) < 2147483647.0 ? ~~$0_1 : -2147483648) | 0; + } + + function $3($0_1) { + $0_1 = +$0_1; + return ($0_1 < 4294967295.0 & $0_1 >= 0.0 ? ~~$0_1 >>> 0 : 0) | 0; + } + + function legalstub$4($0_1) { + var $1_1 = 0, $2_1 = 0; + if (Math_fround(Math_abs($0_1)) < Math_fround(9223372036854775808.0)) { + $2_1 = ~~$0_1 >>> 0; + if (Math_fround(Math_abs($0_1)) >= Math_fround(1.0)) { + $1_1 = ~~($0_1 > Math_fround(0.0) ? Math_fround(Math_min(Math_fround(Math_floor(Math_fround($0_1 * Math_fround(2.3283064365386963e-10)))), Math_fround(4294967296.0))) : Math_fround(Math_ceil(Math_fround(Math_fround($0_1 - Math_fround(~~$0_1 >>> 0 >>> 0)) * Math_fround(2.3283064365386963e-10))))) >>> 0 + } else { + $1_1 = 0 + } + } else { + $1_1 = -2147483648 + } + i64toi32_i32$HIGH_BITS = $1_1; + setTempRet0(i64toi32_i32$HIGH_BITS | 0); + return $2_1; + } + + function legalstub$5($0_1) { + var $1_1 = 0, $2_1 = 0; + if ($0_1 < Math_fround(18446744073709551615.0) & $0_1 >= Math_fround(0.0)) { + $2_1 = ~~$0_1 >>> 0; + if (Math_fround(Math_abs($0_1)) >= Math_fround(1.0)) { + $1_1 = ~~($0_1 > Math_fround(0.0) ? Math_fround(Math_min(Math_fround(Math_floor(Math_fround($0_1 * Math_fround(2.3283064365386963e-10)))), Math_fround(4294967296.0))) : Math_fround(Math_ceil(Math_fround(Math_fround($0_1 - Math_fround(~~$0_1 >>> 0 >>> 0)) * Math_fround(2.3283064365386963e-10))))) >>> 0 + } else { + $1_1 = 0 + } + } else { + $1_1 = 0 + } + i64toi32_i32$HIGH_BITS = $1_1; + setTempRet0(i64toi32_i32$HIGH_BITS | 0); + return $2_1; + } + + function legalstub$6($0_1) { + var $1_1 = 0, $2_1 = 0; + if (Math_abs($0_1) < 9223372036854775808.0) { + $2_1 = ~~$0_1 >>> 0; + if (Math_abs($0_1) >= 1.0) { + $1_1 = ~~($0_1 > 0.0 ? Math_min(Math_floor($0_1 * 2.3283064365386963e-10), 4294967295.0) : Math_ceil(($0_1 - +(~~$0_1 >>> 0 >>> 0)) * 2.3283064365386963e-10)) >>> 0 + } else { + $1_1 = 0 + } + } else { + $1_1 = -2147483648 + } + i64toi32_i32$HIGH_BITS = $1_1; + setTempRet0(i64toi32_i32$HIGH_BITS | 0); + return $2_1; + } + + function legalstub$7($0_1) { + var $1_1 = 0, $2_1 = 0; + if ($0_1 < 18446744073709551615.0 & $0_1 >= 0.0) { + $2_1 = ~~$0_1 >>> 0; + if (Math_abs($0_1) >= 1.0) { + $1_1 = ~~($0_1 > 0.0 ? Math_min(Math_floor($0_1 * 2.3283064365386963e-10), 4294967295.0) : Math_ceil(($0_1 - +(~~$0_1 >>> 0 >>> 0)) * 2.3283064365386963e-10)) >>> 0 + } else { + $1_1 = 0 + } + } else { + $1_1 = 0 + } + i64toi32_i32$HIGH_BITS = $1_1; + setTempRet0(i64toi32_i32$HIGH_BITS | 0); + return $2_1; + } + + // EMSCRIPTEN_END_FUNCS +; + return { + "i32_trunc_sat_f32_s": $0, + "i32_trunc_sat_f32_u": $1, + "i32_trunc_sat_f64_s": $2, + "i32_trunc_sat_f64_u": $3, + "i64_trunc_sat_f32_s": legalstub$4, + "i64_trunc_sat_f32_u": legalstub$5, + "i64_trunc_sat_f64_s": legalstub$6, + "i64_trunc_sat_f64_u": legalstub$7 + }; +} + + return asmFunc(info); +} diff --git a/test/wasm2js/conversions-emscripten-modified.wast b/test/wasm2js/conversions-emscripten-modified.wast new file mode 100644 index 00000000000..05de07c4734 --- /dev/null +++ b/test/wasm2js/conversions-emscripten-modified.wast @@ -0,0 +1,10 @@ +(module + (func (export "i32.trunc_sat_f32_s") (param $x f32) (result i32) (i32.trunc_sat_f32_s (local.get $x))) + (func (export "i32.trunc_sat_f32_u") (param $x f32) (result i32) (i32.trunc_sat_f32_u (local.get $x))) + (func (export "i32.trunc_sat_f64_s") (param $x f64) (result i32) (i32.trunc_sat_f64_s (local.get $x))) + (func (export "i32.trunc_sat_f64_u") (param $x f64) (result i32) (i32.trunc_sat_f64_u (local.get $x))) + (func (export "i64.trunc_sat_f32_s") (param $x f32) (result i64) (i64.trunc_sat_f32_s (local.get $x))) + (func (export "i64.trunc_sat_f32_u") (param $x f32) (result i64) (i64.trunc_sat_f32_u (local.get $x))) + (func (export "i64.trunc_sat_f64_s") (param $x f64) (result i64) (i64.trunc_sat_f64_s (local.get $x))) + (func (export "i64.trunc_sat_f64_u") (param $x f64) (result i64) (i64.trunc_sat_f64_u (local.get $x))) +) From ffc3f2219b18c2a2ddb160c0d81518234faa2cd1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 26 Nov 2024 14:15:02 -0800 Subject: [PATCH 162/622] Error on missing wasm2js test expectations (#7119) --- scripts/test/wasm2js.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/test/wasm2js.py b/scripts/test/wasm2js.py index 224dc01cb3a..95fe4fdac94 100644 --- a/scripts/test/wasm2js.py +++ b/scripts/test/wasm2js.py @@ -65,7 +65,15 @@ def test_wasm2js_output(): expected_file += '.opt' if not os.path.exists(expected_file): - continue + # It is ok to skip tests from other test suites that we also + # test on wasm2js (the basic and spec tests). When such files + # pass in wasm2js, we add expected files for them, so lacking + # such a file just means we should ignore it. But lacking an + # expected file for an explicit wasm2js test is an error. + if t in basic_tests or t in spec_tests: + continue + else: + raise Exception(f'missing expected file {expected_file}') print('..', os.path.basename(t)) From 4ffe27255ce99d452d05d4b352e3f6e1e9ca7d83 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 26 Nov 2024 14:26:57 -0800 Subject: [PATCH 163/622] ReFinalize after merging siblings in TypeMerging (#7121) The LUB of sibling types is their common supertype, but after the sibling types are merged, their LUB is the merged type, which is a strict subtype of the previous LUB. This means that merging sibling types causes `selects` to have stale types when the two select arms previously had the two merged sibling types. To fix any potential stale types, ReFinalize after merging sibling types. --- src/passes/TypeMerging.cpp | 11 ++++++ test/lit/passes/type-merging.wast | 62 ++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 22c2352f292..e7a25cf372c 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -38,6 +38,7 @@ #include "ir/module-utils.h" #include "ir/type-updating.h" +#include "ir/utils.h" #include "pass.h" #include "support/dfa_minimization.h" #include "support/small_set.h" @@ -229,14 +230,24 @@ void TypeMerging::run(Module* module_) { // Merging can unlock more sibling merging opportunities because two identical // types cannot be merged until their respective identical parents have been // merged in a previous step, making them siblings. + // + // If we merge siblings, we also need to refinalize because the LUB of merged + // siblings is the merged type rather than their common supertype after the + // merge. + bool refinalize = false; merge(Supertypes); for (int i = 0; i < MAX_ITERATIONS; ++i) { if (!merge(Siblings)) { break; } + refinalize = true; } applyMerges(); + + if (refinalize) { + ReFinalize().run(getPassRunner(), module); + } } bool TypeMerging::merge(MergeKind kind) { diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast index 942e15b021a..b216cede00c 100644 --- a/test/lit/passes/type-merging.wast +++ b/test/lit/passes/type-merging.wast @@ -877,7 +877,6 @@ ;; leading to a failure to build new types. ;; TODO: Store a heap type on control flow structures to avoid creating ;; standalone function types for them. -;; TODO: Investigate why the rec group contains two of the same type below. (module (rec ;; CHECK: (rec @@ -987,6 +986,67 @@ (export "public" (global $public)) ) +;; Regression test that produces invalid IR if we do not refinalize after +;; merging siblings. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $subA (sub $super (struct (field i32)))) + (type $subA (sub $super (struct (field i32)))) + (type $subB (sub $super (struct (field i32)))) + ) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result (ref $subA)) + ;; CHECK-NEXT: (struct.new_default $subA) + ;; CHECK-NEXT: (struct.new_default $subA) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (select (result (ref $super)) + (struct.new_default $subA) + (struct.new_default $subB) + (i32.const 0) + ) + ) + ) +) + +;; TODO: We should be able to merge $sub into $public here, but we currently do +;; not encode the successors of public types in their DFA states, so we +;; currently fail to do this merge. See issue #7120. +(module + ;; CHECK: (type $public (sub (struct (field (ref null $public))))) + (type $public (sub (struct (field (ref null $public))))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $sub (sub $public (struct (field (ref null $public))))) + (type $sub (sub $public (struct (field (ref null $public))))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (global $g (ref null $public) (ref.null none)) + (global $g (export "g") (ref null $public) (ref.null none)) + + ;; CHECK: (export "g" (global $g)) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (local $0 (ref $public)) + ;; CHECK-NEXT: (local $1 (ref $sub)) + ;; CHECK-NEXT: ) + (func $test + (local (ref $public)) + (local (ref $sub)) + ) +) + ;; Check that a ref.test inhibits merging (ref.cast is already checked above). (module ;; CHECK: (rec From 73971d78e5355e8f08b4026b741992d78bd77476 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 26 Nov 2024 15:12:36 -0800 Subject: [PATCH 164/622] [Fuzzing] Emit secondary wasm files in ClusterFuzz testcases (#7122) The two files are then linked and run by fuzz_shell.js (we had this functionality already in order to fuzz wasm-split). By adding multiple build and run commands of both the primary and secondary wasm files, we can end up with multiple instances of two different wasm files that call between themselves. To help testing, add a script that extracts the wasm files from the testcase. This may also be useful in the future for testcase reduction. --- scripts/clusterfuzz/extract_wasms.py | 74 ++++++++++++++++++++++++++++ scripts/clusterfuzz/run.py | 38 ++++++++++---- test/unit/test_cluster_fuzz.py | 73 ++++++++++++++++++++------- 3 files changed, 158 insertions(+), 27 deletions(-) create mode 100644 scripts/clusterfuzz/extract_wasms.py diff --git a/scripts/clusterfuzz/extract_wasms.py b/scripts/clusterfuzz/extract_wasms.py new file mode 100644 index 00000000000..bb727810d75 --- /dev/null +++ b/scripts/clusterfuzz/extract_wasms.py @@ -0,0 +1,74 @@ +# +# Copyright 2024 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. + +''' +Wasm extractor for testcases generated by the ClusterFuzz run.py script. Usage: + +extract_wasms.py INFILE.js OUTFILE + +That will find embedded wasm files in INFILE.js, of the form + + var .. = new Uint8Array([..wasm_contents..]); + +and extract them into OUTFILE.0.wasm, OUTFILE.1.wasm, etc. It also emits +OUTFILE.js which will no longer contain the embedded contents, after which the +script can be run as + + d8 OUTFILE.js -- OUTFILE.0.wasm + +That is, the embedded file can now be provided as a filename argument. +''' + +import re +import sys + +file_counter = 0 + + +def get_wasm_filename(): + global file_counter + file_counter += 1 + return f'{out}.{file_counter - 1}.wasm' + + +in_js = sys.argv[1] +out = sys.argv[2] + +with open(in_js) as f: + js = f.read() + + +def repl(text): + # We found something of the form + # + # var binary = new Uint8Array([..binary data as numbers..]); + # + # Parse out the numbers into a binary wasm file. + numbers = text.groups()[0] + numbers = numbers.split(',') + numbers = [int(n) for n in numbers] + with open(get_wasm_filename(), 'wb') as f: + f.write(bytes(numbers)) + + # Replace it with nothing. + return '' + + +# Replace the wasm files and write them out. +js = re.sub(r'var \w+ = new Uint8Array\(\[([\d,]+)\]\);', repl, js) + +# Write out the new JS. +with open(f'{out}.js', 'w') as f: + f.write(js) diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index 6bbb74ef886..8ac880e0de0 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -150,7 +150,18 @@ def get_js_file_contents(i, output_dir): # Prepend the wasm contents, so they are used (rather than the normal # mechanism where the wasm file's name is provided in argv). wasm_contents = get_wasm_contents(i, output_dir) - js = f'var binary = {wasm_contents};\n\n' + js + pre = f'var binary = {wasm_contents};\n' + bytes = wasm_contents.count(',') + + # Sometimes add a second wasm file as well. + has_second = False + if system_random.random() < 0.333: + has_second = True + wasm_contents = get_wasm_contents(i, output_dir) + pre += f'var secondBinary = {wasm_contents};\n' + bytes += wasm_contents.count(',') + + js = pre + '\n' + js # The default JS builds and runs the wasm. Append some random additional # operations as well, as more compiles and executions can find things. To @@ -171,16 +182,23 @@ def get_js_file_contents(i, output_dir): x = math.pow(x, power) num = math.floor(x * MAX_EXTRA_JS_OPERATIONS) assert num >= 0 and num <= MAX_EXTRA_JS_OPERATIONS + + extra_js_operations = [ + # Compile and link the wasm again. Each link adds more to the total + # exports that we can call. + 'build(binary);\n', + # Run all the exports we've accumulated. + 'callExports();\n', + ] + if has_second: + extra_js_operations += [ + 'build(secondBinary);\n', + ] + for i in range(num): - js += system_random.choice([ - # Compile and link the wasm again. Each link adds more to the total - # exports that we can call. - 'build(binary);\n', - # Run all the exports we've accumulated. - 'callExports();\n', - ]) - - print(f'Created {wasm_contents.count(",")} wasm bytes') + js += system_random.choice(extra_js_operations) + + print(f'Created {bytes} wasm bytes') return js diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 387f65fd1d5..56250d46aed 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -1,3 +1,4 @@ +import glob import os import platform import re @@ -159,6 +160,9 @@ def test_file_contents(self): seen_sizes = [] seen_exports = [] + # Second wasm files are also emitted sometimes. + seen_second_sizes = [] + # The number of struct.news appears in the metrics report like this: # # StructNew : 18 @@ -179,23 +183,16 @@ def test_file_contents(self): with open(flags_file) as f: self.assertEqual(f.read(), '--wasm-staging') - # The fuzz files begin with - # - # var binary = new Uint8Array([..binary data as numbers..]); - # - with open(fuzz_file) as f: - first_line = f.readline().strip() - start = 'var binary = new Uint8Array([' - end = ']);' - self.assertTrue(first_line.startswith(start)) - self.assertTrue(first_line.endswith(end)) - numbers = first_line[len(start):-len(end)] - - # Convert to binary, and see that it is a valid file. - numbers_array = [int(x) for x in numbers.split(',')] - binary_file = os.path.join(temp_dir.name, 'file.wasm') - with open(binary_file, 'wb') as f: - f.write(bytes(numbers_array)) + # Extract the wasm file(s) from the JS. Make sure to not notice + # stale files. + for f in glob.glob('extracted*'): + os.unlink(f) + extractor = shared.in_binaryen('scripts', 'clusterfuzz', 'extract_wasms.py') + subprocess.check_call([sys.executable, extractor, fuzz_file, 'extracted']) + + # One wasm file must always exist, and must be valid. + binary_file = 'extracted.0.wasm' + assert os.path.exists(binary_file) metrics = subprocess.check_output( shared.WASM_OPT + ['-all', '--metrics', binary_file, '-q'], text=True) @@ -215,6 +212,19 @@ def test_file_contents(self): self.assertEqual(len(exports), 1) seen_exports.append(int(exports[0])) + # Sometimes a second wasm file should exist, and it must be valid + # too. + second_binary_file = 'extracted.1.wasm' + if os.path.exists(second_binary_file): + subprocess.check_call( + shared.WASM_OPT + ['-all', second_binary_file, '-q']) + + # Note its size (we leave detailed metrics for the first one; + # they are generated by the same logic in run.py, so just + # verifying some valid second wasms are emitted, of random + # sizes, is enough). + seen_second_sizes.append(os.path.getsize(second_binary_file)) + print() print('struct.news are distributed as ~ mean 15, stddev 24, median 10') @@ -247,10 +257,27 @@ def test_file_contents(self): print() + # Second files appear in ~ 1/3 of testcases. + print('number of second wasms should be around 33 +- 8') + print(f'number of second wasms: {len(seen_second_sizes)}') + assert seen_second_sizes, 'must see at least one second wasm' + print('second sizes are distributed as ~ mean 2933, stddev 2011, median 2510') + print(f'mean sizes: {statistics.mean(seen_second_sizes)}') + print(f'stdev sizes: {statistics.stdev(seen_second_sizes)}') + print(f'median sizes: {statistics.median(seen_second_sizes)}') + # Relax the assert on the max seen second size compared to the max seen + # primary size, as we see fewer of these. 500 is still proof of an + # interesting wasm file. + self.assertGreaterEqual(max(seen_second_sizes), 500) + self.assertGreater(statistics.stdev(seen_second_sizes), 0) + + print() + # To check for interesting JS file contents, we'll note how many times # we build and run the wasm. seen_builds = [] seen_calls = [] + seen_second_builds = [] for i in range(1, N + 1): fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') @@ -258,6 +285,7 @@ def test_file_contents(self): js = f.read() seen_builds.append(js.count('build(binary);')) seen_calls.append(js.count('callExports();')) + seen_second_builds.append(js.count('build(secondBinary);')) # There is always one build and one call (those are in the default # fuzz_shell.js), and we add a couple of operations, each with equal @@ -284,6 +312,17 @@ def test_file_contents(self): print() + # Second wasm files are more rarely added, only 1/3 of the time or so, + # but over 100 samples we are still overwhelmingly likely to see one. + print('JS second builds are distributed as ~ mean 1.8, stddev 2.2, median 1') + print(f'mean JS second builds: {statistics.mean(seen_second_builds)}') + print(f'stdev JS second builds: {statistics.stdev(seen_second_builds)}') + print(f'median JS second builds: {statistics.median(seen_second_builds)}') + self.assertGreaterEqual(max(seen_second_builds), 2) + self.assertGreater(statistics.stdev(seen_second_builds), 0) + + print() + # "zzz" in test name so that this runs last. If it runs first, it can be # confusing as it appears next to the logging of which bundle we use (see # setUpClass). From 98f57983b0bbb05222ce45bb84cf8b87f36f03f1 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 26 Nov 2024 16:04:07 -0800 Subject: [PATCH 165/622] Handle concrete values in CodeFolding (#7117) CodeFolding previously only worked on blocks that did not produce values. It worked on Ifs that produced values, but only by accident; the logic for folding matching tails was not written to support tails producing concrete values, but it happened to work for Ifs because subsequent ReFinalize runs fixed all the incorrect types it produced. Improve the power of the optimization by explicitly handling tails that produce concrete values for both blocks and ifs. Now that the core logic handles concrete values correctly, remove the unnecessary ReFinalize run. Also remove the separate optimization of Ifs with identical arms; this optimization requires ReFinalize and is already performed by OptimizeInstructions. --- src/passes/CodeFolding.cpp | 234 ++++---- test/lit/passes/code-folding-eh-legacy.wast | 3 + test/lit/passes/code-folding.wast | 506 ++++++++++++++++-- test/passes/O3_low-memory-unused_metrics.txt | 438 ++++++++------- .../remove-unused-names_code-folding.txt | 114 ++-- 5 files changed, 831 insertions(+), 464 deletions(-) diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp index 0cddec4ca3f..42331b74746 100644 --- a/src/passes/CodeFolding.cpp +++ b/src/passes/CodeFolding.cpp @@ -105,19 +105,11 @@ struct CodeFolding Tail(Block* block) : expr(nullptr), block(block), pointer(nullptr) {} // For a break Tail(Expression* expr, Block* block) - : expr(expr), block(block), pointer(nullptr) { - validate(); - } + : expr(expr), block(block), pointer(nullptr) {} Tail(Expression* expr, Expression** pointer) : expr(expr), block(nullptr), pointer(pointer) {} bool isFallthrough() const { return expr == nullptr; } - - void validate() const { - if (expr && block) { - assert(block->list.back() == expr); - } - } }; // state @@ -152,15 +144,13 @@ struct CodeFolding } void visitBreak(Break* curr) { - if (curr->condition || curr->value) { + if (curr->condition) { unoptimizables.insert(curr->name); } else { - // we can only optimize if we are at the end of the parent block, - // and if the parent block does not return a value (we can't move - // elements out of it if there is a value being returned) + // we can only optimize if we are at the end of the parent block. + // TODO: Relax this. Block* parent = controlFlowStack.back()->dynCast(); - if (parent && curr == parent->list.back() && - !parent->list.back()->type.isConcrete()) { + if (parent && curr == parent->list.back()) { breakTails[curr->name].push_back(Tail(curr, parent)); } else { unoptimizables.insert(curr->name); @@ -222,24 +212,19 @@ struct CodeFolding if (unoptimizables.count(curr->name) > 0) { return; } - // we can't optimize a fallthrough value - if (curr->list.back()->type.isConcrete()) { - return; - } auto iter = breakTails.find(curr->name); if (iter == breakTails.end()) { return; } - // looks promising + // Looks promising. auto& tails = iter->second; - // see if there is a fallthrough - bool hasFallthrough = true; - for (auto* child : curr->list) { - if (child->type == Type::unreachable) { - hasFallthrough = false; - } - } - if (hasFallthrough) { + // If the end of the block cannot be reached, then we don't need to include + // it in the set of folded tails. + bool includeFallthrough = + !std::any_of(curr->list.begin(), curr->list.end(), [&](auto* child) { + return child->type == Type::unreachable; + }); + if (includeFallthrough) { tails.push_back({Tail(curr)}); } optimizeExpressionTails(tails, curr); @@ -249,48 +234,34 @@ struct CodeFolding if (!curr->ifFalse) { return; } - // if both sides are identical, this is easy to fold - if (ExpressionAnalyzer::equal(curr->ifTrue, curr->ifFalse)) { + // If both are blocks, look for a tail we can merge. + auto* left = curr->ifTrue->dynCast(); + auto* right = curr->ifFalse->dynCast(); + // If one is a block and the other isn't, and the non-block is a tail of the + // other, we can fold that - for our convenience, we just add a block and + // run the rest of the optimization mormally. + auto maybeAddBlock = [this](Block* block, Expression*& other) -> Block* { + // If other is a suffix of the block, wrap it in a block. + if (block->list.empty() || + !ExpressionAnalyzer::equal(other, block->list.back())) { + return nullptr; + } + // Do it, assign to the out param `other`, and return the block. Builder builder(*getModule()); - // remove if (4 bytes), remove one arm, add drop (1), add block (3), - // so this must be a net savings - markAsModified(curr); - auto* ret = - builder.makeSequence(builder.makeDrop(curr->condition), curr->ifTrue); - // we must ensure we present the same type as the if had - ret->finalize(curr->type); - replaceCurrent(ret); - needEHFixups = true; - } else { - // if both are blocks, look for a tail we can merge - auto* left = curr->ifTrue->dynCast(); - auto* right = curr->ifFalse->dynCast(); - // If one is a block and the other isn't, and the non-block is a tail - // of the other, we can fold that - for our convenience, we just add - // a block and run the rest of the optimization mormally. - auto maybeAddBlock = [this](Block* block, Expression*& other) -> Block* { - // if other is a suffix of the block, wrap it in a block - if (block->list.empty() || - !ExpressionAnalyzer::equal(other, block->list.back())) { - return nullptr; - } - // do it, assign to the out param `other`, and return the block - Builder builder(*getModule()); - auto* ret = builder.makeBlock(other); - other = ret; - return ret; - }; - if (left && !right) { - right = maybeAddBlock(left, curr->ifFalse); - } else if (!left && right) { - left = maybeAddBlock(right, curr->ifTrue); - } - // we need nameless blocks, as if there is a name, someone might branch - // to the end, skipping the code we want to merge - if (left && right && !left->name.is() && !right->name.is()) { - std::vector tails = {Tail(left), Tail(right)}; - optimizeExpressionTails(tails, curr); - } + auto* ret = builder.makeBlock(other); + other = ret; + return ret; + }; + if (left && !right) { + right = maybeAddBlock(left, curr->ifFalse); + } else if (!left && right) { + left = maybeAddBlock(right, curr->ifTrue); + } + // We need nameless blocks, as if there is a name, someone might branch to + // the end, skipping the code we want to merge. + if (left && right && !left->name.is() && !right->name.is()) { + std::vector tails = {Tail(left), Tail(right)}; + optimizeExpressionTails(tails, curr); } } @@ -315,10 +286,6 @@ struct CodeFolding if (needEHFixups) { EHUtils::handleBlockNestedPops(func, *getModule()); } - // if we did any work, types may need to be propagated - if (anotherPass) { - ReFinalize().walkFunctionInModule(func, getModule()); - } } } @@ -372,6 +339,7 @@ struct CodeFolding // identical in all paths leading to the block exit can be merged. template void optimizeExpressionTails(std::vector& tails, T* curr) { + auto oldType = curr->type; if (tails.size() < 2) { return; } @@ -384,50 +352,49 @@ struct CodeFolding return; } // if we were not modified, then we should be valid for processing - tail.validate(); + assert(!tail.expr || !tail.block || + (tail.expr == tail.block->list.back())); } - // we can ignore the final br in a tail - auto effectiveSize = [&](const Tail& tail) { - auto ret = tail.block->list.size(); + auto getMergeable = [&](const Tail& tail, Index num) -> Expression* { if (!tail.isFallthrough()) { - ret--; + // If there is a branch value, it is the first mergeable item. + auto* val = tail.expr->cast()->value; + if (val && num == 0) { + return val; + } + if (!val) { + // Skip the branch instruction at the end; it is not part of the + // merged tail. + ++num; + } } - return ret; - }; - // the mergeable items do not include the final br in a tail - auto getMergeable = [&](const Tail& tail, Index num) { - return tail.block->list[effectiveSize(tail) - num - 1]; + if (num >= tail.block->list.size()) { + return nullptr; + } + return tail.block->list[tail.block->list.size() - num - 1]; }; // we are going to remove duplicate elements and add a block. // so for this to make sense, we need the size of the duplicate // elements to be worth that extra block (although, there is // some chance the block would get merged higher up, see later) std::vector mergeable; // the elements we can merge - Index num = 0; // how many elements back from the tail to look at Index saved = 0; // how much we can save - while (1) { - // check if this num is still relevant - bool stop = false; - for (auto& tail : tails) { - assert(tail.block); - if (num >= effectiveSize(tail)) { - // one of the lists is too short - stop = true; - break; - } - } - if (stop) { + for (Index num = 0; true; ++num) { + auto* item = getMergeable(tails[0], num); + if (!item) { + // The list is too short. break; } - auto* item = getMergeable(tails[0], num); - for (auto& tail : tails) { - if (!ExpressionAnalyzer::equal(item, getMergeable(tail, num))) { - // one of the lists has a different item - stop = true; + Index tail = 1; + for (; tail < tails.size(); ++tail) { + auto* other = getMergeable(tails[tail], num); + if (!other || !ExpressionAnalyzer::equal(item, other)) { + // Other tail too short or has a difference. break; } } - if (stop) { + if (tail != tails.size()) { + // We saw a tail without a matching item. break; } // we may have found another one we can merge - can we move it? @@ -436,7 +403,6 @@ struct CodeFolding } // we found another one we can merge mergeable.push_back(item); - num++; saved += Measurer::measure(item); } if (saved == 0) { @@ -450,7 +416,7 @@ struct CodeFolding for (auto& tail : tails) { // it is enough to zero out the block, or leave just one // element, as then the block can be replaced with that - if (num >= tail.block->list.size() - 1) { + if (mergeable.size() >= tail.block->list.size() - 1) { willEmptyBlock = true; break; } @@ -483,6 +449,7 @@ struct CodeFolding } } } + // this is worth doing, do it! for (auto& tail : tails) { // remove the items we are merging / moving @@ -490,37 +457,61 @@ struct CodeFolding // again in this pass, which might be buggy markAsModified(tail.block); // we must preserve the br if there is one - Expression* last = nullptr; + Break* branch = nullptr; if (!tail.isFallthrough()) { - last = tail.block->list.back(); - tail.block->list.pop_back(); + branch = tail.block->list.back()->cast(); + if (branch->value) { + branch->value = nullptr; + } else { + tail.block->list.pop_back(); + } } - for (Index i = 0; i < mergeable.size(); i++) { + for (Index i = 0; i < mergeable.size(); ++i) { tail.block->list.pop_back(); } - if (!tail.isFallthrough()) { - tail.block->list.push_back(last); + if (tail.isFallthrough()) { + // The block now ends in an expression that was previously in the middle + // of the block, meaning it must have type none. + tail.block->finalize(Type::none); + } else { + tail.block->list.push_back(branch); + // The block still ends with the same branch it previously ended with, + // so its type cannot have changed. + tail.block->finalize(tail.block->type); } - // the block type may change if we removed unreachable stuff, - // but in general it should remain the same, as if it had a - // forced type it should remain, *and*, we don't have a - // fallthrough value (we would never get here), so a concrete - // type was not from that. I.e., any type on the block is - // either forced and/or from breaks with a value, so the - // type cannot be changed by moving code out. - tail.block->finalize(tail.block->type); } // since we managed a merge, then it might open up more opportunities later anotherPass = true; // make a block with curr + the merged code Builder builder(*getModule()); auto* block = builder.makeBlock(); - block->list.push_back(curr); + if constexpr (T::SpecificId == Expression::IfId) { + // If we've moved all the contents out of both arms of the If, then we can + // simplify the output by replacing it entirely with just a drop of the + // condition. + auto* iff = curr->template cast(); + if (iff->ifTrue->template cast()->list.empty() && + iff->ifFalse->template cast()->list.empty()) { + block->list.push_back(builder.makeDrop(iff->condition)); + } else { + block->list.push_back(curr); + } + } else { + block->list.push_back(curr); + } while (!mergeable.empty()) { block->list.push_back(mergeable.back()); mergeable.pop_back(); } - auto oldType = curr->type; + if constexpr (T::SpecificId == Expression::BlockId) { + // If we didn't have a fallthrough tail because the end of the block was + // not reachable, then we might have a concrete expression at the end of + // the block even though the value produced by the block has been moved + // out of it. If so, drop that expression. + auto* currBlock = curr->template cast(); + currBlock->list.back() = + builder.dropIfConcretelyTyped(currBlock->list.back()); + } // NB: we template-specialize so that this calls the proper finalizer for // the type curr->finalize(); @@ -553,9 +544,6 @@ struct CodeFolding if (tail.block && modifieds.count(tail.block) > 0) { return true; } - // if we were not modified, then we should be valid for - // processing - tail.validate(); return false; }), tails.end()); diff --git a/test/lit/passes/code-folding-eh-legacy.wast b/test/lit/passes/code-folding-eh-legacy.wast index 81180d04463..cde0fba2836 100644 --- a/test/lit/passes/code-folding-eh-legacy.wast +++ b/test/lit/passes/code-folding-eh-legacy.wast @@ -355,6 +355,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (i32.const 1) @@ -377,6 +378,7 @@ (if (pop i32) (then + (nop) (drop (i32.eqz (i32.const 1) @@ -384,6 +386,7 @@ ) ) (else + (nop) (drop (i32.eqz (i32.const 1) diff --git a/test/lit/passes/code-folding.wast b/test/lit/passes/code-folding.wast index 35816748149..007aa59099d 100644 --- a/test/lit/passes/code-folding.wast +++ b/test/lit/passes/code-folding.wast @@ -1,18 +1,32 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. -;; RUN: foreach %s %t wasm-opt -all --code-folding -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --code-folding -S -o - | filecheck %s (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $1 (func (result f32))) + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (type $3 (func (result anyref))) + ;; CHECK: (type $13 (func (param f32))) (type $13 (func (param f32))) (table 282 282 funcref) + ;; CHECK: (type $5 (func (param i32))) + + ;; CHECK: (global $global$0 (mut i32) (i32.const 10)) + ;; CHECK: (memory $0 1 1) (memory $0 1 1) + + ;; CHECK: (memory $shared 1 1 shared) + (memory $shared 1 1 shared) + + (global $global$0 (mut i32) (i32.const 10)) + ;; CHECK: (table $0 282 282 funcref) ;; CHECK: (func $0 (type $0) @@ -22,7 +36,7 @@ ;; CHECK-NEXT: (then ;; CHECK-NEXT: (block $label$3 ;; CHECK-NEXT: (call_indirect $0 (type $13) - ;; CHECK-NEXT: (block $label$4 + ;; CHECK-NEXT: (block $label$4 (result f32) ;; CHECK-NEXT: (br $label$3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 105) @@ -52,18 +66,15 @@ ) ) ) + ;; CHECK: (func $negative-zero (type $1) (result f32) ;; CHECK-NEXT: (if (result f32) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (block $label$0 (result f32) - ;; CHECK-NEXT: (f32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (block $label$1 (result f32) - ;; CHECK-NEXT: (f32.const -0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const -0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -71,63 +82,173 @@ (if (result f32) (i32.const 0) (then - (block $label$0 (result f32) + (block (result f32) (f32.const 0) ) ) (else - (block $label$1 (result f32) + (block (result f32) (f32.const -0) ) ) ) ) + ;; CHECK: (func $negative-zero-b (type $1) (result f32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block $label$0 (result f32) - ;; CHECK-NEXT: (f32.const -0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const -0) ;; CHECK-NEXT: ) (func $negative-zero-b (result f32) (if (result f32) (i32.const 0) (then - (block $label$0 (result f32) + (block (result f32) (f32.const -0) ) ) (else - (block $label$1 (result f32) + (block (result f32) (f32.const -0) ) ) ) ) - ;; CHECK: (func $negative-zero-c (type $1) (result f32) - ;; CHECK-NEXT: (drop + + ;; CHECK: (func $positive-zero (type $1) (result f32) + ;; CHECK-NEXT: (if (result f32) ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block $label$0 (result f32) - ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + (func $positive-zero (result f32) + ;; This doesn't get optimized because we only look at Ifs with block arms. + ;; This simpler case will be optimized by OptimizeInstructions. + (if (result f32) + (i32.const 0) + (then + (f32.const 0) + ) + (else + (f32.const 0) + ) + ) + ) + + ;; CHECK: (func $positive-zero-names (type $1) (result f32) + ;; CHECK-NEXT: (if (result f32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $l1 (result f32) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (block $l2 (result f32) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $negative-zero-c (result f32) + (func $positive-zero-names (result f32) + ;; This one has block arms, but doesn't get optimized because the blocks have + ;; names. (if (result f32) (i32.const 0) (then - (block $label$0 (result f32) + (block $l1 (result f32) (f32.const 0) ) ) (else - (block $label$1 (result f32) + (block $l2 (result f32) (f32.const 0) ) ) ) ) + + ;; CHECK: (func $positive-zero-extra-a (type $1) (result f32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + (func $positive-zero-extra-a (result f32) + (if (result f32) + (i32.const 0) + (then + (nop) + (f32.const 0) + ) + (else + (f32.const 0) + ) + ) + ) + + ;; CHECK: (func $positive-zero-extra-b (type $1) (result f32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + (func $positive-zero-extra-b (result f32) + (if (result f32) + (i32.const 0) + (then + (f32.const 0) + ) + (else + (nop) + (f32.const 0) + ) + ) + ) + + ;; CHECK: (func $positive-zero-extra-c (type $1) (result f32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + (func $positive-zero-extra-c (result f32) + (if (result f32) + (i32.const 0) + (then + (nop) + (nop) + (f32.const 0) + ) + (else + (nop) + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $break-target-outside-of-return-merged-code (type $0) ;; CHECK-NEXT: (block $label$A ;; CHECK-NEXT: (if @@ -202,6 +323,7 @@ ) ) ) + ;; CHECK: (func $break-target-inside-all-good (type $0) ;; CHECK-NEXT: (block $folding-inner0 ;; CHECK-NEXT: (block $label$A @@ -269,11 +391,12 @@ ) ) ) + ;; CHECK: (func $leave-inner-block-type (type $0) ;; CHECK-NEXT: (block $label$1 ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label$2 - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $label$2 (result i32) + ;; CHECK-NEXT: (br_if $label$2 ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -304,54 +427,40 @@ ) ) ) -) -(module - ;; CHECK: (type $0 (func (result i32))) - ;; CHECK: (memory $0 1 1 shared) - (memory $0 1 1 shared) - ;; CHECK: (export "func_2224" (func $0)) - (export "func_2224" (func $0)) - ;; CHECK: (func $0 (type $0) (result i32) + ;; CHECK: (func $atomic-load-different (type $2) (result i32) ;; CHECK-NEXT: (local $var$0 i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (i32.load offset=22 + ;; CHECK-NEXT: (i32.load $shared offset=22 ;; CHECK-NEXT: (local.get $var$0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (i32.atomic.load offset=22 + ;; CHECK-NEXT: (i32.atomic.load $shared offset=22 ;; CHECK-NEXT: (local.get $var$0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $0 (result i32) + (func $atomic-load-different (result i32) (local $var$0 i32) (if (result i32) (i32.const 0) (then - (i32.load offset=22 + (i32.load $shared offset=22 (local.get $var$0) ) ) (else - (i32.atomic.load offset=22 + (i32.atomic.load $shared offset=22 (local.get $var$0) ) ) ) ) -) -(module - ;; CHECK: (type $0 (func)) - (type $0 (func)) - ;; CHECK: (type $1 (func (param i32))) - ;; CHECK: (global $global$0 (mut i32) (i32.const 10)) - (global $global$0 (mut i32) (i32.const 10)) ;; CHECK: (func $determinism (type $0) ;; CHECK-NEXT: (block $folding-inner0 ;; CHECK-NEXT: (block @@ -390,7 +499,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $determinism (; 0 ;) (type $0) + (func $determinism (block $label$1 (br_if $label$1 (i32.const 1) @@ -439,7 +548,8 @@ ) (unreachable) ) - ;; CHECK: (func $careful-of-the-switch (type $1) (param $0 i32) + + ;; CHECK: (func $careful-of-the-switch (type $5) (param $0 i32) ;; CHECK-NEXT: (block $label$1 ;; CHECK-NEXT: (block $label$3 ;; CHECK-NEXT: (block $label$5 @@ -481,10 +591,243 @@ (unreachable) ) ) -) -(module - ;; CHECK: (type $0 (func)) + ;; CHECK: (func $br-with-value (type $2) (result i32) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $br-with-value (result i32) + (block $l (result i32) + (block + (br $l + (i32.const 1) + ) + ) + (block + (br $l + (i32.const 1) + ) + ) + (i32.const 1) + ) + ) + + ;; CHECK: (func $br-and-fallthrough-with-value (type $2) (result i32) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $br-and-fallthrough-with-value (result i32) + (block $l (result i32) + (drop + (block (result i32) + (br $l + (i32.const 1) + ) + ) + ) + (drop + (block (result i32) + (br $l + (i32.const 1) + ) + ) + ) + (i32.const 1) + ) + ) + + ;; CHECK: (func $br-with-value-and-more (type $2) (result i32) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $br-with-value-and-more (result i32) + (block $l (result i32) + (block + (nop) + (nop) + (br $l + (i32.const 1) + ) + ) + (block + (nop) + (nop) + (nop) + (br $l + (i32.const 1) + ) + ) + (nop) + (i32.const 1) + ) + ) + + ;; CHECK: (func $br-and-fallthrough-with-value-and-more (type $2) (result i32) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $br-and-fallthrough-with-value-and-more (result i32) + (block $l (result i32) + (drop + (block (result i32) + (nop) + (nop) + (br $l + (i32.const 1) + ) + ) + ) + (drop + (block (result i32) + (nop) + (nop) + (nop) + (br $l + (i32.const 1) + ) + ) + ) + (nop) + (i32.const 1) + ) + ) + + ;; CHECK: (func $unreachable-if (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-if + (if + (unreachable) + (then + (nop) + (drop + (i32.const 1) + ) + ) + (else + (nop) + (drop + (i32.const 1) + ) + ) + ) + ) + + ;; CHECK: (func $unreachable-if-suffix (type $0) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-if-suffix + (if + (unreachable) + (then + (drop + (i32.const 1) + ) + ) + (else + (nop) + (drop + (i32.const 1) + ) + ) + ) + ) + + ;; CHECK: (func $unreachable-if-concrete-arms (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $unreachable-if-concrete-arms + (if (result i32) + (unreachable) + (then + (i32.const 1) + ) + (else + (nop) + (i32.const 1) + ) + ) + (unreachable) + ) ;; CHECK: (func $br-on-null (type $0) ;; CHECK-NEXT: (block $block @@ -520,4 +863,69 @@ (call $br-on-null) ) ) + + ;; CHECK: (func $refined-type (type $3) (result anyref) + ;; CHECK-NEXT: (select (result anyref) + ;; CHECK-NEXT: (if (result anyref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $refined-type (result anyref) + (select (result anyref) + ;; If we fold the identical arms, the select will have a stale type. + (if (result anyref) + (i32.const 0) + (then + (ref.null none) + ) + (else + (ref.null none) + ) + ) + (ref.null none) + (i32.const 0) + ) + ) + + ;; CHECK: (func $refined-type-blocks (type $3) (result anyref) + ;; CHECK-NEXT: (select (result anyref) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $refined-type-blocks (result anyref) + (select (result anyref) + ;; Same, but now the arms are blocks, so they have the stale types (which is + ;; allowed) and the select is ok. + (if (result anyref) + (i32.const 0) + (then + (nop) + (ref.null none) + ) + (else + (nop) + (ref.null none) + ) + ) + (ref.null none) + (i32.const 0) + ) + ) ) diff --git a/test/passes/O3_low-memory-unused_metrics.txt b/test/passes/O3_low-memory-unused_metrics.txt index 8806e9a043c..3117d77785d 100644 --- a/test/passes/O3_low-memory-unused_metrics.txt +++ b/test/passes/O3_low-memory-unused_metrics.txt @@ -9,23 +9,23 @@ total [table-data] : 0 [tables] : 0 [tags] : 0 - [total] : 1964 + [total] : 1953 [vars] : 9 - Binary : 240 + Binary : 238 Block : 68 Break : 90 Call : 22 CallIndirect : 1 - Const : 175 + Const : 174 Drop : 8 If : 27 - Load : 313 - LocalGet : 633 - LocalSet : 181 + Load : 311 + LocalGet : 629 + LocalSet : 180 Loop : 3 Return : 3 Select : 11 - Store : 160 + Store : 159 Unary : 29 (module (type $0 (func (param i32 i32 i32) (result i32))) @@ -2718,307 +2718,289 @@ total (local.get $0) ) ) - (i32.store8 - (block $label$68 (result i32) - (if - (i32.eq - (local.get $4) - (i32.const 2) - ) - (then - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) - ) - ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 + (block $label$68 + (if + (i32.eq + (local.get $4) + (i32.const 2) + ) + (then + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) - (local.get $1) + (i32.const 1) ) - (local.set $1 - (i32.load offset=48 - (local.get $0) + ) + (i32.store8 + (i32.add + (local.get $3) + (i32.load offset=8 + (local.get $2) ) ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) - ) + (local.get $1) + ) + (local.set $1 + (i32.load offset=48 + (local.get $0) ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) - (i32.shr_u - (local.get $1) - (i32.const 8) - ) - ) - (local.set $1 - (i32.load16_u offset=50 - (local.get $0) - ) + (i32.const 1) ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) + ) + (i32.store8 + (i32.add + (local.get $3) + (i32.load offset=8 + (local.get $2) ) ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 - (local.get $2) - ) - ) + (i32.shr_u (local.get $1) + (i32.const 8) ) - (local.set $1 - (i32.load8_u offset=51 - (local.get $0) - ) - ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) - ) + ) + (local.set $1 + (i32.load16_u offset=50 + (local.get $0) ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) - (local.get $1) + (i32.const 1) ) - (local.set $1 + ) + (i32.store8 + (i32.add + (local.get $3) (i32.load offset=8 - (local.get $0) + (local.get $2) ) ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) - ) + (local.get $1) + ) + (local.set $1 + (i32.load8_u offset=51 + (local.get $0) ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) - (local.get $1) + (i32.const 1) ) - (local.set $1 + ) + (i32.store8 + (i32.add + (local.get $3) (i32.load offset=8 - (local.get $0) + (local.get $2) ) ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) - ) + (local.get $1) + ) + (local.set $1 + (i32.load offset=8 + (local.get $0) ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) - (i32.shr_u - (local.get $1) - (i32.const 8) - ) + (i32.const 1) ) - (local.set $1 - (i32.load16_u offset=10 - (local.get $0) + ) + (i32.store8 + (i32.add + (local.get $3) + (i32.load offset=8 + (local.get $2) ) ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) - ) + (local.get $1) + ) + (local.set $1 + (i32.load offset=8 + (local.get $0) ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) - (local.get $1) + (i32.const 1) ) - (local.set $3 - (i32.load8_u offset=11 - (local.get $0) + ) + (i32.store8 + (i32.add + (local.get $3) + (i32.load offset=8 + (local.get $2) ) ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $1 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) - ) + (i32.shr_u + (local.get $1) + (i32.const 8) ) - (br $label$68 - (i32.add - (local.get $1) - (i32.load offset=8 + ) + (local.set $1 + (i32.load16_u offset=10 + (local.get $0) + ) + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) + (i32.const 1) ) ) - ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 + (i32.store8 + (i32.add + (local.get $3) + (i32.load offset=8 (local.get $2) ) ) - (i32.const 1) - ) - ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 - (local.get $2) - ) - ) - (i32.shr_u (local.get $1) - (i32.const 24) ) - ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $3 - (i32.load offset=20 - (local.get $2) - ) + (local.set $3 + (i32.load8_u offset=11 + (local.get $0) ) - (i32.const 1) ) + (br $label$68) ) - (i32.store8 - (i32.add - (local.get $3) - (i32.load offset=8 + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) - (i32.shr_u - (local.get $1) - (i32.const 16) - ) + (i32.const 1) ) - (local.set $3 - (i32.load offset=48 - (local.get $0) + ) + (i32.store8 + (i32.add + (local.get $3) + (i32.load offset=8 + (local.get $2) ) ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $1 - (i32.load offset=20 - (local.get $2) - ) - ) - (i32.const 1) - ) + (i32.shr_u + (local.get $1) + (i32.const 24) ) - (i32.store8 - (i32.add - (local.get $1) - (i32.load offset=8 + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $3 + (i32.load offset=20 (local.get $2) ) ) - (i32.shr_u - (local.get $3) - (i32.const 8) + (i32.const 1) + ) + ) + (i32.store8 + (i32.add + (local.get $3) + (i32.load offset=8 + (local.get $2) ) ) - (i32.store offset=20 - (local.get $2) - (i32.add - (local.tee $1 - (i32.load offset=20 - (local.get $2) - ) + (i32.shr_u + (local.get $1) + (i32.const 16) + ) + ) + (local.set $3 + (i32.load offset=48 + (local.get $0) + ) + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $1 + (i32.load offset=20 + (local.get $2) ) - (i32.const 1) ) + (i32.const 1) ) + ) + (i32.store8 (i32.add (local.get $1) (i32.load offset=8 (local.get $2) ) ) + (i32.shr_u + (local.get $3) + (i32.const 8) + ) + ) + ) + (i32.store offset=20 + (local.get $2) + (i32.add + (local.tee $1 + (i32.load offset=20 + (local.get $2) + ) + ) + (i32.const 1) + ) + ) + (i32.store8 + (i32.add + (local.get $1) + (i32.load offset=8 + (local.get $2) + ) ) (local.get $3) ) diff --git a/test/passes/remove-unused-names_code-folding.txt b/test/passes/remove-unused-names_code-folding.txt index d0486b3c784..85810131b4a 100644 --- a/test/passes/remove-unused-names_code-folding.txt +++ b/test/passes/remove-unused-names_code-folding.txt @@ -10,11 +10,14 @@ (nop) ) ) - (block - (drop - (i32.const 0) + (if + (i32.const 0) + (then + (nop) + ) + (else + (nop) ) - (nop) ) (if (i32.const 0) @@ -26,13 +29,19 @@ ) ) (drop - (block (result i32) - (drop - (i32.const 0) + (if (result i32) + (i32.const 0) + (then + (i32.add + (i32.const 1) + (i32.const 2) + ) ) - (i32.add - (i32.const 1) - (i32.const 2) + (else + (i32.add + (i32.const 1) + (i32.const 2) + ) ) ) ) @@ -59,9 +68,7 @@ (drop (i32.const 0) ) - (block - (nop) - ) + (nop) ) (block (if @@ -111,12 +118,10 @@ (drop (i32.const 0) ) - (block - (drop - (i32.add - (i32.const 1) - (i32.const 2) - ) + (drop + (i32.add + (i32.const 1) + (i32.const 2) ) ) ) @@ -502,12 +507,10 @@ (drop (local.get $x) ) - (block - (br_if $out - (local.get $y) - ) - (nop) + (br_if $out + (local.get $y) ) + (nop) ) (block (if @@ -695,18 +698,16 @@ (drop (i32.const 1) ) - (block - (drop - (i32.const 2) - ) - (nop) - (nop) - (nop) - (nop) - (nop) - (nop) - (br $out) + (drop + (i32.const 2) ) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (br $out) ) ) (block $out2 @@ -745,17 +746,13 @@ (drop (i32.const 1) ) - (block - (br $out3) - ) + (br $out3) ) (block (drop (i32.const 1) ) - (block - (br $out3) - ) + (br $out3) ) (br $out3) ) @@ -788,20 +785,15 @@ ) ) (drop - (block $y (result i32) - (if - (i32.const 0) - (then - (drop - (i32.const 1) - ) - (drop - (i32.const 2) - ) - (br $y - (i32.const 3) + (block (result i32) + (block $y + (if + (i32.const 0) + (then + (br $y) ) ) + (br $y) ) (drop (i32.const 1) @@ -809,9 +801,7 @@ (drop (i32.const 2) ) - (br $y - (i32.const 3) - ) + (i32.const 3) ) ) (drop @@ -1508,9 +1498,7 @@ (drop (i32.const 0) ) - (block - (nop) - ) + (nop) ) (if (i32.const 0) @@ -1527,11 +1515,9 @@ (drop (unreachable) ) - (block (result i32) - (i32.add - (i32.const 1) - (i32.const 2) - ) + (i32.add + (i32.const 1) + (i32.const 2) ) ) ) From cd3805e31e8f2544d5e32aa505fc5e3abcf93df2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 26 Nov 2024 16:24:34 -0800 Subject: [PATCH 166/622] [NFC] Add a node test for fuzz_shell.js running on two input wasms (#7123) This feature is depended on by our ClusterFuzz integration. --- test/lit/node/fuzz_shell_second.wast | 32 +++++++++++++++++++++ test/lit/node/fuzz_shell_second.wast.second | 8 ++++++ 2 files changed, 40 insertions(+) create mode 100644 test/lit/node/fuzz_shell_second.wast create mode 100644 test/lit/node/fuzz_shell_second.wast.second diff --git a/test/lit/node/fuzz_shell_second.wast b/test/lit/node/fuzz_shell_second.wast new file mode 100644 index 00000000000..6fbe8d76422 --- /dev/null +++ b/test/lit/node/fuzz_shell_second.wast @@ -0,0 +1,32 @@ +;; Test that the fuzz_shell.js file will run a second wasm file that is +;; provided, and call its exports as well as the first module's. + +(module + (func $first (export "first") (result i32) + (i32.const 42) + ) +) + +;; Build both files to binary. +;; +;; RUN: wasm-opt %s -o %t.wasm -q +;; RUN: wasm-opt %s.second -o %t.second.wasm -q + +;; Run in node. +;; +;; RUN: node %S/../../../scripts/fuzz_shell.js %t.wasm %t.second.wasm | filecheck %s +;; +;; CHECK: [fuzz-exec] calling first +;; CHECK: [fuzz-exec] note result: first => 42 +;; CHECK: [fuzz-exec] calling second +;; CHECK: [fuzz-exec] note result: second => 1337 + +;; Run in reverse order, flipping the order in the output. +;; +;; RUN: node %S/../../../scripts/fuzz_shell.js %t.second.wasm %t.wasm | filecheck %s --check-prefix=REVERSE +;; +;; REVERSE: [fuzz-exec] calling second +;; REVERSE: [fuzz-exec] note result: second => 1337 +;; REVERSE: [fuzz-exec] calling first +;; REVERSE: [fuzz-exec] note result: first => 42 + diff --git a/test/lit/node/fuzz_shell_second.wast.second b/test/lit/node/fuzz_shell_second.wast.second new file mode 100644 index 00000000000..3c52a59c9cf --- /dev/null +++ b/test/lit/node/fuzz_shell_second.wast.second @@ -0,0 +1,8 @@ +;; Second module for the test. + +(module + (func $second (export "second") (result i32) + (i32.const 1337) + ) +) + From 6f0f2e00521843118b63f41732dc2eb86d39fa09 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 26 Nov 2024 17:12:15 -0800 Subject: [PATCH 167/622] Make more Ifs unreachable (#7094) Previously the only Ifs that were typed unreachable were those in which both arms were unreachable and those in which the condition was unreachable that would have otherwise been typed none. This caused problems in IRBuilder because Ifs with unreachable conditions and value-returning arms would have concrete types, effectively hiding the unreachable condition from the logic for dropping concretely typed expressions preceding an unreachable expression when finishing a scope. Relax the conditions under which an If can be typed unreachable so that all Ifs with unreachable conditions or two unreachable arms are typed unreachable. Propagating unreachability more eagerly this way makes various optimizations of Ifs more powerful. It also requires new handling for unreachable Ifs with concretely typed arms in the Printer to ensure that printed wat remains valid. Also update Unsubtyping, Flatten, and CodeFolding to account for the newly unreachable Ifs. --- src/ir/subtype-exprs.h | 2 +- src/passes/CodeFolding.cpp | 7 ++ src/passes/Flatten.cpp | 16 +++-- src/passes/Print.cpp | 11 +++- src/wasm/wasm-validator.cpp | 23 +++---- src/wasm/wasm.cpp | 30 ++++----- test/lit/passes/code-folding.wast | 44 ++++++++----- test/lit/passes/flatten_all-features.wast | 12 +--- .../optimize-instructions-ignore-traps.wast | 2 +- .../lit/passes/optimize-instructions-mvp.wast | 30 ++++----- test/lit/passes/unsubtyping.wast | 40 ++++++++++++ test/lit/wat-kitchen-sink.wast | 65 ++++++++++++++++++- .../remove-unused-brs_enable-multivalue.txt | 6 +- .../remove-unused-names_code-folding.txt | 18 +++-- test/spec/if.wast | 22 ++++--- test/wasm2js/br_table_temp.2asm.js | 2 +- test/wasm2js/unreachable-if.2asm.js | 19 ++++++ test/wasm2js/unreachable-if.2asm.js.opt | 19 ++++++ test/wasm2js/unreachable-if.wast | 22 +++++++ 19 files changed, 282 insertions(+), 108 deletions(-) create mode 100644 test/wasm2js/unreachable-if.2asm.js create mode 100644 test/wasm2js/unreachable-if.2asm.js.opt create mode 100644 test/wasm2js/unreachable-if.wast diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 1895c856ae1..e6ee1816d3e 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -122,7 +122,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } void visitIf(If* curr) { - if (curr->ifFalse) { + if (curr->ifFalse && curr->type != Type::unreachable) { self()->noteSubtype(curr->ifTrue, curr); self()->noteSubtype(curr->ifFalse, curr); } diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp index 42331b74746..305eb12784f 100644 --- a/src/passes/CodeFolding.cpp +++ b/src/passes/CodeFolding.cpp @@ -234,6 +234,13 @@ struct CodeFolding if (!curr->ifFalse) { return; } + if (curr->condition->type == Type::unreachable) { + // If the arms are foldable and concrete, we would be replacing an + // unreachable If with a concrete block, which may or may not be valid, + // depending on the context. Leave this for DCE rather than trying to + // handle that. + return; + } // If both are blocks, look for a tail we can merge. auto* left = curr->ifTrue->dynCast(); auto* right = curr->ifFalse->dynCast(); diff --git a/src/passes/Flatten.cpp b/src/passes/Flatten.cpp index 37fa15b1185..1c2cfbcd536 100644 --- a/src/passes/Flatten.cpp +++ b/src/passes/Flatten.cpp @@ -147,20 +147,24 @@ struct Flatten // arm preludes go in the arms. we must also remove an if value auto* originalIfTrue = iff->ifTrue; auto* originalIfFalse = iff->ifFalse; - auto type = iff->type; + auto type = iff->ifFalse ? Type::getLeastUpperBound(iff->ifTrue->type, + iff->ifFalse->type) + : Type::none; Expression* prelude = nullptr; if (type.isConcrete()) { Index temp = builder.addVar(getFunction(), type); if (iff->ifTrue->type.isConcrete()) { iff->ifTrue = builder.makeLocalSet(temp, iff->ifTrue); } - if (iff->ifFalse && iff->ifFalse->type.isConcrete()) { + if (iff->ifFalse->type.isConcrete()) { iff->ifFalse = builder.makeLocalSet(temp, iff->ifFalse); } - // the whole if (+any preludes from the condition) is now a prelude - prelude = rep; - // and we leave just a get of the value - rep = builder.makeLocalGet(temp, type); + if (curr->type.isConcrete()) { + // the whole if (+any preludes from the condition) is now a prelude + prelude = rep; + // and we leave just a get of the value + rep = builder.makeLocalGet(temp, type); + } } iff->ifTrue = getPreludesWithExpression(originalIfTrue, iff->ifTrue); if (iff->ifFalse) { diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index bbf5f2a6bea..4ca40f35a30 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -460,9 +460,16 @@ struct PrintExpressionContents } void visitIf(If* curr) { printMedium(o, "if"); - if (curr->type.isConcrete()) { + // Ifs are unreachable if their condition is unreachable, but in that case + // the arms might have some concrete type we have to account for to produce + // valid wat. + auto type = curr->type; + if (curr->condition->type == Type::unreachable && curr->ifFalse) { + type = Type::getLeastUpperBound(curr->ifTrue->type, curr->ifFalse->type); + } + if (type.isConcrete()) { o << ' '; - printBlockType(Signature(Type::none, curr->type)); + printBlockType(Signature(Type::none, type)); } } void visitLoop(Loop* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 64c7fda02ab..e295d393179 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -865,7 +865,16 @@ void FunctionValidator::visitIf(If* curr) { curr, "returning if-else's false must have right type"); } else { - if (curr->condition->type != Type::unreachable) { + if (curr->condition->type == Type::unreachable) { + shouldBeTrue( + curr->ifTrue->type == Type::unreachable || + curr->ifFalse->type == Type::unreachable || + (curr->ifTrue->type == Type::none && + curr->ifFalse->type == Type::none) || + Type::hasLeastUpperBound(curr->ifTrue->type, curr->ifFalse->type), + curr, + "arms of unreachable if-else must have compatible types"); + } else { shouldBeEqual(curr->ifTrue->type, Type(Type::unreachable), curr, @@ -876,18 +885,6 @@ void FunctionValidator::visitIf(If* curr) { "unreachable if-else must have unreachable false"); } } - if (curr->ifTrue->type.isConcrete()) { - shouldBeSubType(curr->ifTrue->type, - curr->type, - curr, - "if type must match concrete ifTrue"); - } - if (curr->ifFalse->type.isConcrete()) { - shouldBeSubType(curr->ifFalse->type, - curr->type, - curr, - "if type must match concrete ifFalse"); - } } } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index f89ef80c21c..38f35411f52 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -215,28 +215,20 @@ void Block::finalize(std::optional type_, Breakability breakability) { } void If::finalize(std::optional type_) { - if (type_) { - type = *type_; - if (type == Type::none && (condition->type == Type::unreachable || - (ifFalse && ifTrue->type == Type::unreachable && - ifFalse->type == Type::unreachable))) { - type = Type::unreachable; - } + // The If is unreachable if the condition is unreachable or both arms are + // unreachable. + if (condition->type == Type::unreachable || + (ifFalse && ifTrue->type == Type::unreachable && + ifFalse->type == Type::unreachable)) { + type = Type::unreachable; return; } - type = ifFalse ? Type::getLeastUpperBound(ifTrue->type, ifFalse->type) - : Type::none; - // if the arms return a value, leave it even if the condition - // is unreachable, we still mark ourselves as having that type, e.g. - // (if (result i32) - // (unreachable) - // (i32.const 10) - // (i32.const 20) - // ) - // otherwise, if the condition is unreachable, so is the if - if (type == Type::none && condition->type == Type::unreachable) { - type = Type::unreachable; + if (type_) { + type = *type_; + } else { + type = ifFalse ? Type::getLeastUpperBound(ifTrue->type, ifFalse->type) + : Type::none; } } diff --git a/test/lit/passes/code-folding.wast b/test/lit/passes/code-folding.wast index 007aa59099d..b256b236cfe 100644 --- a/test/lit/passes/code-folding.wast +++ b/test/lit/passes/code-folding.wast @@ -743,12 +743,20 @@ ) ;; CHECK: (func $unreachable-if (type $0) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $unreachable-if @@ -773,14 +781,17 @@ ;; CHECK-NEXT: (if ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $unreachable-if-suffix (if @@ -800,16 +811,13 @@ ) ;; CHECK: (func $unreachable-if-concrete-arms (type $0) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/flatten_all-features.wast b/test/lit/passes/flatten_all-features.wast index 5db45e2722f..b601b8a1295 100644 --- a/test/lit/passes/flatten_all-features.wast +++ b/test/lit/passes/flatten_all-features.wast @@ -620,7 +620,6 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (block $x ;; CHECK-NEXT: (block ;; CHECK-NEXT: (local.set $0 @@ -646,18 +645,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $a13 (result i32) diff --git a/test/lit/passes/optimize-instructions-ignore-traps.wast b/test/lit/passes/optimize-instructions-ignore-traps.wast index 96ea16449d2..8902cbc28f5 100644 --- a/test/lit/passes/optimize-instructions-ignore-traps.wast +++ b/test/lit/passes/optimize-instructions-ignore-traps.wast @@ -213,7 +213,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.tee $0 ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (i32.or ;; CHECK-NEXT: (i32.eqz diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index f18c94c5c05..077ecf49502 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -5754,15 +5754,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $0 ;; CHECK-NEXT: (local.get $1) @@ -5775,7 +5773,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (if ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (i32.add @@ -15718,23 +15716,21 @@ ) ) ) - ;; CHECK: (func $if-dont-change-to-unreachable (param $x i32) (param $y i32) (param $z i32) (result i32) - ;; CHECK-NEXT: (if (result i32) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (return + ;; CHECK: (func $if-unreachable-return-identical (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (else ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $if-dont-change-to-unreachable (param $x i32) (param $y i32) (param $z i32) (result i32) - ;; if we move the returns outside, we'd become unreachable; avoid that. + (func $if-unreachable-return-identical (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; We can move the returns outside because we are already unreachable. (if (result i32) (local.get $x) (then diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index 97a2dd59ab8..14964716551 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -1815,3 +1815,43 @@ ) ) ) + +;; Regression test for a crash on ifs with unreachable conditions and tuple arms. +(module + ;; CHECK: (type $0 (func (result i32 i64))) + + ;; CHECK: (func $test (type $0) (result i32 i64) + ;; CHECK-NEXT: (if (type $0) (result i32 i64) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i64.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result i32 i64) + (if (result i32 i64) + (unreachable) + (then + (tuple.make 2 + (i32.const 0) + (i64.const 1) + ) + ) + (else + (tuple.make 2 + (i32.const 2) + (i64.const 3) + ) + ) + ) + ) +) diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index 33d2e1d621b..9cf7f27c5d0 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -1280,6 +1280,67 @@ end ) + ;; CHECK: (func $if-else-unreachable (type $1) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-else-unreachable (result i32) + i32.const 0 ;; This will be dropped + unreachable + if (result i32) + i32.const 1 + else + i32.const 2 + end + ) + + ;; CHECK: (func $if-else-nested-unreachable (type $1) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-else-nested-unreachable (result i32) + i32.const 0 ;; This will be dropped + unreachable + if (result i32) + i32.const 1 + else + i32.const 2 + end + if (result i32) + i32.const 3 + else + i32.const 4 + end + ) + ;; CHECK: (func $if-else-labeled-result (type $1) (result i32) ;; CHECK-NEXT: (block $l (result i32) ;; CHECK-NEXT: (if (result i32) @@ -1607,7 +1668,7 @@ ;; CHECK: (func $if-else-brs-i32 (type $1) (result i32) ;; CHECK-NEXT: (block $label (result i32) - ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (br $label @@ -3677,7 +3738,7 @@ (func $ref-func ref.func $ref-func drop - ref.func 162 + ref.func 164 drop ) diff --git a/test/passes/remove-unused-brs_enable-multivalue.txt b/test/passes/remove-unused-brs_enable-multivalue.txt index ee6efbf4dab..54780f0d9ee 100644 --- a/test/passes/remove-unused-brs_enable-multivalue.txt +++ b/test/passes/remove-unused-brs_enable-multivalue.txt @@ -2070,8 +2070,8 @@ (i32.const 2) ) ) - (local.set $x - (if (result i32) + (local.tee $x + (if (local.get $p) (then (br $out) @@ -2094,7 +2094,7 @@ (block $label$4 (result i64) (block $label$5 (block $label$6 - (local.set $var$1 + (local.tee $var$1 (if (result f64) (unreachable) (then diff --git a/test/passes/remove-unused-names_code-folding.txt b/test/passes/remove-unused-names_code-folding.txt index 85810131b4a..62d7e34059d 100644 --- a/test/passes/remove-unused-names_code-folding.txt +++ b/test/passes/remove-unused-names_code-folding.txt @@ -1511,13 +1511,19 @@ ) (nop) (drop - (block (result i32) - (drop - (unreachable) + (if (result i32) + (unreachable) + (then + (i32.add + (i32.const 1) + (i32.const 2) + ) ) - (i32.add - (i32.const 1) - (i32.const 2) + (else + (i32.add + (i32.const 1) + (i32.const 2) + ) ) ) ) diff --git a/test/spec/if.wast b/test/spec/if.wast index ae7f7b38593..0fef1078f06 100644 --- a/test/spec/if.wast +++ b/test/spec/if.wast @@ -643,16 +643,18 @@ )) "type mismatch" ) -(assert_invalid - (module (func $type-else-value-unreached-select (result i32) - (if (result i64) - (i32.const 1) - (then (select (unreachable) (unreachable) (unreachable))) - (else (select (unreachable) (unreachable) (unreachable))) - ) - )) - "type mismatch" -) + +;; We don't pass this test because we type the `if` as unreachable. +;; (assert_invalid +;; (module (func $type-else-value-unreached-select (result i32) +;; (if (result i64) +;; (i32.const 1) +;; (then (select (unreachable) (unreachable) (unreachable))) +;; (else (select (unreachable) (unreachable) (unreachable))) +;; ) +;; )) +;; "type mismatch" +;; ) (assert_invalid (module (func $type-then-break-last-void-vs-num (result i32) diff --git a/test/wasm2js/br_table_temp.2asm.js b/test/wasm2js/br_table_temp.2asm.js index 245792eaf68..704ec068775 100644 --- a/test/wasm2js/br_table_temp.2asm.js +++ b/test/wasm2js/br_table_temp.2asm.js @@ -12671,7 +12671,7 @@ function asmFunc(imports) { } function $30() { - var $1_1 = 0, $2_1 = 0; + var $1_1 = 0; block : { $1_1 = 2; switch (0 | 0) { diff --git a/test/wasm2js/unreachable-if.2asm.js b/test/wasm2js/unreachable-if.2asm.js new file mode 100644 index 00000000000..f5656223cc7 --- /dev/null +++ b/test/wasm2js/unreachable-if.2asm.js @@ -0,0 +1,19 @@ + +function asmFunc(imports) { + var Math_imul = Math.imul; + var Math_fround = Math.fround; + var Math_abs = Math.abs; + var Math_clz32 = Math.clz32; + var Math_min = Math.min; + var Math_max = Math.max; + var Math_floor = Math.floor; + var Math_ceil = Math.ceil; + var Math_trunc = Math.trunc; + var Math_sqrt = Math.sqrt; + return { + + }; +} + +var retasmFunc = asmFunc({ +}); diff --git a/test/wasm2js/unreachable-if.2asm.js.opt b/test/wasm2js/unreachable-if.2asm.js.opt new file mode 100644 index 00000000000..f5656223cc7 --- /dev/null +++ b/test/wasm2js/unreachable-if.2asm.js.opt @@ -0,0 +1,19 @@ + +function asmFunc(imports) { + var Math_imul = Math.imul; + var Math_fround = Math.fround; + var Math_abs = Math.abs; + var Math_clz32 = Math.clz32; + var Math_min = Math.min; + var Math_max = Math.max; + var Math_floor = Math.floor; + var Math_ceil = Math.ceil; + var Math_trunc = Math.trunc; + var Math_sqrt = Math.sqrt; + return { + + }; +} + +var retasmFunc = asmFunc({ +}); diff --git a/test/wasm2js/unreachable-if.wast b/test/wasm2js/unreachable-if.wast new file mode 100644 index 00000000000..5bc0257c0a2 --- /dev/null +++ b/test/wasm2js/unreachable-if.wast @@ -0,0 +1,22 @@ +;; Regression test for bad assertion in autodrop that did not expect the if to +;; be finalized to unreachable. +(module + (func $test (result i32) + (block $l (result i32) + (drop + (br_if $l + (if (result i32) + (unreachable) + (then + (i32.const 0) + ) + (else + (i32.const 0) + ) + ) + ) + (i32.const 0) + ) + ) + ) +) From f8e1622bf0835dec2bb97bcb73281d34dbde4e2d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 26 Nov 2024 21:57:29 -0800 Subject: [PATCH 168/622] Use IRBuilder in the binary parser (#6963) IRBuilder is a utility for turning arbitrary valid streams of Wasm instructions into valid Binaryen IR. It is already used in the text parser, so now use it in the binary parser as well. Since the IRBuilder API for building each intruction requires only the information that the binary and text formats include as immediates to that instruction, the parser is now much simpler than before. In particular, it does not need to manage a stack of instructions to figure out what the children of each expression should be; IRBuilder handles this instead. There are some differences between the IR constructed by IRBuilder and the IR the binary parser constructed before this change. Most importantly, IRBuilder generates better multivalue code because it avoids eagerly breaking up multivalue results into individual components that might need to be immediately reassembled into a tuple. It also parses try-delegate more correctly, allowing the delegate to target arbitrary labels, not just other `try`s. There are also a couple superficial differences in the generated label and scratch local names. As part of this change, add support for recording binary source locations in IRBuilder. --- src/wasm-binary.h | 182 +- src/wasm-ir-builder.h | 25 + src/wasm/wasm-binary.cpp | 5727 +++++------------ src/wasm/wasm-ir-builder.cpp | 34 + test/br_to_exit.wasm.fromBinary | 4 +- test/br_to_try.wasm.fromBinary | 16 +- test/break-to-return.wasm.fromBinary | 4 +- test/break-within-catch.wasm.fromBinary | 6 +- test/consume-stacky.wasm.fromBinary | 6 +- test/elided-br.wasm.fromBinary | 5 +- test/example/c-api-unused-mem.txt | 4 +- test/fib-dbg.wasm.fromBinary | 32 +- ...fn_prolog_epilog.debugInfo.wasm.fromBinary | 6 +- test/lit/basic/exception-handling-legacy.wast | 408 +- test/lit/basic/exception-handling-no-gc.wast | 5 +- test/lit/basic/exception-handling.wast | 576 +- .../lit/basic/fn_prolog_epilog.debugInfo.wast | 12 +- test/lit/basic/min.wast | 28 +- test/lit/basic/polymorphic_stack.wast | 82 +- test/lit/basic/reference-types.wast | 228 +- test/lit/basic/reg_switch.wast | 8 +- .../lit/basic/typed_continuations_resume.wast | 70 +- test/lit/basic/types-function-references.wast | 58 +- test/lit/basic/unit.wat | 226 +- test/lit/basic/unreachable-code.wast | 30 +- test/lit/basic/untaken-br_if.wast | 16 +- test/lit/binary/bad-delegate.test | 17 - .../binary/declarative-element-use-expr.test | 8 +- test/lit/binary/delegate-block.test | 27 + ...ate.test.wasm => delegate-block.test.wasm} | Bin test/lit/binary/dwarf-multivalue.test | 16 +- test/lit/binary/stacky-eh-legacy.test | 19 +- test/lit/binary/stacky-nn-tuple.test | 228 +- test/lit/blocktype.wast | 52 +- test/lit/cast-and-recast-tuple.wast | 381 +- test/lit/cast-and-recast.wast | 28 +- test/lit/cast-to-basic.wast | 12 +- test/lit/debug/source-map-stop.wast | 2 + test/lit/downgrade-reftypes.wast | 16 +- test/lit/multivalue-stack-ir.wast | 6 +- test/lit/multivalue.wast | 328 +- test/lit/parse-double-unreachable.wast | 19 +- test/lit/passes/roundtrip-gc.wast | 6 +- test/lit/passes/roundtrip.wast | 27 +- test/lit/passes/signature-refining_gto.wat | 4 +- .../passes/stack-ir-roundtrip-eh-legacy.wast | 6 +- test/lit/reftypes-without-gc.wast | 4 +- test/lit/source-map.wast | 9 +- test/lit/string.as_wtf16.wast | 98 +- test/lld/em_asm_pthread.wasm.out | 1899 +++--- test/passes/O.bin.txt | 8 +- test/passes/converge_O3_metrics.bin.txt | 36 +- .../dce_vacuum_remove-unused-names.bin.txt | 12 +- test/passes/dwarf-local-order.bin.txt | 32 +- test/passes/dwarf_with_exceptions.bin.txt | 85 +- test/passes/fannkuch0_dwarf.bin.txt | 200 +- test/passes/fannkuch3_dwarf.bin.txt | 232 +- test/passes/fannkuch3_manyopts_dwarf.bin.txt | 194 +- test/passes/fib2_dwarf.bin.txt | 8 +- test/passes/fib2_emptylocspan_dwarf.bin.txt | 8 +- test/passes/fib_nonzero-low-pc_dwarf.bin.txt | 8 +- test/passes/flatten.bin.txt | 114 +- test/passes/print.bin.txt | 8 +- test/passes/print_g.bin.txt | 8 +- test/passes/print_g_metrics.bin.txt | 8 +- test/passes/print_g_strip-dwarf.bin.txt | 8 +- test/passes/reverse_dwarf_abbrevs.bin.txt | 921 ++- test/stacky.wasm.fromBinary | 6 +- test/try-delegate.wasm.fromBinary | 32 +- test/unreachable-pops.wasm.fromBinary | 3 +- 70 files changed, 5003 insertions(+), 7938 deletions(-) delete mode 100644 test/lit/binary/bad-delegate.test create mode 100644 test/lit/binary/delegate-block.test rename test/lit/binary/{bad-delegate.test.wasm => delegate-block.test.wasm} (100%) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 59114cdb8a5..7d928c3e6eb 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -29,6 +29,7 @@ #include "ir/module-utils.h" #include "parsing.h" #include "wasm-builder.h" +#include "wasm-ir-builder.h" #include "wasm-traversal.h" #include "wasm-validator.h" #include "wasm.h" @@ -1543,8 +1544,6 @@ class WasmBinaryReader { Signature getSignatureByTypeIndex(Index index); Signature getSignatureByFunctionIndex(Index index); - size_t nextLabel; - Name getNextLabel(); // We read the names section first so we know in advance what names various @@ -1573,67 +1572,19 @@ class WasmBinaryReader { void readVars(); void setLocalNames(Function& func, Index i); + Result<> readInst(); + void readExports(); // The strings in the strings section (which are referred to by StringConst). std::vector strings; void readStrings(); + Name getIndexedString(); Expression* readExpression(); void readGlobals(); - struct BreakTarget { - Name name; - Type type; - BreakTarget(Name name, Type type) : name(name), type(type) {} - }; - std::vector breakStack; - // the names that breaks target. this lets us know if a block has breaks to it - // or not. - std::unordered_set breakTargetNames; - // the names that delegates target. - std::unordered_set exceptionTargetNames; - - std::vector expressionStack; - - // Control flow structure parsing: these have not just the normal binary - // data for an instruction, but also some bytes later on like "end" or "else". - // We must be aware of the connection between those things, for debug info. - std::vector controlFlowStack; - - // Called when we parse the beginning of a control flow structure. - void startControlFlow(Expression* curr); - - // set when we know code is unreachable in the sense of the wasm spec: we are - // in a block and after an unreachable element. this helps parse stacky wasm - // code, which can be unsuitable for our IR when unreachable. - bool unreachableInTheWasmSense; - - // set when the current code being processed will not be emitted in the - // output, which is the case when it is literally unreachable, for example, - // (block $a - // (unreachable) - // (block $b - // ;; code here is reachable in the wasm sense, even though $b as a whole - // ;; is not - // (unreachable) - // ;; code here is unreachable in the wasm sense - // ) - // ) - bool willBeIgnored; - - BinaryConsts::ASTNodes lastSeparator = BinaryConsts::End; - - // process a block-type scope, until an end or else marker, or the end of the - // function - void processExpressions(); - void skipUnreachableCode(); - - void pushExpression(Expression* curr); - Expression* popExpression(); - Expression* popNonVoidExpression(); - Expression* popTuple(size_t numElems); - Expression* popTypedExpression(Type type); + IRBuilder builder; // validations that cannot be performed on the Module void validateBinary(); @@ -1663,127 +1614,12 @@ class WasmBinaryReader { void readNextDebugLocation(); void readSourceMapHeader(); - // AST reading - int depth = 0; // only for debugging - - BinaryConsts::ASTNodes readExpression(Expression*& curr); - void pushBlockElements(Block* curr, Type type, size_t start); - void visitBlock(Block* curr); - - // Gets a block of expressions. If it's just one, return that singleton. - Expression* getBlockOrSingleton(Type type); - - BreakTarget getBreakTarget(int32_t offset); - Name getExceptionTargetName(int32_t offset); - Index readMemoryAccess(Address& alignment, Address& offset); + std::tuple getMemarg(); - void visitIf(If* curr); - void visitLoop(Loop* curr); - void visitBreak(Break* curr, uint8_t code); - void visitSwitch(Switch* curr); - void visitCall(Call* curr); - void visitCallIndirect(CallIndirect* curr); - void visitLocalGet(LocalGet* curr); - void visitLocalSet(LocalSet* curr, uint8_t code); - void visitGlobalGet(GlobalGet* curr); - void visitGlobalSet(GlobalSet* curr); - bool maybeVisitLoad(Expression*& out, - uint8_t code, - std::optional prefix); - bool maybeVisitStore(Expression*& out, - uint8_t code, - std::optional prefix); - bool maybeVisitNontrappingTrunc(Expression*& out, uint32_t code); - bool maybeVisitAtomicRMW(Expression*& out, uint8_t code); - bool maybeVisitAtomicCmpxchg(Expression*& out, uint8_t code); - bool maybeVisitAtomicWait(Expression*& out, uint8_t code); - bool maybeVisitAtomicNotify(Expression*& out, uint8_t code); - bool maybeVisitAtomicFence(Expression*& out, uint8_t code); - bool maybeVisitConst(Expression*& out, uint8_t code); - bool maybeVisitUnary(Expression*& out, uint8_t code); - bool maybeVisitBinary(Expression*& out, uint8_t code); - bool maybeVisitTruncSat(Expression*& out, uint32_t code); - bool maybeVisitSIMDBinary(Expression*& out, uint32_t code); - bool maybeVisitSIMDUnary(Expression*& out, uint32_t code); - bool maybeVisitSIMDConst(Expression*& out, uint32_t code); - bool maybeVisitSIMDStore(Expression*& out, uint32_t code); - bool maybeVisitSIMDExtract(Expression*& out, uint32_t code); - bool maybeVisitSIMDReplace(Expression*& out, uint32_t code); - bool maybeVisitSIMDShuffle(Expression*& out, uint32_t code); - bool maybeVisitSIMDTernary(Expression*& out, uint32_t code); - bool maybeVisitSIMDShift(Expression*& out, uint32_t code); - bool maybeVisitSIMDLoad(Expression*& out, uint32_t code); - bool maybeVisitSIMDLoadStoreLane(Expression*& out, uint32_t code); - bool maybeVisitMemoryInit(Expression*& out, uint32_t code); - bool maybeVisitDataDrop(Expression*& out, uint32_t code); - bool maybeVisitMemoryCopy(Expression*& out, uint32_t code); - bool maybeVisitMemoryFill(Expression*& out, uint32_t code); - bool maybeVisitTableSize(Expression*& out, uint32_t code); - bool maybeVisitTableGrow(Expression*& out, uint32_t code); - bool maybeVisitTableFill(Expression*& out, uint32_t code); - bool maybeVisitTableCopy(Expression*& out, uint32_t code); - bool maybeVisitTableInit(Expression*& out, uint32_t code); - bool maybeVisitRefI31(Expression*& out, uint32_t code); - bool maybeVisitI31Get(Expression*& out, uint32_t code); - bool maybeVisitRefTest(Expression*& out, uint32_t code); - bool maybeVisitRefCast(Expression*& out, uint32_t code); - bool maybeVisitBrOn(Expression*& out, uint32_t code); - bool maybeVisitStructNew(Expression*& out, uint32_t code); - bool maybeVisitStructGet(Expression*& out, uint32_t code); - bool maybeVisitStructSet(Expression*& out, uint32_t code); - bool maybeVisitArrayNewData(Expression*& out, uint32_t code); - bool maybeVisitArrayNewElem(Expression*& out, uint32_t code); - bool maybeVisitArrayNewFixed(Expression*& out, uint32_t code); - bool maybeVisitArrayGet(Expression*& out, uint32_t code); - bool maybeVisitArraySet(Expression*& out, uint32_t code); - bool maybeVisitArrayLen(Expression*& out, uint32_t code); - bool maybeVisitArrayCopy(Expression*& out, uint32_t code); - bool maybeVisitArrayFill(Expression*& out, uint32_t code); - bool maybeVisitArrayInit(Expression*& out, uint32_t code); - bool maybeVisitStringNew(Expression*& out, uint32_t code); - bool maybeVisitStringAsWTF16(Expression*& out, uint32_t code); - bool maybeVisitStringConst(Expression*& out, uint32_t code); - bool maybeVisitStringMeasure(Expression*& out, uint32_t code); - bool maybeVisitStringEncode(Expression*& out, uint32_t code); - bool maybeVisitStringConcat(Expression*& out, uint32_t code); - bool maybeVisitStringEq(Expression*& out, uint32_t code); - bool maybeVisitStringWTF16Get(Expression*& out, uint32_t code); - bool maybeVisitStringSliceWTF(Expression*& out, uint32_t code); - void visitSelect(Select* curr, uint8_t code); - void visitReturn(Return* curr); - void visitMemorySize(MemorySize* curr); - void visitMemoryGrow(MemoryGrow* curr); - void visitNop(Nop* curr); - void visitUnreachable(Unreachable* curr); - void visitDrop(Drop* curr); - void visitRefNull(RefNull* curr); - void visitRefIsNull(RefIsNull* curr); - void visitRefFunc(RefFunc* curr); - void visitRefEq(RefEq* curr); - void visitTableGet(TableGet* curr); - void visitTableSet(TableSet* curr); - void visitTryOrTryInBlock(Expression*& out); - void visitTryTable(TryTable* curr); - void visitThrow(Throw* curr); - void visitRethrow(Rethrow* curr); - void visitThrowRef(ThrowRef* curr); - void visitCallRef(CallRef* curr); - void visitRefAsCast(RefCast* curr, uint32_t code); - void visitRefAs(RefAs* curr, uint8_t code); - void visitContNew(ContNew* curr); - void visitContBind(ContBind* curr); - void visitResume(Resume* curr); - void visitSuspend(Suspend* curr); - - [[noreturn]] void throwError(std::string text); - - // Struct/Array instructions have an unnecessary heap type that is just for - // validation (except for the case of unreachability, but that's not a problem - // anyhow, we can ignore it there). That is, we also have a reference typed - // child from which we can infer the type anyhow, and we just need to check - // that type is the same. - void validateHeapTypeUsingChild(Expression* child, HeapType heapType); + [[noreturn]] void throwError(std::string text) { + throw ParseException(text, 0, pos); + } private: bool hasDWARFSections(); diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 30e770e28dd..84ac2697930 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -47,6 +47,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { // of instructions after this is called. Result build(); + // If the IRBuilder is empty, then it's ready to parse a new self-contained + // sequence of instructions. + [[nodiscard]] bool empty() { return scopeStack.empty(); } + // Call visit() on an existing Expression with its non-child fields // initialized to initialize the child fields and refinalize it. Result<> visit(Expression*); @@ -59,6 +63,15 @@ class IRBuilder : public UnifiedExpressionVisitor> { // pushed instruction. void setDebugLocation(const std::optional&); + // Give the builder a pointer to the counter tracking the current location in + // the binary. If this pointer is non-null, the builder will record the binary + // locations relative to the given code section offset for all instructions + // and delimiters inside functions. + void setBinaryLocation(size_t* binaryPos, size_t codeSectionOffset) { + this->binaryPos = binaryPos; + this->codeSectionOffset = codeSectionOffset; + } + // Set the function used to add scratch locals when constructing an isolated // sequence of IR. void setFunction(Function* func) { this->func = func; } @@ -232,6 +245,11 @@ class IRBuilder : public UnifiedExpressionVisitor> { Function* func = nullptr; Builder builder; + // Used for setting DWARF expression locations. + size_t* binaryPos = nullptr; + size_t lastBinaryPos = 0; + size_t codeSectionOffset = 0; + // The location lacks debug info as it was marked as not having it. struct NoDebug : public std::monostate {}; // The location lacks debug info, but was not marked as not having @@ -316,6 +334,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { // stack-polymorphic unreachable mode. bool unreachable = false; + // The binary location of the start of the scope, used to set debug info. + size_t startPos = 0; + ScopeCtx() : scope(NoScope{}) {} ScopeCtx(Scope scope) : scope(scope) {} ScopeCtx(Scope scope, Name label, bool labelUsed) @@ -529,6 +550,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { // Record the original label to handle references to it correctly. labelDepths[label].push_back(scopeStack.size() + 1); } + if (binaryPos) { + scope.startPos = lastBinaryPos; + lastBinaryPos = *binaryPos; + } scopeStack.push_back(scope); } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 21842e19bbb..7ebb401c1f2 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -23,6 +23,7 @@ #include "ir/names.h" #include "ir/table-utils.h" #include "ir/type-updating.h" +#include "pass.h" #include "support/bits.h" #include "support/debug.h" #include "support/stdckdint.h" @@ -1739,7 +1740,7 @@ WasmBinaryReader::WasmBinaryReader(Module& wasm, const std::vector& input) : wasm(wasm), allocator(wasm.allocator), input(input), sourceMap(nullptr), nextDebugPos(0), nextDebugLocation{0, 0, 0, std::nullopt}, - nextDebugLocationHasDebugInfo(false), debugLocation() { + nextDebugLocationHasDebugInfo(false), debugLocation(), builder(wasm) { wasm.features = features; } @@ -2632,7 +2633,6 @@ void WasmBinaryReader::readImports() { if (is_shared) { throwError("Tables may not be shared"); } - wasm.addTable(std::move(table)); break; } @@ -2699,17 +2699,6 @@ void WasmBinaryReader::readImports() { numFuncImports = wasm.functions.size(); } -Name WasmBinaryReader::getNextLabel() { - requireFunctionContext("getting a label"); - return makeName("label$", nextLabel++); -} - -void WasmBinaryReader::requireFunctionContext(const char* error) { - if (!currFunction) { - throwError(std::string("in a non-function context: ") + error); - } -} - void WasmBinaryReader::setLocalNames(Function& func, Index i) { if (auto it = localNames.find(i); it != localNames.end()) { for (auto& [local, name] : it->second) { @@ -2794,13 +2783,16 @@ void WasmBinaryReader::readFunctions() { if (numFuncBodies + numFuncImports != wasm.functions.size()) { throwError("invalid function section size, must equal types"); } + if (DWARF) { + builder.setBinaryLocation(&pos, codeSectionLocation); + } for (size_t i = 0; i < numFuncBodies; i++) { auto sizePos = pos; size_t size = getU32LEB(); if (size == 0) { throwError("empty function size"); } - endOfFunction = pos + size; + Index endOfFunction = pos + size; auto& func = wasm.functions[numFuncImports + i]; currFunction = func.get(); @@ -2819,49 +2811,38 @@ void WasmBinaryReader::readFunctions() { func->prologLocation = debugLocation; { - // process the function body - nextLabel = 0; - willBeIgnored = false; - // process body - assert(breakStack.empty()); - assert(breakTargetNames.empty()); - assert(exceptionTargetNames.empty()); - assert(expressionStack.empty()); - assert(controlFlowStack.empty()); - assert(depth == 0); - // Even if we are skipping function bodies we need to not skip the start - // function. That contains important code for wasm-emscripten-finalize in - // the form of pthread-related segment initializations. As this is just - // one function, it doesn't add significant time, so the optimization of - // skipping bodies is still very useful. + // Process the function body. Even if we are skipping function bodies we + // need to not skip the start function. That contains important code for + // wasm-emscripten-finalize in the form of pthread-related segment + // initializations. As this is just one function, it doesn't add + // significant time, so the optimization of skipping bodies is still very + // useful. auto currFunctionIndex = wasm.functions.size(); bool isStart = startIndex == currFunctionIndex; - if (!skipFunctionBodies || isStart) { - func->body = getBlockOrSingleton(func->getResults()); - } else { + if (skipFunctionBodies && !isStart) { // When skipping the function body we need to put something valid in // their place so we validate. An unreachable is always acceptable // there. func->body = Builder(wasm).makeUnreachable(); - // Skip reading the contents. pos = endOfFunction; - } - assert(depth == 0); - assert(breakStack.empty()); - assert(breakTargetNames.empty()); - if (!exceptionTargetNames.empty()) { - // A delegate index existed that did not end up referring to any valid - // outer try-catch (we remove valid ones from exceptionTargetNames as we - // go). - throwError("exceptionTargetNames not empty - invalid delegate"); - } - if (!expressionStack.empty()) { - throwError("stack not empty on function exit"); - } - assert(controlFlowStack.empty()); - if (pos != endOfFunction) { - throwError("binary offset at function exit not at expected location"); + } else { + auto start = builder.visitFunctionStart(func.get()); + if (auto* err = start.getErr()) { + throwError(err->msg); + } + while (pos < endOfFunction) { + auto inst = readInst(); + if (auto* err = inst.getErr()) { + throwError(err->msg); + } + } + if (pos != endOfFunction) { + throwError("function overflowed its bounds"); + } + if (!builder.empty()) { + throwError("expected function end"); + } } } @@ -2897,6 +2878,1370 @@ void WasmBinaryReader::readVars() { } } +Result<> WasmBinaryReader::readInst() { + readNextDebugLocation(); + if (debugLocation.size()) { + builder.setDebugLocation(*debugLocation.begin()); + } + uint8_t code = getInt8(); + switch (code) { + case BinaryConsts::Block: + return builder.makeBlock(Name(), getType()); + case BinaryConsts::If: + return builder.makeIf(Name(), getType()); + case BinaryConsts::Loop: + return builder.makeLoop(Name(), getType()); + case BinaryConsts::Br: + return builder.makeBreak(getU32LEB(), false); + case BinaryConsts::BrIf: + return builder.makeBreak(getU32LEB(), true); + case BinaryConsts::BrTable: { + auto numTargets = getU32LEB(); + std::vector labels(numTargets); + for (Index i = 0; i < numTargets; ++i) { + labels[i] = getU32LEB(); + } + return builder.makeSwitch(labels, getU32LEB()); + } + case BinaryConsts::CallFunction: + case BinaryConsts::RetCallFunction: + return builder.makeCall(getFunctionName(getU32LEB()), + code == BinaryConsts::RetCallFunction); + case BinaryConsts::CallIndirect: + case BinaryConsts::RetCallIndirect: { + auto type = getIndexedHeapType(); + auto table = getTableName(getU32LEB()); + return builder.makeCallIndirect( + table, type, code == BinaryConsts::RetCallIndirect); + } + case BinaryConsts::LocalGet: + return builder.makeLocalGet(getU32LEB()); + case BinaryConsts::LocalSet: + return builder.makeLocalSet(getU32LEB()); + case BinaryConsts::LocalTee: + return builder.makeLocalTee(getU32LEB()); + case BinaryConsts::GlobalGet: + return builder.makeGlobalGet(getGlobalName(getU32LEB())); + case BinaryConsts::GlobalSet: + return builder.makeGlobalSet(getGlobalName(getU32LEB())); + case BinaryConsts::Select: + return builder.makeSelect(std::nullopt); + case BinaryConsts::SelectWithType: { + auto numTypes = getU32LEB(); + std::vector types; + for (Index i = 0; i < numTypes; ++i) { + auto t = getType(); + if (!t.isConcrete()) { + return Err{"bad select type"}; + } + types.push_back(t); + } + return builder.makeSelect(Type(types)); + } + case BinaryConsts::Return: + return builder.makeReturn(); + case BinaryConsts::Nop: + return builder.makeNop(); + case BinaryConsts::Unreachable: + return builder.makeUnreachable(); + case BinaryConsts::Drop: + return builder.makeDrop(); + case BinaryConsts::End: + return builder.visitEnd(); + case BinaryConsts::Else: + return builder.visitElse(); + case BinaryConsts::Catch_Legacy: + return builder.visitCatch(getTagName(getU32LEB())); + case BinaryConsts::CatchAll_Legacy: + return builder.visitCatchAll(); + case BinaryConsts::Delegate: + return builder.visitDelegate(getU32LEB()); + case BinaryConsts::RefNull: + return builder.makeRefNull(getHeapType()); + case BinaryConsts::RefIsNull: + return builder.makeRefIsNull(); + case BinaryConsts::RefFunc: + return builder.makeRefFunc(getFunctionName(getU32LEB())); + case BinaryConsts::RefEq: + return builder.makeRefEq(); + case BinaryConsts::RefAsNonNull: + return builder.makeRefAs(RefAsNonNull); + case BinaryConsts::BrOnNull: + return builder.makeBrOn(getU32LEB(), BrOnNull); + case BinaryConsts::BrOnNonNull: + return builder.makeBrOn(getU32LEB(), BrOnNonNull); + case BinaryConsts::TableGet: + return builder.makeTableGet(getTableName(getU32LEB())); + case BinaryConsts::TableSet: + return builder.makeTableSet(getTableName(getU32LEB())); + case BinaryConsts::Try: + return builder.makeTry(Name(), getType()); + case BinaryConsts::TryTable: { + auto type = getType(); + std::vector tags; + std::vector labels; + std::vector isRefs; + auto numHandlers = getU32LEB(); + for (Index i = 0; i < numHandlers; ++i) { + uint8_t code = getInt8(); + if (code == BinaryConsts::Catch || code == BinaryConsts::CatchRef) { + tags.push_back(getTagName(getU32LEB())); + } else { + tags.push_back(Name()); + } + labels.push_back(getU32LEB()); + isRefs.push_back(code == BinaryConsts::CatchRef || + code == BinaryConsts::CatchAllRef); + } + return builder.makeTryTable(Name(), type, tags, labels, isRefs); + } + case BinaryConsts::Throw: + return builder.makeThrow(getTagName(getU32LEB())); + case BinaryConsts::Rethrow: + return builder.makeRethrow(getU32LEB()); + case BinaryConsts::ThrowRef: + return builder.makeThrowRef(); + case BinaryConsts::MemorySize: + return builder.makeMemorySize(getMemoryName(getU32LEB())); + case BinaryConsts::MemoryGrow: + return builder.makeMemoryGrow(getMemoryName(getU32LEB())); + case BinaryConsts::CallRef: + case BinaryConsts::RetCallRef: + return builder.makeCallRef(getIndexedHeapType(), + code == BinaryConsts::RetCallRef); + case BinaryConsts::ContBind: { + auto before = getIndexedHeapType(); + auto after = getIndexedHeapType(); + return builder.makeContBind(before, after); + } + case BinaryConsts::ContNew: + return builder.makeContNew(getIndexedHeapType()); + case BinaryConsts::Resume: { + auto type = getIndexedHeapType(); + std::vector tags; + std::vector labels; + auto numHandlers = getU32LEB(); + for (Index i = 0; i < numHandlers; ++i) { + tags.push_back(getTagName(getU32LEB())); + labels.push_back(getU32LEB()); + } + return builder.makeResume(type, tags, labels); + } + case BinaryConsts::Suspend: + return builder.makeSuspend(getTagName(getU32LEB())); + +#define BINARY_INT(code) \ + case BinaryConsts::I32##code: \ + return builder.makeBinary(code##Int32); \ + case BinaryConsts::I64##code: \ + return builder.makeBinary(code##Int64); +#define BINARY_FLOAT(code) \ + case BinaryConsts::F32##code: \ + return builder.makeBinary(code##Float32); \ + case BinaryConsts::F64##code: \ + return builder.makeBinary(code##Float64); +#define BINARY_NUM(code) \ + BINARY_INT(code) \ + BINARY_FLOAT(code) + + BINARY_NUM(Add); + BINARY_NUM(Sub); + BINARY_NUM(Mul); + BINARY_INT(DivS); + BINARY_INT(DivU); + BINARY_INT(RemS); + BINARY_INT(RemU); + BINARY_INT(And); + BINARY_INT(Or); + BINARY_INT(Xor); + BINARY_INT(Shl); + BINARY_INT(ShrU); + BINARY_INT(ShrS); + BINARY_INT(RotL); + BINARY_INT(RotR); + BINARY_FLOAT(Div); + BINARY_FLOAT(CopySign); + BINARY_FLOAT(Min); + BINARY_FLOAT(Max); + BINARY_NUM(Eq); + BINARY_NUM(Ne); + BINARY_INT(LtS); + BINARY_INT(LtU); + BINARY_INT(LeS); + BINARY_INT(LeU); + BINARY_INT(GtS); + BINARY_INT(GtU); + BINARY_INT(GeS); + BINARY_INT(GeU); + BINARY_FLOAT(Lt); + BINARY_FLOAT(Le); + BINARY_FLOAT(Gt); + BINARY_FLOAT(Ge); + +#define UNARY_INT(code) \ + case BinaryConsts::I32##code: \ + return builder.makeUnary(code##Int32); \ + case BinaryConsts::I64##code: \ + return builder.makeUnary(code##Int64); +#define UNARY_FLOAT(code) \ + case BinaryConsts::F32##code: \ + return builder.makeUnary(code##Float32); \ + case BinaryConsts::F64##code: \ + return builder.makeUnary(code##Float64); + + UNARY_INT(Clz); + UNARY_INT(Ctz); + UNARY_INT(Popcnt); + UNARY_INT(EqZ); + UNARY_FLOAT(Neg); + UNARY_FLOAT(Abs); + UNARY_FLOAT(Ceil); + UNARY_FLOAT(Floor); + // UNARY_FLOAT(NearestInt); + case BinaryConsts::F32NearestInt: + return builder.makeUnary(NearestFloat32); + case BinaryConsts::F64NearestInt: + return builder.makeUnary(NearestFloat64); + UNARY_FLOAT(Sqrt); + + case BinaryConsts::F32UConvertI32: + return builder.makeUnary(ConvertUInt32ToFloat32); + case BinaryConsts::F64UConvertI32: + return builder.makeUnary(ConvertUInt32ToFloat64); + case BinaryConsts::F32SConvertI32: + return builder.makeUnary(ConvertSInt32ToFloat32); + case BinaryConsts::F64SConvertI32: + return builder.makeUnary(ConvertSInt32ToFloat64); + case BinaryConsts::F32UConvertI64: + return builder.makeUnary(ConvertUInt64ToFloat32); + case BinaryConsts::F64UConvertI64: + return builder.makeUnary(ConvertUInt64ToFloat64); + case BinaryConsts::F32SConvertI64: + return builder.makeUnary(ConvertSInt64ToFloat32); + case BinaryConsts::F64SConvertI64: + return builder.makeUnary(ConvertSInt64ToFloat64); + case BinaryConsts::I64SExtendI32: + return builder.makeUnary(ExtendSInt32); + case BinaryConsts::I64UExtendI32: + return builder.makeUnary(ExtendUInt32); + case BinaryConsts::I32WrapI64: + return builder.makeUnary(WrapInt64); + case BinaryConsts::I32UTruncF32: + return builder.makeUnary(TruncUFloat32ToInt32); + case BinaryConsts::I32UTruncF64: + return builder.makeUnary(TruncUFloat64ToInt32); + case BinaryConsts::I32STruncF32: + return builder.makeUnary(TruncSFloat32ToInt32); + case BinaryConsts::I32STruncF64: + return builder.makeUnary(TruncSFloat64ToInt32); + case BinaryConsts::I64UTruncF32: + return builder.makeUnary(TruncUFloat32ToInt64); + case BinaryConsts::I64UTruncF64: + return builder.makeUnary(TruncUFloat64ToInt64); + case BinaryConsts::I64STruncF32: + return builder.makeUnary(TruncSFloat32ToInt64); + case BinaryConsts::I64STruncF64: + return builder.makeUnary(TruncSFloat64ToInt64); + case BinaryConsts::F32Trunc: + return builder.makeUnary(TruncFloat32); + case BinaryConsts::F64Trunc: + return builder.makeUnary(TruncFloat64); + case BinaryConsts::F32DemoteI64: + return builder.makeUnary(DemoteFloat64); + case BinaryConsts::F64PromoteF32: + return builder.makeUnary(PromoteFloat32); + case BinaryConsts::I32ReinterpretF32: + return builder.makeUnary(ReinterpretFloat32); + case BinaryConsts::I64ReinterpretF64: + return builder.makeUnary(ReinterpretFloat64); + case BinaryConsts::F32ReinterpretI32: + return builder.makeUnary(ReinterpretInt32); + case BinaryConsts::F64ReinterpretI64: + return builder.makeUnary(ReinterpretInt64); + case BinaryConsts::I32ExtendS8: + return builder.makeUnary(ExtendS8Int32); + case BinaryConsts::I32ExtendS16: + return builder.makeUnary(ExtendS16Int32); + case BinaryConsts::I64ExtendS8: + return builder.makeUnary(ExtendS8Int64); + case BinaryConsts::I64ExtendS16: + return builder.makeUnary(ExtendS16Int64); + case BinaryConsts::I64ExtendS32: + return builder.makeUnary(ExtendS32Int64); + case BinaryConsts::I32Const: + return builder.makeConst(Literal(getS32LEB())); + case BinaryConsts::I64Const: + return builder.makeConst(Literal(getS64LEB())); + case BinaryConsts::F32Const: + return builder.makeConst(getFloat32Literal()); + case BinaryConsts::F64Const: + return builder.makeConst(getFloat64Literal()); + case BinaryConsts::I32LoadMem8S: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(1, true, offset, align, Type::i32, mem); + } + case BinaryConsts::I32LoadMem8U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(1, false, offset, align, Type::i32, mem); + } + case BinaryConsts::I32LoadMem16S: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(2, true, offset, align, Type::i32, mem); + } + case BinaryConsts::I32LoadMem16U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(2, false, offset, align, Type::i32, mem); + } + case BinaryConsts::I32LoadMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(4, false, offset, align, Type::i32, mem); + } + case BinaryConsts::I64LoadMem8S: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(1, true, offset, align, Type::i64, mem); + } + case BinaryConsts::I64LoadMem8U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(1, false, offset, align, Type::i64, mem); + } + case BinaryConsts::I64LoadMem16S: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(2, true, offset, align, Type::i64, mem); + } + case BinaryConsts::I64LoadMem16U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(2, false, offset, align, Type::i64, mem); + } + case BinaryConsts::I64LoadMem32S: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(4, true, offset, align, Type::i64, mem); + } + case BinaryConsts::I64LoadMem32U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(4, false, offset, align, Type::i64, mem); + } + case BinaryConsts::I64LoadMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(8, false, offset, align, Type::i64, mem); + } + case BinaryConsts::F32LoadMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(4, false, offset, align, Type::f32, mem); + } + case BinaryConsts::F64LoadMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(8, false, offset, align, Type::f64, mem); + } + case BinaryConsts::I32StoreMem8: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(1, offset, align, Type::i32, mem); + } + case BinaryConsts::I32StoreMem16: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(2, offset, align, Type::i32, mem); + } + case BinaryConsts::I32StoreMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(4, offset, align, Type::i32, mem); + } + case BinaryConsts::I64StoreMem8: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(1, offset, align, Type::i64, mem); + } + case BinaryConsts::I64StoreMem16: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(2, offset, align, Type::i64, mem); + } + case BinaryConsts::I64StoreMem32: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(4, offset, align, Type::i64, mem); + } + case BinaryConsts::I64StoreMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(8, offset, align, Type::i64, mem); + } + case BinaryConsts::F32StoreMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(4, offset, align, Type::f32, mem); + } + case BinaryConsts::F64StoreMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(8, offset, align, Type::f64, mem); + } + case BinaryConsts::AtomicPrefix: { + auto op = getU32LEB(); + switch (op) { + case BinaryConsts::I32AtomicLoad8U: { + // TODO: pass align through for validation. + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicLoad(1, offset, Type::i32, mem); + } + case BinaryConsts::I32AtomicLoad16U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicLoad(2, offset, Type::i32, mem); + } + case BinaryConsts::I32AtomicLoad: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicLoad(4, offset, Type::i32, mem); + } + case BinaryConsts::I64AtomicLoad8U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicLoad(1, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicLoad16U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicLoad(2, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicLoad32U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicLoad(4, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicLoad: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicLoad(8, offset, Type::i64, mem); + } + case BinaryConsts::I32AtomicStore8: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicStore(1, offset, Type::i32, mem); + } + case BinaryConsts::I32AtomicStore16: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicStore(2, offset, Type::i32, mem); + } + case BinaryConsts::I32AtomicStore: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicStore(4, offset, Type::i32, mem); + } + case BinaryConsts::I64AtomicStore8: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicStore(1, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicStore16: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicStore(2, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicStore32: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicStore(4, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicStore: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicStore(8, offset, Type::i64, mem); + } + +#define RMW(op) \ + case BinaryConsts::I32AtomicRMW##op: { \ + auto [mem, align, offset] = getMemarg(); \ + return builder.makeAtomicRMW(RMW##op, 4, offset, Type::i32, mem); \ + } \ + case BinaryConsts::I32AtomicRMW##op##8U: { \ + auto [mem, align, offset] = getMemarg(); \ + return builder.makeAtomicRMW(RMW##op, 1, offset, Type::i32, mem); \ + } \ + case BinaryConsts::I32AtomicRMW##op##16U: { \ + auto [mem, align, offset] = getMemarg(); \ + return builder.makeAtomicRMW(RMW##op, 2, offset, Type::i32, mem); \ + } \ + case BinaryConsts::I64AtomicRMW##op: { \ + auto [mem, align, offset] = getMemarg(); \ + return builder.makeAtomicRMW(RMW##op, 8, offset, Type::i64, mem); \ + } \ + case BinaryConsts::I64AtomicRMW##op##8U: { \ + auto [mem, align, offset] = getMemarg(); \ + return builder.makeAtomicRMW(RMW##op, 1, offset, Type::i64, mem); \ + } \ + case BinaryConsts::I64AtomicRMW##op##16U: { \ + auto [mem, align, offset] = getMemarg(); \ + return builder.makeAtomicRMW(RMW##op, 2, offset, Type::i64, mem); \ + } \ + case BinaryConsts::I64AtomicRMW##op##32U: { \ + auto [mem, align, offset] = getMemarg(); \ + return builder.makeAtomicRMW(RMW##op, 4, offset, Type::i64, mem); \ + } + + RMW(Add); + RMW(Sub); + RMW(And); + RMW(Or); + RMW(Xor); + RMW(Xchg); + + case BinaryConsts::I32AtomicCmpxchg: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicCmpxchg(4, offset, Type::i32, mem); + } + case BinaryConsts::I32AtomicCmpxchg8U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicCmpxchg(1, offset, Type::i32, mem); + } + case BinaryConsts::I32AtomicCmpxchg16U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicCmpxchg(2, offset, Type::i32, mem); + } + case BinaryConsts::I64AtomicCmpxchg: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicCmpxchg(8, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicCmpxchg8U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicCmpxchg(1, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicCmpxchg16U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicCmpxchg(2, offset, Type::i64, mem); + } + case BinaryConsts::I64AtomicCmpxchg32U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicCmpxchg(4, offset, Type::i64, mem); + } + case BinaryConsts::I32AtomicWait: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicWait(Type::i32, offset, mem); + } + case BinaryConsts::I64AtomicWait: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicWait(Type::i64, offset, mem); + } + case BinaryConsts::AtomicNotify: { + auto [mem, align, offset] = getMemarg(); + return builder.makeAtomicNotify(offset, mem); + } + case BinaryConsts::AtomicFence: + if (getInt8() != 0) { + return Err{"expected 0x00 byte immediate on atomic.fence"}; + } + return builder.makeAtomicFence(); + } + return Err{"unknown atomic operation"}; + } + case BinaryConsts::MiscPrefix: { + auto op = getU32LEB(); + switch (op) { + case BinaryConsts::I32STruncSatF32: + return builder.makeUnary(TruncSatSFloat32ToInt32); + case BinaryConsts::I32UTruncSatF32: + return builder.makeUnary(TruncSatUFloat32ToInt32); + case BinaryConsts::I32STruncSatF64: + return builder.makeUnary(TruncSatSFloat64ToInt32); + case BinaryConsts::I32UTruncSatF64: + return builder.makeUnary(TruncSatUFloat64ToInt32); + case BinaryConsts::I64STruncSatF32: + return builder.makeUnary(TruncSatSFloat32ToInt64); + case BinaryConsts::I64UTruncSatF32: + return builder.makeUnary(TruncSatUFloat32ToInt64); + case BinaryConsts::I64STruncSatF64: + return builder.makeUnary(TruncSatSFloat64ToInt64); + case BinaryConsts::I64UTruncSatF64: + return builder.makeUnary(TruncSatUFloat64ToInt64); + case BinaryConsts::MemoryInit: { + auto data = getDataName(getU32LEB()); + auto mem = getMemoryName(getU32LEB()); + return builder.makeMemoryInit(data, mem); + } + case BinaryConsts::DataDrop: + return builder.makeDataDrop(getDataName(getU32LEB())); + case BinaryConsts::MemoryCopy: { + auto dest = getMemoryName(getU32LEB()); + auto src = getMemoryName(getU32LEB()); + return builder.makeMemoryCopy(dest, src); + } + case BinaryConsts::MemoryFill: + return builder.makeMemoryFill(getMemoryName(getU32LEB())); + case BinaryConsts::TableSize: + return builder.makeTableSize(getTableName(getU32LEB())); + case BinaryConsts::TableGrow: + return builder.makeTableGrow(getTableName(getU32LEB())); + case BinaryConsts::TableFill: + return builder.makeTableFill(getTableName(getU32LEB())); + case BinaryConsts::TableCopy: { + auto dest = getTableName(getU32LEB()); + auto src = getTableName(getU32LEB()); + return builder.makeTableCopy(dest, src); + } + case BinaryConsts::TableInit: { + auto elem = getElemName(getU32LEB()); + auto table = getTableName(getU32LEB()); + return builder.makeTableInit(elem, table); + } + case BinaryConsts::F32_F16LoadMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(2, false, offset, align, Type::f32, mem); + } + case BinaryConsts::F32_F16StoreMem: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(2, offset, align, Type::f32, mem); + } + } + return Err{"unknown misc operation"}; + } + case BinaryConsts::SIMDPrefix: { + auto op = getU32LEB(); + switch (op) { + case BinaryConsts::I8x16Eq: + return builder.makeBinary(EqVecI8x16); + case BinaryConsts::I8x16Ne: + return builder.makeBinary(NeVecI8x16); + case BinaryConsts::I8x16LtS: + return builder.makeBinary(LtSVecI8x16); + case BinaryConsts::I8x16LtU: + return builder.makeBinary(LtUVecI8x16); + case BinaryConsts::I8x16GtS: + return builder.makeBinary(GtSVecI8x16); + case BinaryConsts::I8x16GtU: + return builder.makeBinary(GtUVecI8x16); + case BinaryConsts::I8x16LeS: + return builder.makeBinary(LeSVecI8x16); + case BinaryConsts::I8x16LeU: + return builder.makeBinary(LeUVecI8x16); + case BinaryConsts::I8x16GeS: + return builder.makeBinary(GeSVecI8x16); + case BinaryConsts::I8x16GeU: + return builder.makeBinary(GeUVecI8x16); + case BinaryConsts::I16x8Eq: + return builder.makeBinary(EqVecI16x8); + case BinaryConsts::I16x8Ne: + return builder.makeBinary(NeVecI16x8); + case BinaryConsts::I16x8LtS: + return builder.makeBinary(LtSVecI16x8); + case BinaryConsts::I16x8LtU: + return builder.makeBinary(LtUVecI16x8); + case BinaryConsts::I16x8GtS: + return builder.makeBinary(GtSVecI16x8); + case BinaryConsts::I16x8GtU: + return builder.makeBinary(GtUVecI16x8); + case BinaryConsts::I16x8LeS: + return builder.makeBinary(LeSVecI16x8); + case BinaryConsts::I16x8LeU: + return builder.makeBinary(LeUVecI16x8); + case BinaryConsts::I16x8GeS: + return builder.makeBinary(GeSVecI16x8); + case BinaryConsts::I16x8GeU: + return builder.makeBinary(GeUVecI16x8); + case BinaryConsts::I32x4Eq: + return builder.makeBinary(EqVecI32x4); + case BinaryConsts::I32x4Ne: + return builder.makeBinary(NeVecI32x4); + case BinaryConsts::I32x4LtS: + return builder.makeBinary(LtSVecI32x4); + case BinaryConsts::I32x4LtU: + return builder.makeBinary(LtUVecI32x4); + case BinaryConsts::I32x4GtS: + return builder.makeBinary(GtSVecI32x4); + case BinaryConsts::I32x4GtU: + return builder.makeBinary(GtUVecI32x4); + case BinaryConsts::I32x4LeS: + return builder.makeBinary(LeSVecI32x4); + case BinaryConsts::I32x4LeU: + return builder.makeBinary(LeUVecI32x4); + case BinaryConsts::I32x4GeS: + return builder.makeBinary(GeSVecI32x4); + case BinaryConsts::I32x4GeU: + return builder.makeBinary(GeUVecI32x4); + case BinaryConsts::I64x2Eq: + return builder.makeBinary(EqVecI64x2); + case BinaryConsts::I64x2Ne: + return builder.makeBinary(NeVecI64x2); + case BinaryConsts::I64x2LtS: + return builder.makeBinary(LtSVecI64x2); + case BinaryConsts::I64x2GtS: + return builder.makeBinary(GtSVecI64x2); + case BinaryConsts::I64x2LeS: + return builder.makeBinary(LeSVecI64x2); + case BinaryConsts::I64x2GeS: + return builder.makeBinary(GeSVecI64x2); + case BinaryConsts::F16x8Eq: + return builder.makeBinary(EqVecF16x8); + case BinaryConsts::F16x8Ne: + return builder.makeBinary(NeVecF16x8); + case BinaryConsts::F16x8Lt: + return builder.makeBinary(LtVecF16x8); + case BinaryConsts::F16x8Gt: + return builder.makeBinary(GtVecF16x8); + case BinaryConsts::F16x8Le: + return builder.makeBinary(LeVecF16x8); + case BinaryConsts::F16x8Ge: + return builder.makeBinary(GeVecF16x8); + case BinaryConsts::F32x4Eq: + return builder.makeBinary(EqVecF32x4); + case BinaryConsts::F32x4Ne: + return builder.makeBinary(NeVecF32x4); + case BinaryConsts::F32x4Lt: + return builder.makeBinary(LtVecF32x4); + case BinaryConsts::F32x4Gt: + return builder.makeBinary(GtVecF32x4); + case BinaryConsts::F32x4Le: + return builder.makeBinary(LeVecF32x4); + case BinaryConsts::F32x4Ge: + return builder.makeBinary(GeVecF32x4); + case BinaryConsts::F64x2Eq: + return builder.makeBinary(EqVecF64x2); + case BinaryConsts::F64x2Ne: + return builder.makeBinary(NeVecF64x2); + case BinaryConsts::F64x2Lt: + return builder.makeBinary(LtVecF64x2); + case BinaryConsts::F64x2Gt: + return builder.makeBinary(GtVecF64x2); + case BinaryConsts::F64x2Le: + return builder.makeBinary(LeVecF64x2); + case BinaryConsts::F64x2Ge: + return builder.makeBinary(GeVecF64x2); + case BinaryConsts::V128And: + return builder.makeBinary(AndVec128); + case BinaryConsts::V128Or: + return builder.makeBinary(OrVec128); + case BinaryConsts::V128Xor: + return builder.makeBinary(XorVec128); + case BinaryConsts::V128Andnot: + return builder.makeBinary(AndNotVec128); + case BinaryConsts::I8x16Add: + return builder.makeBinary(AddVecI8x16); + case BinaryConsts::I8x16AddSatS: + return builder.makeBinary(AddSatSVecI8x16); + case BinaryConsts::I8x16AddSatU: + return builder.makeBinary(AddSatUVecI8x16); + case BinaryConsts::I8x16Sub: + return builder.makeBinary(SubVecI8x16); + case BinaryConsts::I8x16SubSatS: + return builder.makeBinary(SubSatSVecI8x16); + case BinaryConsts::I8x16SubSatU: + return builder.makeBinary(SubSatUVecI8x16); + case BinaryConsts::I8x16MinS: + return builder.makeBinary(MinSVecI8x16); + case BinaryConsts::I8x16MinU: + return builder.makeBinary(MinUVecI8x16); + case BinaryConsts::I8x16MaxS: + return builder.makeBinary(MaxSVecI8x16); + case BinaryConsts::I8x16MaxU: + return builder.makeBinary(MaxUVecI8x16); + case BinaryConsts::I8x16AvgrU: + return builder.makeBinary(AvgrUVecI8x16); + case BinaryConsts::I16x8Add: + return builder.makeBinary(AddVecI16x8); + case BinaryConsts::I16x8AddSatS: + return builder.makeBinary(AddSatSVecI16x8); + case BinaryConsts::I16x8AddSatU: + return builder.makeBinary(AddSatUVecI16x8); + case BinaryConsts::I16x8Sub: + return builder.makeBinary(SubVecI16x8); + case BinaryConsts::I16x8SubSatS: + return builder.makeBinary(SubSatSVecI16x8); + case BinaryConsts::I16x8SubSatU: + return builder.makeBinary(SubSatUVecI16x8); + case BinaryConsts::I16x8Mul: + return builder.makeBinary(MulVecI16x8); + case BinaryConsts::I16x8MinS: + return builder.makeBinary(MinSVecI16x8); + case BinaryConsts::I16x8MinU: + return builder.makeBinary(MinUVecI16x8); + case BinaryConsts::I16x8MaxS: + return builder.makeBinary(MaxSVecI16x8); + case BinaryConsts::I16x8MaxU: + return builder.makeBinary(MaxUVecI16x8); + case BinaryConsts::I16x8AvgrU: + return builder.makeBinary(AvgrUVecI16x8); + case BinaryConsts::I16x8Q15MulrSatS: + return builder.makeBinary(Q15MulrSatSVecI16x8); + case BinaryConsts::I16x8ExtmulLowI8x16S: + return builder.makeBinary(ExtMulLowSVecI16x8); + case BinaryConsts::I16x8ExtmulHighI8x16S: + return builder.makeBinary(ExtMulHighSVecI16x8); + case BinaryConsts::I16x8ExtmulLowI8x16U: + return builder.makeBinary(ExtMulLowUVecI16x8); + case BinaryConsts::I16x8ExtmulHighI8x16U: + return builder.makeBinary(ExtMulHighUVecI16x8); + case BinaryConsts::I32x4Add: + return builder.makeBinary(AddVecI32x4); + case BinaryConsts::I32x4Sub: + return builder.makeBinary(SubVecI32x4); + case BinaryConsts::I32x4Mul: + return builder.makeBinary(MulVecI32x4); + case BinaryConsts::I32x4MinS: + return builder.makeBinary(MinSVecI32x4); + case BinaryConsts::I32x4MinU: + return builder.makeBinary(MinUVecI32x4); + case BinaryConsts::I32x4MaxS: + return builder.makeBinary(MaxSVecI32x4); + case BinaryConsts::I32x4MaxU: + return builder.makeBinary(MaxUVecI32x4); + case BinaryConsts::I32x4DotI16x8S: + return builder.makeBinary(DotSVecI16x8ToVecI32x4); + case BinaryConsts::I32x4ExtmulLowI16x8S: + return builder.makeBinary(ExtMulLowSVecI32x4); + case BinaryConsts::I32x4ExtmulHighI16x8S: + return builder.makeBinary(ExtMulHighSVecI32x4); + case BinaryConsts::I32x4ExtmulLowI16x8U: + return builder.makeBinary(ExtMulLowUVecI32x4); + case BinaryConsts::I32x4ExtmulHighI16x8U: + return builder.makeBinary(ExtMulHighUVecI32x4); + case BinaryConsts::I64x2Add: + return builder.makeBinary(AddVecI64x2); + case BinaryConsts::I64x2Sub: + return builder.makeBinary(SubVecI64x2); + case BinaryConsts::I64x2Mul: + return builder.makeBinary(MulVecI64x2); + case BinaryConsts::I64x2ExtmulLowI32x4S: + return builder.makeBinary(ExtMulLowSVecI64x2); + case BinaryConsts::I64x2ExtmulHighI32x4S: + return builder.makeBinary(ExtMulHighSVecI64x2); + case BinaryConsts::I64x2ExtmulLowI32x4U: + return builder.makeBinary(ExtMulLowUVecI64x2); + case BinaryConsts::I64x2ExtmulHighI32x4U: + return builder.makeBinary(ExtMulHighUVecI64x2); + case BinaryConsts::F16x8Add: + return builder.makeBinary(AddVecF16x8); + case BinaryConsts::F16x8Sub: + return builder.makeBinary(SubVecF16x8); + case BinaryConsts::F16x8Mul: + return builder.makeBinary(MulVecF16x8); + case BinaryConsts::F16x8Div: + return builder.makeBinary(DivVecF16x8); + case BinaryConsts::F16x8Min: + return builder.makeBinary(MinVecF16x8); + case BinaryConsts::F16x8Max: + return builder.makeBinary(MaxVecF16x8); + case BinaryConsts::F16x8Pmin: + return builder.makeBinary(PMinVecF16x8); + case BinaryConsts::F16x8Pmax: + return builder.makeBinary(PMaxVecF16x8); + case BinaryConsts::F32x4Add: + return builder.makeBinary(AddVecF32x4); + case BinaryConsts::F32x4Sub: + return builder.makeBinary(SubVecF32x4); + case BinaryConsts::F32x4Mul: + return builder.makeBinary(MulVecF32x4); + case BinaryConsts::F32x4Div: + return builder.makeBinary(DivVecF32x4); + case BinaryConsts::F32x4Min: + return builder.makeBinary(MinVecF32x4); + case BinaryConsts::F32x4Max: + return builder.makeBinary(MaxVecF32x4); + case BinaryConsts::F32x4Pmin: + return builder.makeBinary(PMinVecF32x4); + case BinaryConsts::F32x4Pmax: + return builder.makeBinary(PMaxVecF32x4); + case BinaryConsts::F64x2Add: + return builder.makeBinary(AddVecF64x2); + case BinaryConsts::F64x2Sub: + return builder.makeBinary(SubVecF64x2); + case BinaryConsts::F64x2Mul: + return builder.makeBinary(MulVecF64x2); + case BinaryConsts::F64x2Div: + return builder.makeBinary(DivVecF64x2); + case BinaryConsts::F64x2Min: + return builder.makeBinary(MinVecF64x2); + case BinaryConsts::F64x2Max: + return builder.makeBinary(MaxVecF64x2); + case BinaryConsts::F64x2Pmin: + return builder.makeBinary(PMinVecF64x2); + case BinaryConsts::F64x2Pmax: + return builder.makeBinary(PMaxVecF64x2); + case BinaryConsts::I8x16NarrowI16x8S: + return builder.makeBinary(NarrowSVecI16x8ToVecI8x16); + case BinaryConsts::I8x16NarrowI16x8U: + return builder.makeBinary(NarrowUVecI16x8ToVecI8x16); + case BinaryConsts::I16x8NarrowI32x4S: + return builder.makeBinary(NarrowSVecI32x4ToVecI16x8); + case BinaryConsts::I16x8NarrowI32x4U: + return builder.makeBinary(NarrowUVecI32x4ToVecI16x8); + case BinaryConsts::I8x16Swizzle: + return builder.makeBinary(SwizzleVecI8x16); + case BinaryConsts::I8x16RelaxedSwizzle: + return builder.makeBinary(RelaxedSwizzleVecI8x16); + case BinaryConsts::F32x4RelaxedMin: + return builder.makeBinary(RelaxedMinVecF32x4); + case BinaryConsts::F32x4RelaxedMax: + return builder.makeBinary(RelaxedMaxVecF32x4); + case BinaryConsts::F64x2RelaxedMin: + return builder.makeBinary(RelaxedMinVecF64x2); + case BinaryConsts::F64x2RelaxedMax: + return builder.makeBinary(RelaxedMaxVecF64x2); + case BinaryConsts::I16x8RelaxedQ15MulrS: + return builder.makeBinary(RelaxedQ15MulrSVecI16x8); + case BinaryConsts::I16x8DotI8x16I7x16S: + return builder.makeBinary(DotI8x16I7x16SToVecI16x8); + case BinaryConsts::I8x16Splat: + return builder.makeUnary(SplatVecI8x16); + case BinaryConsts::I16x8Splat: + return builder.makeUnary(SplatVecI16x8); + case BinaryConsts::I32x4Splat: + return builder.makeUnary(SplatVecI32x4); + case BinaryConsts::I64x2Splat: + return builder.makeUnary(SplatVecI64x2); + case BinaryConsts::F16x8Splat: + return builder.makeUnary(SplatVecF16x8); + case BinaryConsts::F32x4Splat: + return builder.makeUnary(SplatVecF32x4); + case BinaryConsts::F64x2Splat: + return builder.makeUnary(SplatVecF64x2); + case BinaryConsts::V128Not: + return builder.makeUnary(NotVec128); + case BinaryConsts::V128AnyTrue: + return builder.makeUnary(AnyTrueVec128); + case BinaryConsts::I8x16Popcnt: + return builder.makeUnary(PopcntVecI8x16); + case BinaryConsts::I8x16Abs: + return builder.makeUnary(AbsVecI8x16); + case BinaryConsts::I8x16Neg: + return builder.makeUnary(NegVecI8x16); + case BinaryConsts::I8x16AllTrue: + return builder.makeUnary(AllTrueVecI8x16); + case BinaryConsts::I8x16Bitmask: + return builder.makeUnary(BitmaskVecI8x16); + case BinaryConsts::I16x8Abs: + return builder.makeUnary(AbsVecI16x8); + case BinaryConsts::I16x8Neg: + return builder.makeUnary(NegVecI16x8); + case BinaryConsts::I16x8AllTrue: + return builder.makeUnary(AllTrueVecI16x8); + case BinaryConsts::I16x8Bitmask: + return builder.makeUnary(BitmaskVecI16x8); + case BinaryConsts::I32x4Abs: + return builder.makeUnary(AbsVecI32x4); + case BinaryConsts::I32x4Neg: + return builder.makeUnary(NegVecI32x4); + case BinaryConsts::I32x4AllTrue: + return builder.makeUnary(AllTrueVecI32x4); + case BinaryConsts::I32x4Bitmask: + return builder.makeUnary(BitmaskVecI32x4); + case BinaryConsts::I64x2Abs: + return builder.makeUnary(AbsVecI64x2); + case BinaryConsts::I64x2Neg: + return builder.makeUnary(NegVecI64x2); + case BinaryConsts::I64x2AllTrue: + return builder.makeUnary(AllTrueVecI64x2); + case BinaryConsts::I64x2Bitmask: + return builder.makeUnary(BitmaskVecI64x2); + case BinaryConsts::F16x8Abs: + return builder.makeUnary(AbsVecF16x8); + case BinaryConsts::F16x8Neg: + return builder.makeUnary(NegVecF16x8); + case BinaryConsts::F16x8Sqrt: + return builder.makeUnary(SqrtVecF16x8); + case BinaryConsts::F16x8Ceil: + return builder.makeUnary(CeilVecF16x8); + case BinaryConsts::F16x8Floor: + return builder.makeUnary(FloorVecF16x8); + case BinaryConsts::F16x8Trunc: + return builder.makeUnary(TruncVecF16x8); + case BinaryConsts::F16x8Nearest: + return builder.makeUnary(NearestVecF16x8); + case BinaryConsts::F32x4Abs: + return builder.makeUnary(AbsVecF32x4); + case BinaryConsts::F32x4Neg: + return builder.makeUnary(NegVecF32x4); + case BinaryConsts::F32x4Sqrt: + return builder.makeUnary(SqrtVecF32x4); + case BinaryConsts::F32x4Ceil: + return builder.makeUnary(CeilVecF32x4); + case BinaryConsts::F32x4Floor: + return builder.makeUnary(FloorVecF32x4); + case BinaryConsts::F32x4Trunc: + return builder.makeUnary(TruncVecF32x4); + case BinaryConsts::F32x4Nearest: + return builder.makeUnary(NearestVecF32x4); + case BinaryConsts::F64x2Abs: + return builder.makeUnary(AbsVecF64x2); + case BinaryConsts::F64x2Neg: + return builder.makeUnary(NegVecF64x2); + case BinaryConsts::F64x2Sqrt: + return builder.makeUnary(SqrtVecF64x2); + case BinaryConsts::F64x2Ceil: + return builder.makeUnary(CeilVecF64x2); + case BinaryConsts::F64x2Floor: + return builder.makeUnary(FloorVecF64x2); + case BinaryConsts::F64x2Trunc: + return builder.makeUnary(TruncVecF64x2); + case BinaryConsts::F64x2Nearest: + return builder.makeUnary(NearestVecF64x2); + case BinaryConsts::I16x8ExtaddPairwiseI8x16S: + return builder.makeUnary(ExtAddPairwiseSVecI8x16ToI16x8); + case BinaryConsts::I16x8ExtaddPairwiseI8x16U: + return builder.makeUnary(ExtAddPairwiseUVecI8x16ToI16x8); + case BinaryConsts::I32x4ExtaddPairwiseI16x8S: + return builder.makeUnary(ExtAddPairwiseSVecI16x8ToI32x4); + case BinaryConsts::I32x4ExtaddPairwiseI16x8U: + return builder.makeUnary(ExtAddPairwiseUVecI16x8ToI32x4); + case BinaryConsts::I32x4TruncSatF32x4S: + return builder.makeUnary(TruncSatSVecF32x4ToVecI32x4); + case BinaryConsts::I32x4TruncSatF32x4U: + return builder.makeUnary(TruncSatUVecF32x4ToVecI32x4); + case BinaryConsts::F32x4ConvertI32x4S: + return builder.makeUnary(ConvertSVecI32x4ToVecF32x4); + case BinaryConsts::F32x4ConvertI32x4U: + return builder.makeUnary(ConvertUVecI32x4ToVecF32x4); + case BinaryConsts::I16x8ExtendLowI8x16S: + return builder.makeUnary(ExtendLowSVecI8x16ToVecI16x8); + case BinaryConsts::I16x8ExtendHighI8x16S: + return builder.makeUnary(ExtendHighSVecI8x16ToVecI16x8); + case BinaryConsts::I16x8ExtendLowI8x16U: + return builder.makeUnary(ExtendLowUVecI8x16ToVecI16x8); + case BinaryConsts::I16x8ExtendHighI8x16U: + return builder.makeUnary(ExtendHighUVecI8x16ToVecI16x8); + case BinaryConsts::I32x4ExtendLowI16x8S: + return builder.makeUnary(ExtendLowSVecI16x8ToVecI32x4); + case BinaryConsts::I32x4ExtendHighI16x8S: + return builder.makeUnary(ExtendHighSVecI16x8ToVecI32x4); + case BinaryConsts::I32x4ExtendLowI16x8U: + return builder.makeUnary(ExtendLowUVecI16x8ToVecI32x4); + case BinaryConsts::I32x4ExtendHighI16x8U: + return builder.makeUnary(ExtendHighUVecI16x8ToVecI32x4); + case BinaryConsts::I64x2ExtendLowI32x4S: + return builder.makeUnary(ExtendLowSVecI32x4ToVecI64x2); + case BinaryConsts::I64x2ExtendHighI32x4S: + return builder.makeUnary(ExtendHighSVecI32x4ToVecI64x2); + case BinaryConsts::I64x2ExtendLowI32x4U: + return builder.makeUnary(ExtendLowUVecI32x4ToVecI64x2); + case BinaryConsts::I64x2ExtendHighI32x4U: + return builder.makeUnary(ExtendHighUVecI32x4ToVecI64x2); + case BinaryConsts::F64x2ConvertLowI32x4S: + return builder.makeUnary(ConvertLowSVecI32x4ToVecF64x2); + case BinaryConsts::F64x2ConvertLowI32x4U: + return builder.makeUnary(ConvertLowUVecI32x4ToVecF64x2); + case BinaryConsts::I32x4TruncSatF64x2SZero: + return builder.makeUnary(TruncSatZeroSVecF64x2ToVecI32x4); + case BinaryConsts::I32x4TruncSatF64x2UZero: + return builder.makeUnary(TruncSatZeroUVecF64x2ToVecI32x4); + case BinaryConsts::F32x4DemoteF64x2Zero: + return builder.makeUnary(DemoteZeroVecF64x2ToVecF32x4); + case BinaryConsts::F64x2PromoteLowF32x4: + return builder.makeUnary(PromoteLowVecF32x4ToVecF64x2); + case BinaryConsts::I32x4RelaxedTruncF32x4S: + return builder.makeUnary(RelaxedTruncSVecF32x4ToVecI32x4); + case BinaryConsts::I32x4RelaxedTruncF32x4U: + return builder.makeUnary(RelaxedTruncUVecF32x4ToVecI32x4); + case BinaryConsts::I32x4RelaxedTruncF64x2SZero: + return builder.makeUnary(RelaxedTruncZeroSVecF64x2ToVecI32x4); + case BinaryConsts::I32x4RelaxedTruncF64x2UZero: + return builder.makeUnary(RelaxedTruncZeroUVecF64x2ToVecI32x4); + case BinaryConsts::I16x8TruncSatF16x8S: + return builder.makeUnary(TruncSatSVecF16x8ToVecI16x8); + case BinaryConsts::I16x8TruncSatF16x8U: + return builder.makeUnary(TruncSatUVecF16x8ToVecI16x8); + case BinaryConsts::F16x8ConvertI16x8S: + return builder.makeUnary(ConvertSVecI16x8ToVecF16x8); + case BinaryConsts::F16x8ConvertI16x8U: + return builder.makeUnary(ConvertUVecI16x8ToVecF16x8); + case BinaryConsts::I8x16ExtractLaneS: + return builder.makeSIMDExtract(ExtractLaneSVecI8x16, + getLaneIndex(16)); + case BinaryConsts::I8x16ExtractLaneU: + return builder.makeSIMDExtract(ExtractLaneUVecI8x16, + getLaneIndex(16)); + case BinaryConsts::I16x8ExtractLaneS: + return builder.makeSIMDExtract(ExtractLaneSVecI16x8, getLaneIndex(8)); + case BinaryConsts::I16x8ExtractLaneU: + return builder.makeSIMDExtract(ExtractLaneUVecI16x8, getLaneIndex(8)); + case BinaryConsts::I32x4ExtractLane: + return builder.makeSIMDExtract(ExtractLaneVecI32x4, getLaneIndex(4)); + case BinaryConsts::I64x2ExtractLane: + return builder.makeSIMDExtract(ExtractLaneVecI64x2, getLaneIndex(2)); + case BinaryConsts::F16x8ExtractLane: + return builder.makeSIMDExtract(ExtractLaneVecF16x8, getLaneIndex(8)); + case BinaryConsts::F32x4ExtractLane: + return builder.makeSIMDExtract(ExtractLaneVecF32x4, getLaneIndex(4)); + case BinaryConsts::F64x2ExtractLane: + return builder.makeSIMDExtract(ExtractLaneVecF64x2, getLaneIndex(2)); + case BinaryConsts::I8x16ReplaceLane: + return builder.makeSIMDReplace(ReplaceLaneVecI8x16, getLaneIndex(16)); + case BinaryConsts::I16x8ReplaceLane: + return builder.makeSIMDReplace(ReplaceLaneVecI16x8, getLaneIndex(8)); + case BinaryConsts::I32x4ReplaceLane: + return builder.makeSIMDReplace(ReplaceLaneVecI32x4, getLaneIndex(4)); + case BinaryConsts::I64x2ReplaceLane: + return builder.makeSIMDReplace(ReplaceLaneVecI64x2, getLaneIndex(2)); + case BinaryConsts::F16x8ReplaceLane: + return builder.makeSIMDReplace(ReplaceLaneVecF16x8, getLaneIndex(8)); + case BinaryConsts::F32x4ReplaceLane: + return builder.makeSIMDReplace(ReplaceLaneVecF32x4, getLaneIndex(4)); + case BinaryConsts::F64x2ReplaceLane: + return builder.makeSIMDReplace(ReplaceLaneVecF64x2, getLaneIndex(2)); + case BinaryConsts::I8x16Shuffle: { + std::array lanes; + for (Index i = 0; i < 16; ++i) { + lanes[i] = getLaneIndex(32); + } + return builder.makeSIMDShuffle(lanes); + } + case BinaryConsts::V128Bitselect: + return builder.makeSIMDTernary(Bitselect); + case BinaryConsts::I8x16Laneselect: + return builder.makeSIMDTernary(LaneselectI8x16); + case BinaryConsts::I16x8Laneselect: + return builder.makeSIMDTernary(LaneselectI16x8); + case BinaryConsts::I32x4Laneselect: + return builder.makeSIMDTernary(LaneselectI32x4); + case BinaryConsts::I64x2Laneselect: + return builder.makeSIMDTernary(LaneselectI64x2); + case BinaryConsts::F16x8RelaxedMadd: + return builder.makeSIMDTernary(RelaxedMaddVecF16x8); + case BinaryConsts::F16x8RelaxedNmadd: + return builder.makeSIMDTernary(RelaxedNmaddVecF16x8); + case BinaryConsts::F32x4RelaxedMadd: + return builder.makeSIMDTernary(RelaxedMaddVecF32x4); + case BinaryConsts::F32x4RelaxedNmadd: + return builder.makeSIMDTernary(RelaxedNmaddVecF32x4); + case BinaryConsts::F64x2RelaxedMadd: + return builder.makeSIMDTernary(RelaxedMaddVecF64x2); + case BinaryConsts::F64x2RelaxedNmadd: + return builder.makeSIMDTernary(RelaxedNmaddVecF64x2); + case BinaryConsts::I32x4DotI8x16I7x16AddS: + return builder.makeSIMDTernary(DotI8x16I7x16AddSToVecI32x4); + case BinaryConsts::I8x16Shl: + return builder.makeSIMDShift(ShlVecI8x16); + case BinaryConsts::I8x16ShrS: + return builder.makeSIMDShift(ShrSVecI8x16); + case BinaryConsts::I8x16ShrU: + return builder.makeSIMDShift(ShrUVecI8x16); + case BinaryConsts::I16x8Shl: + return builder.makeSIMDShift(ShlVecI16x8); + case BinaryConsts::I16x8ShrS: + return builder.makeSIMDShift(ShrSVecI16x8); + case BinaryConsts::I16x8ShrU: + return builder.makeSIMDShift(ShrUVecI16x8); + case BinaryConsts::I32x4Shl: + return builder.makeSIMDShift(ShlVecI32x4); + case BinaryConsts::I32x4ShrS: + return builder.makeSIMDShift(ShrSVecI32x4); + case BinaryConsts::I32x4ShrU: + return builder.makeSIMDShift(ShrUVecI32x4); + case BinaryConsts::I64x2Shl: + return builder.makeSIMDShift(ShlVecI64x2); + case BinaryConsts::I64x2ShrS: + return builder.makeSIMDShift(ShrSVecI64x2); + case BinaryConsts::I64x2ShrU: + return builder.makeSIMDShift(ShrUVecI64x2); + case BinaryConsts::V128Const: + return builder.makeConst(getVec128Literal()); + case BinaryConsts::V128Store: { + auto [mem, align, offset] = getMemarg(); + return builder.makeStore(16, offset, align, Type::v128, mem); + } + case BinaryConsts::V128Load: { + auto [mem, align, offset] = getMemarg(); + return builder.makeLoad(16, false, offset, align, Type::v128, mem); + } + case BinaryConsts::V128Load8Splat: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load8SplatVec128, offset, align, mem); + } + case BinaryConsts::V128Load16Splat: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load16SplatVec128, offset, align, mem); + } + case BinaryConsts::V128Load32Splat: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load32SplatVec128, offset, align, mem); + } + case BinaryConsts::V128Load64Splat: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load64SplatVec128, offset, align, mem); + } + case BinaryConsts::V128Load8x8S: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load8x8SVec128, offset, align, mem); + } + case BinaryConsts::V128Load8x8U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load8x8UVec128, offset, align, mem); + } + case BinaryConsts::V128Load16x4S: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load16x4SVec128, offset, align, mem); + } + case BinaryConsts::V128Load16x4U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load16x4UVec128, offset, align, mem); + } + case BinaryConsts::V128Load32x2S: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load32x2SVec128, offset, align, mem); + } + case BinaryConsts::V128Load32x2U: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load32x2UVec128, offset, align, mem); + } + case BinaryConsts::V128Load32Zero: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load32ZeroVec128, offset, align, mem); + } + case BinaryConsts::V128Load64Zero: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoad(Load64ZeroVec128, offset, align, mem); + } + case BinaryConsts::V128Load8Lane: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoadStoreLane( + Load8LaneVec128, offset, align, getLaneIndex(16), mem); + } + case BinaryConsts::V128Load16Lane: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoadStoreLane( + Load16LaneVec128, offset, align, getLaneIndex(8), mem); + } + case BinaryConsts::V128Load32Lane: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoadStoreLane( + Load32LaneVec128, offset, align, getLaneIndex(4), mem); + } + case BinaryConsts::V128Load64Lane: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoadStoreLane( + Load64LaneVec128, offset, align, getLaneIndex(2), mem); + } + case BinaryConsts::V128Store8Lane: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoadStoreLane( + Store8LaneVec128, offset, align, getLaneIndex(16), mem); + } + case BinaryConsts::V128Store16Lane: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoadStoreLane( + Store16LaneVec128, offset, align, getLaneIndex(8), mem); + } + case BinaryConsts::V128Store32Lane: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoadStoreLane( + Store32LaneVec128, offset, align, getLaneIndex(4), mem); + } + case BinaryConsts::V128Store64Lane: { + auto [mem, align, offset] = getMemarg(); + return builder.makeSIMDLoadStoreLane( + Store64LaneVec128, offset, align, getLaneIndex(2), mem); + } + } + return Err{"unknown SIMD operation"}; + } + case BinaryConsts::GCPrefix: { + auto op = getU32LEB(); + switch (op) { + case BinaryConsts::RefI31: + return builder.makeRefI31(Unshared); + case BinaryConsts::RefI31Shared: + return builder.makeRefI31(Shared); + case BinaryConsts::I31GetS: + return builder.makeI31Get(true); + case BinaryConsts::I31GetU: + return builder.makeI31Get(false); + case BinaryConsts::RefTest: + return builder.makeRefTest(Type(getHeapType(), NonNullable)); + case BinaryConsts::RefTestNull: + return builder.makeRefTest(Type(getHeapType(), Nullable)); + case BinaryConsts::RefCast: + return builder.makeRefCast(Type(getHeapType(), NonNullable)); + case BinaryConsts::RefCastNull: + return builder.makeRefCast(Type(getHeapType(), Nullable)); + case BinaryConsts::BrOnCast: + case BinaryConsts::BrOnCastFail: { + auto flags = getInt8(); + auto label = getU32LEB(); + auto in = Type(getHeapType(), (flags & 1) ? Nullable : NonNullable); + auto cast = Type(getHeapType(), (flags & 2) ? Nullable : NonNullable); + auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; + return builder.makeBrOn(label, kind, in, cast); + } + case BinaryConsts::StructNew: + return builder.makeStructNew(getIndexedHeapType()); + case BinaryConsts::StructNewDefault: + return builder.makeStructNewDefault(getIndexedHeapType()); + case BinaryConsts::StructGet: + case BinaryConsts::StructGetS: + case BinaryConsts::StructGetU: { + auto type = getIndexedHeapType(); + auto field = getU32LEB(); + return builder.makeStructGet( + type, field, op == BinaryConsts::StructGetS); + } + case BinaryConsts::StructSet: { + auto type = getIndexedHeapType(); + auto field = getU32LEB(); + return builder.makeStructSet(type, field); + } + case BinaryConsts::ArrayNew: + return builder.makeArrayNew(getIndexedHeapType()); + case BinaryConsts::ArrayNewDefault: + return builder.makeArrayNewDefault(getIndexedHeapType()); + case BinaryConsts::ArrayNewFixed: { + auto type = getIndexedHeapType(); + auto arity = getU32LEB(); + return builder.makeArrayNewFixed(type, arity); + } + case BinaryConsts::ArrayNewData: { + auto type = getIndexedHeapType(); + auto data = getDataName(getU32LEB()); + return builder.makeArrayNewData(type, data); + } + case BinaryConsts::ArrayNewElem: { + auto type = getIndexedHeapType(); + auto elem = getElemName(getU32LEB()); + return builder.makeArrayNewElem(type, elem); + } + case BinaryConsts::ArrayGet: + case BinaryConsts::ArrayGetU: + return builder.makeArrayGet(getIndexedHeapType(), false); + case BinaryConsts::ArrayGetS: + return builder.makeArrayGet(getIndexedHeapType(), true); + case BinaryConsts::ArraySet: + return builder.makeArraySet(getIndexedHeapType()); + case BinaryConsts::ArrayLen: + return builder.makeArrayLen(); + case BinaryConsts::ArrayCopy: { + auto dest = getIndexedHeapType(); + auto src = getIndexedHeapType(); + return builder.makeArrayCopy(dest, src); + } + case BinaryConsts::ArrayFill: + return builder.makeArrayFill(getIndexedHeapType()); + case BinaryConsts::ArrayInitData: { + auto type = getIndexedHeapType(); + auto data = getDataName(getU32LEB()); + return builder.makeArrayInitData(type, data); + } + case BinaryConsts::ArrayInitElem: { + auto type = getIndexedHeapType(); + auto elem = getElemName(getU32LEB()); + return builder.makeArrayInitElem(type, elem); + } + case BinaryConsts::StringNewLossyUTF8Array: + return builder.makeStringNew(StringNewLossyUTF8Array); + case BinaryConsts::StringNewWTF16Array: + return builder.makeStringNew(StringNewWTF16Array); + case BinaryConsts::StringFromCodePoint: + return builder.makeStringNew(StringNewFromCodePoint); + case BinaryConsts::StringAsWTF16: + // This turns into nothing because we do not represent stringviews in + // the IR. + return Ok{}; + case BinaryConsts::StringConst: + return builder.makeStringConst(getIndexedString()); + case BinaryConsts::StringMeasureUTF8: + return builder.makeStringMeasure(StringMeasureUTF8); + case BinaryConsts::StringMeasureWTF16: + return builder.makeStringMeasure(StringMeasureWTF16); + case BinaryConsts::StringEncodeLossyUTF8Array: + return builder.makeStringEncode(StringEncodeLossyUTF8Array); + case BinaryConsts::StringEncodeWTF16Array: + return builder.makeStringEncode(StringEncodeWTF16Array); + case BinaryConsts::StringConcat: + return builder.makeStringConcat(); + case BinaryConsts::StringEq: + return builder.makeStringEq(StringEqEqual); + case BinaryConsts::StringCompare: + return builder.makeStringEq(StringEqCompare); + case BinaryConsts::StringViewWTF16GetCodePoint: + return builder.makeStringWTF16Get(); + case BinaryConsts::StringViewWTF16Slice: + return builder.makeStringSliceWTF(); + case BinaryConsts::AnyConvertExtern: + return builder.makeRefAs(AnyConvertExtern); + case BinaryConsts::ExternConvertAny: + return builder.makeRefAs(ExternConvertAny); + } + return Err{"unknown GC operation"}; + } + } + return Err{"unknown operation"}; +} + void WasmBinaryReader::readExports() { size_t num = getU32LEB(); std::unordered_set names; @@ -3169,14 +4514,19 @@ void WasmBinaryReader::readNextDebugLocation() { } Expression* WasmBinaryReader::readExpression() { - assert(depth == 0); - processExpressions(); - if (expressionStack.size() != 1) { - throwError("expected to read a single expression"); + assert(builder.empty()); + while (input[pos] != BinaryConsts::End) { + auto inst = readInst(); + if (auto* err = inst.getErr()) { + throwError(err->msg); + } } - auto* ret = popExpression(); - assert(depth == 0); - return ret; + ++pos; + auto expr = builder.build(); + if (auto* err = expr.getErr()) { + throwError(err->msg); + } + return *expr; } void WasmBinaryReader::readStrings() { @@ -3197,6 +4547,14 @@ void WasmBinaryReader::readStrings() { } } +Name WasmBinaryReader::getIndexedString() { + auto index = getU32LEB(); + if (index >= strings.size()) { + throwError("bad string index"); + } + return strings[index]; +} + void WasmBinaryReader::readGlobals() { size_t num = getU32LEB(); auto numImports = wasm.globals.size(); @@ -3224,209 +4582,31 @@ void WasmBinaryReader::readGlobals() { } } -void WasmBinaryReader::processExpressions() { - unreachableInTheWasmSense = false; - while (1) { - Expression* curr; - auto ret = readExpression(curr); - if (!curr) { - lastSeparator = ret; - return; - } - pushExpression(curr); - if (curr->type == Type::unreachable) { - // Once we see something unreachable, we don't want to add anything else - // to the stack, as it could be stacky code that is non-representable in - // our AST. but we do need to skip it. - // If there is nothing else here, just stop. Otherwise, go into - // unreachable mode. peek to see what to do. - if (pos == endOfFunction) { - throwError("Reached function end without seeing End opcode"); - } - if (!more()) { - throwError("unexpected end of input"); - } - auto peek = input[pos]; - if (peek == BinaryConsts::End || peek == BinaryConsts::Else || - peek == BinaryConsts::Catch_Legacy || - peek == BinaryConsts::CatchAll_Legacy || - peek == BinaryConsts::Delegate) { - lastSeparator = BinaryConsts::ASTNodes(peek); - // Read the byte we peeked at. No new instruction is generated for it. - Expression* dummy = nullptr; - readExpression(dummy); - assert(!dummy); - return; - } else { - skipUnreachableCode(); - return; - } - } +void WasmBinaryReader::validateBinary() { + if (hasDataCount && wasm.dataSegments.size() != dataCount) { + throwError("Number of segments does not agree with DataCount section"); } -} -void WasmBinaryReader::skipUnreachableCode() { - // preserve the stack, and restore it. it contains the instruction that made - // us unreachable, and we can ignore anything after it. things after it may - // pop, we want to undo that - auto savedStack = expressionStack; - // note we are entering unreachable code, and note what the state as before so - // we can restore it - auto before = willBeIgnored; - willBeIgnored = true; - // clear the stack. nothing should be popped from there anyhow, just stuff - // can be pushed and then popped. Popping past the top of the stack will - // result in uneachables being returned - expressionStack.clear(); - while (1) { - // set the unreachableInTheWasmSense flag each time, as sub-blocks may set - // and unset it - unreachableInTheWasmSense = true; - Expression* curr; - auto ret = readExpression(curr); - if (!curr) { - lastSeparator = ret; - unreachableInTheWasmSense = false; - willBeIgnored = before; - expressionStack = savedStack; - return; - } - if (curr->type == Type::unreachable) { - // Nothing before this unreachable should be available to future - // expressions. They will get `(unreachable)`s if they try to pop past - // this point. - expressionStack.clear(); - } else { - pushExpression(curr); - } + if (functionTypes.size() != numFuncImports + numFuncBodies) { + throwError("function and code sections have inconsistent lengths"); } } -void WasmBinaryReader::pushExpression(Expression* curr) { - auto type = curr->type; - if (type.isTuple()) { - // Store tuple to local and push individual extracted values. - Builder builder(wasm); - requireFunctionContext("pushExpression-tuple"); - Index tuple = builder.addVar(currFunction, type); - expressionStack.push_back(builder.makeLocalSet(tuple, curr)); - for (Index i = 0; i < type.size(); ++i) { - expressionStack.push_back( - builder.makeTupleExtract(builder.makeLocalGet(tuple, type), i)); +void WasmBinaryReader::createDataSegments(Index count) { + std::unordered_set usedNames; + for (auto& [index, name] : dataNames) { + if (index >= count) { + std::cerr << "warning: data index out of bounds in name section: " << name + << " at index " << index << '\n'; } - } else { - expressionStack.push_back(curr); + usedNames.insert(name); } -} - -Expression* WasmBinaryReader::popExpression() { - if (expressionStack.empty()) { - if (unreachableInTheWasmSense) { - // in unreachable code, trying to pop past the polymorphic stack - // area results in receiving unreachables - return allocator.alloc(); - } - throwError( - "attempted pop from empty stack / beyond block start boundary at " + - std::to_string(pos)); - } - // the stack is not empty, and we would not be going out of the current block - auto ret = expressionStack.back(); - assert(!ret->type.isTuple()); - expressionStack.pop_back(); - return ret; -} - -Expression* WasmBinaryReader::popNonVoidExpression() { - auto* ret = popExpression(); - if (ret->type != Type::none) { - return ret; - } - // we found a void, so this is stacky code that we must handle carefully - Builder builder(wasm); - // add elements until we find a non-void - std::vector expressions; - expressions.push_back(ret); - while (1) { - auto* curr = popExpression(); - expressions.push_back(curr); - if (curr->type != Type::none) { - break; - } - } - auto* block = builder.makeBlock(); - while (!expressions.empty()) { - block->list.push_back(expressions.back()); - expressions.pop_back(); - } - requireFunctionContext("popping void where we need a new local"); - auto type = block->list[0]->type; - if (type.isConcrete()) { - auto local = builder.addVar(currFunction, type); - block->list[0] = builder.makeLocalSet(local, block->list[0]); - block->list.push_back(builder.makeLocalGet(local, type)); - } else { - assert(type == Type::unreachable); - // nothing to do here - unreachable anyhow - } - block->finalize(); - return block; -} - -Expression* WasmBinaryReader::popTuple(size_t numElems) { - Builder builder(wasm); - std::vector elements; - elements.resize(numElems); - for (size_t i = 0; i < numElems; i++) { - auto* elem = popNonVoidExpression(); - if (elem->type == Type::unreachable) { - // All the previously-popped items cannot be reached, so ignore them. We - // cannot continue popping because there might not be enough items on the - // expression stack after an unreachable expression. Any remaining - // elements can stay unperturbed on the stack and will be explicitly - // dropped by some parent call to pushBlockElements. - return elem; - } - elements[numElems - i - 1] = elem; - } - return Builder(wasm).makeTupleMake(std::move(elements)); -} - -Expression* WasmBinaryReader::popTypedExpression(Type type) { - if (type.isSingle()) { - return popNonVoidExpression(); - } else if (type.isTuple()) { - return popTuple(type.size()); - } else { - WASM_UNREACHABLE("Invalid popped type"); - } -} - -void WasmBinaryReader::validateBinary() { - if (hasDataCount && wasm.dataSegments.size() != dataCount) { - throwError("Number of segments does not agree with DataCount section"); - } - - if (functionTypes.size() != numFuncImports + numFuncBodies) { - throwError("function and code sections have inconsistent lengths"); - } -} - -void WasmBinaryReader::createDataSegments(Index count) { - std::unordered_set usedNames; - for (auto& [index, name] : dataNames) { - if (index >= count) { - std::cerr << "warning: data index out of bounds in name section: " << name - << " at index " << index << '\n'; - } - usedNames.insert(name); - } - for (size_t i = 0; i < count; ++i) { - auto [name, isExplicit] = - getOrMakeName(dataNames, i, makeName("", i), usedNames); - auto curr = Builder::makeDataSegment(name); - curr->hasExplicitName = isExplicit; - wasm.addDataSegment(std::move(curr)); + for (size_t i = 0; i < count; ++i) { + auto [name, isExplicit] = + getOrMakeName(dataNames, i, makeName("", i), usedNames); + auto curr = Builder::makeDataSegment(name); + curr->hasExplicitName = isExplicit; + wasm.addDataSegment(std::move(curr)); } } @@ -3587,7 +4767,6 @@ void WasmBinaryReader::readElementSegments() { segmentData.push_back(refFunc); } } - wasm.addElementSegment(std::move(segment)); } } @@ -3977,770 +5156,20 @@ void WasmBinaryReader::readDylink0(size_t payloadLen) { } } -BinaryConsts::ASTNodes WasmBinaryReader::readExpression(Expression*& curr) { - if (pos == endOfFunction) { - throwError("Reached function end without seeing End opcode"); +Index WasmBinaryReader::readMemoryAccess(Address& alignment, Address& offset) { + auto rawAlignment = getU32LEB(); + bool hasMemIdx = false; + Index memIdx = 0; + // Check bit 6 in the alignment to know whether a memory index is present per: + // https://github.com/WebAssembly/multi-memory/blob/main/proposals/multi-memory/Overview.md + if (rawAlignment & (1 << (6))) { + hasMemIdx = true; + // Clear the bit before we parse alignment + rawAlignment = rawAlignment & ~(1 << 6); } - readNextDebugLocation(); - std::set currDebugLocation; - if (debugLocation.size()) { - currDebugLocation.insert(*debugLocation.begin()); - } - size_t startPos = pos; - uint8_t code = getInt8(); - switch (code) { - case BinaryConsts::Block: - visitBlock((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::If: - visitIf((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Loop: - visitLoop((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Br: - case BinaryConsts::BrIf: - visitBreak((curr = allocator.alloc())->cast(), code); - break; // code distinguishes br from br_if - case BinaryConsts::BrTable: - visitSwitch((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::CallFunction: - visitCall((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::CallIndirect: - visitCallIndirect( - (curr = allocator.alloc())->cast()); - break; - case BinaryConsts::RetCallFunction: { - auto call = allocator.alloc(); - call->isReturn = true; - curr = call; - visitCall(call); - break; - } - case BinaryConsts::RetCallIndirect: { - auto call = allocator.alloc(); - call->isReturn = true; - curr = call; - visitCallIndirect(call); - break; - } - case BinaryConsts::LocalGet: - visitLocalGet((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::LocalTee: - case BinaryConsts::LocalSet: - visitLocalSet((curr = allocator.alloc())->cast(), - code); - break; - case BinaryConsts::GlobalGet: - visitGlobalGet((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::GlobalSet: - visitGlobalSet((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Select: - case BinaryConsts::SelectWithType: - visitSelect((curr = allocator.alloc(), code); - break; - case BinaryConsts::Return: - visitReturn((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Nop: - visitNop((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Unreachable: - visitUnreachable( - (curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Drop: - visitDrop((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::End: - curr = nullptr; - // Pop the current control flow structure off the stack. If there is none - // then this is the "end" of the function itself, which also emits an - // "end" byte. - if (!controlFlowStack.empty()) { - controlFlowStack.pop_back(); - } - break; - case BinaryConsts::Else: - case BinaryConsts::Catch_Legacy: - case BinaryConsts::CatchAll_Legacy: { - curr = nullptr; - if (DWARF && currFunction) { - assert(!controlFlowStack.empty()); - auto currControlFlow = controlFlowStack.back(); - BinaryLocation delimiterId; - if (currControlFlow->is()) { - delimiterId = BinaryLocations::Else; - } else { - // Both Catch and CatchAll can simply append to the list as we go, as - // we visit them in the right order in the binary, and like the binary - // we store the CatchAll at the end. - delimiterId = - currFunction->delimiterLocations[currControlFlow].size(); - } - currFunction->delimiterLocations[currControlFlow][delimiterId] = - startPos - codeSectionLocation; - } - break; - } - case BinaryConsts::Delegate: { - curr = nullptr; - if (DWARF && currFunction) { - assert(!controlFlowStack.empty()); - controlFlowStack.pop_back(); - } - break; - } - case BinaryConsts::RefNull: - visitRefNull((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::RefIsNull: - visitRefIsNull((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::RefFunc: - visitRefFunc((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::RefEq: - visitRefEq((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::RefAsNonNull: - visitRefAs((curr = allocator.alloc())->cast(), code); - break; - case BinaryConsts::BrOnNull: - maybeVisitBrOn(curr, code); - break; - case BinaryConsts::BrOnNonNull: - maybeVisitBrOn(curr, code); - break; - case BinaryConsts::TableGet: - visitTableGet((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::TableSet: - visitTableSet((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Try: - visitTryOrTryInBlock(curr); - break; - case BinaryConsts::TryTable: - visitTryTable((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Throw: - visitThrow((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::Rethrow: - visitRethrow((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::ThrowRef: - visitThrowRef((curr = allocator.alloc())->cast()); - break; - case BinaryConsts::MemorySize: { - auto size = allocator.alloc(); - curr = size; - visitMemorySize(size); - break; - } - case BinaryConsts::MemoryGrow: { - auto grow = allocator.alloc(); - curr = grow; - visitMemoryGrow(grow); - break; - } - case BinaryConsts::CallRef: - case BinaryConsts::RetCallRef: { - auto call = allocator.alloc(); - call->isReturn = code == BinaryConsts::RetCallRef; - curr = call; - visitCallRef(call); - break; - } - case BinaryConsts::ContBind: { - visitContBind((curr = allocator.alloc())->cast()); - break; - } - case BinaryConsts::ContNew: { - auto contNew = allocator.alloc(); - curr = contNew; - visitContNew(contNew); - break; - } - case BinaryConsts::Resume: { - visitResume((curr = allocator.alloc())->cast()); - break; - } - case BinaryConsts::Suspend: { - visitSuspend((curr = allocator.alloc())->cast()); - break; - } - case BinaryConsts::AtomicPrefix: { - code = static_cast(getU32LEB()); - if (maybeVisitLoad(curr, code, BinaryConsts::AtomicPrefix)) { - break; - } - if (maybeVisitStore(curr, code, BinaryConsts::AtomicPrefix)) { - break; - } - if (maybeVisitAtomicRMW(curr, code)) { - break; - } - if (maybeVisitAtomicCmpxchg(curr, code)) { - break; - } - if (maybeVisitAtomicWait(curr, code)) { - break; - } - if (maybeVisitAtomicNotify(curr, code)) { - break; - } - if (maybeVisitAtomicFence(curr, code)) { - break; - } - throwError("invalid code after atomic prefix: " + std::to_string(code)); - break; - } - case BinaryConsts::MiscPrefix: { - auto opcode = getU32LEB(); - if (maybeVisitTruncSat(curr, opcode)) { - break; - } - if (maybeVisitMemoryInit(curr, opcode)) { - break; - } - if (maybeVisitDataDrop(curr, opcode)) { - break; - } - if (maybeVisitMemoryCopy(curr, opcode)) { - break; - } - if (maybeVisitMemoryFill(curr, opcode)) { - break; - } - if (maybeVisitTableSize(curr, opcode)) { - break; - } - if (maybeVisitTableGrow(curr, opcode)) { - break; - } - if (maybeVisitTableFill(curr, opcode)) { - break; - } - if (maybeVisitTableCopy(curr, opcode)) { - break; - } - if (maybeVisitTableInit(curr, opcode)) { - break; - } - if (maybeVisitLoad(curr, opcode, BinaryConsts::MiscPrefix)) { - break; - } - if (maybeVisitStore(curr, opcode, BinaryConsts::MiscPrefix)) { - break; - } - throwError("invalid code after misc prefix: " + std::to_string(opcode)); - break; - } - case BinaryConsts::SIMDPrefix: { - auto opcode = getU32LEB(); - if (maybeVisitSIMDBinary(curr, opcode)) { - break; - } - if (maybeVisitSIMDUnary(curr, opcode)) { - break; - } - if (maybeVisitSIMDConst(curr, opcode)) { - break; - } - if (maybeVisitSIMDStore(curr, opcode)) { - break; - } - if (maybeVisitSIMDExtract(curr, opcode)) { - break; - } - if (maybeVisitSIMDReplace(curr, opcode)) { - break; - } - if (maybeVisitSIMDShuffle(curr, opcode)) { - break; - } - if (maybeVisitSIMDTernary(curr, opcode)) { - break; - } - if (maybeVisitSIMDShift(curr, opcode)) { - break; - } - if (maybeVisitSIMDLoad(curr, opcode)) { - break; - } - if (maybeVisitSIMDLoadStoreLane(curr, opcode)) { - break; - } - throwError("invalid code after SIMD prefix: " + std::to_string(opcode)); - break; - } - case BinaryConsts::GCPrefix: { - auto opcode = getU32LEB(); - if (maybeVisitRefI31(curr, opcode)) { - break; - } - if (maybeVisitI31Get(curr, opcode)) { - break; - } - if (maybeVisitRefTest(curr, opcode)) { - break; - } - if (maybeVisitRefCast(curr, opcode)) { - break; - } - if (maybeVisitBrOn(curr, opcode)) { - break; - } - if (maybeVisitStructNew(curr, opcode)) { - break; - } - if (maybeVisitStructGet(curr, opcode)) { - break; - } - if (maybeVisitStructSet(curr, opcode)) { - break; - } - if (maybeVisitArrayNewData(curr, opcode)) { - break; - } - if (maybeVisitArrayNewElem(curr, opcode)) { - break; - } - if (maybeVisitArrayNewFixed(curr, opcode)) { - break; - } - if (maybeVisitArrayGet(curr, opcode)) { - break; - } - if (maybeVisitArraySet(curr, opcode)) { - break; - } - if (maybeVisitArrayLen(curr, opcode)) { - break; - } - if (maybeVisitArrayCopy(curr, opcode)) { - break; - } - if (maybeVisitArrayFill(curr, opcode)) { - break; - } - if (maybeVisitArrayInit(curr, opcode)) { - break; - } - if (maybeVisitStringNew(curr, opcode)) { - break; - } - if (maybeVisitStringAsWTF16(curr, opcode)) { - break; - } - if (maybeVisitStringConst(curr, opcode)) { - break; - } - if (maybeVisitStringMeasure(curr, opcode)) { - break; - } - if (maybeVisitStringEncode(curr, opcode)) { - break; - } - if (maybeVisitStringConcat(curr, opcode)) { - break; - } - if (maybeVisitStringEq(curr, opcode)) { - break; - } - if (maybeVisitStringWTF16Get(curr, opcode)) { - break; - } - if (maybeVisitStringSliceWTF(curr, opcode)) { - break; - } - if (opcode == BinaryConsts::AnyConvertExtern || - opcode == BinaryConsts::ExternConvertAny) { - visitRefAs((curr = allocator.alloc())->cast(), opcode); - break; - } - throwError("invalid code after GC prefix: " + std::to_string(opcode)); - break; - } - default: { - // otherwise, the code is a subcode TODO: optimize - if (maybeVisitBinary(curr, code)) { - break; - } - if (maybeVisitUnary(curr, code)) { - break; - } - if (maybeVisitConst(curr, code)) { - break; - } - if (maybeVisitLoad(curr, code, /*prefix=*/std::nullopt)) { - break; - } - if (maybeVisitStore(curr, code, /*prefix=*/std::nullopt)) { - break; - } - throwError("bad node code " + std::to_string(code)); - break; - } - } - if (curr) { - if (currDebugLocation.size()) { - requireFunctionContext("debugLocation"); - currFunction->debugLocations[curr] = *currDebugLocation.begin(); - } - if (DWARF && currFunction) { - currFunction->expressionLocations[curr] = - BinaryLocations::Span{BinaryLocation(startPos - codeSectionLocation), - BinaryLocation(pos - codeSectionLocation)}; - } - } - return BinaryConsts::ASTNodes(code); -} - -void WasmBinaryReader::startControlFlow(Expression* curr) { - if (DWARF && currFunction) { - controlFlowStack.push_back(curr); - } -} - -void WasmBinaryReader::pushBlockElements(Block* curr, Type type, size_t start) { - assert(start <= expressionStack.size()); - // The results of this block are the last values pushed to the expressionStack - Expression* results = nullptr; - if (type.isConcrete()) { - results = popTypedExpression(type); - } - if (expressionStack.size() < start) { - throwError("Block requires more values than are available"); - } - // Everything else on the stack after `start` is either a none-type expression - // or a concretely-type expression that is implicitly dropped due to - // unreachability at the end of the block, like this: - // - // block i32 - // i32.const 1 - // i32.const 2 - // i32.const 3 - // return - // end - // - // The first two const elements will be emitted as drops in the block (the - // optimizer can remove them, of course, but in general we may need dropped - // items here as they may have side effects). - // - for (size_t i = start; i < expressionStack.size(); ++i) { - auto* item = expressionStack[i]; - if (item->type.isConcrete()) { - item = Builder(wasm).makeDrop(item); - } - curr->list.push_back(item); - } - expressionStack.resize(start); - if (results != nullptr) { - curr->list.push_back(results); - } -} - -void WasmBinaryReader::visitBlock(Block* curr) { - startControlFlow(curr); - // special-case Block and de-recurse nested blocks in their first position, as - // that is a common pattern that can be very highly nested. - std::vector stack; - // Track start positions for all blocks except for the outermost block, which - // is already handled in the caller. - std::vector startPosStack; - size_t startPos = -1; - while (1) { - curr->type = getType(); - curr->name = getNextLabel(); - breakStack.push_back({curr->name, curr->type}); - stack.push_back(curr); - if (startPos != size_t(-1)) { - startPosStack.push_back(startPos); - } - if (more() && input[pos] == BinaryConsts::Block) { - // a recursion - startPos = pos; - readNextDebugLocation(); - curr = allocator.alloc(); - startControlFlow(curr); - pos++; - if (debugLocation.size()) { - requireFunctionContext("block-debugLocation"); - currFunction->debugLocations[curr] = *debugLocation.begin(); - } - continue; - } else { - // end of recursion - break; - } - } - Block* last = nullptr; - while (stack.size() > 0) { - curr = stack.back(); - stack.pop_back(); - if (startPosStack.empty()) { - startPos = -1; - } else { - startPos = startPosStack.back(); - startPosStack.pop_back(); - } - // everything after this, that is left when we see the marker, is ours - size_t start = expressionStack.size(); - if (last) { - // the previous block is our first-position element - pushExpression(last); - } - last = curr; - processExpressions(); - size_t end = expressionStack.size(); - if (end < start) { - throwError("block cannot pop from outside"); - } - pushBlockElements(curr, curr->type, start); - curr->finalize(curr->type, - breakTargetNames.find(curr->name) != breakTargetNames.end() - ? Block::HasBreak - : Block::NoBreak); - breakStack.pop_back(); - breakTargetNames.erase(curr->name); - - if (DWARF && currFunction && startPos != size_t(-1)) { - currFunction->expressionLocations[curr] = - BinaryLocations::Span{BinaryLocation(startPos - codeSectionLocation), - BinaryLocation(pos - codeSectionLocation)}; - } - } -} - -// Gets a block of expressions. If it's just one, return that singleton. -Expression* WasmBinaryReader::getBlockOrSingleton(Type type) { - Name label = getNextLabel(); - breakStack.push_back({label, type}); - auto start = expressionStack.size(); - - processExpressions(); - size_t end = expressionStack.size(); - if (end < start) { - throwError("block cannot pop from outside"); - } - breakStack.pop_back(); - auto* block = allocator.alloc(); - pushBlockElements(block, type, start); - block->name = label; - block->finalize(type); - // maybe we don't need a block here? - if (breakTargetNames.find(block->name) == breakTargetNames.end() && - exceptionTargetNames.find(block->name) == exceptionTargetNames.end()) { - block->name = Name(); - if (block->list.size() == 1) { - return block->list[0]; - } - } - breakTargetNames.erase(block->name); - return block; -} - -void WasmBinaryReader::visitIf(If* curr) { - startControlFlow(curr); - curr->type = getType(); - curr->condition = popNonVoidExpression(); - curr->ifTrue = getBlockOrSingleton(curr->type); - if (lastSeparator == BinaryConsts::Else) { - curr->ifFalse = getBlockOrSingleton(curr->type); - } - curr->finalize(curr->type); - if (lastSeparator != BinaryConsts::End) { - throwError("if should end with End"); - } -} - -void WasmBinaryReader::visitLoop(Loop* curr) { - startControlFlow(curr); - curr->type = getType(); - curr->name = getNextLabel(); - breakStack.push_back({curr->name, Type::none}); - // find the expressions in the block, and create the body - // a loop may have a list of instructions in wasm, much like - // a block, but it only has a label at the top of the loop, - // so even if we need a block (if there is more than 1 - // expression) we never need a label on the block. - auto start = expressionStack.size(); - processExpressions(); - size_t end = expressionStack.size(); - if (start > end) { - throwError("block cannot pop from outside"); - } - if (end - start == 1) { - curr->body = popExpression(); - } else { - auto* block = allocator.alloc(); - pushBlockElements(block, curr->type, start); - block->finalize(curr->type); - curr->body = block; - } - breakStack.pop_back(); - breakTargetNames.erase(curr->name); - curr->finalize(curr->type); -} - -WasmBinaryReader::BreakTarget WasmBinaryReader::getBreakTarget(int32_t offset) { - if (breakStack.size() < 1 + size_t(offset)) { - throwError("bad breakindex (low)"); - } - size_t index = breakStack.size() - 1 - offset; - if (index >= breakStack.size()) { - throwError("bad breakindex (high)"); - } - auto& ret = breakStack[index]; - // if the break is in literally unreachable code, then we will not emit it - // anyhow, so do not note that the target has breaks to it - if (!willBeIgnored) { - breakTargetNames.insert(ret.name); - } - return ret; -} - -Name WasmBinaryReader::getExceptionTargetName(int32_t offset) { - // We always start parsing a function by creating a block label and pushing it - // in breakStack in getBlockOrSingleton, so if a 'delegate''s target is that - // block, it does not mean it targets that block; it throws to the caller. - if (breakStack.size() - 1 == size_t(offset)) { - return DELEGATE_CALLER_TARGET; - } - size_t index = breakStack.size() - 1 - offset; - if (index > breakStack.size()) { - throwError("bad try index (high)"); - } - auto& ret = breakStack[index]; - // if the delegate/rethrow is in literally unreachable code, then we will not - // emit it anyhow, so do not note that the target has a reference to it - if (!willBeIgnored) { - exceptionTargetNames.insert(ret.name); - } - return ret.name; -} - -void WasmBinaryReader::visitBreak(Break* curr, uint8_t code) { - BreakTarget target = getBreakTarget(getU32LEB()); - curr->name = target.name; - if (code == BinaryConsts::BrIf) { - curr->condition = popNonVoidExpression(); - } - if (target.type.isConcrete()) { - curr->value = popTypedExpression(target.type); - } - curr->finalize(); -} - -void WasmBinaryReader::visitSwitch(Switch* curr) { - curr->condition = popNonVoidExpression(); - auto numTargets = getU32LEB(); - for (size_t i = 0; i < numTargets; i++) { - curr->targets.push_back(getBreakTarget(getU32LEB()).name); - } - auto defaultTarget = getBreakTarget(getU32LEB()); - curr->default_ = defaultTarget.name; - if (defaultTarget.type.isConcrete()) { - curr->value = popTypedExpression(defaultTarget.type); - } - curr->finalize(); -} - -void WasmBinaryReader::visitCall(Call* curr) { - auto index = getU32LEB(); - auto sig = getSignatureByFunctionIndex(index); - auto num = sig.params.size(); - curr->operands.resize(num); - for (size_t i = 0; i < num; i++) { - curr->operands[num - i - 1] = popNonVoidExpression(); - } - curr->type = sig.results; - curr->target = getFunctionName(index); - curr->finalize(); -} - -void WasmBinaryReader::visitCallIndirect(CallIndirect* curr) { - auto index = getU32LEB(); - curr->heapType = getTypeByIndex(index); - Index tableIdx = getU32LEB(); - // TODO: Handle error cases where `heapType` is not a signature? - auto num = curr->heapType.getSignature().params.size(); - curr->operands.resize(num); - curr->target = popNonVoidExpression(); - for (size_t i = 0; i < num; i++) { - curr->operands[num - i - 1] = popNonVoidExpression(); - } - curr->table = getTableName(tableIdx); - curr->finalize(); -} - -void WasmBinaryReader::visitLocalGet(LocalGet* curr) { - requireFunctionContext("local.get"); - curr->index = getU32LEB(); - if (curr->index >= currFunction->getNumLocals()) { - throwError("bad local.get index"); - } - curr->type = currFunction->getLocalType(curr->index); - curr->finalize(); -} - -void WasmBinaryReader::visitLocalSet(LocalSet* curr, uint8_t code) { - requireFunctionContext("local.set outside of function"); - curr->index = getU32LEB(); - if (curr->index >= currFunction->getNumLocals()) { - throwError("bad local.set index"); - } - curr->value = popNonVoidExpression(); - if (code == BinaryConsts::LocalTee) { - curr->makeTee(currFunction->getLocalType(curr->index)); - } else { - curr->makeSet(); - } - curr->finalize(); -} - -void WasmBinaryReader::visitGlobalGet(GlobalGet* curr) { - auto index = getU32LEB(); - if (index >= wasm.globals.size()) { - throwError("invalid global index"); - } - auto* global = wasm.globals[index].get(); - curr->name = global->name; - curr->type = global->type; -} - -void WasmBinaryReader::visitGlobalSet(GlobalSet* curr) { - auto index = getU32LEB(); - if (index >= wasm.globals.size()) { - throwError("invalid global index"); - } - curr->name = wasm.globals[index]->name; - curr->value = popNonVoidExpression(); - curr->finalize(); -} - -Index WasmBinaryReader::readMemoryAccess(Address& alignment, Address& offset) { - auto rawAlignment = getU32LEB(); - bool hasMemIdx = false; - Index memIdx = 0; - // Check bit 6 in the alignment to know whether a memory index is present per: - // https://github.com/WebAssembly/multi-memory/blob/main/proposals/multi-memory/Overview.md - if (rawAlignment & (1 << (6))) { - hasMemIdx = true; - // Clear the bit before we parse alignment - rawAlignment = rawAlignment & ~(1 << 6); - } - - if (rawAlignment > 8) { - throwError("Alignment must be of a reasonable size"); + + if (rawAlignment > 8) { + throwError("Alignment must be of a reasonable size"); } alignment = Bits::pow2(rawAlignment); @@ -4756,3267 +5185,11 @@ Index WasmBinaryReader::readMemoryAccess(Address& alignment, Address& offset) { return memIdx; } -bool WasmBinaryReader::maybeVisitLoad( - Expression*& out, - uint8_t code, - std::optional prefix) { - Load* curr; - auto allocate = [&]() { curr = allocator.alloc(); }; - if (!prefix) { - switch (code) { - case BinaryConsts::I32LoadMem8S: - allocate(); - curr->bytes = 1; - curr->type = Type::i32; - curr->signed_ = true; - break; - case BinaryConsts::I32LoadMem8U: - allocate(); - curr->bytes = 1; - curr->type = Type::i32; - break; - case BinaryConsts::I32LoadMem16S: - allocate(); - curr->bytes = 2; - curr->type = Type::i32; - curr->signed_ = true; - break; - case BinaryConsts::I32LoadMem16U: - allocate(); - curr->bytes = 2; - curr->type = Type::i32; - break; - case BinaryConsts::I32LoadMem: - allocate(); - curr->bytes = 4; - curr->type = Type::i32; - break; - case BinaryConsts::I64LoadMem8S: - allocate(); - curr->bytes = 1; - curr->type = Type::i64; - curr->signed_ = true; - break; - case BinaryConsts::I64LoadMem8U: - allocate(); - curr->bytes = 1; - curr->type = Type::i64; - break; - case BinaryConsts::I64LoadMem16S: - allocate(); - curr->bytes = 2; - curr->type = Type::i64; - curr->signed_ = true; - break; - case BinaryConsts::I64LoadMem16U: - allocate(); - curr->bytes = 2; - curr->type = Type::i64; - break; - case BinaryConsts::I64LoadMem32S: - allocate(); - curr->bytes = 4; - curr->type = Type::i64; - curr->signed_ = true; - break; - case BinaryConsts::I64LoadMem32U: - allocate(); - curr->bytes = 4; - curr->type = Type::i64; - break; - case BinaryConsts::I64LoadMem: - allocate(); - curr->bytes = 8; - curr->type = Type::i64; - break; - case BinaryConsts::F32LoadMem: - allocate(); - curr->bytes = 4; - curr->type = Type::f32; - break; - case BinaryConsts::F64LoadMem: - allocate(); - curr->bytes = 8; - curr->type = Type::f64; - break; - default: - return false; - } - } else if (prefix == BinaryConsts::AtomicPrefix) { - switch (code) { - case BinaryConsts::I32AtomicLoad8U: - allocate(); - curr->bytes = 1; - curr->type = Type::i32; - break; - case BinaryConsts::I32AtomicLoad16U: - allocate(); - curr->bytes = 2; - curr->type = Type::i32; - break; - case BinaryConsts::I32AtomicLoad: - allocate(); - curr->bytes = 4; - curr->type = Type::i32; - break; - case BinaryConsts::I64AtomicLoad8U: - allocate(); - curr->bytes = 1; - curr->type = Type::i64; - break; - case BinaryConsts::I64AtomicLoad16U: - allocate(); - curr->bytes = 2; - curr->type = Type::i64; - break; - case BinaryConsts::I64AtomicLoad32U: - allocate(); - curr->bytes = 4; - curr->type = Type::i64; - break; - case BinaryConsts::I64AtomicLoad: - allocate(); - curr->bytes = 8; - curr->type = Type::i64; - break; - default: - return false; - } - } else if (prefix == BinaryConsts::MiscPrefix) { - switch (code) { - case BinaryConsts::F32_F16LoadMem: - allocate(); - curr->bytes = 2; - curr->type = Type::f32; - break; - default: - return false; - } - } else { - return false; - } - - curr->isAtomic = prefix == BinaryConsts::AtomicPrefix; - Index memIdx = readMemoryAccess(curr->align, curr->offset); - curr->memory = getMemoryName(memIdx); - curr->ptr = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitStore( - Expression*& out, - uint8_t code, - std::optional prefix) { - Store* curr; - if (!prefix) { - switch (code) { - case BinaryConsts::I32StoreMem8: - curr = allocator.alloc(); - curr->bytes = 1; - curr->valueType = Type::i32; - break; - case BinaryConsts::I32StoreMem16: - curr = allocator.alloc(); - curr->bytes = 2; - curr->valueType = Type::i32; - break; - case BinaryConsts::I32StoreMem: - curr = allocator.alloc(); - curr->bytes = 4; - curr->valueType = Type::i32; - break; - case BinaryConsts::I64StoreMem8: - curr = allocator.alloc(); - curr->bytes = 1; - curr->valueType = Type::i64; - break; - case BinaryConsts::I64StoreMem16: - curr = allocator.alloc(); - curr->bytes = 2; - curr->valueType = Type::i64; - break; - case BinaryConsts::I64StoreMem32: - curr = allocator.alloc(); - curr->bytes = 4; - curr->valueType = Type::i64; - break; - case BinaryConsts::I64StoreMem: - curr = allocator.alloc(); - curr->bytes = 8; - curr->valueType = Type::i64; - break; - case BinaryConsts::F32StoreMem: - curr = allocator.alloc(); - curr->bytes = 4; - curr->valueType = Type::f32; - break; - case BinaryConsts::F64StoreMem: - curr = allocator.alloc(); - curr->bytes = 8; - curr->valueType = Type::f64; - break; - default: - return false; - } - } else if (prefix == BinaryConsts::AtomicPrefix) { - switch (code) { - case BinaryConsts::I32AtomicStore8: - curr = allocator.alloc(); - curr->bytes = 1; - curr->valueType = Type::i32; - break; - case BinaryConsts::I32AtomicStore16: - curr = allocator.alloc(); - curr->bytes = 2; - curr->valueType = Type::i32; - break; - case BinaryConsts::I32AtomicStore: - curr = allocator.alloc(); - curr->bytes = 4; - curr->valueType = Type::i32; - break; - case BinaryConsts::I64AtomicStore8: - curr = allocator.alloc(); - curr->bytes = 1; - curr->valueType = Type::i64; - break; - case BinaryConsts::I64AtomicStore16: - curr = allocator.alloc(); - curr->bytes = 2; - curr->valueType = Type::i64; - break; - case BinaryConsts::I64AtomicStore32: - curr = allocator.alloc(); - curr->bytes = 4; - curr->valueType = Type::i64; - break; - case BinaryConsts::I64AtomicStore: - curr = allocator.alloc(); - curr->bytes = 8; - curr->valueType = Type::i64; - break; - default: - return false; - } - } else if (prefix == BinaryConsts::MiscPrefix) { - switch (code) { - case BinaryConsts::F32_F16StoreMem: - curr = allocator.alloc(); - curr->bytes = 2; - curr->valueType = Type::f32; - break; - default: - return false; - } - } else { - return false; - } - - curr->isAtomic = prefix == BinaryConsts::AtomicPrefix; - Index memIdx = readMemoryAccess(curr->align, curr->offset); - curr->memory = getMemoryName(memIdx); - curr->value = popNonVoidExpression(); - curr->ptr = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitAtomicRMW(Expression*& out, uint8_t code) { - if (code < BinaryConsts::AtomicRMWOps_Begin || - code > BinaryConsts::AtomicRMWOps_End) { - return false; - } - auto* curr = allocator.alloc(); - - // Set curr to the given opcode, type and size. -#define SET(opcode, optype, size) \ - curr->op = RMW##opcode; \ - curr->type = optype; \ - curr->bytes = size - - // Handle the cases for all the valid types for a particular opcode -#define SET_FOR_OP(Op) \ - case BinaryConsts::I32AtomicRMW##Op: \ - SET(Op, Type::i32, 4); \ - break; \ - case BinaryConsts::I32AtomicRMW##Op##8U: \ - SET(Op, Type::i32, 1); \ - break; \ - case BinaryConsts::I32AtomicRMW##Op##16U: \ - SET(Op, Type::i32, 2); \ - break; \ - case BinaryConsts::I64AtomicRMW##Op: \ - SET(Op, Type::i64, 8); \ - break; \ - case BinaryConsts::I64AtomicRMW##Op##8U: \ - SET(Op, Type::i64, 1); \ - break; \ - case BinaryConsts::I64AtomicRMW##Op##16U: \ - SET(Op, Type::i64, 2); \ - break; \ - case BinaryConsts::I64AtomicRMW##Op##32U: \ - SET(Op, Type::i64, 4); \ - break; - - switch (code) { - SET_FOR_OP(Add); - SET_FOR_OP(Sub); - SET_FOR_OP(And); - SET_FOR_OP(Or); - SET_FOR_OP(Xor); - SET_FOR_OP(Xchg); - default: - WASM_UNREACHABLE("unexpected opcode"); - } -#undef SET_FOR_OP -#undef SET - - Address readAlign; - Index memIdx = readMemoryAccess(readAlign, curr->offset); - curr->memory = getMemoryName(memIdx); - if (readAlign != curr->bytes) { - throwError("Align of AtomicRMW must match size"); - } - curr->value = popNonVoidExpression(); - curr->ptr = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitAtomicCmpxchg(Expression*& out, uint8_t code) { - if (code < BinaryConsts::AtomicCmpxchgOps_Begin || - code > BinaryConsts::AtomicCmpxchgOps_End) { - return false; - } - auto* curr = allocator.alloc(); - - // Set curr to the given type and size. -#define SET(optype, size) \ - curr->type = optype; \ - curr->bytes = size - - switch (code) { - case BinaryConsts::I32AtomicCmpxchg: - SET(Type::i32, 4); - break; - case BinaryConsts::I64AtomicCmpxchg: - SET(Type::i64, 8); - break; - case BinaryConsts::I32AtomicCmpxchg8U: - SET(Type::i32, 1); - break; - case BinaryConsts::I32AtomicCmpxchg16U: - SET(Type::i32, 2); - break; - case BinaryConsts::I64AtomicCmpxchg8U: - SET(Type::i64, 1); - break; - case BinaryConsts::I64AtomicCmpxchg16U: - SET(Type::i64, 2); - break; - case BinaryConsts::I64AtomicCmpxchg32U: - SET(Type::i64, 4); - break; - default: - WASM_UNREACHABLE("unexpected opcode"); - } - - Address readAlign; - Index memIdx = readMemoryAccess(readAlign, curr->offset); - curr->memory = getMemoryName(memIdx); - if (readAlign != curr->bytes) { - throwError("Align of AtomicCpxchg must match size"); - } - curr->replacement = popNonVoidExpression(); - curr->expected = popNonVoidExpression(); - curr->ptr = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitAtomicWait(Expression*& out, uint8_t code) { - if (code < BinaryConsts::I32AtomicWait || - code > BinaryConsts::I64AtomicWait) { - return false; - } - auto* curr = allocator.alloc(); - - switch (code) { - case BinaryConsts::I32AtomicWait: - curr->expectedType = Type::i32; - break; - case BinaryConsts::I64AtomicWait: - curr->expectedType = Type::i64; - break; - default: - WASM_UNREACHABLE("unexpected opcode"); - } - curr->type = Type::i32; - curr->timeout = popNonVoidExpression(); - curr->expected = popNonVoidExpression(); - curr->ptr = popNonVoidExpression(); - Address readAlign; - Index memIdx = readMemoryAccess(readAlign, curr->offset); - curr->memory = getMemoryName(memIdx); - if (readAlign != curr->expectedType.getByteSize()) { - throwError("Align of AtomicWait must match size"); - } - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitAtomicNotify(Expression*& out, uint8_t code) { - if (code != BinaryConsts::AtomicNotify) { - return false; - } - auto* curr = allocator.alloc(); - - curr->type = Type::i32; - curr->notifyCount = popNonVoidExpression(); - curr->ptr = popNonVoidExpression(); - Address readAlign; - Index memIdx = readMemoryAccess(readAlign, curr->offset); - curr->memory = getMemoryName(memIdx); - if (readAlign != curr->type.getByteSize()) { - throwError("Align of AtomicNotify must match size"); - } - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitAtomicFence(Expression*& out, uint8_t code) { - if (code != BinaryConsts::AtomicFence) { - return false; - } - auto* curr = allocator.alloc(); - curr->order = getU32LEB(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitConst(Expression*& out, uint8_t code) { - Const* curr; - switch (code) { - case BinaryConsts::I32Const: - curr = allocator.alloc(); - curr->value = Literal(getS32LEB()); - break; - case BinaryConsts::I64Const: - curr = allocator.alloc(); - curr->value = Literal(getS64LEB()); - break; - case BinaryConsts::F32Const: - curr = allocator.alloc(); - curr->value = getFloat32Literal(); - break; - case BinaryConsts::F64Const: - curr = allocator.alloc(); - curr->value = getFloat64Literal(); - break; - default: - return false; - } - curr->type = curr->value.type; - out = curr; - - return true; -} - -bool WasmBinaryReader::maybeVisitUnary(Expression*& out, uint8_t code) { - Unary* curr; - switch (code) { - case BinaryConsts::I32Clz: - curr = allocator.alloc(); - curr->op = ClzInt32; - break; - case BinaryConsts::I64Clz: - curr = allocator.alloc(); - curr->op = ClzInt64; - break; - case BinaryConsts::I32Ctz: - curr = allocator.alloc(); - curr->op = CtzInt32; - break; - case BinaryConsts::I64Ctz: - curr = allocator.alloc(); - curr->op = CtzInt64; - break; - case BinaryConsts::I32Popcnt: - curr = allocator.alloc(); - curr->op = PopcntInt32; - break; - case BinaryConsts::I64Popcnt: - curr = allocator.alloc(); - curr->op = PopcntInt64; - break; - case BinaryConsts::I32EqZ: - curr = allocator.alloc(); - curr->op = EqZInt32; - break; - case BinaryConsts::I64EqZ: - curr = allocator.alloc(); - curr->op = EqZInt64; - break; - case BinaryConsts::F32Neg: - curr = allocator.alloc(); - curr->op = NegFloat32; - break; - case BinaryConsts::F64Neg: - curr = allocator.alloc(); - curr->op = NegFloat64; - break; - case BinaryConsts::F32Abs: - curr = allocator.alloc(); - curr->op = AbsFloat32; - break; - case BinaryConsts::F64Abs: - curr = allocator.alloc(); - curr->op = AbsFloat64; - break; - case BinaryConsts::F32Ceil: - curr = allocator.alloc(); - curr->op = CeilFloat32; - break; - case BinaryConsts::F64Ceil: - curr = allocator.alloc(); - curr->op = CeilFloat64; - break; - case BinaryConsts::F32Floor: - curr = allocator.alloc(); - curr->op = FloorFloat32; - break; - case BinaryConsts::F64Floor: - curr = allocator.alloc(); - curr->op = FloorFloat64; - break; - case BinaryConsts::F32NearestInt: - curr = allocator.alloc(); - curr->op = NearestFloat32; - break; - case BinaryConsts::F64NearestInt: - curr = allocator.alloc(); - curr->op = NearestFloat64; - break; - case BinaryConsts::F32Sqrt: - curr = allocator.alloc(); - curr->op = SqrtFloat32; - break; - case BinaryConsts::F64Sqrt: - curr = allocator.alloc(); - curr->op = SqrtFloat64; - break; - case BinaryConsts::F32UConvertI32: - curr = allocator.alloc(); - curr->op = ConvertUInt32ToFloat32; - break; - case BinaryConsts::F64UConvertI32: - curr = allocator.alloc(); - curr->op = ConvertUInt32ToFloat64; - break; - case BinaryConsts::F32SConvertI32: - curr = allocator.alloc(); - curr->op = ConvertSInt32ToFloat32; - break; - case BinaryConsts::F64SConvertI32: - curr = allocator.alloc(); - curr->op = ConvertSInt32ToFloat64; - break; - case BinaryConsts::F32UConvertI64: - curr = allocator.alloc(); - curr->op = ConvertUInt64ToFloat32; - break; - case BinaryConsts::F64UConvertI64: - curr = allocator.alloc(); - curr->op = ConvertUInt64ToFloat64; - break; - case BinaryConsts::F32SConvertI64: - curr = allocator.alloc(); - curr->op = ConvertSInt64ToFloat32; - break; - case BinaryConsts::F64SConvertI64: - curr = allocator.alloc(); - curr->op = ConvertSInt64ToFloat64; - break; - - case BinaryConsts::I64SExtendI32: - curr = allocator.alloc(); - curr->op = ExtendSInt32; - break; - case BinaryConsts::I64UExtendI32: - curr = allocator.alloc(); - curr->op = ExtendUInt32; - break; - case BinaryConsts::I32WrapI64: - curr = allocator.alloc(); - curr->op = WrapInt64; - break; - - case BinaryConsts::I32UTruncF32: - curr = allocator.alloc(); - curr->op = TruncUFloat32ToInt32; - break; - case BinaryConsts::I32UTruncF64: - curr = allocator.alloc(); - curr->op = TruncUFloat64ToInt32; - break; - case BinaryConsts::I32STruncF32: - curr = allocator.alloc(); - curr->op = TruncSFloat32ToInt32; - break; - case BinaryConsts::I32STruncF64: - curr = allocator.alloc(); - curr->op = TruncSFloat64ToInt32; - break; - case BinaryConsts::I64UTruncF32: - curr = allocator.alloc(); - curr->op = TruncUFloat32ToInt64; - break; - case BinaryConsts::I64UTruncF64: - curr = allocator.alloc(); - curr->op = TruncUFloat64ToInt64; - break; - case BinaryConsts::I64STruncF32: - curr = allocator.alloc(); - curr->op = TruncSFloat32ToInt64; - break; - case BinaryConsts::I64STruncF64: - curr = allocator.alloc(); - curr->op = TruncSFloat64ToInt64; - break; - - case BinaryConsts::F32Trunc: - curr = allocator.alloc(); - curr->op = TruncFloat32; - break; - case BinaryConsts::F64Trunc: - curr = allocator.alloc(); - curr->op = TruncFloat64; - break; - - case BinaryConsts::F32DemoteI64: - curr = allocator.alloc(); - curr->op = DemoteFloat64; - break; - case BinaryConsts::F64PromoteF32: - curr = allocator.alloc(); - curr->op = PromoteFloat32; - break; - case BinaryConsts::I32ReinterpretF32: - curr = allocator.alloc(); - curr->op = ReinterpretFloat32; - break; - case BinaryConsts::I64ReinterpretF64: - curr = allocator.alloc(); - curr->op = ReinterpretFloat64; - break; - case BinaryConsts::F32ReinterpretI32: - curr = allocator.alloc(); - curr->op = ReinterpretInt32; - break; - case BinaryConsts::F64ReinterpretI64: - curr = allocator.alloc(); - curr->op = ReinterpretInt64; - break; - - case BinaryConsts::I32ExtendS8: - curr = allocator.alloc(); - curr->op = ExtendS8Int32; - break; - case BinaryConsts::I32ExtendS16: - curr = allocator.alloc(); - curr->op = ExtendS16Int32; - break; - case BinaryConsts::I64ExtendS8: - curr = allocator.alloc(); - curr->op = ExtendS8Int64; - break; - case BinaryConsts::I64ExtendS16: - curr = allocator.alloc(); - curr->op = ExtendS16Int64; - break; - case BinaryConsts::I64ExtendS32: - curr = allocator.alloc(); - curr->op = ExtendS32Int64; - break; - - default: - return false; - } - curr->value = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitTruncSat(Expression*& out, uint32_t code) { - Unary* curr; - switch (code) { - case BinaryConsts::I32STruncSatF32: - curr = allocator.alloc(); - curr->op = TruncSatSFloat32ToInt32; - break; - case BinaryConsts::I32UTruncSatF32: - curr = allocator.alloc(); - curr->op = TruncSatUFloat32ToInt32; - break; - case BinaryConsts::I32STruncSatF64: - curr = allocator.alloc(); - curr->op = TruncSatSFloat64ToInt32; - break; - case BinaryConsts::I32UTruncSatF64: - curr = allocator.alloc(); - curr->op = TruncSatUFloat64ToInt32; - break; - case BinaryConsts::I64STruncSatF32: - curr = allocator.alloc(); - curr->op = TruncSatSFloat32ToInt64; - break; - case BinaryConsts::I64UTruncSatF32: - curr = allocator.alloc(); - curr->op = TruncSatUFloat32ToInt64; - break; - case BinaryConsts::I64STruncSatF64: - curr = allocator.alloc(); - curr->op = TruncSatSFloat64ToInt64; - break; - case BinaryConsts::I64UTruncSatF64: - curr = allocator.alloc(); - curr->op = TruncSatUFloat64ToInt64; - break; - default: - return false; - } - curr->value = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitMemoryInit(Expression*& out, uint32_t code) { - if (code != BinaryConsts::MemoryInit) { - return false; - } - auto* curr = allocator.alloc(); - curr->size = popNonVoidExpression(); - curr->offset = popNonVoidExpression(); - curr->dest = popNonVoidExpression(); - Index segIdx = getU32LEB(); - curr->segment = getDataName(segIdx); - Index memIdx = getU32LEB(); - curr->memory = getMemoryName(memIdx); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitDataDrop(Expression*& out, uint32_t code) { - if (code != BinaryConsts::DataDrop) { - return false; - } - auto* curr = allocator.alloc(); - Index segIdx = getU32LEB(); - curr->segment = getDataName(segIdx); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitMemoryCopy(Expression*& out, uint32_t code) { - if (code != BinaryConsts::MemoryCopy) { - return false; - } - auto* curr = allocator.alloc(); - curr->size = popNonVoidExpression(); - curr->source = popNonVoidExpression(); - curr->dest = popNonVoidExpression(); - Index destIdx = getU32LEB(); - Index sourceIdx = getU32LEB(); - curr->finalize(); - curr->destMemory = getMemoryName(destIdx); - curr->sourceMemory = getMemoryName(sourceIdx); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitMemoryFill(Expression*& out, uint32_t code) { - if (code != BinaryConsts::MemoryFill) { - return false; - } - auto* curr = allocator.alloc(); - curr->size = popNonVoidExpression(); - curr->value = popNonVoidExpression(); - curr->dest = popNonVoidExpression(); - Index memIdx = getU32LEB(); - curr->finalize(); - curr->memory = getMemoryName(memIdx); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitTableSize(Expression*& out, uint32_t code) { - if (code != BinaryConsts::TableSize) { - return false; - } - Index tableIdx = getU32LEB(); - if (tableIdx >= wasm.tables.size()) { - throwError("bad table index"); - } - auto* curr = allocator.alloc(); - if (getTable(tableIdx)->is64()) { - curr->type = Type::i64; - } - curr->table = getTableName(tableIdx); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitTableGrow(Expression*& out, uint32_t code) { - if (code != BinaryConsts::TableGrow) { - return false; - } - Index tableIdx = getU32LEB(); - if (tableIdx >= wasm.tables.size()) { - throwError("bad table index"); - } - auto* curr = allocator.alloc(); - curr->delta = popNonVoidExpression(); - curr->value = popNonVoidExpression(); - if (getTable(tableIdx)->is64()) { - curr->type = Type::i64; - } - curr->table = getTableName(tableIdx); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitTableFill(Expression*& out, uint32_t code) { - if (code != BinaryConsts::TableFill) { - return false; - } - Index tableIdx = getU32LEB(); - if (tableIdx >= wasm.tables.size()) { - throwError("bad table index"); - } - auto* size = popNonVoidExpression(); - auto* value = popNonVoidExpression(); - auto* dest = popNonVoidExpression(); - auto* ret = Builder(wasm).makeTableFill(Name(), dest, value, size); - ret->table = getTableName(tableIdx); - out = ret; - return true; -} - -bool WasmBinaryReader::maybeVisitTableCopy(Expression*& out, uint32_t code) { - if (code != BinaryConsts::TableCopy) { - return false; - } - Index destTableIdx = getU32LEB(); - if (destTableIdx >= wasm.tables.size()) { - throwError("bad table index"); - } - Index sourceTableIdx = getU32LEB(); - if (sourceTableIdx >= wasm.tables.size()) { - throwError("bad table index"); - } - auto* size = popNonVoidExpression(); - auto* source = popNonVoidExpression(); - auto* dest = popNonVoidExpression(); - auto* ret = Builder(wasm).makeTableCopy(dest, source, size, Name(), Name()); - ret->destTable = getTableName(destTableIdx); - ret->sourceTable = getTableName(sourceTableIdx); - out = ret; - return true; -} - -bool WasmBinaryReader::maybeVisitTableInit(Expression*& out, uint32_t code) { - if (code != BinaryConsts::TableInit) { - return false; - } - auto* curr = allocator.alloc(); - curr->size = popNonVoidExpression(); - curr->offset = popNonVoidExpression(); - curr->dest = popNonVoidExpression(); - Index segIdx = getU32LEB(); - curr->segment = getElemName(segIdx); - Index tableIdx = getU32LEB(); - curr->table = getTableName(tableIdx); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitBinary(Expression*& out, uint8_t code) { - Binary* curr; -#define INT_TYPED_CODE(code) \ - { \ - case BinaryConsts::I32##code: \ - curr = allocator.alloc(); \ - curr->op = code##Int32; \ - break; \ - case BinaryConsts::I64##code: \ - curr = allocator.alloc(); \ - curr->op = code##Int64; \ - break; \ - } -#define FLOAT_TYPED_CODE(code) \ - { \ - case BinaryConsts::F32##code: \ - curr = allocator.alloc(); \ - curr->op = code##Float32; \ - break; \ - case BinaryConsts::F64##code: \ - curr = allocator.alloc(); \ - curr->op = code##Float64; \ - break; \ - } -#define TYPED_CODE(code) \ - { \ - INT_TYPED_CODE(code) \ - FLOAT_TYPED_CODE(code) \ - } - - switch (code) { - TYPED_CODE(Add); - TYPED_CODE(Sub); - TYPED_CODE(Mul); - INT_TYPED_CODE(DivS); - INT_TYPED_CODE(DivU); - INT_TYPED_CODE(RemS); - INT_TYPED_CODE(RemU); - INT_TYPED_CODE(And); - INT_TYPED_CODE(Or); - INT_TYPED_CODE(Xor); - INT_TYPED_CODE(Shl); - INT_TYPED_CODE(ShrU); - INT_TYPED_CODE(ShrS); - INT_TYPED_CODE(RotL); - INT_TYPED_CODE(RotR); - FLOAT_TYPED_CODE(Div); - FLOAT_TYPED_CODE(CopySign); - FLOAT_TYPED_CODE(Min); - FLOAT_TYPED_CODE(Max); - TYPED_CODE(Eq); - TYPED_CODE(Ne); - INT_TYPED_CODE(LtS); - INT_TYPED_CODE(LtU); - INT_TYPED_CODE(LeS); - INT_TYPED_CODE(LeU); - INT_TYPED_CODE(GtS); - INT_TYPED_CODE(GtU); - INT_TYPED_CODE(GeS); - INT_TYPED_CODE(GeU); - FLOAT_TYPED_CODE(Lt); - FLOAT_TYPED_CODE(Le); - FLOAT_TYPED_CODE(Gt); - FLOAT_TYPED_CODE(Ge); - default: - return false; - } - curr->right = popNonVoidExpression(); - curr->left = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -#undef TYPED_CODE -#undef INT_TYPED_CODE -#undef FLOAT_TYPED_CODE -} - -bool WasmBinaryReader::maybeVisitSIMDBinary(Expression*& out, uint32_t code) { - Binary* curr; - switch (code) { - case BinaryConsts::I8x16Eq: - curr = allocator.alloc(); - curr->op = EqVecI8x16; - break; - case BinaryConsts::I8x16Ne: - curr = allocator.alloc(); - curr->op = NeVecI8x16; - break; - case BinaryConsts::I8x16LtS: - curr = allocator.alloc(); - curr->op = LtSVecI8x16; - break; - case BinaryConsts::I8x16LtU: - curr = allocator.alloc(); - curr->op = LtUVecI8x16; - break; - case BinaryConsts::I8x16GtS: - curr = allocator.alloc(); - curr->op = GtSVecI8x16; - break; - case BinaryConsts::I8x16GtU: - curr = allocator.alloc(); - curr->op = GtUVecI8x16; - break; - case BinaryConsts::I8x16LeS: - curr = allocator.alloc(); - curr->op = LeSVecI8x16; - break; - case BinaryConsts::I8x16LeU: - curr = allocator.alloc(); - curr->op = LeUVecI8x16; - break; - case BinaryConsts::I8x16GeS: - curr = allocator.alloc(); - curr->op = GeSVecI8x16; - break; - case BinaryConsts::I8x16GeU: - curr = allocator.alloc(); - curr->op = GeUVecI8x16; - break; - case BinaryConsts::I16x8Eq: - curr = allocator.alloc(); - curr->op = EqVecI16x8; - break; - case BinaryConsts::I16x8Ne: - curr = allocator.alloc(); - curr->op = NeVecI16x8; - break; - case BinaryConsts::I16x8LtS: - curr = allocator.alloc(); - curr->op = LtSVecI16x8; - break; - case BinaryConsts::I16x8LtU: - curr = allocator.alloc(); - curr->op = LtUVecI16x8; - break; - case BinaryConsts::I16x8GtS: - curr = allocator.alloc(); - curr->op = GtSVecI16x8; - break; - case BinaryConsts::I16x8GtU: - curr = allocator.alloc(); - curr->op = GtUVecI16x8; - break; - case BinaryConsts::I16x8LeS: - curr = allocator.alloc(); - curr->op = LeSVecI16x8; - break; - case BinaryConsts::I16x8LeU: - curr = allocator.alloc(); - curr->op = LeUVecI16x8; - break; - case BinaryConsts::I16x8GeS: - curr = allocator.alloc(); - curr->op = GeSVecI16x8; - break; - case BinaryConsts::I16x8GeU: - curr = allocator.alloc(); - curr->op = GeUVecI16x8; - break; - case BinaryConsts::I32x4Eq: - curr = allocator.alloc(); - curr->op = EqVecI32x4; - break; - case BinaryConsts::I32x4Ne: - curr = allocator.alloc(); - curr->op = NeVecI32x4; - break; - case BinaryConsts::I32x4LtS: - curr = allocator.alloc(); - curr->op = LtSVecI32x4; - break; - case BinaryConsts::I32x4LtU: - curr = allocator.alloc(); - curr->op = LtUVecI32x4; - break; - case BinaryConsts::I32x4GtS: - curr = allocator.alloc(); - curr->op = GtSVecI32x4; - break; - case BinaryConsts::I32x4GtU: - curr = allocator.alloc(); - curr->op = GtUVecI32x4; - break; - case BinaryConsts::I32x4LeS: - curr = allocator.alloc(); - curr->op = LeSVecI32x4; - break; - case BinaryConsts::I32x4LeU: - curr = allocator.alloc(); - curr->op = LeUVecI32x4; - break; - case BinaryConsts::I32x4GeS: - curr = allocator.alloc(); - curr->op = GeSVecI32x4; - break; - case BinaryConsts::I32x4GeU: - curr = allocator.alloc(); - curr->op = GeUVecI32x4; - break; - case BinaryConsts::I64x2Eq: - curr = allocator.alloc(); - curr->op = EqVecI64x2; - break; - case BinaryConsts::I64x2Ne: - curr = allocator.alloc(); - curr->op = NeVecI64x2; - break; - case BinaryConsts::I64x2LtS: - curr = allocator.alloc(); - curr->op = LtSVecI64x2; - break; - case BinaryConsts::I64x2GtS: - curr = allocator.alloc(); - curr->op = GtSVecI64x2; - break; - case BinaryConsts::I64x2LeS: - curr = allocator.alloc(); - curr->op = LeSVecI64x2; - break; - case BinaryConsts::I64x2GeS: - curr = allocator.alloc(); - curr->op = GeSVecI64x2; - break; - case BinaryConsts::F16x8Eq: - curr = allocator.alloc(); - curr->op = EqVecF16x8; - break; - case BinaryConsts::F16x8Ne: - curr = allocator.alloc(); - curr->op = NeVecF16x8; - break; - case BinaryConsts::F16x8Lt: - curr = allocator.alloc(); - curr->op = LtVecF16x8; - break; - case BinaryConsts::F16x8Gt: - curr = allocator.alloc(); - curr->op = GtVecF16x8; - break; - case BinaryConsts::F16x8Le: - curr = allocator.alloc(); - curr->op = LeVecF16x8; - break; - case BinaryConsts::F16x8Ge: - curr = allocator.alloc(); - curr->op = GeVecF16x8; - break; - case BinaryConsts::F32x4Eq: - curr = allocator.alloc(); - curr->op = EqVecF32x4; - break; - case BinaryConsts::F32x4Ne: - curr = allocator.alloc(); - curr->op = NeVecF32x4; - break; - case BinaryConsts::F32x4Lt: - curr = allocator.alloc(); - curr->op = LtVecF32x4; - break; - case BinaryConsts::F32x4Gt: - curr = allocator.alloc(); - curr->op = GtVecF32x4; - break; - case BinaryConsts::F32x4Le: - curr = allocator.alloc(); - curr->op = LeVecF32x4; - break; - case BinaryConsts::F32x4Ge: - curr = allocator.alloc(); - curr->op = GeVecF32x4; - break; - case BinaryConsts::F64x2Eq: - curr = allocator.alloc(); - curr->op = EqVecF64x2; - break; - case BinaryConsts::F64x2Ne: - curr = allocator.alloc(); - curr->op = NeVecF64x2; - break; - case BinaryConsts::F64x2Lt: - curr = allocator.alloc(); - curr->op = LtVecF64x2; - break; - case BinaryConsts::F64x2Gt: - curr = allocator.alloc(); - curr->op = GtVecF64x2; - break; - case BinaryConsts::F64x2Le: - curr = allocator.alloc(); - curr->op = LeVecF64x2; - break; - case BinaryConsts::F64x2Ge: - curr = allocator.alloc(); - curr->op = GeVecF64x2; - break; - case BinaryConsts::V128And: - curr = allocator.alloc(); - curr->op = AndVec128; - break; - case BinaryConsts::V128Or: - curr = allocator.alloc(); - curr->op = OrVec128; - break; - case BinaryConsts::V128Xor: - curr = allocator.alloc(); - curr->op = XorVec128; - break; - case BinaryConsts::V128Andnot: - curr = allocator.alloc(); - curr->op = AndNotVec128; - break; - case BinaryConsts::I8x16Add: - curr = allocator.alloc(); - curr->op = AddVecI8x16; - break; - case BinaryConsts::I8x16AddSatS: - curr = allocator.alloc(); - curr->op = AddSatSVecI8x16; - break; - case BinaryConsts::I8x16AddSatU: - curr = allocator.alloc(); - curr->op = AddSatUVecI8x16; - break; - case BinaryConsts::I8x16Sub: - curr = allocator.alloc(); - curr->op = SubVecI8x16; - break; - case BinaryConsts::I8x16SubSatS: - curr = allocator.alloc(); - curr->op = SubSatSVecI8x16; - break; - case BinaryConsts::I8x16SubSatU: - curr = allocator.alloc(); - curr->op = SubSatUVecI8x16; - break; - case BinaryConsts::I8x16MinS: - curr = allocator.alloc(); - curr->op = MinSVecI8x16; - break; - case BinaryConsts::I8x16MinU: - curr = allocator.alloc(); - curr->op = MinUVecI8x16; - break; - case BinaryConsts::I8x16MaxS: - curr = allocator.alloc(); - curr->op = MaxSVecI8x16; - break; - case BinaryConsts::I8x16MaxU: - curr = allocator.alloc(); - curr->op = MaxUVecI8x16; - break; - case BinaryConsts::I8x16AvgrU: - curr = allocator.alloc(); - curr->op = AvgrUVecI8x16; - break; - case BinaryConsts::I16x8Add: - curr = allocator.alloc(); - curr->op = AddVecI16x8; - break; - case BinaryConsts::I16x8AddSatS: - curr = allocator.alloc(); - curr->op = AddSatSVecI16x8; - break; - case BinaryConsts::I16x8AddSatU: - curr = allocator.alloc(); - curr->op = AddSatUVecI16x8; - break; - case BinaryConsts::I16x8Sub: - curr = allocator.alloc(); - curr->op = SubVecI16x8; - break; - case BinaryConsts::I16x8SubSatS: - curr = allocator.alloc(); - curr->op = SubSatSVecI16x8; - break; - case BinaryConsts::I16x8SubSatU: - curr = allocator.alloc(); - curr->op = SubSatUVecI16x8; - break; - case BinaryConsts::I16x8Mul: - curr = allocator.alloc(); - curr->op = MulVecI16x8; - break; - case BinaryConsts::I16x8MinS: - curr = allocator.alloc(); - curr->op = MinSVecI16x8; - break; - case BinaryConsts::I16x8MinU: - curr = allocator.alloc(); - curr->op = MinUVecI16x8; - break; - case BinaryConsts::I16x8MaxS: - curr = allocator.alloc(); - curr->op = MaxSVecI16x8; - break; - case BinaryConsts::I16x8MaxU: - curr = allocator.alloc(); - curr->op = MaxUVecI16x8; - break; - case BinaryConsts::I16x8AvgrU: - curr = allocator.alloc(); - curr->op = AvgrUVecI16x8; - break; - case BinaryConsts::I16x8Q15MulrSatS: - curr = allocator.alloc(); - curr->op = Q15MulrSatSVecI16x8; - break; - case BinaryConsts::I16x8ExtmulLowI8x16S: - curr = allocator.alloc(); - curr->op = ExtMulLowSVecI16x8; - break; - case BinaryConsts::I16x8ExtmulHighI8x16S: - curr = allocator.alloc(); - curr->op = ExtMulHighSVecI16x8; - break; - case BinaryConsts::I16x8ExtmulLowI8x16U: - curr = allocator.alloc(); - curr->op = ExtMulLowUVecI16x8; - break; - case BinaryConsts::I16x8ExtmulHighI8x16U: - curr = allocator.alloc(); - curr->op = ExtMulHighUVecI16x8; - break; - case BinaryConsts::I32x4Add: - curr = allocator.alloc(); - curr->op = AddVecI32x4; - break; - case BinaryConsts::I32x4Sub: - curr = allocator.alloc(); - curr->op = SubVecI32x4; - break; - case BinaryConsts::I32x4Mul: - curr = allocator.alloc(); - curr->op = MulVecI32x4; - break; - case BinaryConsts::I32x4MinS: - curr = allocator.alloc(); - curr->op = MinSVecI32x4; - break; - case BinaryConsts::I32x4MinU: - curr = allocator.alloc(); - curr->op = MinUVecI32x4; - break; - case BinaryConsts::I32x4MaxS: - curr = allocator.alloc(); - curr->op = MaxSVecI32x4; - break; - case BinaryConsts::I32x4MaxU: - curr = allocator.alloc(); - curr->op = MaxUVecI32x4; - break; - case BinaryConsts::I32x4DotI16x8S: - curr = allocator.alloc(); - curr->op = DotSVecI16x8ToVecI32x4; - break; - case BinaryConsts::I32x4ExtmulLowI16x8S: - curr = allocator.alloc(); - curr->op = ExtMulLowSVecI32x4; - break; - case BinaryConsts::I32x4ExtmulHighI16x8S: - curr = allocator.alloc(); - curr->op = ExtMulHighSVecI32x4; - break; - case BinaryConsts::I32x4ExtmulLowI16x8U: - curr = allocator.alloc(); - curr->op = ExtMulLowUVecI32x4; - break; - case BinaryConsts::I32x4ExtmulHighI16x8U: - curr = allocator.alloc(); - curr->op = ExtMulHighUVecI32x4; - break; - case BinaryConsts::I64x2Add: - curr = allocator.alloc(); - curr->op = AddVecI64x2; - break; - case BinaryConsts::I64x2Sub: - curr = allocator.alloc(); - curr->op = SubVecI64x2; - break; - case BinaryConsts::I64x2Mul: - curr = allocator.alloc(); - curr->op = MulVecI64x2; - break; - case BinaryConsts::I64x2ExtmulLowI32x4S: - curr = allocator.alloc(); - curr->op = ExtMulLowSVecI64x2; - break; - case BinaryConsts::I64x2ExtmulHighI32x4S: - curr = allocator.alloc(); - curr->op = ExtMulHighSVecI64x2; - break; - case BinaryConsts::I64x2ExtmulLowI32x4U: - curr = allocator.alloc(); - curr->op = ExtMulLowUVecI64x2; - break; - case BinaryConsts::I64x2ExtmulHighI32x4U: - curr = allocator.alloc(); - curr->op = ExtMulHighUVecI64x2; - break; - case BinaryConsts::F16x8Add: - curr = allocator.alloc(); - curr->op = AddVecF16x8; - break; - case BinaryConsts::F16x8Sub: - curr = allocator.alloc(); - curr->op = SubVecF16x8; - break; - case BinaryConsts::F16x8Mul: - curr = allocator.alloc(); - curr->op = MulVecF16x8; - break; - case BinaryConsts::F16x8Div: - curr = allocator.alloc(); - curr->op = DivVecF16x8; - break; - case BinaryConsts::F16x8Min: - curr = allocator.alloc(); - curr->op = MinVecF16x8; - break; - case BinaryConsts::F16x8Max: - curr = allocator.alloc(); - curr->op = MaxVecF16x8; - break; - case BinaryConsts::F16x8Pmin: - curr = allocator.alloc(); - curr->op = PMinVecF16x8; - break; - case BinaryConsts::F16x8Pmax: - curr = allocator.alloc(); - curr->op = PMaxVecF16x8; - break; - case BinaryConsts::F32x4Add: - curr = allocator.alloc(); - curr->op = AddVecF32x4; - break; - case BinaryConsts::F32x4Sub: - curr = allocator.alloc(); - curr->op = SubVecF32x4; - break; - case BinaryConsts::F32x4Mul: - curr = allocator.alloc(); - curr->op = MulVecF32x4; - break; - case BinaryConsts::F32x4Div: - curr = allocator.alloc(); - curr->op = DivVecF32x4; - break; - case BinaryConsts::F32x4Min: - curr = allocator.alloc(); - curr->op = MinVecF32x4; - break; - case BinaryConsts::F32x4Max: - curr = allocator.alloc(); - curr->op = MaxVecF32x4; - break; - case BinaryConsts::F32x4Pmin: - curr = allocator.alloc(); - curr->op = PMinVecF32x4; - break; - case BinaryConsts::F32x4Pmax: - curr = allocator.alloc(); - curr->op = PMaxVecF32x4; - break; - case BinaryConsts::F64x2Add: - curr = allocator.alloc(); - curr->op = AddVecF64x2; - break; - case BinaryConsts::F64x2Sub: - curr = allocator.alloc(); - curr->op = SubVecF64x2; - break; - case BinaryConsts::F64x2Mul: - curr = allocator.alloc(); - curr->op = MulVecF64x2; - break; - case BinaryConsts::F64x2Div: - curr = allocator.alloc(); - curr->op = DivVecF64x2; - break; - case BinaryConsts::F64x2Min: - curr = allocator.alloc(); - curr->op = MinVecF64x2; - break; - case BinaryConsts::F64x2Max: - curr = allocator.alloc(); - curr->op = MaxVecF64x2; - break; - case BinaryConsts::F64x2Pmin: - curr = allocator.alloc(); - curr->op = PMinVecF64x2; - break; - case BinaryConsts::F64x2Pmax: - curr = allocator.alloc(); - curr->op = PMaxVecF64x2; - break; - case BinaryConsts::I8x16NarrowI16x8S: - curr = allocator.alloc(); - curr->op = NarrowSVecI16x8ToVecI8x16; - break; - case BinaryConsts::I8x16NarrowI16x8U: - curr = allocator.alloc(); - curr->op = NarrowUVecI16x8ToVecI8x16; - break; - case BinaryConsts::I16x8NarrowI32x4S: - curr = allocator.alloc(); - curr->op = NarrowSVecI32x4ToVecI16x8; - break; - case BinaryConsts::I16x8NarrowI32x4U: - curr = allocator.alloc(); - curr->op = NarrowUVecI32x4ToVecI16x8; - break; - case BinaryConsts::I8x16Swizzle: - curr = allocator.alloc(); - curr->op = SwizzleVecI8x16; - break; - case BinaryConsts::I8x16RelaxedSwizzle: - curr = allocator.alloc(); - curr->op = RelaxedSwizzleVecI8x16; - break; - case BinaryConsts::F32x4RelaxedMin: - curr = allocator.alloc(); - curr->op = RelaxedMinVecF32x4; - break; - case BinaryConsts::F32x4RelaxedMax: - curr = allocator.alloc(); - curr->op = RelaxedMaxVecF32x4; - break; - case BinaryConsts::F64x2RelaxedMin: - curr = allocator.alloc(); - curr->op = RelaxedMinVecF64x2; - break; - case BinaryConsts::F64x2RelaxedMax: - curr = allocator.alloc(); - curr->op = RelaxedMaxVecF64x2; - break; - case BinaryConsts::I16x8RelaxedQ15MulrS: - curr = allocator.alloc(); - curr->op = RelaxedQ15MulrSVecI16x8; - break; - case BinaryConsts::I16x8DotI8x16I7x16S: - curr = allocator.alloc(); - curr->op = DotI8x16I7x16SToVecI16x8; - break; - default: - return false; - } - curr->right = popNonVoidExpression(); - curr->left = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} -bool WasmBinaryReader::maybeVisitSIMDUnary(Expression*& out, uint32_t code) { - Unary* curr; - switch (code) { - case BinaryConsts::I8x16Splat: - curr = allocator.alloc(); - curr->op = SplatVecI8x16; - break; - case BinaryConsts::I16x8Splat: - curr = allocator.alloc(); - curr->op = SplatVecI16x8; - break; - case BinaryConsts::I32x4Splat: - curr = allocator.alloc(); - curr->op = SplatVecI32x4; - break; - case BinaryConsts::I64x2Splat: - curr = allocator.alloc(); - curr->op = SplatVecI64x2; - break; - case BinaryConsts::F16x8Splat: - curr = allocator.alloc(); - curr->op = SplatVecF16x8; - break; - case BinaryConsts::F32x4Splat: - curr = allocator.alloc(); - curr->op = SplatVecF32x4; - break; - case BinaryConsts::F64x2Splat: - curr = allocator.alloc(); - curr->op = SplatVecF64x2; - break; - case BinaryConsts::V128Not: - curr = allocator.alloc(); - curr->op = NotVec128; - break; - case BinaryConsts::V128AnyTrue: - curr = allocator.alloc(); - curr->op = AnyTrueVec128; - break; - case BinaryConsts::I8x16Popcnt: - curr = allocator.alloc(); - curr->op = PopcntVecI8x16; - break; - case BinaryConsts::I8x16Abs: - curr = allocator.alloc(); - curr->op = AbsVecI8x16; - break; - case BinaryConsts::I8x16Neg: - curr = allocator.alloc(); - curr->op = NegVecI8x16; - break; - case BinaryConsts::I8x16AllTrue: - curr = allocator.alloc(); - curr->op = AllTrueVecI8x16; - break; - case BinaryConsts::I8x16Bitmask: - curr = allocator.alloc(); - curr->op = BitmaskVecI8x16; - break; - case BinaryConsts::I16x8Abs: - curr = allocator.alloc(); - curr->op = AbsVecI16x8; - break; - case BinaryConsts::I16x8Neg: - curr = allocator.alloc(); - curr->op = NegVecI16x8; - break; - case BinaryConsts::I16x8AllTrue: - curr = allocator.alloc(); - curr->op = AllTrueVecI16x8; - break; - case BinaryConsts::I16x8Bitmask: - curr = allocator.alloc(); - curr->op = BitmaskVecI16x8; - break; - case BinaryConsts::I32x4Abs: - curr = allocator.alloc(); - curr->op = AbsVecI32x4; - break; - case BinaryConsts::I32x4Neg: - curr = allocator.alloc(); - curr->op = NegVecI32x4; - break; - case BinaryConsts::I32x4AllTrue: - curr = allocator.alloc(); - curr->op = AllTrueVecI32x4; - break; - case BinaryConsts::I32x4Bitmask: - curr = allocator.alloc(); - curr->op = BitmaskVecI32x4; - break; - case BinaryConsts::I64x2Abs: - curr = allocator.alloc(); - curr->op = AbsVecI64x2; - break; - case BinaryConsts::I64x2Neg: - curr = allocator.alloc(); - curr->op = NegVecI64x2; - break; - case BinaryConsts::I64x2AllTrue: - curr = allocator.alloc(); - curr->op = AllTrueVecI64x2; - break; - case BinaryConsts::I64x2Bitmask: - curr = allocator.alloc(); - curr->op = BitmaskVecI64x2; - break; - case BinaryConsts::F16x8Abs: - curr = allocator.alloc(); - curr->op = AbsVecF16x8; - break; - case BinaryConsts::F16x8Neg: - curr = allocator.alloc(); - curr->op = NegVecF16x8; - break; - case BinaryConsts::F16x8Sqrt: - curr = allocator.alloc(); - curr->op = SqrtVecF16x8; - break; - case BinaryConsts::F16x8Ceil: - curr = allocator.alloc(); - curr->op = CeilVecF16x8; - break; - case BinaryConsts::F16x8Floor: - curr = allocator.alloc(); - curr->op = FloorVecF16x8; - break; - case BinaryConsts::F16x8Trunc: - curr = allocator.alloc(); - curr->op = TruncVecF16x8; - break; - case BinaryConsts::F16x8Nearest: - curr = allocator.alloc(); - curr->op = NearestVecF16x8; - break; - case BinaryConsts::F32x4Abs: - curr = allocator.alloc(); - curr->op = AbsVecF32x4; - break; - case BinaryConsts::F32x4Neg: - curr = allocator.alloc(); - curr->op = NegVecF32x4; - break; - case BinaryConsts::F32x4Sqrt: - curr = allocator.alloc(); - curr->op = SqrtVecF32x4; - break; - case BinaryConsts::F32x4Ceil: - curr = allocator.alloc(); - curr->op = CeilVecF32x4; - break; - case BinaryConsts::F32x4Floor: - curr = allocator.alloc(); - curr->op = FloorVecF32x4; - break; - case BinaryConsts::F32x4Trunc: - curr = allocator.alloc(); - curr->op = TruncVecF32x4; - break; - case BinaryConsts::F32x4Nearest: - curr = allocator.alloc(); - curr->op = NearestVecF32x4; - break; - case BinaryConsts::F64x2Abs: - curr = allocator.alloc(); - curr->op = AbsVecF64x2; - break; - case BinaryConsts::F64x2Neg: - curr = allocator.alloc(); - curr->op = NegVecF64x2; - break; - case BinaryConsts::F64x2Sqrt: - curr = allocator.alloc(); - curr->op = SqrtVecF64x2; - break; - case BinaryConsts::F64x2Ceil: - curr = allocator.alloc(); - curr->op = CeilVecF64x2; - break; - case BinaryConsts::F64x2Floor: - curr = allocator.alloc(); - curr->op = FloorVecF64x2; - break; - case BinaryConsts::F64x2Trunc: - curr = allocator.alloc(); - curr->op = TruncVecF64x2; - break; - case BinaryConsts::F64x2Nearest: - curr = allocator.alloc(); - curr->op = NearestVecF64x2; - break; - case BinaryConsts::I16x8ExtaddPairwiseI8x16S: - curr = allocator.alloc(); - curr->op = ExtAddPairwiseSVecI8x16ToI16x8; - break; - case BinaryConsts::I16x8ExtaddPairwiseI8x16U: - curr = allocator.alloc(); - curr->op = ExtAddPairwiseUVecI8x16ToI16x8; - break; - case BinaryConsts::I32x4ExtaddPairwiseI16x8S: - curr = allocator.alloc(); - curr->op = ExtAddPairwiseSVecI16x8ToI32x4; - break; - case BinaryConsts::I32x4ExtaddPairwiseI16x8U: - curr = allocator.alloc(); - curr->op = ExtAddPairwiseUVecI16x8ToI32x4; - break; - case BinaryConsts::I32x4TruncSatF32x4S: - curr = allocator.alloc(); - curr->op = TruncSatSVecF32x4ToVecI32x4; - break; - case BinaryConsts::I32x4TruncSatF32x4U: - curr = allocator.alloc(); - curr->op = TruncSatUVecF32x4ToVecI32x4; - break; - case BinaryConsts::F32x4ConvertI32x4S: - curr = allocator.alloc(); - curr->op = ConvertSVecI32x4ToVecF32x4; - break; - case BinaryConsts::F32x4ConvertI32x4U: - curr = allocator.alloc(); - curr->op = ConvertUVecI32x4ToVecF32x4; - break; - case BinaryConsts::I16x8ExtendLowI8x16S: - curr = allocator.alloc(); - curr->op = ExtendLowSVecI8x16ToVecI16x8; - break; - case BinaryConsts::I16x8ExtendHighI8x16S: - curr = allocator.alloc(); - curr->op = ExtendHighSVecI8x16ToVecI16x8; - break; - case BinaryConsts::I16x8ExtendLowI8x16U: - curr = allocator.alloc(); - curr->op = ExtendLowUVecI8x16ToVecI16x8; - break; - case BinaryConsts::I16x8ExtendHighI8x16U: - curr = allocator.alloc(); - curr->op = ExtendHighUVecI8x16ToVecI16x8; - break; - case BinaryConsts::I32x4ExtendLowI16x8S: - curr = allocator.alloc(); - curr->op = ExtendLowSVecI16x8ToVecI32x4; - break; - case BinaryConsts::I32x4ExtendHighI16x8S: - curr = allocator.alloc(); - curr->op = ExtendHighSVecI16x8ToVecI32x4; - break; - case BinaryConsts::I32x4ExtendLowI16x8U: - curr = allocator.alloc(); - curr->op = ExtendLowUVecI16x8ToVecI32x4; - break; - case BinaryConsts::I32x4ExtendHighI16x8U: - curr = allocator.alloc(); - curr->op = ExtendHighUVecI16x8ToVecI32x4; - break; - case BinaryConsts::I64x2ExtendLowI32x4S: - curr = allocator.alloc(); - curr->op = ExtendLowSVecI32x4ToVecI64x2; - break; - case BinaryConsts::I64x2ExtendHighI32x4S: - curr = allocator.alloc(); - curr->op = ExtendHighSVecI32x4ToVecI64x2; - break; - case BinaryConsts::I64x2ExtendLowI32x4U: - curr = allocator.alloc(); - curr->op = ExtendLowUVecI32x4ToVecI64x2; - break; - case BinaryConsts::I64x2ExtendHighI32x4U: - curr = allocator.alloc(); - curr->op = ExtendHighUVecI32x4ToVecI64x2; - break; - case BinaryConsts::F64x2ConvertLowI32x4S: - curr = allocator.alloc(); - curr->op = ConvertLowSVecI32x4ToVecF64x2; - break; - case BinaryConsts::F64x2ConvertLowI32x4U: - curr = allocator.alloc(); - curr->op = ConvertLowUVecI32x4ToVecF64x2; - break; - case BinaryConsts::I32x4TruncSatF64x2SZero: - curr = allocator.alloc(); - curr->op = TruncSatZeroSVecF64x2ToVecI32x4; - break; - case BinaryConsts::I32x4TruncSatF64x2UZero: - curr = allocator.alloc(); - curr->op = TruncSatZeroUVecF64x2ToVecI32x4; - break; - case BinaryConsts::F32x4DemoteF64x2Zero: - curr = allocator.alloc(); - curr->op = DemoteZeroVecF64x2ToVecF32x4; - break; - case BinaryConsts::F64x2PromoteLowF32x4: - curr = allocator.alloc(); - curr->op = PromoteLowVecF32x4ToVecF64x2; - break; - case BinaryConsts::I32x4RelaxedTruncF32x4S: - curr = allocator.alloc(); - curr->op = RelaxedTruncSVecF32x4ToVecI32x4; - break; - case BinaryConsts::I32x4RelaxedTruncF32x4U: - curr = allocator.alloc(); - curr->op = RelaxedTruncUVecF32x4ToVecI32x4; - break; - case BinaryConsts::I32x4RelaxedTruncF64x2SZero: - curr = allocator.alloc(); - curr->op = RelaxedTruncZeroSVecF64x2ToVecI32x4; - break; - case BinaryConsts::I32x4RelaxedTruncF64x2UZero: - curr = allocator.alloc(); - curr->op = RelaxedTruncZeroUVecF64x2ToVecI32x4; - break; - case BinaryConsts::I16x8TruncSatF16x8S: - curr = allocator.alloc(); - curr->op = TruncSatSVecF16x8ToVecI16x8; - break; - case BinaryConsts::I16x8TruncSatF16x8U: - curr = allocator.alloc(); - curr->op = TruncSatUVecF16x8ToVecI16x8; - break; - case BinaryConsts::F16x8ConvertI16x8S: - curr = allocator.alloc(); - curr->op = ConvertSVecI16x8ToVecF16x8; - break; - case BinaryConsts::F16x8ConvertI16x8U: - curr = allocator.alloc(); - curr->op = ConvertUVecI16x8ToVecF16x8; - break; - default: - return false; - } - curr->value = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDConst(Expression*& out, uint32_t code) { - if (code != BinaryConsts::V128Const) { - return false; - } - auto* curr = allocator.alloc(); - curr->value = getVec128Literal(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDStore(Expression*& out, uint32_t code) { - if (code != BinaryConsts::V128Store) { - return false; - } - auto* curr = allocator.alloc(); - curr->bytes = 16; - curr->valueType = Type::v128; - Index memIdx = readMemoryAccess(curr->align, curr->offset); - curr->memory = getMemoryName(memIdx); - curr->isAtomic = false; - curr->value = popNonVoidExpression(); - curr->ptr = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDExtract(Expression*& out, uint32_t code) { - SIMDExtract* curr; - switch (code) { - case BinaryConsts::I8x16ExtractLaneS: - curr = allocator.alloc(); - curr->op = ExtractLaneSVecI8x16; - curr->index = getLaneIndex(16); - break; - case BinaryConsts::I8x16ExtractLaneU: - curr = allocator.alloc(); - curr->op = ExtractLaneUVecI8x16; - curr->index = getLaneIndex(16); - break; - case BinaryConsts::I16x8ExtractLaneS: - curr = allocator.alloc(); - curr->op = ExtractLaneSVecI16x8; - curr->index = getLaneIndex(8); - break; - case BinaryConsts::I16x8ExtractLaneU: - curr = allocator.alloc(); - curr->op = ExtractLaneUVecI16x8; - curr->index = getLaneIndex(8); - break; - case BinaryConsts::I32x4ExtractLane: - curr = allocator.alloc(); - curr->op = ExtractLaneVecI32x4; - curr->index = getLaneIndex(4); - break; - case BinaryConsts::I64x2ExtractLane: - curr = allocator.alloc(); - curr->op = ExtractLaneVecI64x2; - curr->index = getLaneIndex(2); - break; - case BinaryConsts::F16x8ExtractLane: - curr = allocator.alloc(); - curr->op = ExtractLaneVecF16x8; - curr->index = getLaneIndex(8); - break; - case BinaryConsts::F32x4ExtractLane: - curr = allocator.alloc(); - curr->op = ExtractLaneVecF32x4; - curr->index = getLaneIndex(4); - break; - case BinaryConsts::F64x2ExtractLane: - curr = allocator.alloc(); - curr->op = ExtractLaneVecF64x2; - curr->index = getLaneIndex(2); - break; - default: - return false; - } - curr->vec = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDReplace(Expression*& out, uint32_t code) { - SIMDReplace* curr; - switch (code) { - case BinaryConsts::I8x16ReplaceLane: - curr = allocator.alloc(); - curr->op = ReplaceLaneVecI8x16; - curr->index = getLaneIndex(16); - break; - case BinaryConsts::I16x8ReplaceLane: - curr = allocator.alloc(); - curr->op = ReplaceLaneVecI16x8; - curr->index = getLaneIndex(8); - break; - case BinaryConsts::I32x4ReplaceLane: - curr = allocator.alloc(); - curr->op = ReplaceLaneVecI32x4; - curr->index = getLaneIndex(4); - break; - case BinaryConsts::I64x2ReplaceLane: - curr = allocator.alloc(); - curr->op = ReplaceLaneVecI64x2; - curr->index = getLaneIndex(2); - break; - case BinaryConsts::F16x8ReplaceLane: - curr = allocator.alloc(); - curr->op = ReplaceLaneVecF16x8; - curr->index = getLaneIndex(8); - break; - case BinaryConsts::F32x4ReplaceLane: - curr = allocator.alloc(); - curr->op = ReplaceLaneVecF32x4; - curr->index = getLaneIndex(4); - break; - case BinaryConsts::F64x2ReplaceLane: - curr = allocator.alloc(); - curr->op = ReplaceLaneVecF64x2; - curr->index = getLaneIndex(2); - break; - default: - return false; - } - curr->value = popNonVoidExpression(); - curr->vec = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDShuffle(Expression*& out, uint32_t code) { - if (code != BinaryConsts::I8x16Shuffle) { - return false; - } - auto* curr = allocator.alloc(); - for (auto i = 0; i < 16; ++i) { - curr->mask[i] = getLaneIndex(32); - } - curr->right = popNonVoidExpression(); - curr->left = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDTernary(Expression*& out, uint32_t code) { - SIMDTernary* curr; - switch (code) { - case BinaryConsts::V128Bitselect: - curr = allocator.alloc(); - curr->op = Bitselect; - break; - case BinaryConsts::I8x16Laneselect: - curr = allocator.alloc(); - curr->op = LaneselectI8x16; - break; - case BinaryConsts::I16x8Laneselect: - curr = allocator.alloc(); - curr->op = LaneselectI16x8; - break; - case BinaryConsts::I32x4Laneselect: - curr = allocator.alloc(); - curr->op = LaneselectI32x4; - break; - case BinaryConsts::I64x2Laneselect: - curr = allocator.alloc(); - curr->op = LaneselectI64x2; - break; - case BinaryConsts::F16x8RelaxedMadd: - curr = allocator.alloc(); - curr->op = RelaxedMaddVecF16x8; - break; - case BinaryConsts::F16x8RelaxedNmadd: - curr = allocator.alloc(); - curr->op = RelaxedNmaddVecF16x8; - break; - case BinaryConsts::F32x4RelaxedMadd: - curr = allocator.alloc(); - curr->op = RelaxedMaddVecF32x4; - break; - case BinaryConsts::F32x4RelaxedNmadd: - curr = allocator.alloc(); - curr->op = RelaxedNmaddVecF32x4; - break; - case BinaryConsts::F64x2RelaxedMadd: - curr = allocator.alloc(); - curr->op = RelaxedMaddVecF64x2; - break; - case BinaryConsts::F64x2RelaxedNmadd: - curr = allocator.alloc(); - curr->op = RelaxedNmaddVecF64x2; - break; - case BinaryConsts::I32x4DotI8x16I7x16AddS: - curr = allocator.alloc(); - curr->op = DotI8x16I7x16AddSToVecI32x4; - break; - default: - return false; - } - curr->c = popNonVoidExpression(); - curr->b = popNonVoidExpression(); - curr->a = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDShift(Expression*& out, uint32_t code) { - SIMDShift* curr; - switch (code) { - case BinaryConsts::I8x16Shl: - curr = allocator.alloc(); - curr->op = ShlVecI8x16; - break; - case BinaryConsts::I8x16ShrS: - curr = allocator.alloc(); - curr->op = ShrSVecI8x16; - break; - case BinaryConsts::I8x16ShrU: - curr = allocator.alloc(); - curr->op = ShrUVecI8x16; - break; - case BinaryConsts::I16x8Shl: - curr = allocator.alloc(); - curr->op = ShlVecI16x8; - break; - case BinaryConsts::I16x8ShrS: - curr = allocator.alloc(); - curr->op = ShrSVecI16x8; - break; - case BinaryConsts::I16x8ShrU: - curr = allocator.alloc(); - curr->op = ShrUVecI16x8; - break; - case BinaryConsts::I32x4Shl: - curr = allocator.alloc(); - curr->op = ShlVecI32x4; - break; - case BinaryConsts::I32x4ShrS: - curr = allocator.alloc(); - curr->op = ShrSVecI32x4; - break; - case BinaryConsts::I32x4ShrU: - curr = allocator.alloc(); - curr->op = ShrUVecI32x4; - break; - case BinaryConsts::I64x2Shl: - curr = allocator.alloc(); - curr->op = ShlVecI64x2; - break; - case BinaryConsts::I64x2ShrS: - curr = allocator.alloc(); - curr->op = ShrSVecI64x2; - break; - case BinaryConsts::I64x2ShrU: - curr = allocator.alloc(); - curr->op = ShrUVecI64x2; - break; - default: - return false; - } - curr->shift = popNonVoidExpression(); - curr->vec = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDLoad(Expression*& out, uint32_t code) { - if (code == BinaryConsts::V128Load) { - auto* curr = allocator.alloc(); - curr->type = Type::v128; - curr->bytes = 16; - Index memIdx = readMemoryAccess(curr->align, curr->offset); - curr->memory = getMemoryName(memIdx); - curr->isAtomic = false; - curr->ptr = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; - } - SIMDLoad* curr; - switch (code) { - case BinaryConsts::V128Load8Splat: - curr = allocator.alloc(); - curr->op = Load8SplatVec128; - break; - case BinaryConsts::V128Load16Splat: - curr = allocator.alloc(); - curr->op = Load16SplatVec128; - break; - case BinaryConsts::V128Load32Splat: - curr = allocator.alloc(); - curr->op = Load32SplatVec128; - break; - case BinaryConsts::V128Load64Splat: - curr = allocator.alloc(); - curr->op = Load64SplatVec128; - break; - case BinaryConsts::V128Load8x8S: - curr = allocator.alloc(); - curr->op = Load8x8SVec128; - break; - case BinaryConsts::V128Load8x8U: - curr = allocator.alloc(); - curr->op = Load8x8UVec128; - break; - case BinaryConsts::V128Load16x4S: - curr = allocator.alloc(); - curr->op = Load16x4SVec128; - break; - case BinaryConsts::V128Load16x4U: - curr = allocator.alloc(); - curr->op = Load16x4UVec128; - break; - case BinaryConsts::V128Load32x2S: - curr = allocator.alloc(); - curr->op = Load32x2SVec128; - break; - case BinaryConsts::V128Load32x2U: - curr = allocator.alloc(); - curr->op = Load32x2UVec128; - break; - case BinaryConsts::V128Load32Zero: - curr = allocator.alloc(); - curr->op = Load32ZeroVec128; - break; - case BinaryConsts::V128Load64Zero: - curr = allocator.alloc(); - curr->op = Load64ZeroVec128; - break; - default: - return false; - } - Index memIdx = readMemoryAccess(curr->align, curr->offset); - curr->memory = getMemoryName(memIdx); - curr->ptr = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitSIMDLoadStoreLane(Expression*& out, - uint32_t code) { - SIMDLoadStoreLaneOp op; - size_t lanes; - switch (code) { - case BinaryConsts::V128Load8Lane: - op = Load8LaneVec128; - lanes = 16; - break; - case BinaryConsts::V128Load16Lane: - op = Load16LaneVec128; - lanes = 8; - break; - case BinaryConsts::V128Load32Lane: - op = Load32LaneVec128; - lanes = 4; - break; - case BinaryConsts::V128Load64Lane: - op = Load64LaneVec128; - lanes = 2; - break; - case BinaryConsts::V128Store8Lane: - op = Store8LaneVec128; - lanes = 16; - break; - case BinaryConsts::V128Store16Lane: - op = Store16LaneVec128; - lanes = 8; - break; - case BinaryConsts::V128Store32Lane: - op = Store32LaneVec128; - lanes = 4; - break; - case BinaryConsts::V128Store64Lane: - op = Store64LaneVec128; - lanes = 2; - break; - default: - return false; - } - auto* curr = allocator.alloc(); - curr->op = op; - Index memIdx = readMemoryAccess(curr->align, curr->offset); - curr->memory = getMemoryName(memIdx); - curr->index = getLaneIndex(lanes); - curr->vec = popNonVoidExpression(); - curr->ptr = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -void WasmBinaryReader::visitSelect(Select* curr, uint8_t code) { - Type annotated = Type::none; - if (code == BinaryConsts::SelectWithType) { - size_t numTypes = getU32LEB(); - std::vector types; - for (size_t i = 0; i < numTypes; i++) { - auto t = getType(); - if (!t.isConcrete()) { - throwError("bad select type"); - } - types.push_back(t); - } - annotated = Type(types); - } - curr->condition = popNonVoidExpression(); - curr->ifFalse = popNonVoidExpression(); - curr->ifTrue = popNonVoidExpression(); - curr->finalize(); - if (code == BinaryConsts::SelectWithType && - !Type::isSubType(curr->type, annotated)) { - throwError("select type does not match annotation"); - } -} - -void WasmBinaryReader::visitReturn(Return* curr) { - requireFunctionContext("return"); - Type type = currFunction->getResults(); - if (type.isConcrete()) { - curr->value = popTypedExpression(type); - } - curr->finalize(); -} - -void WasmBinaryReader::visitMemorySize(MemorySize* curr) { - Index index = getU32LEB(); - if (getMemory(index)->is64()) { - curr->type = Type::i64; - } - curr->memory = getMemoryName(index); - curr->finalize(); -} - -void WasmBinaryReader::visitMemoryGrow(MemoryGrow* curr) { - curr->delta = popNonVoidExpression(); - Index index = getU32LEB(); - if (getMemory(index)->is64()) { - curr->type = Type::i64; - } - curr->memory = getMemoryName(index); -} - -void WasmBinaryReader::visitNop(Nop* curr) {} - -void WasmBinaryReader::visitUnreachable(Unreachable* curr) {} - -void WasmBinaryReader::visitDrop(Drop* curr) { - curr->value = popNonVoidExpression(); - curr->finalize(); -} - -void WasmBinaryReader::visitRefNull(RefNull* curr) { - curr->finalize(getHeapType().getBottom()); -} - -void WasmBinaryReader::visitRefIsNull(RefIsNull* curr) { - curr->value = popNonVoidExpression(); - curr->finalize(); -} - -void WasmBinaryReader::visitRefFunc(RefFunc* curr) { - Index index = getU32LEB(); - curr->func = getFunctionName(index); - // To support typed function refs, we give the reference not just a general - // funcref, but a specific subtype with the actual signature. - curr->finalize(Type(getTypeByFunctionIndex(index), NonNullable)); -} - -void WasmBinaryReader::visitRefEq(RefEq* curr) { - curr->right = popNonVoidExpression(); - curr->left = popNonVoidExpression(); - curr->finalize(); -} - -void WasmBinaryReader::visitTableGet(TableGet* curr) { - Index tableIdx = getU32LEB(); - if (tableIdx >= wasm.tables.size()) { - throwError("bad table index"); - } - curr->index = popNonVoidExpression(); - curr->type = wasm.tables[tableIdx]->type; - curr->table = getTableName(tableIdx); - curr->finalize(); -} - -void WasmBinaryReader::visitTableSet(TableSet* curr) { - Index tableIdx = getU32LEB(); - if (tableIdx >= wasm.tables.size()) { - throwError("bad table index"); - } - curr->value = popNonVoidExpression(); - curr->index = popNonVoidExpression(); - curr->table = getTableName(tableIdx); - curr->finalize(); -} - -void WasmBinaryReader::visitTryOrTryInBlock(Expression*& out) { - auto* curr = allocator.alloc(); - startControlFlow(curr); - // For simplicity of implementation, like if scopes, we create a hidden block - // within each try-body and catch-body, and let branches target those inner - // blocks instead. - curr->type = getType(); - curr->body = getBlockOrSingleton(curr->type); - - Builder builder(wasm); - // A nameless label shared by all catch body blocks - Name catchLabel = getNextLabel(); - breakStack.push_back({catchLabel, curr->type}); - - auto readCatchBody = [&](Type tagType) { - auto start = expressionStack.size(); - if (tagType != Type::none) { - pushExpression(builder.makePop(tagType)); - } - processExpressions(); - size_t end = expressionStack.size(); - if (start > end) { - throwError("block cannot pop from outside"); - } - if (end - start == 1) { - curr->catchBodies.push_back(popExpression()); - } else { - auto* block = allocator.alloc(); - pushBlockElements(block, curr->type, start); - block->finalize(curr->type); - curr->catchBodies.push_back(block); - } - }; - - // We cannot immediately update tagRefs in the loop below, as catchTags is - // being grown, an so references would get invalidated. Store the indexes - // here, then do that later. - std::vector tagIndexes; - - while (lastSeparator == BinaryConsts::Catch_Legacy || - lastSeparator == BinaryConsts::CatchAll_Legacy) { - if (lastSeparator == BinaryConsts::Catch_Legacy) { - auto index = getU32LEB(); - if (index >= wasm.tags.size()) { - throwError("bad tag index"); - } - tagIndexes.push_back(index); - auto* tag = wasm.tags[index].get(); - curr->catchTags.push_back(tag->name); - readCatchBody(tag->sig.params); - } else { // catch_all - if (curr->hasCatchAll()) { - throwError("there should be at most one 'catch_all' clause per try"); - } - readCatchBody(Type::none); - } - } - breakStack.pop_back(); - - for (Index i = 0; i < tagIndexes.size(); i++) { - curr->catchTags[i] = getTagName(tagIndexes[i]); - } - - if (lastSeparator == BinaryConsts::Delegate) { - curr->delegateTarget = getExceptionTargetName(getU32LEB()); - } - - // For simplicity, we ensure that try's labels can only be targeted by - // delegates and rethrows, and delegates/rethrows can only target try's - // labels. (If they target blocks or loops, it is a validation failure.) - // Because we create an inner block within each try and catch body, if any - // delegate/rethrow targets those inner blocks, we should make them target the - // try's label instead. - curr->name = getNextLabel(); - if (auto* block = curr->body->dynCast()) { - if (block->name.is()) { - if (exceptionTargetNames.find(block->name) != - exceptionTargetNames.end()) { - BranchUtils::replaceExceptionTargets(block, block->name, curr->name); - exceptionTargetNames.erase(block->name); - } - } - } - if (exceptionTargetNames.find(catchLabel) != exceptionTargetNames.end()) { - for (auto* catchBody : curr->catchBodies) { - BranchUtils::replaceExceptionTargets(catchBody, catchLabel, curr->name); - } - exceptionTargetNames.erase(catchLabel); - } - - // If catch bodies contained stacky code, 'pop's can be nested within a block. - // Fix that up. - EHUtils::handleBlockNestedPop(curr, currFunction, wasm); - curr->finalize(curr->type); - - // For simplicity, we create an inner block within the catch body too, but the - // one within the 'catch' *must* be omitted when we write out the binary back - // later, because the 'catch' instruction pushes a value onto the stack and - // the inner block does not support block input parameters without multivalue - // support. - // try - // ... - // catch $e ;; Pushes value(s) onto the stack - // block ;; Inner block. Should be deleted when writing binary! - // use the pushed value - // end - // end - // - // But when input binary code is like - // try - // ... - // catch $e - // br 0 - // end - // - // 'br 0' accidentally happens to target the inner block, creating code like - // this in Binaryen IR, making the inner block not deletable, resulting in a - // validation error: - // (try - // ... - // (catch $e - // (block $label0 ;; Cannot be deleted, because there's a branch to this - // ... - // (br $label0) - // ) - // ) - // ) - // - // When this happens, we fix this by creating a block that wraps the whole - // try-catch, and making the branches target that block instead, like this: - // (block $label ;; New enclosing block, new target for the branch - // (try - // ... - // (catch $e - // (block ;; Now this can be deleted when writing binary - // ... - // (br $label) - // ) - // ) - // ) - // ) - if (breakTargetNames.find(catchLabel) == breakTargetNames.end()) { - out = curr; - } else { - // Create a new block that encloses the whole try-catch - auto* block = builder.makeBlock(catchLabel, curr); - out = block; - } - breakTargetNames.erase(catchLabel); -} - -void WasmBinaryReader::visitTryTable(TryTable* curr) { - - // For simplicity of implementation, like if scopes, we create a hidden block - // within each try-body, and let branches target those inner blocks instead. - curr->type = getType(); - auto numCatches = getU32LEB(); - // We cannot immediately update tagRefs in the loop below, as catchTags is - // being grown, an so references would get invalidated. Store the indexes - // here, then do that later. - std::vector tagIndexes; - - for (size_t i = 0; i < numCatches; i++) { - uint8_t code = getInt8(); - if (code == BinaryConsts::Catch || code == BinaryConsts::CatchRef) { - auto index = getU32LEB(); - if (index >= wasm.tags.size()) { - throwError("bad tag index"); - } - tagIndexes.push_back(index); - auto* tag = wasm.tags[index].get(); - curr->catchTags.push_back(tag->name); - } else { - tagIndexes.push_back(-1); // unused - curr->catchTags.push_back(Name()); - } - curr->catchDests.push_back(getBreakTarget(getU32LEB()).name); - curr->catchRefs.push_back(code == BinaryConsts::CatchRef || - code == BinaryConsts::CatchAllRef); - } - - for (Index i = 0; i < tagIndexes.size(); i++) { - if (curr->catchTags[i]) { - curr->catchTags[i] = getTagName(tagIndexes[i]); - } - } - - // catch_*** clauses should refer to block labels without entering the try - // scope. So we do this after reading catch clauses. - startControlFlow(curr); - curr->body = getBlockOrSingleton(curr->type); - curr->finalize(curr->type, &wasm); -} - -void WasmBinaryReader::visitThrow(Throw* curr) { - auto index = getU32LEB(); - if (index >= wasm.tags.size()) { - throwError("bad tag index"); - } - auto* tag = wasm.tags[index].get(); - curr->tag = tag->name; - size_t num = tag->sig.params.size(); - curr->operands.resize(num); - for (size_t i = 0; i < num; i++) { - curr->operands[num - i - 1] = popNonVoidExpression(); - } - curr->finalize(); -} - -void WasmBinaryReader::visitRethrow(Rethrow* curr) { - curr->target = getExceptionTargetName(getU32LEB()); - // This special target is valid only for delegates - if (curr->target == DELEGATE_CALLER_TARGET) { - throwError(std::string("rethrow target cannot use internal name ") + - DELEGATE_CALLER_TARGET.toString()); - } - curr->finalize(); -} - -void WasmBinaryReader::visitThrowRef(ThrowRef* curr) { - curr->exnref = popNonVoidExpression(); - curr->finalize(); -} - -void WasmBinaryReader::visitCallRef(CallRef* curr) { - curr->target = popNonVoidExpression(); - HeapType heapType = getTypeByIndex(getU32LEB()); - if (!Type::isSubType(curr->target->type, Type(heapType, Nullable))) { - throwError("Call target has invalid type: " + - curr->target->type.toString()); - } - if (!heapType.isSignature()) { - throwError("Invalid reference type for a call_ref: " + heapType.toString()); - } - auto sig = heapType.getSignature(); - auto num = sig.params.size(); - curr->operands.resize(num); - for (size_t i = 0; i < num; i++) { - curr->operands[num - i - 1] = popNonVoidExpression(); - } - // If the target has bottom type, we won't be able to infer the correct type - // from it, so set the type manually to be safe. - curr->type = sig.results; - curr->finalize(); -} - -bool WasmBinaryReader::maybeVisitRefI31(Expression*& out, uint32_t code) { - Shareability share; - switch (code) { - case BinaryConsts::RefI31: - share = Unshared; - break; - case BinaryConsts::RefI31Shared: - share = Shared; - break; - default: - return false; - } - auto* value = popNonVoidExpression(); - out = Builder(wasm).makeRefI31(value, share); - return true; -} - -bool WasmBinaryReader::maybeVisitI31Get(Expression*& out, uint32_t code) { - I31Get* curr; - switch (code) { - case BinaryConsts::I31GetS: - curr = allocator.alloc(); - curr->signed_ = true; - break; - case BinaryConsts::I31GetU: - curr = allocator.alloc(); - curr->signed_ = false; - break; - default: - return false; - } - curr->i31 = popNonVoidExpression(); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitRefTest(Expression*& out, uint32_t code) { - if (code == BinaryConsts::RefTest || code == BinaryConsts::RefTestNull) { - auto castType = getHeapType(); - auto nullability = - (code == BinaryConsts::RefTestNull) ? Nullable : NonNullable; - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeRefTest(ref, Type(castType, nullability)); - return true; - } - return false; -} - -bool WasmBinaryReader::maybeVisitRefCast(Expression*& out, uint32_t code) { - if (code == BinaryConsts::RefCast || code == BinaryConsts::RefCastNull) { - auto heapType = getHeapType(); - auto nullability = code == BinaryConsts::RefCast ? NonNullable : Nullable; - auto type = Type(heapType, nullability); - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeRefCast(ref, type); - return true; - } - return false; -} - -bool WasmBinaryReader::maybeVisitBrOn(Expression*& out, uint32_t code) { - Type castType = Type::none; - BrOnOp op; - switch (code) { - case BinaryConsts::BrOnNull: - op = BrOnNull; - break; - case BinaryConsts::BrOnNonNull: - op = BrOnNonNull; - break; - case BinaryConsts::BrOnCast: - op = BrOnCast; - break; - case BinaryConsts::BrOnCastFail: - op = BrOnCastFail; - break; - default: - return false; - } - bool isCast = - code == BinaryConsts::BrOnCast || code == BinaryConsts::BrOnCastFail; - uint8_t flags = 0; - if (isCast) { - flags = getInt8(); - } - auto name = getBreakTarget(getU32LEB()).name; - auto* ref = popNonVoidExpression(); - if (!ref->type.isRef() && ref->type != Type::unreachable) { - throwError("bad input type for br_on*"); - } - if (isCast) { - auto inputNullability = (flags & 1) ? Nullable : NonNullable; - auto castNullability = (flags & 2) ? Nullable : NonNullable; - auto inputHeapType = getHeapType(); - auto castHeapType = getHeapType(); - castType = Type(castHeapType, castNullability); - auto inputType = Type(inputHeapType, inputNullability); - if (!Type::isSubType(castType, inputType)) { - throwError("br_on_cast* cast type must be subtype of input type"); - } - if (!Type::isSubType(ref->type, inputType)) { - throwError(std::string("Invalid reference type for ") + - ((op == BrOnCast) ? "br_on_cast" : "br_on_cast_fail")); - } - } - out = Builder(wasm).makeBrOn(op, name, ref, castType); - return true; -} - -bool WasmBinaryReader::maybeVisitStructNew(Expression*& out, uint32_t code) { - if (code == BinaryConsts::StructNew || - code == BinaryConsts::StructNewDefault) { - auto heapType = getIndexedHeapType(); - if (!heapType.isStruct()) { - throwError("Expected struct heaptype"); - } - std::vector operands; - if (code == BinaryConsts::StructNew) { - auto numOperands = heapType.getStruct().fields.size(); - operands.resize(numOperands); - for (Index i = 0; i < numOperands; i++) { - operands[numOperands - i - 1] = popNonVoidExpression(); - } - } - out = Builder(wasm).makeStructNew(heapType, operands); - return true; - } - return false; -} - -bool WasmBinaryReader::maybeVisitStructGet(Expression*& out, uint32_t code) { - bool signed_ = false; - switch (code) { - case BinaryConsts::StructGet: - case BinaryConsts::StructGetU: - break; - case BinaryConsts::StructGetS: - signed_ = true; - break; - default: - return false; - } - auto heapType = getIndexedHeapType(); - if (!heapType.isStruct()) { - throwError("Expected struct heaptype"); - } - auto index = getU32LEB(); - if (index >= heapType.getStruct().fields.size()) { - throwError("Struct field index out of bounds"); - } - auto type = heapType.getStruct().fields[index].type; - auto ref = popNonVoidExpression(); - validateHeapTypeUsingChild(ref, heapType); - out = Builder(wasm).makeStructGet(index, ref, type, signed_); - return true; -} - -bool WasmBinaryReader::maybeVisitStructSet(Expression*& out, uint32_t code) { - if (code != BinaryConsts::StructSet) { - return false; - } - auto* curr = allocator.alloc(); - auto heapType = getIndexedHeapType(); - if (!heapType.isStruct()) { - throwError("Expected struct heaptype"); - } - curr->index = getU32LEB(); - curr->value = popNonVoidExpression(); - curr->ref = popNonVoidExpression(); - validateHeapTypeUsingChild(curr->ref, heapType); - curr->finalize(); - out = curr; - return true; -} - -bool WasmBinaryReader::maybeVisitArrayNewData(Expression*& out, uint32_t code) { - if (code == BinaryConsts::ArrayNew || code == BinaryConsts::ArrayNewDefault) { - auto heapType = getIndexedHeapType(); - if (!heapType.isArray()) { - throwError("Expected array heaptype"); - } - auto* size = popNonVoidExpression(); - Expression* init = nullptr; - if (code == BinaryConsts::ArrayNew) { - init = popNonVoidExpression(); - } - out = Builder(wasm).makeArrayNew(heapType, size, init); - return true; - } - return false; -} - -bool WasmBinaryReader::maybeVisitArrayNewElem(Expression*& out, uint32_t code) { - if (code == BinaryConsts::ArrayNewData || - code == BinaryConsts::ArrayNewElem) { - auto isData = code == BinaryConsts::ArrayNewData; - auto heapType = getIndexedHeapType(); - if (!heapType.isArray()) { - throwError("Expected array heaptype"); - } - auto segIdx = getU32LEB(); - auto* size = popNonVoidExpression(); - auto* offset = popNonVoidExpression(); - if (isData) { - auto* curr = Builder(wasm).makeArrayNewData( - heapType, getDataName(segIdx), offset, size); - out = curr; - } else { - auto* curr = Builder(wasm).makeArrayNewElem( - heapType, getElemName(segIdx), offset, size); - out = curr; - } - return true; - } - return false; -} - -bool WasmBinaryReader::maybeVisitArrayNewFixed(Expression*& out, - uint32_t code) { - if (code == BinaryConsts::ArrayNewFixed) { - auto heapType = getIndexedHeapType(); - if (!heapType.isArray()) { - throwError("Expected array heaptype"); - } - auto size = getU32LEB(); - std::vector values(size); - for (size_t i = 0; i < size; i++) { - values[size - i - 1] = popNonVoidExpression(); - } - out = Builder(wasm).makeArrayNewFixed(heapType, values); - return true; - } - return false; -} - -bool WasmBinaryReader::maybeVisitArrayGet(Expression*& out, uint32_t code) { - bool signed_ = false; - switch (code) { - case BinaryConsts::ArrayGet: - case BinaryConsts::ArrayGetU: - break; - case BinaryConsts::ArrayGetS: - signed_ = true; - break; - default: - return false; - } - auto heapType = getIndexedHeapType(); - if (!heapType.isArray()) { - throwError("Expected array heaptype"); - } - auto type = heapType.getArray().element.type; - auto* index = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - validateHeapTypeUsingChild(ref, heapType); - out = Builder(wasm).makeArrayGet(ref, index, type, signed_); - return true; -} - -bool WasmBinaryReader::maybeVisitArraySet(Expression*& out, uint32_t code) { - if (code != BinaryConsts::ArraySet) { - return false; - } - auto heapType = getIndexedHeapType(); - if (!heapType.isArray()) { - throwError("Expected array heaptype"); - } - auto* value = popNonVoidExpression(); - auto* index = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - validateHeapTypeUsingChild(ref, heapType); - out = Builder(wasm).makeArraySet(ref, index, value); - return true; -} - -bool WasmBinaryReader::maybeVisitArrayLen(Expression*& out, uint32_t code) { - if (code != BinaryConsts::ArrayLen) { - return false; - } - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeArrayLen(ref); - return true; -} - -bool WasmBinaryReader::maybeVisitArrayCopy(Expression*& out, uint32_t code) { - if (code != BinaryConsts::ArrayCopy) { - return false; - } - auto destHeapType = getIndexedHeapType(); - if (!destHeapType.isArray()) { - throwError("Expected array heaptype"); - } - auto srcHeapType = getIndexedHeapType(); - if (!srcHeapType.isArray()) { - throwError("Expected array heaptype"); - } - auto* length = popNonVoidExpression(); - auto* srcIndex = popNonVoidExpression(); - auto* srcRef = popNonVoidExpression(); - auto* destIndex = popNonVoidExpression(); - auto* destRef = popNonVoidExpression(); - validateHeapTypeUsingChild(destRef, destHeapType); - validateHeapTypeUsingChild(srcRef, srcHeapType); - out = - Builder(wasm).makeArrayCopy(destRef, destIndex, srcRef, srcIndex, length); - return true; -} - -bool WasmBinaryReader::maybeVisitArrayFill(Expression*& out, uint32_t code) { - if (code != BinaryConsts::ArrayFill) { - return false; - } - auto heapType = getIndexedHeapType(); - if (!heapType.isArray()) { - throwError("Expected array heaptype"); - } - auto* size = popNonVoidExpression(); - auto* value = popNonVoidExpression(); - auto* index = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - validateHeapTypeUsingChild(ref, heapType); - out = Builder(wasm).makeArrayFill(ref, index, value, size); - return true; -} - -bool WasmBinaryReader::maybeVisitArrayInit(Expression*& out, uint32_t code) { - bool isData = true; - switch (code) { - case BinaryConsts::ArrayInitData: - break; - case BinaryConsts::ArrayInitElem: - isData = false; - break; - default: - return false; - } - auto heapType = getIndexedHeapType(); - if (!heapType.isArray()) { - throwError("Expected array heaptype"); - } - Index segIdx = getU32LEB(); - auto* size = popNonVoidExpression(); - auto* offset = popNonVoidExpression(); - auto* index = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - validateHeapTypeUsingChild(ref, heapType); - if (isData) { - auto* curr = Builder(wasm).makeArrayInitData( - getDataName(segIdx), ref, index, offset, size); - out = curr; - } else { - auto* curr = Builder(wasm).makeArrayInitElem( - getElemName(segIdx), ref, index, offset, size); - out = curr; - } - return true; -} - -bool WasmBinaryReader::maybeVisitStringNew(Expression*& out, uint32_t code) { - StringNewOp op; - if (code == BinaryConsts::StringNewLossyUTF8Array) { - op = StringNewLossyUTF8Array; - } else if (code == BinaryConsts::StringNewWTF16Array) { - op = StringNewWTF16Array; - } else if (code == BinaryConsts::StringFromCodePoint) { - out = Builder(wasm).makeStringNew(StringNewFromCodePoint, - popNonVoidExpression()); - return true; - } else { - return false; - } - Expression* end = popNonVoidExpression(); - Expression* start = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeStringNew(op, ref, start, end); - return true; -} - -bool WasmBinaryReader::maybeVisitStringAsWTF16(Expression*& out, - uint32_t code) { - if (code != BinaryConsts::StringAsWTF16) { - return false; - } - // Accept but ignore `string.as_wtf16`, parsing the next expression in its - // place. We do not support this instruction in the IR, but we need to accept - // it in the parser because it is emitted as part of the instruction sequence - // for `stringview_wtf16.get_codeunit` and `stringview_wtf16.slice`. - readExpression(out); - return true; -} - -bool WasmBinaryReader::maybeVisitStringConst(Expression*& out, uint32_t code) { - if (code != BinaryConsts::StringConst) { - return false; - } - auto index = getU32LEB(); - if (index >= strings.size()) { - throwError("bad string index"); - } - out = Builder(wasm).makeStringConst(strings[index]); - return true; -} - -bool WasmBinaryReader::maybeVisitStringMeasure(Expression*& out, - uint32_t code) { - StringMeasureOp op; - if (code == BinaryConsts::StringMeasureUTF8) { - op = StringMeasureUTF8; - } else if (code == BinaryConsts::StringMeasureWTF16) { - op = StringMeasureWTF16; - } else { - return false; - } - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeStringMeasure(op, ref); - return true; -} - -bool WasmBinaryReader::maybeVisitStringEncode(Expression*& out, uint32_t code) { - StringEncodeOp op; - if (code == BinaryConsts::StringEncodeLossyUTF8Array) { - op = StringEncodeLossyUTF8Array; - } else if (code == BinaryConsts::StringEncodeWTF16Array) { - op = StringEncodeWTF16Array; - } else { - return false; - } - auto* start = popNonVoidExpression(); - auto* ptr = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeStringEncode(op, ref, ptr, start); - return true; -} - -bool WasmBinaryReader::maybeVisitStringConcat(Expression*& out, uint32_t code) { - if (code != BinaryConsts::StringConcat) { - return false; - } - auto* right = popNonVoidExpression(); - auto* left = popNonVoidExpression(); - out = Builder(wasm).makeStringConcat(left, right); - return true; -} - -bool WasmBinaryReader::maybeVisitStringEq(Expression*& out, uint32_t code) { - StringEqOp op; - if (code == BinaryConsts::StringEq) { - op = StringEqEqual; - } else if (code == BinaryConsts::StringCompare) { - op = StringEqCompare; - } else { - return false; - } - auto* right = popNonVoidExpression(); - auto* left = popNonVoidExpression(); - out = Builder(wasm).makeStringEq(op, left, right); - return true; -} - -bool WasmBinaryReader::maybeVisitStringWTF16Get(Expression*& out, - uint32_t code) { - if (code != BinaryConsts::StringViewWTF16GetCodePoint) { - return false; - } - auto* pos = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeStringWTF16Get(ref, pos); - return true; -} - -bool WasmBinaryReader::maybeVisitStringSliceWTF(Expression*& out, - uint32_t code) { - if (code != BinaryConsts::StringViewWTF16Slice) { - return false; - } - auto* end = popNonVoidExpression(); - auto* start = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeStringSliceWTF(ref, start, end); - return true; -} - -void WasmBinaryReader::visitRefAs(RefAs* curr, uint8_t code) { - switch (code) { - case BinaryConsts::RefAsNonNull: - curr->op = RefAsNonNull; - break; - case BinaryConsts::AnyConvertExtern: - curr->op = AnyConvertExtern; - break; - case BinaryConsts::ExternConvertAny: - curr->op = ExternConvertAny; - break; - default: - WASM_UNREACHABLE("invalid code for ref.as_*"); - } - curr->value = popNonVoidExpression(); - if (!curr->value->type.isRef() && curr->value->type != Type::unreachable) { - throwError("bad input type for ref.as: " + curr->value->type.toString()); - } - curr->finalize(); -} - -void WasmBinaryReader::visitContBind(ContBind* curr) { - - auto contTypeBeforeIndex = getU32LEB(); - curr->contTypeBefore = getTypeByIndex(contTypeBeforeIndex); - - auto contTypeAfterIndex = getU32LEB(); - curr->contTypeAfter = getTypeByIndex(contTypeAfterIndex); - - for (auto& ct : {curr->contTypeBefore, curr->contTypeAfter}) { - if (!ct.isContinuation()) { - throwError("non-continuation type in cont.bind instruction " + - ct.toString()); - } - } - - curr->cont = popNonVoidExpression(); - - size_t paramsBefore = - curr->contTypeBefore.getContinuation().type.getSignature().params.size(); - size_t paramsAfter = - curr->contTypeAfter.getContinuation().type.getSignature().params.size(); - if (paramsBefore < paramsAfter) { - throwError("incompatible continuation types in cont.bind: source type " + - curr->contTypeBefore.toString() + - " has fewer parameters than destination " + - curr->contTypeAfter.toString()); - } - size_t numArgs = paramsBefore - paramsAfter; - curr->operands.resize(numArgs); - for (size_t i = 0; i < numArgs; i++) { - curr->operands[numArgs - i - 1] = popNonVoidExpression(); - } - - curr->finalize(); -} - -void WasmBinaryReader::visitContNew(ContNew* curr) { - - auto contTypeIndex = getU32LEB(); - curr->contType = getTypeByIndex(contTypeIndex); - if (!curr->contType.isContinuation()) { - throwError("non-continuation type in cont.new instruction " + - curr->contType.toString()); - } - - curr->func = popNonVoidExpression(); - curr->finalize(); -} - -void WasmBinaryReader::visitResume(Resume* curr) { - - auto contTypeIndex = getU32LEB(); - curr->contType = getTypeByIndex(contTypeIndex); - if (!curr->contType.isContinuation()) { - throwError("non-continuation type in resume instruction " + - curr->contType.toString()); - } - - auto numHandlers = getU32LEB(); - - // We *must* bring the handlerTags vector to an appropriate size to ensure - // that we do not invalidate the pointers we add to tagRefs. They need to stay - // valid until processNames ran. - curr->handlerTags.resize(numHandlers); - curr->handlerBlocks.resize(numHandlers); - - for (size_t i = 0; i < numHandlers; i++) { - auto tagIndex = getU32LEB(); - auto tag = getTagName(tagIndex); - - auto handlerIndex = getU32LEB(); - auto handler = getBreakTarget(handlerIndex).name; - - curr->handlerTags[i] = tag; - curr->handlerBlocks[i] = handler; - } - - curr->cont = popNonVoidExpression(); - - auto numArgs = - curr->contType.getContinuation().type.getSignature().params.size(); - curr->operands.resize(numArgs); - for (size_t i = 0; i < numArgs; i++) { - curr->operands[numArgs - i - 1] = popNonVoidExpression(); - } - - curr->finalize(&wasm); -} - -void WasmBinaryReader::visitSuspend(Suspend* curr) { - - auto tagIndex = getU32LEB(); - if (tagIndex >= wasm.tags.size()) { - throwError("bad tag index"); - } - auto* tag = wasm.tags[tagIndex].get(); - curr->tag = tag->name; - - auto numArgs = tag->sig.params.size(); - curr->operands.resize(numArgs); - for (size_t i = 0; i < numArgs; i++) { - curr->operands[numArgs - i - 1] = popNonVoidExpression(); - } - - curr->finalize(&wasm); -} - -void WasmBinaryReader::throwError(std::string text) { - throw ParseException(text, 0, pos); -} - -void WasmBinaryReader::validateHeapTypeUsingChild(Expression* child, - HeapType heapType) { - if (child->type == Type::unreachable) { - return; - } - if (!child->type.isRef() || - !HeapType::isSubType(child->type.getHeapType(), heapType)) { - throwError("bad heap type: expected " + heapType.toString() + - " but found " + child->type.toString()); - } +// TODO: make this the only version +std::tuple WasmBinaryReader::getMemarg() { + Address alignment, offset; + auto memIdx = readMemoryAccess(alignment, offset); + return {getMemoryName(memIdx), alignment, offset}; } } // namespace wasm diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 54cd0149e5c..fcb1fc48d9a 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -150,6 +150,12 @@ void IRBuilder::push(Expression* expr) { scope.exprStack.push_back(expr); applyDebugLoc(expr); + if (binaryPos && func && lastBinaryPos != *binaryPos) { + func->expressionLocations[expr] = + BinaryLocations::Span{BinaryLocation(lastBinaryPos - codeSectionOffset), + BinaryLocation(*binaryPos - codeSectionOffset)}; + lastBinaryPos = *binaryPos; + } DBG(std::cerr << "After pushing " << ShallowExpression{expr} << ":\n"); DBG(dump()); @@ -708,6 +714,11 @@ Result<> IRBuilder::visitFunctionStart(Function* func) { debugLoc = CanReceiveDebug(); scopeStack.push_back(ScopeCtx::makeFunc(func)); this->func = func; + + if (binaryPos) { + lastBinaryPos = *binaryPos; + } + return Ok{}; } @@ -841,6 +852,12 @@ Result<> IRBuilder::visitElse() { auto expr = finishScope(); CHECK_ERR(expr); iff->ifTrue = *expr; + + if (binaryPos && func) { + func->delimiterLocations[iff][BinaryLocations::Else] = + lastBinaryPos - codeSectionOffset; + } + pushScope(ScopeCtx::makeElse(iff, originalLabel, label, labelUsed)); return Ok{}; } @@ -868,6 +885,12 @@ Result<> IRBuilder::visitCatch(Name tag) { tryy->catchBodies.push_back(*expr); } tryy->catchTags.push_back(tag); + + if (binaryPos && func) { + auto& delimiterLocs = func->delimiterLocations[tryy]; + delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; + } + pushScope( ScopeCtx::makeCatch(tryy, originalLabel, label, labelUsed, branchLabel)); // Push a pop for the exception payload if necessary. @@ -878,6 +901,7 @@ Result<> IRBuilder::visitCatch(Name tag) { scopeStack[0].notePop(); push(builder.makePop(params)); } + return Ok{}; } @@ -903,6 +927,12 @@ Result<> IRBuilder::visitCatchAll() { } else { tryy->catchBodies.push_back(*expr); } + + if (binaryPos && func) { + auto& delimiterLocs = func->delimiterLocations[tryy]; + delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; + } + pushScope( ScopeCtx::makeCatchAll(tryy, originalLabel, label, labelUsed, branchLabel)); return Ok{}; @@ -980,6 +1010,10 @@ Result<> IRBuilder::visitEnd() { return block; }; + // The binary position we record for the block instruction should start at the + // beginning of the block, not at the beginning of the `end`. + lastBinaryPos = scope.startPos; + if (auto* func = scope.getFunction()) { func->body = maybeWrapForLabel(*expr); labelDepths.clear(); diff --git a/test/br_to_exit.wasm.fromBinary b/test/br_to_exit.wasm.fromBinary index faee4e21b23..a25acc93761 100644 --- a/test/br_to_exit.wasm.fromBinary +++ b/test/br_to_exit.wasm.fromBinary @@ -1,8 +1,8 @@ (module (type $0 (func)) (func $0 - (block $label$0 - (br $label$0) + (block $label + (br $label) ) ) ) diff --git a/test/br_to_try.wasm.fromBinary b/test/br_to_try.wasm.fromBinary index 4a1f7be3772..a3bed8ee0fa 100644 --- a/test/br_to_try.wasm.fromBinary +++ b/test/br_to_try.wasm.fromBinary @@ -3,15 +3,15 @@ (type $1 (func)) (tag $tag$0 (param i32)) (func $0 - (try $label$3 - (do - (block $label$1 - (br $label$1) + (block $label + (try + (do + (br $label) ) - ) - (catch $tag$0 - (drop - (pop i32) + (catch $tag$0 + (drop + (pop i32) + ) ) ) ) diff --git a/test/break-to-return.wasm.fromBinary b/test/break-to-return.wasm.fromBinary index d9c1f4fddef..8492cb0077d 100644 --- a/test/break-to-return.wasm.fromBinary +++ b/test/break-to-return.wasm.fromBinary @@ -3,8 +3,8 @@ (memory $0 256 256) (export "add" (func $0)) (func $0 (param $0 i32) (param $1 i32) (result i32) - (block $label$0 (result i32) - (br $label$0 + (block $label (result i32) + (br $label (i32.add (local.get $0) (local.get $1) diff --git a/test/break-within-catch.wasm.fromBinary b/test/break-within-catch.wasm.fromBinary index 3fe738104b6..7d151f48c48 100644 --- a/test/break-within-catch.wasm.fromBinary +++ b/test/break-within-catch.wasm.fromBinary @@ -3,8 +3,8 @@ (type $1 (func)) (tag $tag$0 (param i32)) (func $0 - (block $label$2 - (try $label$3 + (block $label + (try (do (nop) ) @@ -12,7 +12,7 @@ (drop (pop i32) ) - (br $label$2) + (br $label) ) ) ) diff --git a/test/consume-stacky.wasm.fromBinary b/test/consume-stacky.wasm.fromBinary index 25cc9a54130..76ec73d7b89 100644 --- a/test/consume-stacky.wasm.fromBinary +++ b/test/consume-stacky.wasm.fromBinary @@ -2,15 +2,15 @@ (type $0 (func (result i32))) (memory $0 1 1) (func $0 (result i32) - (local $0 i32) - (local.set $0 + (local $scratch i32) + (local.set $scratch (i32.const 1) ) (i32.store (i32.const 2) (i32.const 3) ) - (local.get $0) + (local.get $scratch) ) ) diff --git a/test/elided-br.wasm.fromBinary b/test/elided-br.wasm.fromBinary index 04ec30dfb6e..24331dbb5f9 100644 --- a/test/elided-br.wasm.fromBinary +++ b/test/elided-br.wasm.fromBinary @@ -1,8 +1,11 @@ (module (type $0 (func)) (func $0 - (block $label$1 + (block $block (unreachable) + (block + (br $block) + ) ) ) ) diff --git a/test/example/c-api-unused-mem.txt b/test/example/c-api-unused-mem.txt index 36d0a9a6b34..48ea5982f47 100644 --- a/test/example/c-api-unused-mem.txt +++ b/test/example/c-api-unused-mem.txt @@ -47,13 +47,13 @@ (local $0 i32) (local $1 i32) (local $2 i64) - (block $label$1 + (block $block (local.set $0 (i32.load (i32.const 0) ) ) - (br $label$1) + (br $block) ) (i32.store (i32.const 0) diff --git a/test/fib-dbg.wasm.fromBinary b/test/fib-dbg.wasm.fromBinary index f1b263234d0..58c86ec7119 100644 --- a/test/fib-dbg.wasm.fromBinary +++ b/test/fib-dbg.wasm.fromBinary @@ -50,7 +50,7 @@ (export "stackAlloc" (func $stackAlloc)) (func $stackAlloc (param $0 i32) (result i32) (local $1 i32) - (block $label$1 + (block (local.set $1 (global.get $global$3) ) @@ -72,7 +72,9 @@ (return (local.get $1) ) + (unreachable) ) + (unreachable) ) (func $stackSave (result i32) (return @@ -85,13 +87,11 @@ ) ) (func $establishStackSpace (param $0 i32) (param $1 i32) - (block $label$1 - (global.set $global$3 - (local.get $0) - ) - (global.set $global$4 - (local.get $1) - ) + (global.set $global$3 + (local.get $0) + ) + (global.set $global$4 + (local.get $1) ) ) (func $setThrew (param $0 i32) (param $1 i32) @@ -122,7 +122,7 @@ (local $9 i32) (local $10 i32) (local $11 i32) - (block $label$1 + (block (local.set $11 (global.get $global$3) ) @@ -158,8 +158,8 @@ ) ) ;;@ fib.c:8:0 - (loop $label$4 - (block $label$5 + (loop $label + (block $block ;;@ fib.c:4:0 (local.set $3 (i32.add @@ -188,7 +188,7 @@ (local.set $4 (local.get $3) ) - (br $label$5) + (br $block) ) (else (local.set $2 @@ -206,15 +206,21 @@ ) ) ;;@ fib.c:3:0 - (br $label$4) + (br $label) ) ) ;;@ fib.c:8:0 (return (local.get $4) ) + ;;@ fib.c:8:0 + (unreachable) + ;;@ fib.c:8:0 + (unreachable) ) ;;@ fib.c:8:0 + (unreachable) + ;;@ fib.c:8:0 ) (func $runPostSets (local $0 i32) diff --git a/test/fn_prolog_epilog.debugInfo.wasm.fromBinary b/test/fn_prolog_epilog.debugInfo.wasm.fromBinary index ff96a6a4717..73b8d1d4235 100644 --- a/test/fn_prolog_epilog.debugInfo.wasm.fromBinary +++ b/test/fn_prolog_epilog.debugInfo.wasm.fromBinary @@ -4,10 +4,10 @@ (func $0 (nop) ;;@ src.cpp:2:1 - (block $label$1 + (block ;;@ src.cpp:2:2 - (block $label$2 - (br $label$2) + (block $block + (br $block) ) ) ;;@ src.cpp:3:1 diff --git a/test/lit/basic/exception-handling-legacy.wast b/test/lit/basic/exception-handling-legacy.wast index 70cc3f5bca6..791745b69ef 100644 --- a/test/lit/basic/exception-handling-legacy.wast +++ b/test/lit/basic/exception-handling-legacy.wast @@ -76,7 +76,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $simple-try-catch (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (throw $e-i32 ;; CHECK-BIN-NEXT: (i32.const 0) @@ -124,9 +124,10 @@ ;; CHECK-BIN: (func $try-catch-multivalue-tag (type $0) ;; CHECK-BIN-NEXT: (local $x i32) ;; CHECK-BIN-NEXT: (local $1 i64) - ;; CHECK-BIN-NEXT: (local $2 (tuple i32 i64)) - ;; CHECK-BIN-NEXT: (local $3 i32) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-BIN-NEXT: (local $scratch_3 i32) + ;; CHECK-BIN-NEXT: (local $4 (tuple i32 i64)) + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (throw $e-i32-i64 ;; CHECK-BIN-NEXT: (i32.const 0) @@ -134,26 +135,30 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-i32-i64 - ;; CHECK-BIN-NEXT: (local.set $2 + ;; CHECK-BIN-NEXT: (local.set $4 ;; CHECK-BIN-NEXT: (pop (tuple i32 i64)) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.set $x - ;; CHECK-BIN-NEXT: (block (result i32) - ;; CHECK-BIN-NEXT: (local.set $3 - ;; CHECK-BIN-NEXT: (tuple.extract 2 0 - ;; CHECK-BIN-NEXT: (local.get $2) + ;; CHECK-BIN-NEXT: (block + ;; CHECK-BIN-NEXT: (local.set $x + ;; CHECK-BIN-NEXT: (block (result i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_3 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (local.get $4) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.set $1 - ;; CHECK-BIN-NEXT: (tuple.extract 2 1 - ;; CHECK-BIN-NEXT: (local.get $2) + ;; CHECK-BIN-NEXT: (local.set $1 + ;; CHECK-BIN-NEXT: (tuple.extract 2 1 + ;; CHECK-BIN-NEXT: (local.get $scratch) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_3) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $3) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -190,18 +195,19 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $try-with-block-label (type $0) - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (try $label$4 + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-i32 ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (pop i32) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) (func $try-with-block-label @@ -228,7 +234,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $empty-try-body (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-i32 @@ -263,7 +269,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $multiple-insts-within-try-and-catch-bodies (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: (call $bar) @@ -311,7 +317,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $multiple-catches (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (throw $e-i32 ;; CHECK-BIN-NEXT: (i32.const 0) @@ -355,7 +361,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $catch-all (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (throw $e-i32 ;; CHECK-BIN-NEXT: (i32.const 0) @@ -398,7 +404,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $catch-and-catch-all-together (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (throw $e-i32 ;; CHECK-BIN-NEXT: (i32.const 0) @@ -480,9 +486,9 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $nested-try-catch (type $0) - ;; CHECK-BIN-NEXT: (try $label$9 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (try $label$4 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (throw $e-i32 ;; CHECK-BIN-NEXT: (i32.const 0) @@ -503,7 +509,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (try $label$8 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (throw $e-i32 ;; CHECK-BIN-NEXT: (i32.const 0) @@ -560,13 +566,14 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $catchless-delegateless-try (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (throw $e-i32 ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $catchless-delegateless-try (try @@ -597,21 +604,19 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $inner-delegate-target-outer-catch (type $0) - ;; CHECK-BIN-NEXT: (try $label$9 + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (try $label$4 - ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (call $foo) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (delegate $label$9) + ;; CHECK-BIN-NEXT: (try + ;; CHECK-BIN-NEXT: (do + ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (try $label$7 - ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (call $foo) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (delegate $label$9) + ;; CHECK-BIN-NEXT: (delegate $label) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (try + ;; CHECK-BIN-NEXT: (do + ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (delegate $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all @@ -665,26 +670,24 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $branch-and-delegate-target-same-try-label (type $0) - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (try $label$10 + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (block $label$2 - ;; CHECK-BIN-NEXT: (try $label$5 - ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (br_if $label$1 - ;; CHECK-BIN-NEXT: (i32.const 1) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (try + ;; CHECK-BIN-NEXT: (do + ;; CHECK-BIN-NEXT: (br_if $block + ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (delegate $label$10) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (try $label$8 - ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (br_if $label$1 - ;; CHECK-BIN-NEXT: (i32.const 1) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (delegate $label) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (try + ;; CHECK-BIN-NEXT: (do + ;; CHECK-BIN-NEXT: (br_if $block + ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (delegate $label$10) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (delegate $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all @@ -731,15 +734,13 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $inner-delegate-target-outer-delegate (type $0) - ;; CHECK-BIN-NEXT: (try $label$6 + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (try $label$4 - ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (call $foo) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (delegate $label$6) + ;; CHECK-BIN-NEXT: (try + ;; CHECK-BIN-NEXT: (do + ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (delegate $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (delegate 0) @@ -770,7 +771,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $empty-catch-body (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-empty @@ -802,7 +803,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $try-catch-rethrow (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) @@ -810,10 +811,10 @@ ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (pop i32) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (rethrow $label$3) + ;; CHECK-BIN-NEXT: (rethrow $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (rethrow $label$3) + ;; CHECK-BIN-NEXT: (rethrow $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -852,8 +853,8 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $branch-and-rethrow-target-same-try-label (type $0) - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (try $label$4 + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) @@ -861,10 +862,10 @@ ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (pop i32) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (rethrow $label$4) + ;; CHECK-BIN-NEXT: (rethrow $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -913,12 +914,12 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $nested-rethrow (type $0) - ;; CHECK-BIN-NEXT: (try $label$6 + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (try $label$5 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) @@ -926,10 +927,10 @@ ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (pop i32) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (rethrow $label$6) + ;; CHECK-BIN-NEXT: (rethrow $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (rethrow $label$6) + ;; CHECK-BIN-NEXT: (rethrow $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -986,12 +987,12 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $rnested-rethrow-with-interleaving-block (type $0) - ;; CHECK-BIN-NEXT: (try $label$7 + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (try $label$6 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) @@ -999,12 +1000,13 @@ ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (pop i32) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (block $label$5 - ;; CHECK-BIN-NEXT: (rethrow $label$7) + ;; CHECK-BIN-NEXT: (block + ;; CHECK-BIN-NEXT: (rethrow $label) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (rethrow $label$7) + ;; CHECK-BIN-NEXT: (rethrow $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1068,28 +1070,28 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $rethrow-within-nested-try-part (type $0) - ;; CHECK-BIN-NEXT: (try $label$6 + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (try $label$5 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (rethrow $label$6) + ;; CHECK-BIN-NEXT: (rethrow $label) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (try $label$12 + ;; CHECK-BIN-NEXT: (try $label1 ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all - ;; CHECK-BIN-NEXT: (try $label$11 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (rethrow $label$12) + ;; CHECK-BIN-NEXT: (rethrow $label1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch_all ;; CHECK-BIN-NEXT: ) @@ -1147,7 +1149,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $pop-within-if-condition (type $0) - ;; CHECK-BIN-NEXT: (try $label$5 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-i32 @@ -1197,7 +1199,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $pop-can-be-supertype (type $0) - ;; CHECK-BIN-NEXT: (try $label$3 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (catch $e-eqref @@ -1233,20 +1235,20 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $catchless-try-with-inner-delegate (type $0) - ;; CHECK-BIN-NEXT: (try $label$6 + ;; CHECK-BIN-NEXT: (try $label ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (try $label$4 - ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: (throw $e-i32 - ;; CHECK-BIN-NEXT: (i32.const 0) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (try + ;; CHECK-BIN-NEXT: (do + ;; CHECK-BIN-NEXT: (throw $e-i32 + ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (delegate $label$6) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (delegate $label) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $catchless-try-with-inner-delegate (try $label$0 @@ -1276,10 +1278,10 @@ ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $nested-delegate-within-block (type $0) - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (block $label$2 + ;; CHECK-BIN-NEXT: (block + ;; CHECK-BIN-NEXT: (block ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (try $label$5 + ;; CHECK-BIN-NEXT: (try ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (delegate 1) @@ -1327,7 +1329,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $2 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) @@ -1344,9 +1346,10 @@ ;; CHECK-BIN-NODEBUG: (func $3 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (local $0 i32) ;; CHECK-BIN-NODEBUG-NEXT: (local $1 i64) -;; CHECK-BIN-NODEBUG-NEXT: (local $2 (tuple i32 i64)) -;; CHECK-BIN-NODEBUG-NEXT: (local $3 i32) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 i64)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_3 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $4 (tuple i32 i64)) +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$2 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) @@ -1354,49 +1357,54 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$2 -;; CHECK-BIN-NODEBUG-NEXT: (local.set $2 +;; CHECK-BIN-NODEBUG-NEXT: (local.set $4 ;; CHECK-BIN-NODEBUG-NEXT: (pop (tuple i32 i64)) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $3 -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 +;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_3 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (local.get $4) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_3) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $3) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $4 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (try $label$4 +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (pop i32) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $5 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$0 @@ -1408,7 +1416,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $6 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: (call $1) @@ -1424,7 +1432,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $7 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) @@ -1444,7 +1452,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $8 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) @@ -1456,7 +1464,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $9 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) @@ -1480,9 +1488,9 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $10 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$9 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (try $label$4 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) @@ -1503,7 +1511,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (try $label$8 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) @@ -1522,31 +1530,30 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $11 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $12 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$9 +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (try $label$4 -;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (call $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (delegate $label$9) +;; CHECK-BIN-NODEBUG-NEXT: (try +;; CHECK-BIN-NODEBUG-NEXT: (do +;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$7 -;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (call $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (delegate $label$9) +;; CHECK-BIN-NODEBUG-NEXT: (delegate $label) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (try +;; CHECK-BIN-NODEBUG-NEXT: (do +;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (delegate $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all @@ -1555,26 +1562,24 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $13 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (try $label$10 +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 -;; CHECK-BIN-NODEBUG-NEXT: (try $label$5 -;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (try +;; CHECK-BIN-NODEBUG-NEXT: (do +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (delegate $label$10) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$8 -;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (delegate $label) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (try +;; CHECK-BIN-NODEBUG-NEXT: (do +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (delegate $label$10) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (delegate $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all @@ -1584,15 +1589,13 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $14 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$6 +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (try $label$4 -;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (call $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (delegate $label$6) +;; CHECK-BIN-NODEBUG-NEXT: (try +;; CHECK-BIN-NODEBUG-NEXT: (do +;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (delegate $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (delegate 0) @@ -1600,7 +1603,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $15 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$4 @@ -1609,7 +1612,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $16 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1617,17 +1620,17 @@ ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (pop i32) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$3) +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$3) +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $17 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (try $label$4 +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1635,22 +1638,22 @@ ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (pop i32) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$4) +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $18 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$6 +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (try $label$5 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1658,10 +1661,10 @@ ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (pop i32) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$6) +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$6) +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1669,12 +1672,12 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $19 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$7 +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (try $label$6 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1682,12 +1685,13 @@ ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (pop i32) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$5 -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$7) +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$7) +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1695,28 +1699,28 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $20 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$6 +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (try $label$5 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$6) +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$12 +;; CHECK-BIN-NODEBUG-NEXT: (try $label1 ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all -;; CHECK-BIN-NODEBUG-NEXT: (try $label$11 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label$12) +;; CHECK-BIN-NODEBUG-NEXT: (rethrow $label1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch_all ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1726,7 +1730,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $21 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$5 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$0 @@ -1746,7 +1750,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $22 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$3 @@ -1758,27 +1762,27 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $23 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$6 +;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (try $label$4 -;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (try +;; CHECK-BIN-NODEBUG-NEXT: (do +;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (delegate $label$6) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (delegate $label) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $24 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (block ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (try $label$5 +;; CHECK-BIN-NODEBUG-NEXT: (try ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (delegate 1) diff --git a/test/lit/basic/exception-handling-no-gc.wast b/test/lit/basic/exception-handling-no-gc.wast index 4ab0708c4c2..611853a9398 100644 --- a/test/lit/basic/exception-handling-no-gc.wast +++ b/test/lit/basic/exception-handling-no-gc.wast @@ -7,10 +7,11 @@ (module ;; CHECK: (func $test (result exnref) - ;; CHECK-NEXT: (block $label$1 (result exnref) - ;; CHECK-NEXT: (try_table (catch_all_ref $label$1) + ;; CHECK-NEXT: (block $block (result exnref) + ;; CHECK-NEXT: (try_table (catch_all_ref $block) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (result exnref) diff --git a/test/lit/basic/exception-handling.wast b/test/lit/basic/exception-handling.wast index 97be09c04ca..41c10f6311a 100644 --- a/test/lit/basic/exception-handling.wast +++ b/test/lit/basic/exception-handling.wast @@ -147,6 +147,7 @@ ;; CHECK-BIN-NEXT: (try_table ;; CHECK-BIN-NEXT: (throw $e-empty) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $catchless-try-table (try_table) @@ -165,12 +166,13 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $simple-try-table-and-throw (type $4) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) - ;; CHECK-BIN-NEXT: (try_table (catch $e-i32 $label$1) + ;; CHECK-BIN-NEXT: (block $block (result i32) + ;; CHECK-BIN-NEXT: (try_table (catch $e-i32 $block) ;; CHECK-BIN-NEXT: (throw $e-i32 ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) (func $simple-try-table-and-throw (result i32) @@ -194,12 +196,13 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $try-table-and-throw-ref (type $0) ;; CHECK-BIN-NEXT: (throw_ref - ;; CHECK-BIN-NEXT: (block $label$1 (result exnref) - ;; CHECK-BIN-NEXT: (try_table (catch_all_ref $label$1) + ;; CHECK-BIN-NEXT: (block $block (result exnref) + ;; CHECK-BIN-NEXT: (try_table (catch_all_ref $block) ;; CHECK-BIN-NEXT: (throw $e-i64 ;; CHECK-BIN-NEXT: (i64.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -233,65 +236,64 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $try-table-multivalue-tag (type $0) - ;; CHECK-BIN-NEXT: (local $0 (tuple i32 i64)) - ;; CHECK-BIN-NEXT: (local $1 i32) - ;; CHECK-BIN-NEXT: (local $2 (tuple i32 i64 exnref)) - ;; CHECK-BIN-NEXT: (local $3 i64) - ;; CHECK-BIN-NEXT: (local $4 i32) - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (local.set $2 - ;; CHECK-BIN-NEXT: (block $label$2 (type $2) (result i32 i64 exnref) - ;; CHECK-BIN-NEXT: (local.set $0 - ;; CHECK-BIN-NEXT: (block $label$3 (type $1) (result i32 i64) - ;; CHECK-BIN-NEXT: (try_table (catch $e-i32-i64 $label$3) (catch_ref $e-i32-i64 $label$2) - ;; CHECK-BIN-NEXT: (throw $e-i32-i64 - ;; CHECK-BIN-NEXT: (i32.const 0) - ;; CHECK-BIN-NEXT: (i64.const 0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block (result i32) - ;; CHECK-BIN-NEXT: (local.set $1 - ;; CHECK-BIN-NEXT: (tuple.extract 2 0 - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (tuple.extract 2 1 - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-BIN-NEXT: (local $scratch_1 i32) + ;; CHECK-BIN-NEXT: (local $scratch_2 (tuple i32 i64 exnref)) + ;; CHECK-BIN-NEXT: (local $scratch_3 i64) + ;; CHECK-BIN-NEXT: (local $scratch_4 i32) + ;; CHECK-BIN-NEXT: (block $block2 ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result i32) - ;; CHECK-BIN-NEXT: (local.set $4 + ;; CHECK-BIN-NEXT: (local.set $scratch_4 ;; CHECK-BIN-NEXT: (tuple.extract 3 0 - ;; CHECK-BIN-NEXT: (local.get $2) + ;; CHECK-BIN-NEXT: (local.tee $scratch_2 + ;; CHECK-BIN-NEXT: (block $block1 (type $2) (result i32 i64 exnref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block (result i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_1 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block $block (type $1) (result i32 i64) + ;; CHECK-BIN-NEXT: (try_table (catch $e-i32-i64 $block) (catch_ref $e-i32-i64 $block1) + ;; CHECK-BIN-NEXT: (throw $e-i32-i64 + ;; CHECK-BIN-NEXT: (i32.const 0) + ;; CHECK-BIN-NEXT: (i64.const 0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (tuple.extract 2 1 + ;; CHECK-BIN-NEXT: (local.get $scratch) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (br $block2) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result i64) - ;; CHECK-BIN-NEXT: (local.set $3 + ;; CHECK-BIN-NEXT: (local.set $scratch_3 ;; CHECK-BIN-NEXT: (tuple.extract 3 1 - ;; CHECK-BIN-NEXT: (local.get $2) + ;; CHECK-BIN-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (tuple.extract 3 2 - ;; CHECK-BIN-NEXT: (local.get $2) + ;; CHECK-BIN-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $3) + ;; CHECK-BIN-NEXT: (local.get $scratch_3) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $4) + ;; CHECK-BIN-NEXT: (local.get $scratch_4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -338,25 +340,25 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $try-table-all-catch-clauses-empty-tag (type $0) - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (block $label$2 + ;; CHECK-BIN-NEXT: (block $block4 + ;; CHECK-BIN-NEXT: (block $block ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$3 (result exnref) - ;; CHECK-BIN-NEXT: (block $label$4 + ;; CHECK-BIN-NEXT: (block $block1 (result exnref) + ;; CHECK-BIN-NEXT: (block $block2 ;; CHECK-BIN-NEXT: (throw_ref - ;; CHECK-BIN-NEXT: (block $label$5 (result exnref) - ;; CHECK-BIN-NEXT: (try_table (catch $e-empty $label$2) (catch_ref $e-empty $label$3) (catch_all $label$4) (catch_all_ref $label$5) + ;; CHECK-BIN-NEXT: (block $block3 (result exnref) + ;; CHECK-BIN-NEXT: (try_table (catch $e-empty $block) (catch_ref $e-empty $block1) (catch_all $block2) (catch_all_ref $block3) ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: (call $foo) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -415,43 +417,42 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $try-table-all-catch-clauses-i32-tag (type $0) - ;; CHECK-BIN-NEXT: (local $0 (tuple i32 exnref)) - ;; CHECK-BIN-NEXT: (local $1 i32) - ;; CHECK-BIN-NEXT: (block $label$1 + ;; CHECK-BIN-NEXT: (local $scratch (tuple i32 exnref)) + ;; CHECK-BIN-NEXT: (local $scratch_1 i32) + ;; CHECK-BIN-NEXT: (block $block4 ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$2 (result i32) - ;; CHECK-BIN-NEXT: (local.set $0 - ;; CHECK-BIN-NEXT: (block $label$3 (type $5) (result i32 exnref) - ;; CHECK-BIN-NEXT: (block $label$4 - ;; CHECK-BIN-NEXT: (throw_ref - ;; CHECK-BIN-NEXT: (block $label$5 (result exnref) - ;; CHECK-BIN-NEXT: (try_table (catch $e-i32 $label$2) (catch_ref $e-i32 $label$3) (catch_all $label$4) (catch_all_ref $label$5) - ;; CHECK-BIN-NEXT: (call $foo) - ;; CHECK-BIN-NEXT: (call $foo) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (block $block (result i32) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result i32) - ;; CHECK-BIN-NEXT: (local.set $1 + ;; CHECK-BIN-NEXT: (local.set $scratch_1 ;; CHECK-BIN-NEXT: (tuple.extract 2 0 - ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block $block1 (type $5) (result i32 exnref) + ;; CHECK-BIN-NEXT: (block $block2 + ;; CHECK-BIN-NEXT: (throw_ref + ;; CHECK-BIN-NEXT: (block $block3 (result exnref) + ;; CHECK-BIN-NEXT: (try_table (catch $e-i32 $block) (catch_ref $e-i32 $block1) (catch_all $block2) (catch_all_ref $block3) + ;; CHECK-BIN-NEXT: (call $foo) + ;; CHECK-BIN-NEXT: (call $foo) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (br $block4) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (br $block4) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (tuple.extract 2 1 - ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: (local.get $scratch) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $1) + ;; CHECK-BIN-NEXT: (local.get $scratch_1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -513,71 +514,69 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $try-table-all-catch-clauses-multivalue-tag (type $0) - ;; CHECK-BIN-NEXT: (local $0 (tuple i32 i64 exnref)) - ;; CHECK-BIN-NEXT: (local $1 i64) - ;; CHECK-BIN-NEXT: (local $2 i32) - ;; CHECK-BIN-NEXT: (local $3 (tuple i32 i64)) - ;; CHECK-BIN-NEXT: (local $4 i32) - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (local.set $3 - ;; CHECK-BIN-NEXT: (block $label$2 (type $1) (result i32 i64) - ;; CHECK-BIN-NEXT: (local.set $0 - ;; CHECK-BIN-NEXT: (block $label$3 (type $2) (result i32 i64 exnref) - ;; CHECK-BIN-NEXT: (block $label$4 - ;; CHECK-BIN-NEXT: (throw_ref - ;; CHECK-BIN-NEXT: (block $label$5 (result exnref) - ;; CHECK-BIN-NEXT: (try_table (catch $e-i32-i64 $label$2) (catch_ref $e-i32-i64 $label$3) (catch_all $label$4) (catch_all_ref $label$5) - ;; CHECK-BIN-NEXT: (call $foo) - ;; CHECK-BIN-NEXT: (call $foo) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block (result i32) - ;; CHECK-BIN-NEXT: (local.set $2 - ;; CHECK-BIN-NEXT: (tuple.extract 3 0 - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block (result i64) - ;; CHECK-BIN-NEXT: (local.set $1 - ;; CHECK-BIN-NEXT: (tuple.extract 3 1 - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local $scratch (tuple i32 i64 exnref)) + ;; CHECK-BIN-NEXT: (local $scratch_1 i64) + ;; CHECK-BIN-NEXT: (local $scratch_2 i32) + ;; CHECK-BIN-NEXT: (local $scratch_3 (tuple i32 i64)) + ;; CHECK-BIN-NEXT: (local $scratch_4 i32) + ;; CHECK-BIN-NEXT: (block $block4 + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block (result i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_4 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch_3 + ;; CHECK-BIN-NEXT: (block $block (type $1) (result i32 i64) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (tuple.extract 3 2 - ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: (block (result i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_2 + ;; CHECK-BIN-NEXT: (tuple.extract 3 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block $block1 (type $2) (result i32 i64 exnref) + ;; CHECK-BIN-NEXT: (block $block2 + ;; CHECK-BIN-NEXT: (throw_ref + ;; CHECK-BIN-NEXT: (block $block3 (result exnref) + ;; CHECK-BIN-NEXT: (try_table (catch $e-i32-i64 $block) (catch_ref $e-i32-i64 $block1) (catch_all $block2) (catch_all_ref $block3) + ;; CHECK-BIN-NEXT: (call $foo) + ;; CHECK-BIN-NEXT: (call $foo) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (br $block4) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (br $block4) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block (result i64) + ;; CHECK-BIN-NEXT: (local.set $scratch_1 + ;; CHECK-BIN-NEXT: (tuple.extract 3 1 + ;; CHECK-BIN-NEXT: (local.get $scratch) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (tuple.extract 3 2 + ;; CHECK-BIN-NEXT: (local.get $scratch) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $1) + ;; CHECK-BIN-NEXT: (br $block4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $2) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block (result i32) - ;; CHECK-BIN-NEXT: (local.set $4 - ;; CHECK-BIN-NEXT: (tuple.extract 2 0 - ;; CHECK-BIN-NEXT: (local.get $3) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (tuple.extract 2 1 - ;; CHECK-BIN-NEXT: (local.get $3) + ;; CHECK-BIN-NEXT: (local.get $scratch_3) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $4) + ;; CHECK-BIN-NEXT: (local.get $scratch_4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -625,10 +624,10 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $try-table-with-label-and-br (type $4) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) - ;; CHECK-BIN-NEXT: (block $label$2 (result i32) - ;; CHECK-BIN-NEXT: (try_table (result i32) (catch $e-i32 $label$1) - ;; CHECK-BIN-NEXT: (br $label$2 + ;; CHECK-BIN-NEXT: (block $block (result i32) + ;; CHECK-BIN-NEXT: (block $block1 (result i32) + ;; CHECK-BIN-NEXT: (try_table (result i32) (catch $e-i32 $block) + ;; CHECK-BIN-NEXT: (br $block1 ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -670,11 +669,11 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $nested-try-table (type $3) (result exnref) - ;; CHECK-BIN-NEXT: (block $label$1 (result exnref) + ;; CHECK-BIN-NEXT: (block $block (result exnref) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$2 (result i32) - ;; CHECK-BIN-NEXT: (try_table (catch_all_ref $label$1) - ;; CHECK-BIN-NEXT: (try_table (catch $e-i32 $label$2) + ;; CHECK-BIN-NEXT: (block $block1 (result i32) + ;; CHECK-BIN-NEXT: (try_table (catch_all_ref $block) + ;; CHECK-BIN-NEXT: (try_table (catch $e-i32 $block1) ;; CHECK-BIN-NEXT: (if ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: (then @@ -688,8 +687,11 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (ref.null noexn) @@ -779,238 +781,237 @@ ;; CHECK-BIN-NODEBUG-NEXT: (try_table ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$4) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $3 (type $4) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$0 $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$0 $block) ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $4 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (throw_ref -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result exnref) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch_all_ref $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result exnref) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch_all_ref $block) ;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$1 ;; CHECK-BIN-NODEBUG-NEXT: (i64.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $5 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (local $0 (tuple i32 i64)) -;; CHECK-BIN-NODEBUG-NEXT: (local $1 i32) -;; CHECK-BIN-NODEBUG-NEXT: (local $2 (tuple i32 i64 exnref)) -;; CHECK-BIN-NODEBUG-NEXT: (local $3 i64) -;; CHECK-BIN-NODEBUG-NEXT: (local $4 i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (local.set $2 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (type $2) (result i32 i64 exnref) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 (type $1) (result i32 i64) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$2 $label$3) (catch_ref $tag$2 $label$2) -;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$2 -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) -;; CHECK-BIN-NODEBUG-NEXT: (i64.const 0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 i64)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_1 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_2 (tuple i32 i64 exnref)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_3 i64) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_4 i32) +;; CHECK-BIN-NODEBUG-NEXT: (block $block2 ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $4 +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_4 ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 0 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch_2 +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 (type $2) (result i32 i64 exnref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_1 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (block $block (type $1) (result i32 i64) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$2 $block) (catch_ref $tag$2 $block1) +;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$2 +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) +;; CHECK-BIN-NODEBUG-NEXT: (i64.const 0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (br $block2) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result i64) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $3 +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_3 ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 1 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 2 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $3) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_3) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $4) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $6 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 +;; CHECK-BIN-NODEBUG-NEXT: (block $block4 +;; CHECK-BIN-NODEBUG-NEXT: (block $block ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 (result exnref) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$4 +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 (result exnref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block2 ;; CHECK-BIN-NODEBUG-NEXT: (throw_ref -;; CHECK-BIN-NODEBUG-NEXT: (block $label$5 (result exnref) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$4 $label$2) (catch_ref $tag$4 $label$3) (catch_all $label$4) (catch_all_ref $label$5) +;; CHECK-BIN-NODEBUG-NEXT: (block $block3 (result exnref) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$4 $block) (catch_ref $tag$4 $block1) (catch_all $block2) (catch_all_ref $block3) ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: (call $0) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $7 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (local $0 (tuple i32 exnref)) -;; CHECK-BIN-NODEBUG-NEXT: (local $1 i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 exnref)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_1 i32) +;; CHECK-BIN-NODEBUG-NEXT: (block $block4 ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 (type $5) (result i32 exnref) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$4 -;; CHECK-BIN-NODEBUG-NEXT: (throw_ref -;; CHECK-BIN-NODEBUG-NEXT: (block $label$5 (result exnref) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$0 $label$2) (catch_ref $tag$0 $label$3) (catch_all $label$4) (catch_all_ref $label$5) -;; CHECK-BIN-NODEBUG-NEXT: (call $0) -;; CHECK-BIN-NODEBUG-NEXT: (call $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result i32) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_1 ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 (type $5) (result i32 exnref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block2 +;; CHECK-BIN-NODEBUG-NEXT: (throw_ref +;; CHECK-BIN-NODEBUG-NEXT: (block $block3 (result exnref) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$0 $block) (catch_ref $tag$0 $block1) (catch_all $block2) (catch_all_ref $block3) +;; CHECK-BIN-NODEBUG-NEXT: (call $0) +;; CHECK-BIN-NODEBUG-NEXT: (call $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $8 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (local $0 (tuple i32 i64 exnref)) -;; CHECK-BIN-NODEBUG-NEXT: (local $1 i64) -;; CHECK-BIN-NODEBUG-NEXT: (local $2 i32) -;; CHECK-BIN-NODEBUG-NEXT: (local $3 (tuple i32 i64)) -;; CHECK-BIN-NODEBUG-NEXT: (local $4 i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (local.set $3 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (type $1) (result i32 i64) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 (type $2) (result i32 i64 exnref) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$4 -;; CHECK-BIN-NODEBUG-NEXT: (throw_ref -;; CHECK-BIN-NODEBUG-NEXT: (block $label$5 (result exnref) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$2 $label$2) (catch_ref $tag$2 $label$3) (catch_all $label$4) (catch_all_ref $label$5) -;; CHECK-BIN-NODEBUG-NEXT: (call $0) -;; CHECK-BIN-NODEBUG-NEXT: (call $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $2 -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 0 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block (result i64) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 1 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 i64 exnref)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_1 i64) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_2 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_3 (tuple i32 i64)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_4 i32) +;; CHECK-BIN-NODEBUG-NEXT: (block $block4 +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_4 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch_3 +;; CHECK-BIN-NODEBUG-NEXT: (block $block (type $1) (result i32 i64) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 2 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_2 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 (type $2) (result i32 i64 exnref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block2 +;; CHECK-BIN-NODEBUG-NEXT: (throw_ref +;; CHECK-BIN-NODEBUG-NEXT: (block $block3 (result exnref) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$2 $block) (catch_ref $tag$2 $block1) (catch_all $block2) (catch_all_ref $block3) +;; CHECK-BIN-NODEBUG-NEXT: (call $0) +;; CHECK-BIN-NODEBUG-NEXT: (call $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block (result i64) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_1 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 2 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $4 -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $3) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $3) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_3) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $4) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $9 (type $4) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (result i32) (catch $tag$0 $label$1) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$2 +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (result i32) (catch $tag$0 $block) +;; CHECK-BIN-NODEBUG-NEXT: (br $block1 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1019,11 +1020,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $10 (type $3) (result exnref) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result exnref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result exnref) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch_all_ref $label$1) -;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$0 $label$2) +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch_all_ref $block) +;; CHECK-BIN-NODEBUG-NEXT: (try_table (catch $tag$0 $block1) ;; CHECK-BIN-NODEBUG-NEXT: (if ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: (then @@ -1037,8 +1038,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (ref.null noexn) diff --git a/test/lit/basic/fn_prolog_epilog.debugInfo.wast b/test/lit/basic/fn_prolog_epilog.debugInfo.wast index 636c3346e9b..511ea9ce8aa 100644 --- a/test/lit/basic/fn_prolog_epilog.debugInfo.wast +++ b/test/lit/basic/fn_prolog_epilog.debugInfo.wast @@ -45,9 +45,9 @@ ;; CHECK-BIN: (func $0 (type $0) ;; CHECK-BIN-NEXT: (nop) -;; CHECK-BIN-NEXT: (block $label$1 -;; CHECK-BIN-NEXT: (block $label$2 -;; CHECK-BIN-NEXT: (br $label$2) +;; CHECK-BIN-NEXT: (block +;; CHECK-BIN-NEXT: (block $block +;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (return) @@ -57,9 +57,9 @@ ;; CHECK-BIN-NODEBUG: (func $0 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (nop) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 -;; CHECK-BIN-NODEBUG-NEXT: (br $label$2) +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (return) diff --git a/test/lit/basic/min.wast b/test/lit/basic/min.wast index 44928cd832f..e4de0018546 100644 --- a/test/lit/basic/min.wast +++ b/test/lit/basic/min.wast @@ -76,7 +76,7 @@ ;; CHECK-BIN-NEXT: (local $n f32) ;; CHECK-BIN-NEXT: (local.tee $n ;; CHECK-BIN-NEXT: (f32.neg - ;; CHECK-BIN-NEXT: (block $label$1 (result f32) + ;; CHECK-BIN-NEXT: (block (result f32) ;; CHECK-BIN-NEXT: (i32.store ;; CHECK-BIN-NEXT: (local.get $k) ;; CHECK-BIN-NEXT: (local.get $p) @@ -127,21 +127,21 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $littleswitch (type $2) (param $x i32) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) - ;; CHECK-BIN-NEXT: (block $label$2 - ;; CHECK-BIN-NEXT: (block $label$3 - ;; CHECK-BIN-NEXT: (br_table $label$3 $label$2 $label$3 + ;; CHECK-BIN-NEXT: (block $block2 (result i32) + ;; CHECK-BIN-NEXT: (block $block1 + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (br_table $block $block1 $block ;; CHECK-BIN-NEXT: (i32.sub ;; CHECK-BIN-NEXT: (local.get $x) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block2 ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block2 ;; CHECK-BIN-NEXT: (i32.const 2) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -196,7 +196,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local $2 f32) ;; CHECK-BIN-NODEBUG-NEXT: (local.tee $2 ;; CHECK-BIN-NODEBUG-NEXT: (f32.neg -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result f32) +;; CHECK-BIN-NODEBUG-NEXT: (block (result f32) ;; CHECK-BIN-NODEBUG-NEXT: (i32.store ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) @@ -210,21 +210,21 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $2 (type $2) (param $0 i32) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 -;; CHECK-BIN-NODEBUG-NEXT: (br_table $label$3 $label$2 $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (block $block2 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (br_table $block $block1 $block ;; CHECK-BIN-NODEBUG-NEXT: (i32.sub ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block2 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block2 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/polymorphic_stack.wast b/test/lit/basic/polymorphic_stack.wast index fc743bf2aea..2ccaa51e7db 100644 --- a/test/lit/basic/polymorphic_stack.wast +++ b/test/lit/basic/polymorphic_stack.wast @@ -47,9 +47,7 @@ ;; CHECK-BIN: (import "env" "table" (table $timport$0 9 9 funcref)) ;; CHECK-BIN: (func $break-and-binary (type $0) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $break-and-binary (result i32) (block $x (result i32) @@ -227,15 +225,13 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $untaken-break-should-have-value (type $0) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) - ;; CHECK-BIN-NEXT: (block $label$2 - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (i32.const 0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: (block + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $untaken-break-should-have-value (result i32) (block $x (result i32) @@ -275,7 +271,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (block $label$2 (result i32) + ;; CHECK-BIN-NEXT: (block (result i32) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) @@ -314,10 +310,8 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $br_table_unreachable_to_also_unreachable (type $0) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) - ;; CHECK-BIN-NEXT: (block $label$2 (result i32) - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (block (result i32) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) (func $br_table_unreachable_to_also_unreachable (result i32) @@ -352,20 +346,19 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $untaken-br_if (type $0) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) - ;; CHECK-BIN-NEXT: (block $label$2 - ;; CHECK-BIN-NEXT: (if - ;; CHECK-BIN-NEXT: (i32.const 0) - ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (else - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (block + ;; CHECK-BIN-NEXT: (if + ;; CHECK-BIN-NEXT: (i32.const 0) + ;; CHECK-BIN-NEXT: (then + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (else + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $untaken-br_if (result i32) (block $label$8 (result i32) @@ -399,9 +392,7 @@ ;; CHECK-BIN-NODEBUG: (import "env" "table" (table $timport$0 9 9 funcref)) ;; CHECK-BIN-NODEBUG: (func $0 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $1 (type $1) (param $0 i32) (result i32) @@ -428,15 +419,13 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $5 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $6 (type $1) (param $0 i32) (result i32) @@ -448,7 +437,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -459,26 +448,23 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $7 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $8 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 -;; CHECK-BIN-NODEBUG-NEXT: (if -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) -;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (else -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (if +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) +;; CHECK-BIN-NODEBUG-NEXT: (then +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (else +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 22250c9a0b4..61497e6124f 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -944,25 +944,25 @@ ;; CHECK-BIN-NEXT: (i32.const 3) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$1 (result eqref) - ;; CHECK-BIN-NEXT: (br_if $label$1 + ;; CHECK-BIN-NEXT: (block $block (result eqref) + ;; CHECK-BIN-NEXT: (br_if $block ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$2 (result eqref) - ;; CHECK-BIN-NEXT: (br_if $label$2 + ;; CHECK-BIN-NEXT: (block $block1 (result eqref) + ;; CHECK-BIN-NEXT: (br_if $block1 ;; CHECK-BIN-NEXT: (global.get $global_eqref) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$3 (result eqref) + ;; CHECK-BIN-NEXT: (block $block2 (result eqref) ;; CHECK-BIN-NEXT: (ref.cast nullref - ;; CHECK-BIN-NEXT: (br_if $label$3 + ;; CHECK-BIN-NEXT: (br_if $block2 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) @@ -970,25 +970,25 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$4 (result funcref) - ;; CHECK-BIN-NEXT: (br_if $label$4 + ;; CHECK-BIN-NEXT: (block $block3 (result funcref) + ;; CHECK-BIN-NEXT: (br_if $block3 ;; CHECK-BIN-NEXT: (local.get $local_funcref) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$5 (result funcref) - ;; CHECK-BIN-NEXT: (br_if $label$5 + ;; CHECK-BIN-NEXT: (block $block4 (result funcref) + ;; CHECK-BIN-NEXT: (br_if $block4 ;; CHECK-BIN-NEXT: (global.get $global_funcref) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$6 (result funcref) + ;; CHECK-BIN-NEXT: (block $block5 (result funcref) ;; CHECK-BIN-NEXT: (ref.cast nullfuncref - ;; CHECK-BIN-NEXT: (br_if $label$6 + ;; CHECK-BIN-NEXT: (br_if $block5 ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) @@ -996,9 +996,9 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$7 (result funcref) + ;; CHECK-BIN-NEXT: (block $block6 (result funcref) ;; CHECK-BIN-NEXT: (ref.cast (ref $3) - ;; CHECK-BIN-NEXT: (br_if $label$7 + ;; CHECK-BIN-NEXT: (br_if $block6 ;; CHECK-BIN-NEXT: (ref.func $foo) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) @@ -1006,25 +1006,25 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$8 (result anyref) - ;; CHECK-BIN-NEXT: (br_if $label$8 + ;; CHECK-BIN-NEXT: (block $block7 (result anyref) + ;; CHECK-BIN-NEXT: (br_if $block7 ;; CHECK-BIN-NEXT: (local.get $local_anyref) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$9 (result anyref) - ;; CHECK-BIN-NEXT: (br_if $label$9 + ;; CHECK-BIN-NEXT: (block $block8 (result anyref) + ;; CHECK-BIN-NEXT: (br_if $block8 ;; CHECK-BIN-NEXT: (global.get $global_anyref) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$10 (result anyref) + ;; CHECK-BIN-NEXT: (block $block9 (result anyref) ;; CHECK-BIN-NEXT: (ref.cast nullref - ;; CHECK-BIN-NEXT: (br_if $label$10 + ;; CHECK-BIN-NEXT: (br_if $block9 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) @@ -1032,9 +1032,9 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$11 (result anyref) + ;; CHECK-BIN-NEXT: (block $block10 (result anyref) ;; CHECK-BIN-NEXT: (ref.cast eqref - ;; CHECK-BIN-NEXT: (br_if $label$11 + ;; CHECK-BIN-NEXT: (br_if $block10 ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) @@ -1042,9 +1042,9 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$12 (result anyref) + ;; CHECK-BIN-NEXT: (block $block11 (result anyref) ;; CHECK-BIN-NEXT: (ref.cast nullref - ;; CHECK-BIN-NEXT: (br_if $label$12 + ;; CHECK-BIN-NEXT: (br_if $block11 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) @@ -1052,67 +1052,67 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$13 (result eqref) + ;; CHECK-BIN-NEXT: (loop (result eqref) ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$14 (result eqref) + ;; CHECK-BIN-NEXT: (loop (result eqref) ;; CHECK-BIN-NEXT: (global.get $global_eqref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$15 (result eqref) + ;; CHECK-BIN-NEXT: (loop (result eqref) ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$16 (result funcref) + ;; CHECK-BIN-NEXT: (loop (result funcref) ;; CHECK-BIN-NEXT: (local.get $local_funcref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$17 (result funcref) + ;; CHECK-BIN-NEXT: (loop (result funcref) ;; CHECK-BIN-NEXT: (global.get $global_funcref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$18 (result funcref) + ;; CHECK-BIN-NEXT: (loop (result funcref) ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$19 (result funcref) + ;; CHECK-BIN-NEXT: (loop (result funcref) ;; CHECK-BIN-NEXT: (ref.func $foo) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$20 (result anyref) + ;; CHECK-BIN-NEXT: (loop (result anyref) ;; CHECK-BIN-NEXT: (local.get $local_anyref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$21 (result anyref) + ;; CHECK-BIN-NEXT: (loop (result anyref) ;; CHECK-BIN-NEXT: (global.get $global_anyref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$22 (result anyref) + ;; CHECK-BIN-NEXT: (loop (result anyref) ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$23 (result anyref) + ;; CHECK-BIN-NEXT: (loop (result anyref) ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$24 (result anyref) + ;; CHECK-BIN-NEXT: (loop (result anyref) ;; CHECK-BIN-NEXT: (global.get $global_eqref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (loop $label$25 (result anyref) + ;; CHECK-BIN-NEXT: (loop (result anyref) ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1185,7 +1185,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (try $label$40 (result eqref) + ;; CHECK-BIN-NEXT: (try (result eqref) ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: ) @@ -1198,7 +1198,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (try $label$43 (result funcref) + ;; CHECK-BIN-NEXT: (try (result funcref) ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (ref.func $foo) ;; CHECK-BIN-NEXT: ) @@ -1211,7 +1211,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (try $label$46 (result anyref) + ;; CHECK-BIN-NEXT: (try (result anyref) ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: ) @@ -1224,7 +1224,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (try $label$49 (result anyref) + ;; CHECK-BIN-NEXT: (try (result anyref) ;; CHECK-BIN-NEXT: (do ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) @@ -1237,11 +1237,11 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$50 (result eqref) + ;; CHECK-BIN-NEXT: (block $block13 (result eqref) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$51 (result i32) - ;; CHECK-BIN-NEXT: (br $label$50 - ;; CHECK-BIN-NEXT: (try_table (result eqref) (catch $e-i32 $label$51) + ;; CHECK-BIN-NEXT: (block $block12 (result i32) + ;; CHECK-BIN-NEXT: (br $block13 + ;; CHECK-BIN-NEXT: (try_table (result eqref) (catch $e-i32 $block12) ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1251,11 +1251,11 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$53 (result funcref) + ;; CHECK-BIN-NEXT: (block $block15 (result funcref) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$54 (result i32) - ;; CHECK-BIN-NEXT: (br $label$53 - ;; CHECK-BIN-NEXT: (try_table (result funcref) (catch $e-i32 $label$54) + ;; CHECK-BIN-NEXT: (block $block14 (result i32) + ;; CHECK-BIN-NEXT: (br $block15 + ;; CHECK-BIN-NEXT: (try_table (result funcref) (catch $e-i32 $block14) ;; CHECK-BIN-NEXT: (ref.func $foo) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1265,11 +1265,11 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$56 (result anyref) + ;; CHECK-BIN-NEXT: (block $block17 (result anyref) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$57 (result i32) - ;; CHECK-BIN-NEXT: (br $label$56 - ;; CHECK-BIN-NEXT: (try_table (result anyref) (catch $e-i32 $label$57) + ;; CHECK-BIN-NEXT: (block $block16 (result i32) + ;; CHECK-BIN-NEXT: (br $block17 + ;; CHECK-BIN-NEXT: (try_table (result anyref) (catch $e-i32 $block16) ;; CHECK-BIN-NEXT: (local.get $local_eqref) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1279,11 +1279,11 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$59 (result anyref) + ;; CHECK-BIN-NEXT: (block $block19 (result anyref) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (block $label$60 (result i32) - ;; CHECK-BIN-NEXT: (br $label$59 - ;; CHECK-BIN-NEXT: (try_table (result anyref) (catch $e-i32 $label$60) + ;; CHECK-BIN-NEXT: (block $block18 (result i32) + ;; CHECK-BIN-NEXT: (br $block19 + ;; CHECK-BIN-NEXT: (try_table (result anyref) (catch $e-i32 $block18) ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -2281,25 +2281,25 @@ ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 3) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result eqref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result eqref) +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (result eqref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$2 +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 (result eqref) +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block1 ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 (result eqref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block2 (result eqref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block2 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2307,25 +2307,25 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$4 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$4 +;; CHECK-BIN-NODEBUG-NEXT: (block $block3 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block3 ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$5 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$5 +;; CHECK-BIN-NODEBUG-NEXT: (block $block4 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block4 ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$1) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$6 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block5 (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullfuncref -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$6 +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block5 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2333,9 +2333,9 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$7 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block6 (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref $3) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$7 +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block6 ;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2343,25 +2343,25 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$8 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$8 +;; CHECK-BIN-NODEBUG-NEXT: (block $block7 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block7 ;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$9 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$9 +;; CHECK-BIN-NODEBUG-NEXT: (block $block8 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block8 ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$3) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$10 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block9 (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$10 +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block9 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2369,9 +2369,9 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$11 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block10 (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast eqref -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$11 +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block10 ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2379,9 +2379,9 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$12 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block11 (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$12 +;; CHECK-BIN-NODEBUG-NEXT: (br_if $block11 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2389,67 +2389,67 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$13 (result eqref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result eqref) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$14 (result eqref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result eqref) ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$15 (result eqref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result eqref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$16 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$17 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$18 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$19 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$20 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$21 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$3) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$22 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$23 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$24 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$25 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2522,7 +2522,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (try $label$40 (result eqref) +;; CHECK-BIN-NODEBUG-NEXT: (try (result eqref) ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2535,7 +2535,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (try $label$43 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (try (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2548,7 +2548,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (try $label$46 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (try (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2561,7 +2561,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (try $label$49 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (try (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2574,11 +2574,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$50 (result eqref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block13 (result eqref) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$51 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$50 -;; CHECK-BIN-NODEBUG-NEXT: (try_table (result eqref) (catch $tag$0 $label$51) +;; CHECK-BIN-NODEBUG-NEXT: (block $block12 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (br $block13 +;; CHECK-BIN-NODEBUG-NEXT: (try_table (result eqref) (catch $tag$0 $block12) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2588,11 +2588,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$53 (result funcref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block15 (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$54 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$53 -;; CHECK-BIN-NODEBUG-NEXT: (try_table (result funcref) (catch $tag$0 $label$54) +;; CHECK-BIN-NODEBUG-NEXT: (block $block14 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (br $block15 +;; CHECK-BIN-NODEBUG-NEXT: (try_table (result funcref) (catch $tag$0 $block14) ;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2602,11 +2602,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$56 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block17 (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$57 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$56 -;; CHECK-BIN-NODEBUG-NEXT: (try_table (result anyref) (catch $tag$0 $label$57) +;; CHECK-BIN-NODEBUG-NEXT: (block $block16 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (br $block17 +;; CHECK-BIN-NODEBUG-NEXT: (try_table (result anyref) (catch $tag$0 $block16) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2616,11 +2616,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$59 (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block19 (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (block $label$60 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$59 -;; CHECK-BIN-NODEBUG-NEXT: (try_table (result anyref) (catch $tag$0 $label$60) +;; CHECK-BIN-NODEBUG-NEXT: (block $block18 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (br $block19 +;; CHECK-BIN-NODEBUG-NEXT: (try_table (result anyref) (catch $tag$0 $block18) ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/reg_switch.wast b/test/lit/basic/reg_switch.wast index db749451145..a1d00e8aa1c 100644 --- a/test/lit/basic/reg_switch.wast +++ b/test/lit/basic/reg_switch.wast @@ -35,8 +35,8 @@ ;; CHECK-BIN-NEXT: (if ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (block $label$2 - ;; CHECK-BIN-NEXT: (br_table $label$2 + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (br_table $block ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -47,8 +47,8 @@ ;; CHECK-BIN-NODEBUG-NEXT: (if ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: (then - ;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 - ;; CHECK-BIN-NODEBUG-NEXT: (br_table $label$2 + ;; CHECK-BIN-NODEBUG-NEXT: (block $block + ;; CHECK-BIN-NODEBUG-NEXT: (br_table $block ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/typed_continuations_resume.wast b/test/lit/basic/typed_continuations_resume.wast index 5cd2277d311..07afb9388f3 100644 --- a/test/lit/basic/typed_continuations_resume.wast +++ b/test/lit/basic/typed_continuations_resume.wast @@ -61,31 +61,28 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $go (type $3) (param $x (ref $ct)) (result i32) - ;; CHECK-BIN-NEXT: (local $1 (tuple i32 (ref $ct))) - ;; CHECK-BIN-NEXT: (local $2 i32) - ;; CHECK-BIN-NEXT: (local.set $1 - ;; CHECK-BIN-NEXT: (block $label$1 (type $2) (result i32 (ref $ct)) - ;; CHECK-BIN-NEXT: (return - ;; CHECK-BIN-NEXT: (resume $ct (on $t $label$1) - ;; CHECK-BIN-NEXT: (i32.const 123) - ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: (local $scratch (tuple i32 (ref $ct))) + ;; CHECK-BIN-NEXT: (local $scratch_2 i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_2 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block $block (type $2) (result i32 (ref $ct)) + ;; CHECK-BIN-NEXT: (return + ;; CHECK-BIN-NEXT: (resume $ct (on $t $block) + ;; CHECK-BIN-NEXT: (i32.const 123) + ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (block (result i32) - ;; CHECK-BIN-NEXT: (local.set $2 - ;; CHECK-BIN-NEXT: (tuple.extract 2 0 - ;; CHECK-BIN-NEXT: (local.get $1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (tuple.extract 2 1 - ;; CHECK-BIN-NEXT: (local.get $1) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (tuple.extract 2 1 + ;; CHECK-BIN-NEXT: (local.get $scratch) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $2) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NEXT: ) (func $go (param $x (ref $ct)) (result i32) (tuple.extract 2 0 @@ -135,29 +132,26 @@ ;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32) (result i32)) ;; CHECK-BIN-NODEBUG: (func $0 (type $3) (param $0 (ref $1)) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local $1 (tuple i32 (ref $1))) -;; CHECK-BIN-NODEBUG-NEXT: (local $2 i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (type $2) (result i32 (ref $1)) -;; CHECK-BIN-NODEBUG-NEXT: (return -;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$0 $label$1) -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 (ref $1))) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_2 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_2 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (block $block (type $2) (result i32 (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (return +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$0 $block) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $2 -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/types-function-references.wast b/test/lit/basic/types-function-references.wast index 9500fa4c94c..8997e1a3af0 100644 --- a/test/lit/basic/types-function-references.wast +++ b/test/lit/basic/types-function-references.wast @@ -192,37 +192,36 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $type-only-in-tuple-block (type $void) - ;; CHECK-BIN-NEXT: (local $0 (tuple i32 (ref null $mixed_results) f64)) - ;; CHECK-BIN-NEXT: (local $1 (ref null $mixed_results)) - ;; CHECK-BIN-NEXT: (local $2 i32) - ;; CHECK-BIN-NEXT: (local.set $0 - ;; CHECK-BIN-NEXT: (block $label$1 (type $3) (result i32 (ref null $mixed_results) f64) - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local $scratch (tuple i32 (ref null $mixed_results) f64)) + ;; CHECK-BIN-NEXT: (local $scratch_1 (ref null $mixed_results)) + ;; CHECK-BIN-NEXT: (local $scratch_2 i32) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result i32) - ;; CHECK-BIN-NEXT: (local.set $2 + ;; CHECK-BIN-NEXT: (local.set $scratch_2 ;; CHECK-BIN-NEXT: (tuple.extract 3 0 - ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block (type $3) (result i32 (ref null $mixed_results) f64) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result (ref null $mixed_results)) - ;; CHECK-BIN-NEXT: (local.set $1 + ;; CHECK-BIN-NEXT: (local.set $scratch_1 ;; CHECK-BIN-NEXT: (tuple.extract 3 1 - ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: (local.get $scratch) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (tuple.extract 3 2 - ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: (local.get $scratch) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $1) + ;; CHECK-BIN-NEXT: (local.get $scratch_1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $2) + ;; CHECK-BIN-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -423,37 +422,36 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $8 (type $2) -;; CHECK-BIN-NODEBUG-NEXT: (local $0 (tuple i32 (ref null $0) f64)) -;; CHECK-BIN-NODEBUG-NEXT: (local $1 (ref null $0)) -;; CHECK-BIN-NODEBUG-NEXT: (local $2 i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (type $3) (result i32 (ref null $0) f64) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 (ref null $0) f64)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_1 (ref null $0)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_2 i32) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $2 +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_2 ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 0 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (block (type $3) (result i32 (ref null $0) f64) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result (ref null $0)) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_1 ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 1 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 3 2 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/unit.wat b/test/lit/basic/unit.wat index 5846febd55d..43901e3bc55 100644 --- a/test/lit/basic/unit.wat +++ b/test/lit/basic/unit.wat @@ -215,7 +215,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $importedDoubles (type $4) (result f64) ;; CHECK-BIN-NEXT: (local $temp f64) - ;; CHECK-BIN-NEXT: (block $label$1 (result f64) + ;; CHECK-BIN-NEXT: (block $block (result f64) ;; CHECK-BIN-NEXT: (local.set $temp ;; CHECK-BIN-NEXT: (f64.add ;; CHECK-BIN-NEXT: (f64.add @@ -248,7 +248,7 @@ ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block ;; CHECK-BIN-NEXT: (f64.const -3.4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -261,7 +261,7 @@ ;; CHECK-BIN-NEXT: (f64.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block ;; CHECK-BIN-NEXT: (f64.const 5.6) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -381,14 +381,14 @@ ;; CHECK-BIN-NEXT: (local $t f64) ;; CHECK-BIN-NEXT: (local $Int f64) ;; CHECK-BIN-NEXT: (local $Double i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result f64) + ;; CHECK-BIN-NEXT: (block $block (result f64) ;; CHECK-BIN-NEXT: (if ;; CHECK-BIN-NEXT: (f64.gt ;; CHECK-BIN-NEXT: (local.get $x) ;; CHECK-BIN-NEXT: (f64.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block ;; CHECK-BIN-NEXT: (f64.const 1.2) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -399,7 +399,7 @@ ;; CHECK-BIN-NEXT: (f64.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block ;; CHECK-BIN-NEXT: (f64.const -3.4) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -410,7 +410,7 @@ ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block ;; CHECK-BIN-NEXT: (f64.const 5.6) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -421,7 +421,7 @@ ;; CHECK-BIN-NEXT: (local.get $y) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block ;; CHECK-BIN-NEXT: (local.get $x) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -628,13 +628,13 @@ ;; CHECK-BIN-NEXT: (local $J f64) ;; CHECK-BIN-NEXT: (local.set $J ;; CHECK-BIN-NEXT: (f64.sub - ;; CHECK-BIN-NEXT: (block $label$1 (result f64) + ;; CHECK-BIN-NEXT: (block (result f64) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (f64.const 0.1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (f64.const 5.1) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (block $label$2 (result f64) + ;; CHECK-BIN-NEXT: (block (result f64) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (f64.const 3.2) ;; CHECK-BIN-NEXT: ) @@ -749,77 +749,80 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $switcher (type $6) (param $x i32) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) - ;; CHECK-BIN-NEXT: (block $label$2 - ;; CHECK-BIN-NEXT: (block $label$3 - ;; CHECK-BIN-NEXT: (block $label$4 - ;; CHECK-BIN-NEXT: (block $label$5 - ;; CHECK-BIN-NEXT: (br_table $label$5 $label$4 $label$3 + ;; CHECK-BIN-NEXT: (block $block3 (result i32) + ;; CHECK-BIN-NEXT: (block + ;; CHECK-BIN-NEXT: (block $block2 + ;; CHECK-BIN-NEXT: (block $block1 + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (br_table $block $block1 $block2 ;; CHECK-BIN-NEXT: (i32.sub ;; CHECK-BIN-NEXT: (local.get $x) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block3 ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block3 ;; CHECK-BIN-NEXT: (i32.const 2) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (block $label$6 - ;; CHECK-BIN-NEXT: (block $label$7 - ;; CHECK-BIN-NEXT: (block $label$8 - ;; CHECK-BIN-NEXT: (block $label$9 - ;; CHECK-BIN-NEXT: (br_table $label$8 $label$7 $label$7 $label$7 $label$7 $label$7 $label$7 $label$9 $label$7 + ;; CHECK-BIN-NEXT: (block + ;; CHECK-BIN-NEXT: (block $block5 + ;; CHECK-BIN-NEXT: (block $block4 + ;; CHECK-BIN-NEXT: (block $block6 + ;; CHECK-BIN-NEXT: (br_table $block4 $block5 $block5 $block5 $block5 $block5 $block5 $block6 $block5 ;; CHECK-BIN-NEXT: (i32.sub ;; CHECK-BIN-NEXT: (local.get $x) ;; CHECK-BIN-NEXT: (i32.const 5) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block3 ;; CHECK-BIN-NEXT: (i32.const 121) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1 + ;; CHECK-BIN-NEXT: (br $block3 ;; CHECK-BIN-NEXT: (i32.const 51) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (block $label$10 - ;; CHECK-BIN-NEXT: (block $label$11 - ;; CHECK-BIN-NEXT: (block $label$12 - ;; CHECK-BIN-NEXT: (block $label$13 - ;; CHECK-BIN-NEXT: (block $label$14 - ;; CHECK-BIN-NEXT: (block $label$15 - ;; CHECK-BIN-NEXT: (br_table $label$12 $label$11 $label$11 $label$13 $label$11 $label$11 $label$11 $label$11 $label$14 $label$11 $label$15 $label$11 + ;; CHECK-BIN-NEXT: (block $block12 + ;; CHECK-BIN-NEXT: (block $block8 + ;; CHECK-BIN-NEXT: (block $block7 + ;; CHECK-BIN-NEXT: (block $block9 + ;; CHECK-BIN-NEXT: (block $block10 + ;; CHECK-BIN-NEXT: (block $block11 + ;; CHECK-BIN-NEXT: (br_table $block7 $block8 $block8 $block9 $block8 $block8 $block8 $block8 $block10 $block8 $block11 $block8 ;; CHECK-BIN-NEXT: (i32.sub ;; CHECK-BIN-NEXT: (local.get $x) ;; CHECK-BIN-NEXT: (i32.const 2) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$10) + ;; CHECK-BIN-NEXT: (br $block12) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$10) + ;; CHECK-BIN-NEXT: (br $block12) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (block $label$16 - ;; CHECK-BIN-NEXT: (loop $label$17 - ;; CHECK-BIN-NEXT: (br $label$16) + ;; CHECK-BIN-NEXT: (block $block13 + ;; CHECK-BIN-NEXT: (loop + ;; CHECK-BIN-NEXT: (br $block13) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (block $label$18 - ;; CHECK-BIN-NEXT: (loop $label$19 - ;; CHECK-BIN-NEXT: (br $label$10) + ;; CHECK-BIN-NEXT: (block + ;; CHECK-BIN-NEXT: (loop + ;; CHECK-BIN-NEXT: (br $block12) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) @@ -918,8 +921,8 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $blocker (type $FUNCSIG$v) - ;; CHECK-BIN-NEXT: (block $label$1 - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) (func $blocker (type $FUNCSIG$v) @@ -1115,7 +1118,7 @@ ;; CHECK-BIN-NEXT: (local $y f64) ;; CHECK-BIN-NEXT: (local $z f32) ;; CHECK-BIN-NEXT: (local.set $x - ;; CHECK-BIN-NEXT: (block $label$1 (result i32) + ;; CHECK-BIN-NEXT: (block (result i32) ;; CHECK-BIN-NEXT: (local.set $asm2wasm_i32_temp ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) @@ -1337,11 +1340,11 @@ ;; CHECK-TEXT-NEXT: (i32.const 0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $block_and_after (type $5) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 + ;; CHECK-BIN-NEXT: (block $block ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) @@ -1363,7 +1366,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $loop-roundtrip (type $7) (param $0 f64) (result f64) - ;; CHECK-BIN-NEXT: (loop $label$1 (result f64) + ;; CHECK-BIN-NEXT: (loop (result f64) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) @@ -1514,11 +1517,11 @@ ;; CHECK-TEXT-NEXT: (i32.const 1) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $unreachable-block-with-br (type $5) (result i32) - ;; CHECK-BIN-NEXT: (block $label$1 + ;; CHECK-BIN-NEXT: (block $block ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) @@ -1560,6 +1563,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $unreachable-if (result i32) (f64.abs @@ -1603,6 +1607,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $unreachable-if-toplevel (result i32) (if ;; note no type - valid in binaryen IR, in wasm must be i32 @@ -1626,12 +1631,13 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $unreachable-loop (type $5) (result i32) - ;; CHECK-BIN-NEXT: (loop $label$1 + ;; CHECK-BIN-NEXT: (loop ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: (return ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $unreachable-loop (result i32) (f64.abs @@ -1651,11 +1657,12 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $unreachable-loop0 (type $5) (result i32) - ;; CHECK-BIN-NEXT: (loop $label$1 + ;; CHECK-BIN-NEXT: (loop ;; CHECK-BIN-NEXT: (return ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $unreachable-loop0 (result i32) (f64.abs @@ -1673,12 +1680,13 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $unreachable-loop-toplevel (type $5) (result i32) - ;; CHECK-BIN-NEXT: (loop $label$1 + ;; CHECK-BIN-NEXT: (loop ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: (return ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $unreachable-loop-toplevel (result i32) (loop ;; note no type - valid in binaryen IR, in wasm must be i32 @@ -1694,11 +1702,12 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $unreachable-loop0-toplevel (type $5) (result i32) - ;; CHECK-BIN-NEXT: (loop $label$1 + ;; CHECK-BIN-NEXT: (loop ;; CHECK-BIN-NEXT: (return ;; CHECK-BIN-NEXT: (i32.const 1) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $unreachable-loop0-toplevel (result i32) (loop ;; note no type - valid in binaryen IR, in wasm must be i32 @@ -1870,7 +1879,7 @@ ;; CHECK-BIN-NODEBUG: (func $1 (type $3) (result f64) ;; CHECK-BIN-NODEBUG-NEXT: (local $0 f64) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result f64) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result f64) ;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 ;; CHECK-BIN-NODEBUG-NEXT: (f64.add ;; CHECK-BIN-NODEBUG-NEXT: (f64.add @@ -1903,7 +1912,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block ;; CHECK-BIN-NODEBUG-NEXT: (f64.const -3.4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1916,7 +1925,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 5.6) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1929,14 +1938,14 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local $2 f64) ;; CHECK-BIN-NODEBUG-NEXT: (local $3 f64) ;; CHECK-BIN-NODEBUG-NEXT: (local $4 i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result f64) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result f64) ;; CHECK-BIN-NODEBUG-NEXT: (if ;; CHECK-BIN-NODEBUG-NEXT: (f64.gt ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 1.2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1947,7 +1956,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block ;; CHECK-BIN-NODEBUG-NEXT: (f64.const -3.4) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1958,7 +1967,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 5.6) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1969,7 +1978,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2025,13 +2034,13 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local $0 f64) ;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 ;; CHECK-BIN-NODEBUG-NEXT: (f64.sub -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result f64) +;; CHECK-BIN-NODEBUG-NEXT: (block (result f64) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 0.1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 5.1) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 (result f64) +;; CHECK-BIN-NODEBUG-NEXT: (block (result f64) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (f64.const 3.2) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2042,77 +2051,80 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $7 (type $6) (param $0 i32) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$4 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$5 -;; CHECK-BIN-NODEBUG-NEXT: (br_table $label$5 $label$4 $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (block $block3 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (block $block2 +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (br_table $block $block1 $block2 ;; CHECK-BIN-NODEBUG-NEXT: (i32.sub ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block3 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block3 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$6 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$7 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$8 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$9 -;; CHECK-BIN-NODEBUG-NEXT: (br_table $label$8 $label$7 $label$7 $label$7 $label$7 $label$7 $label$7 $label$9 $label$7 +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (block $block5 +;; CHECK-BIN-NODEBUG-NEXT: (block $block4 +;; CHECK-BIN-NODEBUG-NEXT: (block $block6 +;; CHECK-BIN-NODEBUG-NEXT: (br_table $block4 $block5 $block5 $block5 $block5 $block5 $block5 $block6 $block5 ;; CHECK-BIN-NODEBUG-NEXT: (i32.sub ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 5) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block3 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 121) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (br $block3 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 51) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$10 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$11 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$12 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$13 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$14 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$15 -;; CHECK-BIN-NODEBUG-NEXT: (br_table $label$12 $label$11 $label$11 $label$13 $label$11 $label$11 $label$11 $label$11 $label$14 $label$11 $label$15 $label$11 +;; CHECK-BIN-NODEBUG-NEXT: (block $block12 +;; CHECK-BIN-NODEBUG-NEXT: (block $block8 +;; CHECK-BIN-NODEBUG-NEXT: (block $block7 +;; CHECK-BIN-NODEBUG-NEXT: (block $block9 +;; CHECK-BIN-NODEBUG-NEXT: (block $block10 +;; CHECK-BIN-NODEBUG-NEXT: (block $block11 +;; CHECK-BIN-NODEBUG-NEXT: (br_table $block7 $block8 $block8 $block9 $block8 $block8 $block8 $block8 $block10 $block8 $block11 $block8 ;; CHECK-BIN-NODEBUG-NEXT: (i32.sub ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$10) +;; CHECK-BIN-NODEBUG-NEXT: (br $block12) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$10) +;; CHECK-BIN-NODEBUG-NEXT: (br $block12) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$16 -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$17 -;; CHECK-BIN-NODEBUG-NEXT: (br $label$16) +;; CHECK-BIN-NODEBUG-NEXT: (block $block13 +;; CHECK-BIN-NODEBUG-NEXT: (loop +;; CHECK-BIN-NODEBUG-NEXT: (br $block13) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$18 -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$19 -;; CHECK-BIN-NODEBUG-NEXT: (br $label$10) +;; CHECK-BIN-NODEBUG-NEXT: (block +;; CHECK-BIN-NODEBUG-NEXT: (loop +;; CHECK-BIN-NODEBUG-NEXT: (br $block12) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2121,8 +2133,8 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $8 (type $1) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2182,7 +2194,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local $2 f64) ;; CHECK-BIN-NODEBUG-NEXT: (local $3 f32) ;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) ;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2265,17 +2277,17 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $19 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (block $block ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $20 (type $7) (param $0 f64) (result f64) -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$1 (result f64) +;; CHECK-BIN-NODEBUG-NEXT: (loop (result f64) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2331,11 +2343,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $28 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (block $block ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2354,6 +2366,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $30 (type $0) (result i32) @@ -2370,40 +2383,45 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $31 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (loop ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: (return ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $32 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (loop ;; CHECK-BIN-NODEBUG-NEXT: (return ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $33 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (loop ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: (return ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $34 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (loop $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (loop ;; CHECK-BIN-NODEBUG-NEXT: (return ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $35 (type $1) diff --git a/test/lit/basic/unreachable-code.wast b/test/lit/basic/unreachable-code.wast index 0dfb00c4022..baf1564960f 100644 --- a/test/lit/basic/unreachable-code.wast +++ b/test/lit/basic/unreachable-code.wast @@ -59,6 +59,7 @@ ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $b (if (i32.const 1) @@ -118,6 +119,7 @@ ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $b-block (block @@ -186,6 +188,7 @@ ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $b-prepost (nop) @@ -260,6 +263,7 @@ ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $b-block-prepost (nop) @@ -289,9 +293,9 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $recurse (type $0) ;; CHECK-BIN-NEXT: (nop) - ;; CHECK-BIN-NEXT: (block $label$1 + ;; CHECK-BIN-NEXT: (block $block ;; CHECK-BIN-NEXT: (nop) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) @@ -319,12 +323,13 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $recurse-b (type $0) - ;; CHECK-BIN-NEXT: (block $label$1 + ;; CHECK-BIN-NEXT: (block $block ;; CHECK-BIN-NEXT: (nop) - ;; CHECK-BIN-NEXT: (block $label$2 + ;; CHECK-BIN-NEXT: (block ;; CHECK-BIN-NEXT: (nop) - ;; CHECK-BIN-NEXT: (br $label$1) + ;; CHECK-BIN-NEXT: (br $block) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) (func $recurse-b @@ -360,6 +365,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $2 (type $0) @@ -381,6 +387,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $4 (type $0) @@ -405,6 +412,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $6 (type $0) @@ -429,23 +437,25 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $8 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (nop) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (block $block ;; CHECK-BIN-NODEBUG-NEXT: (nop) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $9 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 +;; CHECK-BIN-NODEBUG-NEXT: (block $block ;; CHECK-BIN-NODEBUG-NEXT: (nop) -;; CHECK-BIN-NODEBUG-NEXT: (block $label$2 +;; CHECK-BIN-NODEBUG-NEXT: (block ;; CHECK-BIN-NODEBUG-NEXT: (nop) -;; CHECK-BIN-NODEBUG-NEXT: (br $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (br $block) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/untaken-br_if.wast b/test/lit/basic/untaken-br_if.wast index bf2ad1ce3db..4d982654977 100644 --- a/test/lit/basic/untaken-br_if.wast +++ b/test/lit/basic/untaken-br_if.wast @@ -37,12 +37,10 @@ ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (else - ;; CHECK-BIN-NEXT: (block $label$3 (result f32) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (f32.const 1) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (f32.const 1) ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -72,12 +70,10 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (else -;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 (result f32) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (f32.const 1) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (f32.const 1) ;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/binary/bad-delegate.test b/test/lit/binary/bad-delegate.test deleted file mode 100644 index 8e6a011a548..00000000000 --- a/test/lit/binary/bad-delegate.test +++ /dev/null @@ -1,17 +0,0 @@ -;; Test that we error properly on a file with a bad delegate (a delegate of an -;; index that does not refer to a valid try-catch). - -;; Disassembled binary from wabt: -;; -;; (module -;; (type (;0;) (func)) -;; (func (;0;) (type 0) -;; block ;; label = @1 -;; try ;; label = @2 -;; nop -;; delegate 0 -;; end)) - -;; RUN: not wasm-opt -all %s.wasm 2>&1 | filecheck %s - -;; CHECK: exceptionTargetNames not empty - invalid delegate diff --git a/test/lit/binary/declarative-element-use-expr.test b/test/lit/binary/declarative-element-use-expr.test index aecdf9ebde9..fa0eb965f52 100644 --- a/test/lit/binary/declarative-element-use-expr.test +++ b/test/lit/binary/declarative-element-use-expr.test @@ -13,16 +13,14 @@ ;; preserve declarative segments. This is fine, as we test that the ;; binary parser can parse it correctly. -;; RUN: wasm-opt -all %s.wasm -all --print | filecheck %s +;; RUN: wasm-opt -all %s.wasm -S -o - | filecheck %s ;; CHECK: (module ;; CHECK-NEXT: (type $0 (func)) ;; CHECK-NEXT: (elem declare func $0) ;; CHECK-NEXT: (func $0 (type $0) -;; CHECK-NEXT: (block $label$1 -;; CHECK-NEXT: (drop -;; CHECK-NEXT: (ref.func $0) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (ref.func $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/binary/delegate-block.test b/test/lit/binary/delegate-block.test new file mode 100644 index 00000000000..7da386cb0c1 --- /dev/null +++ b/test/lit/binary/delegate-block.test @@ -0,0 +1,27 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Test that we can parse a binary with a delegate that targets a block instead +;; of a try-catch. + +;; Disassembled binary from wabt: +;; +;; (module +;; (type (;0;) (func)) +;; (func (;0;) (type 0) +;; block ;; label = @1 +;; try ;; label = @2 +;; nop +;; delegate 0 +;; end)) + +;; RUN: wasm-opt -all %s.wasm -S -o - | filecheck %s +;; CHECK: (type $0 (func)) + +;; CHECK: (func $0 (type $0) +;; CHECK-NEXT: (try +;; CHECK-NEXT: (do +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (delegate 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) diff --git a/test/lit/binary/bad-delegate.test.wasm b/test/lit/binary/delegate-block.test.wasm similarity index 100% rename from test/lit/binary/bad-delegate.test.wasm rename to test/lit/binary/delegate-block.test.wasm diff --git a/test/lit/binary/dwarf-multivalue.test b/test/lit/binary/dwarf-multivalue.test index c803dea1400..250a861bf3c 100644 --- a/test/lit/binary/dwarf-multivalue.test +++ b/test/lit/binary/dwarf-multivalue.test @@ -39,8 +39,8 @@ ;; (local $10 f32) ;; If we parse this wasm file into Binaryen IR, two locals are added in the -;; process. Here $11 is added for tuple parsing and $12 is added for stacky IR -;; resolving during binary reading process. +;; process. Here $scratch is added for tuple parsing and $scratch_12 is added +;; for stacky IR resolving during binary reading process. ;; RUN: wasm-dis %s.wasm -o - | filecheck %s --check-prefix=ORIG ;; ORIG: (func $test ;; ORIG-NEXT: (local $0 i32) @@ -54,8 +54,8 @@ ;; ORIG-NEXT: (local $8 f32) ;; ORIG-NEXT: (local $9 i32) ;; ORIG-NEXT: (local $10 f32) -;; ORIG-NEXT: (local $11 (tuple i32 f32)) -;; ORIG-NEXT: (local $12 i32) +;; ORIG-NEXT: (local $scratch (tuple i32 f32)) +;; ORIG-NEXT: (local $scratch_12 i32) ;; If we write this IR into binary, even if this cannot be displayed in the wast ;; format, the local order of $test will look like this, because we don't @@ -92,11 +92,11 @@ ;; ROUNDTRIP-NEXT: (local $8 f32) ;; ROUNDTRIP-NEXT: (local $9 i32) ;; ROUNDTRIP-NEXT: (local $10 f32) -;; ROUNDTRIP-NEXT: (local $11 i32) +;; ROUNDTRIP-NEXT: (local $scratch i32) ;; ROUNDTRIP-NEXT: (local $12 f32) -;; ROUNDTRIP-NEXT: (local $13 i32) -;; ROUNDTRIP-NEXT: (local $14 (tuple i32 f32)) -;; ROUNDTRIP-NEXT: (local $15 i32) +;; ROUNDTRIP-NEXT: (local $scratch_12 i32) +;; ROUNDTRIP-NEXT: (local $scratch_14 (tuple i32 f32)) +;; ROUNDTRIP-NEXT: (local $scratch_15 i32) ;; We can see that we don't reorder the locals during the process and the ;; original list of locals, local $0~$10, is untouched, to NOT invalidate DWARF diff --git a/test/lit/binary/stacky-eh-legacy.test b/test/lit/binary/stacky-eh-legacy.test index c22a5165d5d..c77435f0b10 100644 --- a/test/lit/binary/stacky-eh-legacy.test +++ b/test/lit/binary/stacky-eh-legacy.test @@ -1,3 +1,4 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; Verify stacky EH binary can be parsed correctly. ;; ;; stacky-eh-old.test.wasm contains below: @@ -34,15 +35,21 @@ ;; The fixup will hoist the 'pop' and create another local to store it right ;; after 'catch'. -RUN: wasm-opt -all %s.wasm --print | filecheck %s +;; RUN: wasm-opt -all %s.wasm -S -o - | filecheck %s -;; CHECK: (func $0 +;; CHECK: (type $0 (func (param i32))) + +;; CHECK: (type $1 (func)) + +;; CHECK: (tag $tag$0 (param i32)) + +;; CHECK: (func $0 (type $1) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) -;; CHECK-NEXT: (local $3 i32) +;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local $4 i32) -;; CHECK-NEXT: (try $label$3 +;; CHECK-NEXT: (try ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) @@ -52,13 +59,13 @@ RUN: wasm-opt -all %s.wasm --print | filecheck %s ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (block (result i32) -;; CHECK-NEXT: (local.set $3 +;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.get $3) +;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/binary/stacky-nn-tuple.test b/test/lit/binary/stacky-nn-tuple.test index e8a0475b596..1f6b5bb59eb 100644 --- a/test/lit/binary/stacky-nn-tuple.test +++ b/test/lit/binary/stacky-nn-tuple.test @@ -1,112 +1,120 @@ -# Verify stacky non-nullable tuples binary can be parsed correctly. The wasm -# contains code that uses pops to get a tuple and store it in a local, then -# reads those values. The file contains this: -# -# (module -# (type $A (struct (field (mut i32)))) -# (type $B (struct (field (mut i32)) (field (mut i32)))) -# (tag $tag$0 (param (ref $A) (ref $B))) -# (func $foo -# (local $temp ((ref null $A) (ref null $B))) -# (try $label$3 -# (do -# (nop) -# ) -# (catch $tag$0 -# (local.set $temp -# (pop (ref $A) (ref $B)) -# ) -# (drop -# (ref.as_non_null -# (tuple.extract 0 -# (local.get $temp) -# ) -# ) -# ) -# (drop -# (ref.as_non_null -# (tuple.extract 1 -# (local.get $temp) -# ) -# ) -# ) -# (unreachable) -# ) -# ) -# ) -# ) +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; Verify stacky non-nullable tuples binary can be parsed correctly. The wasm +;; contains code that uses pops to get a tuple and store it in a local, then +;; reads those values. The file contains this: +;; +;; (module +;; (type $A (struct (field (mut i32)))) +;; (type $B (struct (field (mut i32)) (field (mut i32)))) +;; (tag $tag$0 (param (ref $A) (ref $B))) +;; (func $foo +;; (local $temp ((ref null $A) (ref null $B))) +;; (try $label$3 +;; (do +;; (nop) +;; ) +;; (catch $tag$0 +;; (local.set $temp +;; (pop (ref $A) (ref $B)) +;; ) +;; (drop +;; (ref.as_non_null +;; (tuple.extract 0 +;; (local.get $temp) +;; ) +;; ) +;; ) +;; (drop +;; (ref.as_non_null +;; (tuple.extract 1 +;; (local.get $temp) +;; ) +;; ) +;; ) +;; (unreachable) +;; ) +;; ) +;; ) +;; ) -RUN: wasm-opt -all %s.wasm -all --print +;; RUN: wasm-opt -all %s.wasm -all -S -o - | filecheck %s -# CHECK: (module -# CHECK-NEXT: (type ${mut:i32} (struct (field (mut i32)))) -# CHECK-NEXT: (type ${mut:i32_mut:i32} (struct (field (mut i32)) (field (mut i32)))) -# CHECK-NEXT: (type $ref|{mut:i32}|_ref|{mut:i32_mut:i32}|_=>_none (func (param (ref ${mut:i32}) (ref ${mut:i32_mut:i32})))) -# CHECK-NEXT: (type $none_=>_none (func)) -# CHECK-NEXT: (tag $tag$0 (param (ref ${mut:i32}) (ref ${mut:i32_mut:i32}))) -# CHECK-NEXT: (func $0 -# CHECK-NEXT: (local $0 (ref null ${mut:i32})) -# CHECK-NEXT: (local $1 (ref null ${mut:i32_mut:i32})) -# CHECK-NEXT: (local $2 (ref null ${mut:i32_mut:i32})) -# CHECK-NEXT: (local $3 ((ref ${mut:i32}) (ref ${mut:i32_mut:i32}))) -# CHECK-NEXT: (local $4 (ref ${mut:i32})) -# CHECK-NEXT: (local $5 (ref null ${mut:i32})) -# CHECK-NEXT: (local $6 (ref null ${mut:i32})) -# CHECK-NEXT: (try $label$3 -# CHECK-NEXT: (do -# CHECK-NEXT: (nop) -# CHECK-NEXT: ) -# CHECK-NEXT: (catch $tag$0 -# CHECK-NEXT: (local.set $3 -# CHECK-NEXT: (pop (ref ${mut:i32}) (ref ${mut:i32_mut:i32})) -# CHECK-NEXT: ) -# CHECK-NEXT: (local.set $0 -# CHECK-NEXT: (block (result (ref ${mut:i32})) -# CHECK-NEXT: (local.set $4 -# CHECK-NEXT: (tuple.extract 0 -# CHECK-NEXT: (local.get $3) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: (local.set $1 -# CHECK-NEXT: (tuple.extract 1 -# CHECK-NEXT: (local.get $3) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: (local.get $4) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: (drop -# CHECK-NEXT: (ref.as_non_null -# CHECK-NEXT: (block (result (ref null ${mut:i32})) -# CHECK-NEXT: (local.set $5 -# CHECK-NEXT: (local.get $0) -# CHECK-NEXT: ) -# CHECK-NEXT: (drop -# CHECK-NEXT: (local.get $1) -# CHECK-NEXT: ) -# CHECK-NEXT: (local.get $5) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: (drop -# CHECK-NEXT: (block (result (ref null ${mut:i32})) -# CHECK-NEXT: (local.set $6 -# CHECK-NEXT: (local.get $0) -# CHECK-NEXT: ) -# CHECK-NEXT: (local.set $2 -# CHECK-NEXT: (local.get $1) -# CHECK-NEXT: ) -# CHECK-NEXT: (local.get $6) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: (drop -# CHECK-NEXT: (ref.as_non_null -# CHECK-NEXT: (local.get $2) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: (unreachable) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: ) -# CHECK-NEXT: +;; CHECK: (type $0 (struct (field (mut i32)))) + +;; CHECK: (type $1 (struct (field (mut i32)) (field (mut i32)))) + +;; CHECK: (type $2 (func (param (ref null $0) (ref null $1)))) + +;; CHECK: (type $3 (func)) + +;; CHECK: (tag $tag$0 (param (ref null $0) (ref null $1))) + +;; CHECK: (func $0 (type $3) +;; CHECK-NEXT: (local $0 (ref null $0)) +;; CHECK-NEXT: (local $1 (ref null $1)) +;; CHECK-NEXT: (local $2 (ref null $1)) +;; CHECK-NEXT: (local $scratch (tuple (ref null $0) (ref null $1))) +;; CHECK-NEXT: (local $scratch_4 (ref null $0)) +;; CHECK-NEXT: (local $scratch_5 (ref null $0)) +;; CHECK-NEXT: (local $scratch_6 (ref null $0)) +;; CHECK-NEXT: (local $7 (tuple (ref null $0) (ref null $1))) +;; CHECK-NEXT: (try +;; CHECK-NEXT: (do +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (catch $tag$0 +;; CHECK-NEXT: (local.set $7 +;; CHECK-NEXT: (pop (tuple (ref null $0) (ref null $1))) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (block +;; CHECK-NEXT: (local.set $0 +;; CHECK-NEXT: (block (result (ref null $0)) +;; CHECK-NEXT: (local.set $scratch_4 +;; CHECK-NEXT: (tuple.extract 2 0 +;; CHECK-NEXT: (local.tee $scratch +;; CHECK-NEXT: (local.get $7) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $1 +;; CHECK-NEXT: (tuple.extract 2 1 +;; CHECK-NEXT: (local.get $scratch) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $scratch_4) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (ref.as_non_null +;; CHECK-NEXT: (block (result (ref null $0)) +;; CHECK-NEXT: (local.set $scratch_5 +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $scratch_5) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (block (result (ref null $0)) +;; CHECK-NEXT: (local.set $scratch_6 +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $2 +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $scratch_6) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (ref.as_non_null +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) diff --git a/test/lit/blocktype.wast b/test/lit/blocktype.wast index 0150f0a37c0..805d5d07e51 100644 --- a/test/lit/blocktype.wast +++ b/test/lit/blocktype.wast @@ -25,30 +25,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; RTRIP: (func $f1 (type $f1) (result (ref $f1) (ref $f2)) - ;; RTRIP-NEXT: (local $0 (tuple (ref $f1) (ref $f2))) - ;; RTRIP-NEXT: (local $1 (tuple (ref $f1) (ref $f2))) - ;; RTRIP-NEXT: (local.set $1 - ;; RTRIP-NEXT: (loop $label$1 (type $f1) (result (ref $f1) (ref $f2)) - ;; RTRIP-NEXT: (local.set $0 - ;; RTRIP-NEXT: (call $f1) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (tuple.make 2 - ;; RTRIP-NEXT: (tuple.extract 2 0 - ;; RTRIP-NEXT: (local.get $0) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (tuple.extract 2 1 - ;; RTRIP-NEXT: (local.get $0) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (tuple.make 2 - ;; RTRIP-NEXT: (tuple.extract 2 0 - ;; RTRIP-NEXT: (local.get $1) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (tuple.extract 2 1 - ;; RTRIP-NEXT: (local.get $1) - ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (loop (type $f1) (result (ref $f1) (ref $f2)) + ;; RTRIP-NEXT: (call $f1) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) (func $f1 (type $f1) (result (ref $f1) (ref $f2)) @@ -64,30 +42,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; RTRIP: (func $f2 (type $f2) (result (ref $f2) (ref $f1)) - ;; RTRIP-NEXT: (local $0 (tuple (ref $f2) (ref $f1))) - ;; RTRIP-NEXT: (local $1 (tuple (ref $f2) (ref $f1))) - ;; RTRIP-NEXT: (local.set $1 - ;; RTRIP-NEXT: (loop $label$1 (type $f2) (result (ref $f2) (ref $f1)) - ;; RTRIP-NEXT: (local.set $0 - ;; RTRIP-NEXT: (call $f2) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (tuple.make 2 - ;; RTRIP-NEXT: (tuple.extract 2 0 - ;; RTRIP-NEXT: (local.get $0) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (tuple.extract 2 1 - ;; RTRIP-NEXT: (local.get $0) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (tuple.make 2 - ;; RTRIP-NEXT: (tuple.extract 2 0 - ;; RTRIP-NEXT: (local.get $1) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (tuple.extract 2 1 - ;; RTRIP-NEXT: (local.get $1) - ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (loop (type $f2) (result (ref $f2) (ref $f1)) + ;; RTRIP-NEXT: (call $f2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) (func $f2 (type $f2) (result (ref $f2) (ref $f1)) diff --git a/test/lit/cast-and-recast-tuple.wast b/test/lit/cast-and-recast-tuple.wast index 3febcbff7ef..6ceefe7d605 100644 --- a/test/lit/cast-and-recast-tuple.wast +++ b/test/lit/cast-and-recast-tuple.wast @@ -16,46 +16,34 @@ (type $B (sub $A (struct))) ) - ;; CHECK: (func $test-local-tuple-1 (type $5) (param $B (ref $B)) (param $x i32) (result anyref i32) - ;; CHECK-NEXT: (local $2 (tuple (ref $B) i32)) - ;; CHECK-NEXT: (local $3 (ref $B)) - ;; CHECK-NEXT: (local $4 (tuple (ref $A) i32)) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (block $label$1 (type $3) (result (ref $A) i32) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (br_if $label$1 - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $B)) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $2) + ;; CHECK: (func $test-local-tuple-1 (type $3) (param $B (ref $B)) (param $x i32) (result anyref i32) + ;; CHECK-NEXT: (local $scratch (tuple (ref $B) i32)) + ;; CHECK-NEXT: (local $scratch_3 (ref $B)) + ;; CHECK-NEXT: (block $block (type $2) (result (ref $A) i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_3) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test-local-tuple-1 (param $B (ref $B)) (param $x i32) (result anyref i32) @@ -77,48 +65,36 @@ ) ) - ;; CHECK: (func $test-local-tuple-2 (type $9) (param $B (ref $B)) (param $x i32) (result i32 i32) + ;; CHECK: (func $test-local-tuple-2 (type $7) (param $B (ref $B)) (param $x i32) (result i32 i32) ;; CHECK-NEXT: (local $temp i32) ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local $4 (tuple i32 i32)) - ;; CHECK-NEXT: (local $5 i32) - ;; CHECK-NEXT: (local $6 (tuple i32 i32)) - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (block $label$1 (type $4) (result i32 i32) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (br_if $label$1 - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (i32.const -1) - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $temp - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (local $scratch (tuple i32 i32)) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (block $block (type $4) (result i32 i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $6) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $6) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test-local-tuple-2 (param $B (ref $B)) (param $x i32) (result i32 i32) @@ -139,48 +115,36 @@ ) ) - ;; CHECK: (func $test-local-tuple-3 (type $5) (param $B (ref $B)) (param $x i32) (result anyref i32) + ;; CHECK: (func $test-local-tuple-3 (type $3) (param $B (ref $B)) (param $x i32) (result anyref i32) ;; CHECK-NEXT: (local $temp (ref $B)) ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local $4 (tuple (ref $B) i32)) - ;; CHECK-NEXT: (local $5 (ref $B)) - ;; CHECK-NEXT: (local $6 (tuple (ref $B) i32)) - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (block $label$1 (type $6) (result (ref $B) i32) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (br_if $label$1 - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $temp - ;; CHECK-NEXT: (block (result (ref $B)) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (local $scratch (tuple (ref $B) i32)) + ;; CHECK-NEXT: (local $scratch_5 (ref $B)) + ;; CHECK-NEXT: (block $block (type $5) (result (ref $B) i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $6) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $6) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test-local-tuple-3 (param $B (ref $B)) (param $x i32) (result anyref i32) @@ -201,64 +165,52 @@ ) ) - ;; CHECK: (func $test-local-tuple-4-bad (type $5) (param $B (ref $B)) (param $x i32) (result anyref i32) + ;; CHECK: (func $test-local-tuple-4-bad (type $3) (param $B (ref $B)) (param $x i32) (result anyref i32) ;; CHECK-NEXT: (local $temp (ref $B)) ;; CHECK-NEXT: (local $3 (ref $A)) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) - ;; CHECK-NEXT: (local $6 (tuple (ref $B) i32)) - ;; CHECK-NEXT: (local $7 (ref $B)) - ;; CHECK-NEXT: (local $8 (ref $B)) - ;; CHECK-NEXT: (local $9 (tuple (ref $A) i32)) - ;; CHECK-NEXT: (local.set $9 - ;; CHECK-NEXT: (block $label$1 (type $3) (result (ref $A) i32) - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (br_if $label$1 - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (block (result (ref $B)) - ;; CHECK-NEXT: (local.set $7 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (local $scratch (tuple (ref $B) i32)) + ;; CHECK-NEXT: (local $scratch_7 (ref $B)) + ;; CHECK-NEXT: (local $scratch_8 (ref $B)) + ;; CHECK-NEXT: (block $block (type $2) (result (ref $A) i32) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $6) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $temp - ;; CHECK-NEXT: (block (result (ref $B)) - ;; CHECK-NEXT: (local.set $8 - ;; CHECK-NEXT: (ref.cast (ref $B) - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $8) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_8) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $9) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $9) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test-local-tuple-4-bad (param $B (ref $B)) (param $x i32) (result anyref i32) @@ -284,7 +236,7 @@ ) ) - ;; CHECK: (func $test-local-tuple-4-bad-dupes (type $10) (param $B (ref $B)) (param $x i32) (result i32 anyref i32) + ;; CHECK: (func $test-local-tuple-4-bad-dupes (type $8) (param $B (ref $B)) (param $x i32) (result i32 anyref i32) ;; CHECK-NEXT: (local $temp (ref $B)) ;; CHECK-NEXT: (local $3 (ref $B)) ;; CHECK-NEXT: (local $4 (ref $A)) @@ -293,97 +245,82 @@ ;; CHECK-NEXT: (local $7 i32) ;; CHECK-NEXT: (local $8 i32) ;; CHECK-NEXT: (local $9 i32) - ;; CHECK-NEXT: (local $10 (tuple i32 (ref $B) i32)) - ;; CHECK-NEXT: (local $11 (ref $B)) - ;; CHECK-NEXT: (local $12 i32) - ;; CHECK-NEXT: (local $13 (ref $B)) - ;; CHECK-NEXT: (local $14 i32) - ;; CHECK-NEXT: (local $15 (ref $B)) - ;; CHECK-NEXT: (local $16 (tuple i32 (ref $A) i32)) - ;; CHECK-NEXT: (local.set $16 - ;; CHECK-NEXT: (block $label$1 (type $7) (result i32 (ref $A) i32) - ;; CHECK-NEXT: (local.set $10 - ;; CHECK-NEXT: (br_if $label$1 - ;; CHECK-NEXT: (tuple.make 3 - ;; CHECK-NEXT: (i32.const -3) - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $9 - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $12 - ;; CHECK-NEXT: (tuple.extract 3 0 - ;; CHECK-NEXT: (local.get $10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (block (result (ref $B)) - ;; CHECK-NEXT: (local.set $11 - ;; CHECK-NEXT: (tuple.extract 3 1 - ;; CHECK-NEXT: (local.get $10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $8 - ;; CHECK-NEXT: (tuple.extract 3 2 - ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: (local $scratch_10 (tuple i32 (ref $B) i32)) + ;; CHECK-NEXT: (local $scratch_11 (ref $B)) + ;; CHECK-NEXT: (local $scratch_12 i32) + ;; CHECK-NEXT: (local $scratch_13 (ref $B)) + ;; CHECK-NEXT: (local $scratch_14 i32) + ;; CHECK-NEXT: (local $scratch_15 (ref $B)) + ;; CHECK-NEXT: (block $block (type $6) (result i32 (ref $A) i32) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_12 + ;; CHECK-NEXT: (tuple.extract 3 0 + ;; CHECK-NEXT: (local.tee $scratch_10 + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (i32.const -3) + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $11) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $12) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $scratch - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $14 - ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $scratch_11 + ;; CHECK-NEXT: (tuple.extract 3 1 + ;; CHECK-NEXT: (local.get $scratch_10) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (block (result (ref $B)) - ;; CHECK-NEXT: (local.set $13 - ;; CHECK-NEXT: (ref.cast (ref $B) - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $7 - ;; CHECK-NEXT: (local.get $8) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (tuple.extract 3 2 + ;; CHECK-NEXT: (local.get $scratch_10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: (local.get $scratch_11) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_12) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $temp - ;; CHECK-NEXT: (block (result (ref $B)) - ;; CHECK-NEXT: (local.set $15 - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_14 + ;; CHECK-NEXT: (local.get $9) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $scratch_13 + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_13) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $15) + ;; CHECK-NEXT: (local.get $scratch_14) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 3 - ;; CHECK-NEXT: (tuple.extract 3 0 - ;; CHECK-NEXT: (local.get $16) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 3 1 - ;; CHECK-NEXT: (local.get $16) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 3 2 - ;; CHECK-NEXT: (local.get $16) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $scratch_15 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_15) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test-local-tuple-4-bad-dupes (param $B (ref $B)) (param $x i32) (result i32 anyref i32) diff --git a/test/lit/cast-and-recast.wast b/test/lit/cast-and-recast.wast index a2fe4371146..ce2af9d9845 100644 --- a/test/lit/cast-and-recast.wast +++ b/test/lit/cast-and-recast.wast @@ -19,9 +19,9 @@ ) ;; CHECK: (func $test (type $3) (param $B (ref $B)) (param $x i32) (result anyref) - ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) ;; CHECK-NEXT: (ref.cast (ref $B) - ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -41,9 +41,9 @@ ) ;; CHECK: (func $test-cast (type $3) (param $B (ref $B)) (param $x i32) (result anyref) - ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) ;; CHECK-NEXT: (ref.cast (ref $B) - ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -64,9 +64,9 @@ ) ;; CHECK: (func $test-cast-more (type $3) (param $B (ref $B)) (param $x i32) (result anyref) - ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) ;; CHECK-NEXT: (ref.cast (ref $C) - ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -87,9 +87,9 @@ ) ;; CHECK: (func $test-cast-less (type $3) (param $B (ref $B)) (param $x i32) (result anyref) - ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) ;; CHECK-NEXT: (ref.cast (ref $B) - ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -112,10 +112,10 @@ ;; CHECK: (func $test-local (type $3) (param $B (ref $B)) (param $x i32) (result anyref) ;; CHECK-NEXT: (local $temp (ref $B)) - ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (ref.cast (ref $B) - ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -140,9 +140,9 @@ ) ;; CHECK: (func $test-drop (type $3) (param $B (ref $B)) (param $x i32) (result anyref) - ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -164,8 +164,8 @@ ) ;; CHECK: (func $test-same (type $4) (param $A (ref $A)) (param $x i32) (result anyref) - ;; CHECK-NEXT: (block $label$1 (result (ref $A)) - ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (block $block (result (ref $A)) + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) diff --git a/test/lit/cast-to-basic.wast b/test/lit/cast-to-basic.wast index 4c2e7c047a0..7c45cf8a3e4 100644 --- a/test/lit/cast-to-basic.wast +++ b/test/lit/cast-to-basic.wast @@ -33,9 +33,9 @@ ;; CHECK: (func $br (type $0) (param $anyref anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label$1 (result structref) + ;; CHECK-NEXT: (block $block (result structref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label$1 anyref (ref struct) + ;; CHECK-NEXT: (br_on_cast $block anyref (ref struct) ;; CHECK-NEXT: (local.get $anyref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -58,9 +58,9 @@ ;; CHECK: (func $br-null (type $0) (param $anyref anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label$1 (result structref) + ;; CHECK-NEXT: (block $block (result structref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label$1 anyref structref + ;; CHECK-NEXT: (br_on_cast $block anyref structref ;; CHECK-NEXT: (local.get $anyref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -83,9 +83,9 @@ ;; CHECK: (func $br-fail-null (type $0) (param $anyref anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label$1 (result anyref) + ;; CHECK-NEXT: (block $block (result anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast_fail $label$1 anyref structref + ;; CHECK-NEXT: (br_on_cast_fail $block anyref structref ;; CHECK-NEXT: (local.get $anyref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/debug/source-map-stop.wast b/test/lit/debug/source-map-stop.wast index 95545e65a9b..04a77d9eaed 100644 --- a/test/lit/debug/source-map-stop.wast +++ b/test/lit/debug/source-map-stop.wast @@ -142,6 +142,8 @@ ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ;;@ + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $foo (param $x i32) (param $y i32) ;;@ src.cpp:90:1 diff --git a/test/lit/downgrade-reftypes.wast b/test/lit/downgrade-reftypes.wast index 76d8d99750f..71d009dee0c 100644 --- a/test/lit/downgrade-reftypes.wast +++ b/test/lit/downgrade-reftypes.wast @@ -13,29 +13,29 @@ ;; CHECK: (func $foo (type $f) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label$1 (result funcref) - ;; CHECK-NEXT: (br $label$1 + ;; CHECK-NEXT: (block $block (result funcref) + ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label$2 (result funcref) - ;; CHECK-NEXT: (br $label$2 + ;; CHECK-NEXT: (block $block1 (result funcref) + ;; CHECK-NEXT: (br $block1 ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label$3 (result externref) - ;; CHECK-NEXT: (br $label$3 + ;; CHECK-NEXT: (block $block2 (result externref) + ;; CHECK-NEXT: (br $block2 ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label$4 (result stringref) - ;; CHECK-NEXT: (br $label$4 + ;; CHECK-NEXT: (block $block3 (result stringref) + ;; CHECK-NEXT: (br $block3 ;; CHECK-NEXT: (string.const "hello world") ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/multivalue-stack-ir.wast b/test/lit/multivalue-stack-ir.wast index b4a394a05bc..97c353eb416 100644 --- a/test/lit/multivalue-stack-ir.wast +++ b/test/lit/multivalue-stack-ir.wast @@ -9,16 +9,16 @@ ;; CHECK-NEXT: (local $pair f32) ;; CHECK-NEXT: (local $f32 f32) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local $3 f32) + ;; CHECK-NEXT: (local $scratch f32) ;; CHECK-NEXT: (local.set $pair ;; CHECK-NEXT: (block (result f32) - ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (f32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $f32 diff --git a/test/lit/multivalue.wast b/test/lit/multivalue.wast index 0cfafb2a989..d9cc14e05ab 100644 --- a/test/lit/multivalue.wast +++ b/test/lit/multivalue.wast @@ -28,35 +28,32 @@ ) ;; CHECK: (func $get-first (type $6) (result i32) - ;; CHECK-NEXT: (local $0 (tuple i32 i64 f32)) - ;; CHECK-NEXT: (local $1 i64) - ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (call $triple) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (tuple.extract 3 0 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64 f32)) + ;; CHECK-NEXT: (local $scratch_1 i64) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (tuple.extract 3 0 + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (call $triple) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i64) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (tuple.extract 3 1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (tuple.extract 3 1 + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (tuple.extract 3 2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 3 2 + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_2) ;; CHECK-NEXT: ) (func $get-first (result i32) (tuple.extract 3 0 @@ -64,37 +61,36 @@ ) ) - ;; CHECK: (func $get-second (type $3) (result i64) + ;; CHECK: (func $get-second (type $2) (result i64) ;; CHECK-NEXT: (local $0 i64) - ;; CHECK-NEXT: (local $1 (tuple i32 i64 f32)) - ;; CHECK-NEXT: (local $2 i64) - ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (call $triple) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64 f32)) + ;; CHECK-NEXT: (local $scratch_2 i64) + ;; CHECK-NEXT: (local $scratch_3 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.set $scratch_3 ;; CHECK-NEXT: (tuple.extract 3 0 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (call $triple) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (block (result i64) - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $scratch_2 ;; CHECK-NEXT: (tuple.extract 3 1 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 3 2 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $scratch_2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $scratch_3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $0) @@ -107,35 +103,34 @@ ;; CHECK: (func $get-third (type $7) (result f32) ;; CHECK-NEXT: (local $0 f32) - ;; CHECK-NEXT: (local $1 (tuple i32 i64 f32)) - ;; CHECK-NEXT: (local $2 i64) - ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (call $triple) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64 f32)) + ;; CHECK-NEXT: (local $scratch_2 i64) + ;; CHECK-NEXT: (local $scratch_3 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.set $scratch_3 ;; CHECK-NEXT: (tuple.extract 3 0 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (call $triple) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i64) - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $scratch_2 ;; CHECK-NEXT: (tuple.extract 3 1 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (tuple.extract 3 2 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $scratch_2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $scratch_3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $0) @@ -146,39 +141,38 @@ ) ) - ;; CHECK: (func $reverse (type $4) (result f32 i64 i32) + ;; CHECK: (func $reverse (type $3) (result f32 i64 i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 f32) - ;; CHECK-NEXT: (local $3 (tuple i32 i64 f32)) - ;; CHECK-NEXT: (local $4 i64) - ;; CHECK-NEXT: (local $5 i32) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (call $triple) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64 f32)) + ;; CHECK-NEXT: (local $scratch_4 i64) + ;; CHECK-NEXT: (local $scratch_5 i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.set $scratch_5 ;; CHECK-NEXT: (tuple.extract 3 0 - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (call $triple) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (block (result i64) - ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.set $scratch_4 ;; CHECK-NEXT: (tuple.extract 3 1 - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (tuple.extract 3 2 - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (local.get $scratch_4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: (local.get $scratch_5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (tuple.make 3 @@ -205,7 +199,7 @@ ) ) - ;; CHECK: (func $unreachable (type $3) (result i64) + ;; CHECK: (func $unreachable (type $2) (result i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) @@ -226,16 +220,16 @@ ;; Test multivalue globals ;; CHECK: (func $global (type $0) (result i32 i64) - ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (global.set $g1 ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $g2 ;; CHECK-NEXT: (i64.const 7) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -263,24 +257,23 @@ ;; Test lowering of multivalue drops ;; CHECK: (func $drop-call (type $1) - ;; CHECK-NEXT: (local $0 (tuple i32 i64)) - ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (call $pair) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.set $scratch_1 ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (call $pair) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $scratch_1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -291,16 +284,16 @@ ) ;; CHECK: (func $drop-tuple-make (type $1) - ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -314,29 +307,28 @@ ) ;; CHECK: (func $drop-block (type $1) - ;; CHECK-NEXT: (local $0 (tuple i32 i64)) - ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $label$1 (type $0) (result i32 i64) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i64.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.set $scratch_1 ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (block (type $0) (result i32 i64) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i64.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $scratch_1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -389,25 +381,14 @@ ) ;; CHECK: (func $mv-block-break (type $0) (result i32 i64) - ;; CHECK-NEXT: (local $0 (tuple i32 i64)) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $label$1 (type $0) (result i32 i64) - ;; CHECK-NEXT: (br $label$1 - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i64.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $block (type $0) (result i32 i64) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i64.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $mv-block-break (result i32 i64) (block $l (result i32 i64) @@ -421,35 +402,13 @@ ) ;; CHECK: (func $mv-block-br-if (type $0) (result i32 i64) - ;; CHECK-NEXT: (local $0 (tuple i32 i64)) - ;; CHECK-NEXT: (local $1 (tuple i32 i64)) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (block $label$1 (type $0) (result i32 i64) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (br_if $label$1 - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i64.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $block (type $0) (result i32 i64) + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i64.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -465,36 +424,22 @@ ) ) - ;; CHECK: (func $mv-if (type $2) (result i32 i64 externref) - ;; CHECK-NEXT: (local $0 (tuple i32 i64 externref)) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (if (type $2) (result i32 i64 externref) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (tuple.make 3 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i64.const 42) - ;; CHECK-NEXT: (ref.null noextern) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (tuple.make 3 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i64.const 42) - ;; CHECK-NEXT: (ref.null noextern) - ;; CHECK-NEXT: ) + ;; CHECK: (func $mv-if (type $4) (result i32 i64 externref) + ;; CHECK-NEXT: (if (type $4) (result i32 i64 externref) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i64.const 42) + ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 3 - ;; CHECK-NEXT: (tuple.extract 3 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 3 1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 3 2 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i64.const 42) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -519,21 +464,10 @@ ) ;; CHECK: (func $mv-loop (type $0) (result i32 i64) - ;; CHECK-NEXT: (local $0 (tuple i32 i64)) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (loop $label$1 (type $0) (result i32 i64) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i64.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (loop (type $0) (result i32 i64) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i64.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -547,39 +481,17 @@ ) ;; CHECK: (func $mv-switch (type $0) (result i32 i64) - ;; CHECK-NEXT: (local $0 (tuple i32 i64)) - ;; CHECK-NEXT: (local $1 (tuple i32 i64)) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (block $label$1 (type $0) (result i32 i64) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $label$2 (type $0) (result i32 i64) - ;; CHECK-NEXT: (br_table $label$1 $label$2 - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: (i64.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block $block (type $0) (result i32 i64) + ;; CHECK-NEXT: (block $block1 (type $0) (result i32 i64) + ;; CHECK-NEXT: (br_table $block $block1 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i64.const 42) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $mv-switch (result i32 i64) (block $a (result i32 i64) diff --git a/test/lit/parse-double-unreachable.wast b/test/lit/parse-double-unreachable.wast index b232a40fbdc..9b0e8f573e8 100644 --- a/test/lit/parse-double-unreachable.wast +++ b/test/lit/parse-double-unreachable.wast @@ -15,7 +15,24 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayGet we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $double-unreachable (param $x (ref $array)) (result i32) diff --git a/test/lit/passes/roundtrip-gc.wast b/test/lit/passes/roundtrip-gc.wast index 57300def512..d2c32542c58 100644 --- a/test/lit/passes/roundtrip-gc.wast +++ b/test/lit/passes/roundtrip-gc.wast @@ -6,14 +6,14 @@ ;; CHECK: (export "export" (func $test)) (export "export" (func $test)) ;; CHECK: (func $test (type $1) - ;; CHECK-NEXT: (local $0 (ref $\7bi32\7d)) + ;; CHECK-NEXT: (local $scratch (ref $\7bi32\7d)) ;; CHECK-NEXT: (call $help ;; CHECK-NEXT: (block (result (ref $\7bi32\7d)) - ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (struct.new_default $\7bi32\7d) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $other) - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/roundtrip.wast b/test/lit/passes/roundtrip.wast index 59e303eafae..21aa1f67160 100644 --- a/test/lit/passes/roundtrip.wast +++ b/test/lit/passes/roundtrip.wast @@ -5,29 +5,28 @@ ;; CHECK: (type $none (func)) (type $none (func)) ;; CHECK: (func $foo (type $none) - ;; CHECK-NEXT: (local $0 (tuple funcref (ref $none))) - ;; CHECK-NEXT: (local $1 funcref) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (block $label$1 (type $1) (result funcref (ref $none)) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (ref.null nofunc) - ;; CHECK-NEXT: (ref.func $foo) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local $scratch (tuple funcref (ref $none))) + ;; CHECK-NEXT: (local $scratch_1 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result funcref) - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.set $scratch_1 ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (block (type $1) (result funcref (ref $none)) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: (ref.func $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $scratch_1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat index ec2b517b14b..c69eeb24455 100644 --- a/test/lit/passes/signature-refining_gto.wat +++ b/test/lit/passes/signature-refining_gto.wat @@ -17,7 +17,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $struct.get (param $0 (ref $A)) ;; This function is always called with a null, so the parameter type will be diff --git a/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast b/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast index 439681a8fd5..dbaa2c6015c 100644 --- a/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast +++ b/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast @@ -5,16 +5,16 @@ ;; CHECK: (tag $tag (param i32)) (tag $tag (param i32)) ;; CHECK: (func $delegate-child (type $1) - ;; CHECK-NEXT: (try $label$9 + ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (try $label$7 + ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (try $label$6 + ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do ;; CHECK-NEXT: ) ;; CHECK-NEXT: (delegate 2) diff --git a/test/lit/reftypes-without-gc.wast b/test/lit/reftypes-without-gc.wast index e71aa526919..d251d0e0b84 100644 --- a/test/lit/reftypes-without-gc.wast +++ b/test/lit/reftypes-without-gc.wast @@ -10,8 +10,8 @@ (module ;; CHECK: (func $test (param $x i32) (result funcref) - ;; CHECK-NEXT: (block $label$1 (result funcref) - ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (block $block (result funcref) + ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (ref.func $test) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) diff --git a/test/lit/source-map.wast b/test/lit/source-map.wast index b4fc55374a5..fe534043bd3 100644 --- a/test/lit/source-map.wast +++ b/test/lit/source-map.wast @@ -48,10 +48,7 @@ ;;@ src.cpp:40:1 (local.get $y) ) - ;; For the legacy parser - ;;@ src.cpp:50:1 (then - ;; For the new parser ;;@ src.cpp:50:1 (return) ) @@ -68,10 +65,10 @@ ;; CHECK: (func $nested-blocks ;; CHECK-NEXT: ;;@ src.cpp:2:1 - ;; CHECK-NEXT: (block $label$1 + ;; CHECK-NEXT: (block ;; CHECK-NEXT: ;;@ src.cpp:2:2 - ;; CHECK-NEXT: (block $label$2 - ;; CHECK-NEXT: (br $label$2) + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (br $block) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ;;@ src.cpp:3:1 diff --git a/test/lit/string.as_wtf16.wast b/test/lit/string.as_wtf16.wast index 60bdcf5ecb3..74bbc97eaff 100644 --- a/test/lit/string.as_wtf16.wast +++ b/test/lit/string.as_wtf16.wast @@ -26,33 +26,33 @@ ;; CHECK-NEXT: ) ;; RTRIP: (func $codeunit (type $1) (result i32) ;; RTRIP-NEXT: (local $0 i32) - ;; RTRIP-NEXT: (local $1 (ref string)) + ;; RTRIP-NEXT: (local $scratch (ref string)) ;; RTRIP-NEXT: (stringview_wtf16.get_codeunit ;; RTRIP-NEXT: (block (result (ref string)) - ;; RTRIP-NEXT: (local.set $1 + ;; RTRIP-NEXT: (local.set $scratch ;; RTRIP-NEXT: (string.const "abc") ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.set $0 ;; RTRIP-NEXT: (i32.const 0) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $1) + ;; RTRIP-NEXT: (local.get $scratch) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.get $0) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RRTRP: (func $codeunit (type $1) (result i32) ;; RRTRP-NEXT: (local $0 i32) - ;; RRTRP-NEXT: (local $1 (ref string)) - ;; RRTRP-NEXT: (local $2 (ref string)) + ;; RRTRP-NEXT: (local $scratch (ref string)) + ;; RRTRP-NEXT: (local $scratch_2 (ref string)) ;; RRTRP-NEXT: (stringview_wtf16.get_codeunit ;; RRTRP-NEXT: (block (result (ref string)) - ;; RRTRP-NEXT: (local.set $2 + ;; RRTRP-NEXT: (local.set $scratch_2 ;; RRTRP-NEXT: (string.const "abc") ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $0 ;; RRTRP-NEXT: (i32.const 0) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $2) + ;; RRTRP-NEXT: (local.get $scratch_2) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.get $0) ;; RRTRP-NEXT: ) @@ -108,25 +108,25 @@ ;; RTRIP: (func $slice (type $0) (result stringref) ;; RTRIP-NEXT: (local $0 i32) ;; RTRIP-NEXT: (local $1 i32) - ;; RTRIP-NEXT: (local $2 i32) - ;; RTRIP-NEXT: (local $3 (ref string)) + ;; RTRIP-NEXT: (local $scratch i32) + ;; RTRIP-NEXT: (local $scratch_3 (ref string)) ;; RTRIP-NEXT: (stringview_wtf16.slice ;; RTRIP-NEXT: (block (result (ref string)) - ;; RTRIP-NEXT: (local.set $3 + ;; RTRIP-NEXT: (local.set $scratch_3 ;; RTRIP-NEXT: (string.const "abc") ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.set $0 ;; RTRIP-NEXT: (block (result i32) - ;; RTRIP-NEXT: (local.set $2 + ;; RTRIP-NEXT: (local.set $scratch ;; RTRIP-NEXT: (i32.const 1) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.set $1 ;; RTRIP-NEXT: (i32.const 2) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $2) + ;; RTRIP-NEXT: (local.get $scratch) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $3) + ;; RTRIP-NEXT: (local.get $scratch_3) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.get $0) ;; RTRIP-NEXT: (local.get $1) @@ -135,27 +135,27 @@ ;; RRTRP: (func $slice (type $0) (result stringref) ;; RRTRP-NEXT: (local $0 i32) ;; RRTRP-NEXT: (local $1 i32) - ;; RRTRP-NEXT: (local $2 i32) - ;; RRTRP-NEXT: (local $3 (ref string)) - ;; RRTRP-NEXT: (local $4 i32) - ;; RRTRP-NEXT: (local $5 (ref string)) + ;; RRTRP-NEXT: (local $scratch i32) + ;; RRTRP-NEXT: (local $scratch_3 (ref string)) + ;; RRTRP-NEXT: (local $scratch_4 i32) + ;; RRTRP-NEXT: (local $scratch_5 (ref string)) ;; RRTRP-NEXT: (stringview_wtf16.slice ;; RRTRP-NEXT: (block (result (ref string)) - ;; RRTRP-NEXT: (local.set $5 + ;; RRTRP-NEXT: (local.set $scratch_5 ;; RRTRP-NEXT: (string.const "abc") ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $0 ;; RRTRP-NEXT: (block (result i32) - ;; RRTRP-NEXT: (local.set $4 + ;; RRTRP-NEXT: (local.set $scratch_4 ;; RRTRP-NEXT: (i32.const 1) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $1 ;; RRTRP-NEXT: (i32.const 2) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $4) + ;; RRTRP-NEXT: (local.get $scratch_4) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $5) + ;; RRTRP-NEXT: (local.get $scratch_5) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.get $0) ;; RRTRP-NEXT: (local.get $1) @@ -185,25 +185,25 @@ ;; RTRIP-NEXT: (local $start i32) ;; RTRIP-NEXT: (local $1 i32) ;; RTRIP-NEXT: (local $2 i32) - ;; RTRIP-NEXT: (local $3 i32) - ;; RTRIP-NEXT: (local $4 (ref string)) + ;; RTRIP-NEXT: (local $scratch i32) + ;; RTRIP-NEXT: (local $scratch_4 (ref string)) ;; RTRIP-NEXT: (stringview_wtf16.slice ;; RTRIP-NEXT: (block (result (ref string)) - ;; RTRIP-NEXT: (local.set $4 + ;; RTRIP-NEXT: (local.set $scratch_4 ;; RTRIP-NEXT: (string.const "abc") ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.set $1 ;; RTRIP-NEXT: (block (result i32) - ;; RTRIP-NEXT: (local.set $3 + ;; RTRIP-NEXT: (local.set $scratch ;; RTRIP-NEXT: (local.get $start) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.set $2 ;; RTRIP-NEXT: (i32.const 2) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $3) + ;; RTRIP-NEXT: (local.get $scratch) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $4) + ;; RTRIP-NEXT: (local.get $scratch_4) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.get $1) ;; RTRIP-NEXT: (local.get $2) @@ -213,27 +213,27 @@ ;; RRTRP-NEXT: (local $start i32) ;; RRTRP-NEXT: (local $1 i32) ;; RRTRP-NEXT: (local $2 i32) - ;; RRTRP-NEXT: (local $3 i32) - ;; RRTRP-NEXT: (local $4 (ref string)) - ;; RRTRP-NEXT: (local $5 i32) - ;; RRTRP-NEXT: (local $6 (ref string)) + ;; RRTRP-NEXT: (local $scratch i32) + ;; RRTRP-NEXT: (local $scratch_4 (ref string)) + ;; RRTRP-NEXT: (local $scratch_5 i32) + ;; RRTRP-NEXT: (local $scratch_6 (ref string)) ;; RRTRP-NEXT: (stringview_wtf16.slice ;; RRTRP-NEXT: (block (result (ref string)) - ;; RRTRP-NEXT: (local.set $6 + ;; RRTRP-NEXT: (local.set $scratch_6 ;; RRTRP-NEXT: (string.const "abc") ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $1 ;; RRTRP-NEXT: (block (result i32) - ;; RRTRP-NEXT: (local.set $5 + ;; RRTRP-NEXT: (local.set $scratch_5 ;; RRTRP-NEXT: (local.get $start) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $2 ;; RRTRP-NEXT: (i32.const 2) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $5) + ;; RRTRP-NEXT: (local.get $scratch_5) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $6) + ;; RRTRP-NEXT: (local.get $scratch_6) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.get $1) ;; RRTRP-NEXT: (local.get $2) @@ -261,25 +261,25 @@ ;; RTRIP-NEXT: (local $end i32) ;; RTRIP-NEXT: (local $1 i32) ;; RTRIP-NEXT: (local $2 i32) - ;; RTRIP-NEXT: (local $3 i32) - ;; RTRIP-NEXT: (local $4 (ref string)) + ;; RTRIP-NEXT: (local $scratch i32) + ;; RTRIP-NEXT: (local $scratch_4 (ref string)) ;; RTRIP-NEXT: (stringview_wtf16.slice ;; RTRIP-NEXT: (block (result (ref string)) - ;; RTRIP-NEXT: (local.set $4 + ;; RTRIP-NEXT: (local.set $scratch_4 ;; RTRIP-NEXT: (string.const "abc") ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.set $1 ;; RTRIP-NEXT: (block (result i32) - ;; RTRIP-NEXT: (local.set $3 + ;; RTRIP-NEXT: (local.set $scratch ;; RTRIP-NEXT: (i32.const 1) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.set $2 ;; RTRIP-NEXT: (local.get $end) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $3) + ;; RTRIP-NEXT: (local.get $scratch) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $4) + ;; RTRIP-NEXT: (local.get $scratch_4) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (local.get $1) ;; RTRIP-NEXT: (local.get $2) @@ -289,27 +289,27 @@ ;; RRTRP-NEXT: (local $end i32) ;; RRTRP-NEXT: (local $1 i32) ;; RRTRP-NEXT: (local $2 i32) - ;; RRTRP-NEXT: (local $3 i32) - ;; RRTRP-NEXT: (local $4 (ref string)) - ;; RRTRP-NEXT: (local $5 i32) - ;; RRTRP-NEXT: (local $6 (ref string)) + ;; RRTRP-NEXT: (local $scratch i32) + ;; RRTRP-NEXT: (local $scratch_4 (ref string)) + ;; RRTRP-NEXT: (local $scratch_5 i32) + ;; RRTRP-NEXT: (local $scratch_6 (ref string)) ;; RRTRP-NEXT: (stringview_wtf16.slice ;; RRTRP-NEXT: (block (result (ref string)) - ;; RRTRP-NEXT: (local.set $6 + ;; RRTRP-NEXT: (local.set $scratch_6 ;; RRTRP-NEXT: (string.const "abc") ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $1 ;; RRTRP-NEXT: (block (result i32) - ;; RRTRP-NEXT: (local.set $5 + ;; RRTRP-NEXT: (local.set $scratch_5 ;; RRTRP-NEXT: (i32.const 1) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $2 ;; RRTRP-NEXT: (local.get $end) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $5) + ;; RRTRP-NEXT: (local.get $scratch_5) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $6) + ;; RRTRP-NEXT: (local.get $scratch_6) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.get $1) ;; RRTRP-NEXT: (local.get $2) diff --git a/test/lld/em_asm_pthread.wasm.out b/test/lld/em_asm_pthread.wasm.out index f275876a937..8284cc6dc59 100644 --- a/test/lld/em_asm_pthread.wasm.out +++ b/test/lld/em_asm_pthread.wasm.out @@ -269,8 +269,8 @@ ) (func $5 (local $0 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.eqz (local.tee $0 (global.get $global$1) @@ -348,9 +348,9 @@ (i32.const 2147483647) ) ) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (i32.ne (i32.and (local.get $1) @@ -359,7 +359,7 @@ (i32.const 1) ) ) - (br_if $label$2 + (br_if $block (i32.ne (local.get $5) (local.get $3) @@ -368,7 +368,7 @@ (local.set $6 (i32.const 6) ) - (br_if $label$1 + (br_if $block1 (i32.gt_u (local.tee $5 (i32.load offset=20 @@ -392,14 +392,14 @@ (local.set $6 (i32.const 56) ) - (br_if $label$1 + (br_if $block1 (i32.eq (local.get $5) (i32.const 2147483647) ) ) - (block $label$3 - (br_if $label$3 + (block $block2 + (br_if $block2 (i32.eqz (i32.and (i32.load8_u @@ -409,8 +409,8 @@ ) ) ) - (block $label$4 - (br_if $label$4 + (block $block3 + (br_if $block3 (i32.load (i32.add (local.get $2) @@ -449,15 +449,15 @@ ) ) ) - (block $label$5 - (block $label$6 - (block $label$7 - (br_if $label$7 + (block $block6 + (block $block5 + (block $block4 + (br_if $block4 (i32.eqz (local.get $5) ) ) - (br_if $label$6 + (br_if $block5 (i32.eqz (i32.and (local.get $1) @@ -465,7 +465,7 @@ ) ) ) - (br_if $label$6 + (br_if $block5 (i32.eqz (i32.and (local.get $4) @@ -474,7 +474,7 @@ ) ) ) - (br_if $label$5 + (br_if $block6 (i32.eq (call $12 (i32.add @@ -523,8 +523,8 @@ (i32.const 16) ) ) - (block $label$8 - (br_if $label$8 + (block $block7 + (br_if $block7 (i32.eq (local.get $3) (local.get $6) @@ -552,7 +552,7 @@ ) (i32.const 0) ) - (br_if $label$1 + (br_if $block1 (i32.eqz (local.get $5) ) @@ -584,8 +584,8 @@ ) ) (func $13 (param $0 i32) (result i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.and (i32.load8_u (local.get $0) @@ -626,14 +626,14 @@ ) ) (func $17 - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.ne (call $18) (i32.const 1) ) ) - (br_if $label$1 + (br_if $block (i32.eqz (i32.load offset=1796 (i32.const 0) @@ -683,10 +683,10 @@ (local.get $0) ) ) - (block $label$1 - (block $label$2 - (block $label$3 - (br_if $label$3 + (block $block2 + (block $block1 + (block $block + (br_if $block (local.tee $4 (i32.and (local.get $1) @@ -694,7 +694,7 @@ ) ) ) - (br $label$2) + (br $block1) ) (local.set $5 (call $7) @@ -702,7 +702,7 @@ (local.set $6 (i32.const 63) ) - (br_if $label$1 + (br_if $block2 (i32.ne (i32.and (i32.load offset=4 @@ -715,8 +715,8 @@ ) ) ) - (block $label$4 - (br_if $label$4 + (block $block3 + (br_if $block3 (i32.ne (i32.and (local.get $1) @@ -725,7 +725,7 @@ (i32.const 1) ) ) - (br_if $label$4 + (br_if $block3 (i32.eqz (local.tee $6 (i32.load offset=20 @@ -745,8 +745,8 @@ (i32.const 0) ) ) - (block $label$5 - (br_if $label$5 + (block $block4 + (br_if $block4 (local.get $2) ) (i32.store @@ -773,7 +773,7 @@ ) ) ) - (br_if $label$2 + (br_if $block1 (i32.eq (local.get $6) (i32.add @@ -810,13 +810,13 @@ ) ) ) - (block $label$6 - (br_if $label$6 + (block $block5 + (br_if $block5 (i32.eqz (local.get $4) ) ) - (br_if $label$6 + (br_if $block5 (local.get $2) ) (i32.store @@ -831,11 +831,11 @@ (local.set $6 (i32.const 0) ) - (block $label$7 - (br_if $label$7 + (block $block6 + (br_if $block6 (local.get $3) ) - (br_if $label$1 + (br_if $block2 (i32.gt_s (local.get $0) (i32.const -1) @@ -925,14 +925,14 @@ (local $1 i32) (local $2 i32) (local $3 i32) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (i32.eqz (call $10) ) ) - (br_if $label$1 + (br_if $block1 (i32.load8_u offset=1832 (i32.const 0) ) @@ -947,8 +947,8 @@ (i32.const 1804) ) ) - (block $label$3 - (br_if $label$3 + (block $block2 + (br_if $block2 (local.tee $0 (call $32 (call $14) @@ -960,7 +960,7 @@ (i32.const 1804) ) ) - (br_if $label$1 + (br_if $block1 (i32.eqz (call $10) ) @@ -971,8 +971,8 @@ ) (return) ) - (block $label$4 - (br_if $label$4 + (block $block3 + (br_if $block3 (i32.eq (local.tee $2 (call $23 @@ -994,7 +994,7 @@ ) ) ) - (loop $label$5 + (loop $label (drop (call $20 (i32.const 1804) @@ -1032,7 +1032,7 @@ ) ) ) - (br_if $label$5 + (br_if $label (i32.ne (local.get $2) (call $23 @@ -1053,7 +1053,7 @@ (i32.const 2147483647) ) ) - (br_if $label$1 + (br_if $block1 (i32.eqz (call $10) ) @@ -1066,14 +1066,14 @@ ) (func $32 (param $0 i32) (result i32) (local $1 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.eqz (local.get $0) ) ) - (block $label$2 - (br_if $label$2 + (block $block1 + (br_if $block1 (i32.eqz (local.tee $1 (i32.load offset=1840 @@ -1082,9 +1082,9 @@ ) ) ) - (loop $label$3 - (block $label$4 - (br_if $label$4 + (loop $label + (block $block2 + (br_if $block2 (i32.ne (i32.load (local.get $1) @@ -1096,7 +1096,7 @@ (local.get $1) ) ) - (br_if $label$3 + (br_if $label (local.tee $1 (i32.load offset=16 (local.get $1) @@ -1119,10 +1119,10 @@ ) (func $33 (param $0 i32) (local $1 i32) - (block $label$1 - (block $label$2 - (block $label$3 - (br_if $label$3 + (block $block9 + (block $block42 + (block $block + (br_if $block (i32.eq (i32.and (local.tee $1 @@ -1135,67 +1135,67 @@ (i32.const 402653184) ) ) - (block $label$4 - (block $label$5 - (block $label$6 - (block $label$7 - (block $label$8 - (block $label$9 - (block $label$10 - (block $label$11 - (block $label$12 - (block $label$13 - (block $label$14 - (block $label$15 - (block $label$16 - (block $label$17 - (block $label$18 - (block $label$19 - (block $label$20 - (block $label$21 - (block $label$22 - (block $label$23 - (block $label$24 - (block $label$25 - (block $label$26 - (block $label$27 - (block $label$28 - (block $label$29 - (br_if $label$29 + (block $block8 + (block $block5 + (block $block13 + (block $block41 + (block $block40 + (block $block39 + (block $block37 + (block $block36 + (block $block33 + (block $block32 + (block $block31 + (block $block29 + (block $block28 + (block $block24 + (block $block23 + (block $block22 + (block $block21 + (block $block19 + (block $block17 + (block $block16 + (block $block14 + (block $block12 + (block $block11 + (block $block6 + (block $block4 + (block $block1 + (br_if $block1 (i32.gt_s (local.get $1) (i32.const 234881023) ) ) - (block $label$30 - (br_if $label$30 + (block $block2 + (br_if $block2 (i32.gt_s (local.get $1) (i32.const 100663335) ) ) - (block $label$31 - (br_if $label$31 + (block $block3 + (br_if $block3 (i32.gt_s (local.get $1) (i32.const 67108863) ) ) - (block $label$32 - (br_table $label$28 $label$5 $label$27 $label$32 + (block $block7 + (br_table $block4 $block5 $block6 $block7 (i32.add (local.get $1) (i32.const -33554432) ) ) ) - (br_if $label$4 + (br_if $block8 (i32.eq (local.get $1) (i32.const -2126512128) ) ) - (br_if $label$5 + (br_if $block5 (local.get $1) ) (call_indirect (type $2) @@ -1203,29 +1203,29 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) - (block $label$33 - (br_if $label$33 + (block $block10 + (br_if $block10 (i32.gt_s (local.get $1) (i32.const 100663295) ) ) - (br_table $label$26 $label$5 $label$25 $label$6 + (br_table $block11 $block5 $block12 $block13 (i32.add (local.get $1) (i32.const -67108872) ) ) ) - (br_if $label$24 + (br_if $block14 (i32.eq (local.get $1) (i32.const 100663296) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 100663328) @@ -1251,30 +1251,30 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) - (block $label$34 - (br_if $label$34 + (block $block15 + (br_if $block15 (i32.gt_s (local.get $1) (i32.const 134217895) ) ) - (block $label$35 - (br_table $label$23 $label$5 $label$22 $label$35 + (block $block18 + (br_table $block16 $block5 $block17 $block18 (i32.add (local.get $1) (i32.const -100663336) ) ) ) - (br_if $label$21 + (br_if $block19 (i32.eq (local.get $1) (i32.const 134217728) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 134217760) @@ -1306,29 +1306,29 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) - (block $label$36 - (br_if $label$36 + (block $block20 + (br_if $block20 (i32.gt_s (local.get $1) (i32.const 167772839) ) ) - (br_table $label$20 $label$5 $label$19 $label$18 + (br_table $block21 $block5 $block22 $block23 (i32.add (local.get $1) (i32.const -134217896) ) ) ) - (br_if $label$17 + (br_if $block24 (i32.eq (local.get $1) (i32.const 167772840) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 201326592) @@ -1372,36 +1372,36 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) - (block $label$37 - (br_if $label$37 + (block $block25 + (br_if $block25 (i32.gt_s (local.get $1) (i32.const 637534207) ) ) - (block $label$38 - (br_if $label$38 + (block $block26 + (br_if $block26 (i32.gt_s (local.get $1) (i32.const 369098751) ) ) - (block $label$39 - (br_if $label$39 + (block $block27 + (br_if $block27 (i32.gt_s (local.get $1) (i32.const 301989887) ) ) - (br_if $label$16 + (br_if $block28 (i32.eq (local.get $1) (i32.const 234881024) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 268435456) @@ -1457,15 +1457,15 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) - (br_if $label$15 + (br_if $block29 (i32.eq (local.get $1) (i32.const 301989888) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 335544320) @@ -1533,22 +1533,22 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) - (block $label$40 - (br_if $label$40 + (block $block30 + (br_if $block30 (i32.gt_s (local.get $1) (i32.const 570425343) ) ) - (br_if $label$14 + (br_if $block31 (i32.eq (local.get $1) (i32.const 369098752) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 536870912) @@ -1562,21 +1562,21 @@ ) ) ) - (br $label$1) + (br $block9) ) - (br_if $label$13 + (br_if $block32 (i32.eq (local.get $1) (i32.const 570425344) ) ) - (br_if $label$12 + (br_if $block33 (i32.eq (local.get $1) (i32.const 603979776) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 622854144) @@ -1596,29 +1596,29 @@ ) ) ) - (br $label$1) + (br $block9) ) - (block $label$41 - (br_if $label$41 + (block $block34 + (br_if $block34 (i32.gt_s (local.get $1) (i32.const 704643071) ) ) - (block $label$42 - (br_if $label$42 + (block $block35 + (br_if $block35 (i32.gt_s (local.get $1) (i32.const 671088639) ) ) - (br_if $label$11 + (br_if $block36 (i32.eq (local.get $1) (i32.const 637534208) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 657457152) @@ -1644,15 +1644,15 @@ ) ) ) - (br $label$1) + (br $block9) ) - (br_if $label$10 + (br_if $block37 (i32.eq (local.get $1) (i32.const 671088640) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 687865856) @@ -1684,22 +1684,22 @@ ) ) ) - (br $label$1) + (br $block9) ) - (block $label$43 - (br_if $label$43 + (block $block38 + (br_if $block38 (i32.gt_s (local.get $1) (i32.const 771751935) ) ) - (br_if $label$9 + (br_if $block39 (i32.eq (local.get $1) (i32.const 704643072) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 738197504) @@ -1746,21 +1746,21 @@ ) ) ) - (br $label$1) + (br $block9) ) - (br_if $label$8 + (br_if $block40 (i32.eq (local.get $1) (i32.const 771751936) ) ) - (br_if $label$7 + (br_if $block41 (i32.eq (local.get $1) (i32.const 805306368) ) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 838860800) @@ -1825,7 +1825,7 @@ ) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $1) (i32.load offset=16 @@ -1835,7 +1835,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $22) (f32.load offset=16 @@ -1845,7 +1845,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $23) (i32.load offset=16 @@ -1861,7 +1861,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $24) (f32.load offset=16 @@ -1877,7 +1877,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $11) (i32.load offset=16 @@ -1899,7 +1899,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $25) (i32.load offset=16 @@ -1921,7 +1921,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $26) (f32.load offset=16 @@ -1943,7 +1943,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $10) (i32.load offset=16 @@ -1971,7 +1971,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $27) (i32.load offset=16 @@ -1999,7 +1999,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $28) (f32.load offset=16 @@ -2027,9 +2027,9 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) - (br_if $label$5 + (br_if $block5 (i32.ne (local.get $1) (i32.const 167772160) @@ -2067,7 +2067,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $30) (i32.load offset=16 @@ -2101,7 +2101,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $31) (i32.load offset=16 @@ -2147,7 +2147,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $32) (i32.load offset=16 @@ -2205,7 +2205,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (call_indirect (type $33) (i32.load offset=16 @@ -2275,7 +2275,7 @@ (local.get $0) ) ) - (br $label$1) + (br $block9) ) (i32.store offset=176 (local.get $0) @@ -2288,7 +2288,7 @@ ) ) ) - (br $label$1) + (br $block9) ) (i32.store offset=176 (local.get $0) @@ -2307,7 +2307,7 @@ ) ) ) - (br $label$1) + (br $block9) ) (i32.store offset=176 (local.get $0) @@ -2332,7 +2332,7 @@ ) ) ) - (br $label$1) + (br $block9) ) (i32.store offset=176 (local.get $0) @@ -2363,7 +2363,7 @@ ) ) ) - (br $label$1) + (br $block9) ) (i32.store offset=176 (local.get $0) @@ -2400,7 +2400,7 @@ ) ) ) - (br $label$1) + (br $block9) ) (i32.store offset=176 (local.get $0) @@ -2449,7 +2449,7 @@ ) ) ) - (br $label$1) + (br $block9) ) (i32.store offset=176 (local.get $0) @@ -2504,9 +2504,9 @@ ) ) ) - (br $label$1) + (br $block9) ) - (br_if $label$2 + (br_if $block42 (i32.eq (local.get $1) (i32.const 67108864) @@ -2536,7 +2536,7 @@ ) ) ) - (br $label$1) + (br $block9) ) (call $fimport$8 (i32.const 1254) @@ -2561,8 +2561,8 @@ ) ) ) - (block $label$44 - (br_if $label$44 + (block $block43 + (br_if $block43 (i32.eqz (i32.load offset=188 (local.get $0) @@ -2589,8 +2589,8 @@ ) ) (func $34 (param $0 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.eqz (local.get $0) ) @@ -2608,8 +2608,8 @@ (func $35 (param $0 i32) (param $1 f64) (result i32) (local $2 i32) (local $3 f64) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (local.tee $0 (call $23 (local.tee $2 @@ -2630,8 +2630,8 @@ (local.set $0 (i32.const 0) ) - (block $label$2 - (br_if $label$2 + (block $block1 + (br_if $block1 (i32.xor (f64.lt (local.get $3) @@ -2645,7 +2645,7 @@ (i32.const 1) ) ) - (loop $label$3 + (loop $label (drop (call $fimport$6 (local.get $2) @@ -2664,10 +2664,10 @@ (local.set $3 (call $fimport$4) ) - (br_if $label$2 + (br_if $block1 (local.get $0) ) - (br_if $label$3 + (br_if $label (f64.lt (local.get $3) (local.get $1) @@ -2703,18 +2703,18 @@ (local $5 i32) (local $6 i32) (local $7 i32) - (block $label$1 - (block $label$2 - (block $label$3 - (br_if $label$3 + (block $block10 + (block $block8 + (block $block + (br_if $block (i32.eqz (local.get $1) ) ) - (block $label$4 - (block $label$5 - (block $label$6 - (br_table $label$6 $label$5 $label$4 + (block $block3 + (block $block2 + (block $block1 + (br_table $block1 $block2 $block3 (local.get $0) ) ) @@ -2730,15 +2730,15 @@ (call $37) ) ) - (block $label$7 - (block $label$8 - (br_if $label$8 + (block $block5 + (block $block4 + (br_if $block4 (i32.eq (local.get $0) (i32.const 2) ) ) - (br_if $label$7 + (br_if $block5 (i32.ne (local.get $0) (call $14) @@ -2757,8 +2757,8 @@ (i32.const 1804) ) ) - (block $label$9 - (br_if $label$9 + (block $block6 + (br_if $block6 (i32.load offset=4 (local.tee $2 (call $39 @@ -2774,8 +2774,8 @@ ) ) ) - (block $label$10 - (br_if $label$10 + (block $block7 + (br_if $block7 (i32.ne (local.tee $4 (call $23 @@ -2807,13 +2807,13 @@ ) ) ) - (loop $label$11 + (loop $label (drop (call $20 (i32.const 1804) ) ) - (br_if $label$2 + (br_if $block8 (i32.ne (local.get $0) (call $37) @@ -2831,7 +2831,7 @@ (i32.const 1804) ) ) - (br_if $label$11 + (br_if $label (i32.eq (local.tee $4 (call $23 @@ -2867,14 +2867,14 @@ ) (local.get $1) ) - (block $label$12 - (br_if $label$12 + (block $block9 + (br_if $block9 (i32.ne (local.get $4) (local.get $6) ) ) - (br_if $label$12 + (br_if $block9 (call $fimport$10 (local.get $0) (call $37) @@ -2888,7 +2888,7 @@ (i32.const 1804) ) ) - (br $label$1) + (br $block10) ) (drop (call $24 @@ -2901,7 +2901,7 @@ (i32.const 1804) ) ) - (br $label$1) + (br $block10) ) (call $fimport$8 (i32.const 1090) @@ -2920,8 +2920,8 @@ (func $39 (param $0 i32) (result i32) (local $1 i32) (local $2 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (local.tee $1 (call $32 (local.get $0) @@ -2944,9 +2944,9 @@ (local.get $1) (local.get $0) ) - (block $label$2 - (block $label$3 - (br_if $label$3 + (block $block2 + (block $block1 + (br_if $block1 (local.tee $0 (i32.load offset=1840 (i32.const 0) @@ -2956,10 +2956,10 @@ (local.set $0 (i32.const 1840) ) - (br $label$2) + (br $block2) ) - (loop $label$4 - (br_if $label$4 + (loop $label + (br_if $label (local.tee $0 (i32.load offset=16 (local.tee $2 @@ -3117,13 +3117,13 @@ (local.get $0) ) (func $44 - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.eqz (call $9) ) ) - (br_if $label$1 + (br_if $block (i32.eqz (i32.load offset=1432 (i32.const 0) @@ -3146,9 +3146,9 @@ ) ) ) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (i32.eqz (local.get $3) ) @@ -3164,7 +3164,7 @@ (local.set $5 (local.get $4) ) - (br $label$1) + (br $block1) ) (local.set $5 (call $46) @@ -3185,8 +3185,8 @@ (local.get $3) ) ) - (block $label$3 - (br_if $label$3 + (block $block2 + (br_if $block2 (i32.ge_s (local.get $1) (i32.const 20) @@ -3199,14 +3199,14 @@ (local.set $0 (i32.const 0) ) - (block $label$4 - (br_if $label$4 + (block $block3 + (br_if $block3 (i32.le_s (local.get $1) (i32.const 0) ) ) - (loop $label$5 + (loop $label (i64.store (i32.add (i32.add @@ -3236,7 +3236,7 @@ (local.set $0 (local.get $6) ) - (br_if $label$5 + (br_if $label (i32.ne (local.get $6) (local.get $1) @@ -3244,9 +3244,9 @@ ) ) ) - (block $label$6 - (block $label$7 - (br_if $label$7 + (block $block5 + (block $block4 + (br_if $block4 (i32.eqz (local.get $3) ) @@ -3259,7 +3259,7 @@ (local.get $4) ) ) - (br $label$6) + (br $block5) ) (call $40 (local.get $5) @@ -3288,8 +3288,8 @@ ) (func $46 (result i32) (local $0 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (local.tee $0 (call $60 (i32.const 192) @@ -3325,8 +3325,8 @@ ) ) ) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.eqz (local.tee $7 (call $46) @@ -3349,8 +3349,8 @@ (local.get $6) (local.get $5) ) - (block $label$2 - (br_if $label$2 + (block $block1 + (br_if $block1 (i32.eqz (local.tee $4 (i32.and @@ -3372,13 +3372,13 @@ (local.set $3 (i32.const 0) ) - (loop $label$3 - (block $label$4 - (block $label$5 - (block $label$6 - (block $label$7 - (block $label$8 - (br_table $label$8 $label$7 $label$6 $label$5 $label$8 + (loop $label + (block $block6 + (block $block5 + (block $block4 + (block $block3 + (block $block2 + (br_table $block2 $block3 $block4 $block5 $block2 (i32.and (local.get $2) (i32.const 3) @@ -3411,7 +3411,7 @@ (local.get $5) ) ) - (br $label$4) + (br $block6) ) (i32.store offset=12 (local.get $6) @@ -3445,7 +3445,7 @@ (local.get $5) ) ) - (br $label$4) + (br $block6) ) (i32.store offset=12 (local.get $6) @@ -3481,7 +3481,7 @@ ) ) ) - (br $label$4) + (br $block6) ) (i32.store offset=12 (local.get $6) @@ -3522,7 +3522,7 @@ (i32.const 2) ) ) - (br_if $label$3 + (br_if $label (i32.ne (local.tee $3 (i32.add @@ -3539,9 +3539,9 @@ (local.get $7) (i32.const 1) ) - (block $label$9 - (block $label$10 - (br_if $label$10 + (block $block8 + (block $block7 + (br_if $block7 (i32.eqz (local.get $0) ) @@ -3575,7 +3575,7 @@ (local.get $6) ) ) - (br $label$9) + (br $block8) ) (local.set $2 (call $38 @@ -3607,8 +3607,8 @@ (local.set $2 (i32.const 28) ) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.gt_u (local.get $0) (i32.const 2) @@ -3617,8 +3617,8 @@ (local.set $2 (call $7) ) - (block $label$2 - (br_if $label$2 + (block $block1 + (br_if $block1 (i32.eqz (local.get $1) ) @@ -3653,22 +3653,22 @@ ) ) ) - (block $label$1 - (block $label$2 - (block $label$3 - (block $label$4 - (br_if $label$4 + (block $block2 + (block $block4 + (block $block1 + (block $block + (br_if $block (local.get $3) ) (local.set $6 (f64.const inf) ) - (br $label$3) + (br $block1) ) (local.set $7 (i32.const 28) ) - (br_if $label$1 + (br_if $block2 (i32.gt_u (i32.load offset=4 (local.get $3) @@ -3676,7 +3676,7 @@ (i32.const 999999999) ) ) - (br_if $label$1 + (br_if $block2 (call $fimport$3 (local.get $2) (i32.add @@ -3711,8 +3711,8 @@ ) ) ) - (block $label$5 - (br_if $label$5 + (block $block3 + (br_if $block3 (i32.gt_s (local.get $3) (i32.const -1) @@ -3737,7 +3737,7 @@ ) ) ) - (br_if $label$2 + (br_if $block4 (i32.lt_s (local.get $7) (i32.const 0) @@ -3760,15 +3760,15 @@ ) ) ) - (block $label$6 - (block $label$7 - (block $label$8 - (br_if $label$8 + (block $block9 + (block $block6 + (block $block5 + (br_if $block5 (local.tee $3 (call $10) ) ) - (br_if $label$8 + (br_if $block5 (i32.ne (i32.load offset=56 (call $14) @@ -3776,7 +3776,7 @@ (i32.const 1) ) ) - (br_if $label$7 + (br_if $block6 (i32.ne (i32.load offset=60 (call $14) @@ -3791,9 +3791,9 @@ (call $fimport$4) ) ) - (loop $label$9 - (block $label$10 - (br_if $label$10 + (loop $label + (block $block7 + (br_if $block7 (i32.eqz (call $30 (call $14) @@ -3803,17 +3803,17 @@ (local.set $7 (i32.const 11) ) - (br $label$1) + (br $block2) ) - (block $label$11 - (br_if $label$11 + (block $block8 + (br_if $block8 (i32.eqz (local.get $3) ) ) (call $44) ) - (br_if $label$2 + (br_if $block4 (f64.le (local.tee $6 (f64.sub @@ -3824,7 +3824,7 @@ (f64.const 0) ) ) - (br_if $label$9 + (br_if $label (i32.eq (local.tee $7 (i32.sub @@ -3855,7 +3855,7 @@ (i32.const 73) ) ) - (br $label$6) + (br $block9) ) ) (local.set $7 @@ -3869,19 +3869,19 @@ ) ) ) - (br_if $label$1 + (br_if $block2 (i32.eq (local.get $7) (i32.const 11) ) ) - (br_if $label$1 + (br_if $block2 (i32.eq (local.get $7) (i32.const 27) ) ) - (br_if $label$1 + (br_if $block2 (i32.eq (local.get $7) (i32.const 73) @@ -3890,7 +3890,7 @@ (local.set $7 (i32.const 0) ) - (br $label$1) + (br $block2) ) (local.set $7 (i32.const 73) @@ -3962,9 +3962,9 @@ (local $4 i32) (local $5 i32) (local $6 i32) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (i32.and (local.tee $2 (i32.load @@ -3977,7 +3977,7 @@ (local.set $3 (i32.const 0) ) - (br_if $label$1 + (br_if $block1 (i32.eqz (call $52 (i32.add @@ -3995,7 +3995,7 @@ ) ) ) - (br_if $label$1 + (br_if $block1 (i32.ne (local.tee $3 (call $13 @@ -4029,14 +4029,14 @@ (local.set $3 (i32.const 100) ) - (block $label$3 - (loop $label$4 - (br_if $label$3 + (block $block2 + (loop $label + (br_if $block2 (i32.eqz (local.get $3) ) ) - (br_if $label$3 + (br_if $block2 (i32.eqz (i32.load (local.get $2) @@ -4049,7 +4049,7 @@ (i32.const -1) ) ) - (br_if $label$4 + (br_if $label (i32.eqz (i32.load (local.get $5) @@ -4058,7 +4058,7 @@ ) ) ) - (br_if $label$1 + (br_if $block1 (i32.ne (local.tee $3 (call $13 @@ -4068,9 +4068,9 @@ (i32.const 10) ) ) - (loop $label$5 - (block $label$6 - (br_if $label$6 + (loop $label1 + (block $block3 + (br_if $block3 (i32.eqz (local.tee $3 (i32.load @@ -4084,8 +4084,8 @@ (local.get $0) ) ) - (block $label$7 - (br_if $label$7 + (block $block4 + (br_if $block4 (i32.eqz (i32.and (local.get $3) @@ -4093,15 +4093,15 @@ ) ) ) - (br_if $label$6 + (br_if $block3 (i32.and (local.get $6) (i32.const 4) ) ) ) - (block $label$8 - (br_if $label$8 + (block $block5 + (br_if $block5 (i32.ne (i32.and (local.get $6) @@ -4110,7 +4110,7 @@ (i32.const 2) ) ) - (br_if $label$8 + (br_if $block5 (i32.ne (i32.and (local.get $3) @@ -4152,19 +4152,19 @@ (call $54 (local.get $5) ) - (br_if $label$6 + (br_if $block3 (i32.eqz (local.get $3) ) ) - (br_if $label$1 + (br_if $block1 (i32.ne (local.get $3) (i32.const 27) ) ) ) - (br_if $label$5 + (br_if $label1 (i32.eq (local.tee $3 (call $13 @@ -4202,8 +4202,8 @@ ) ) (func $55 (param $0 i32) (result i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.and (i32.load8_u (local.get $0) @@ -4211,7 +4211,7 @@ (i32.const 15) ) ) - (br_if $label$1 + (br_if $block (call $56 (i32.add (local.get $0) @@ -4316,8 +4316,8 @@ (local.get $5) ) ) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.eqz (local.get $1) ) @@ -4346,17 +4346,17 @@ (local $9 i32) (local $10 i32) (local $11 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.load offset=1844 (i32.const 0) ) ) (call $61) ) - (block $label$2 - (block $label$3 - (br_if $label$3 + (block $block2 + (block $block1 + (br_if $block1 (i32.eqz (i32.and (i32.load8_u offset=2312 @@ -4369,32 +4369,32 @@ (local.set $1 (i32.const 0) ) - (br_if $label$2 + (br_if $block2 (call $55 (i32.const 2316) ) ) ) - (block $label$4 - (block $label$5 - (block $label$6 - (block $label$7 - (block $label$8 - (block $label$9 - (block $label$10 - (block $label$11 - (block $label$12 - (block $label$13 - (block $label$14 - (block $label$15 - (br_if $label$15 + (block $block7 + (block $block19 + (block $block31 + (block $block40 + (block $block50 + (block $block44 + (block $block51 + (block $block33 + (block $block21 + (block $block17 + (block $block8 + (block $block3 + (br_if $block3 (i32.gt_u (local.get $0) (i32.const 244) ) ) - (block $label$16 - (br_if $label$16 + (block $block4 + (br_if $block4 (i32.eqz (i32.and (local.tee $0 @@ -4460,9 +4460,9 @@ (i32.const 8) ) ) - (block $label$17 - (block $label$18 - (br_if $label$18 + (block $block6 + (block $block5 + (br_if $block5 (i32.ne (local.tee $3 (i32.load offset=8 @@ -4487,7 +4487,7 @@ ) ) ) - (br $label$17) + (br $block6) ) (i32.store offset=12 (local.get $3) @@ -4524,9 +4524,9 @@ (i32.const 1) ) ) - (br $label$4) + (br $block7) ) - (br_if $label$14 + (br_if $block8 (i32.le_u (local.get $3) (local.tee $6 @@ -4536,15 +4536,15 @@ ) ) ) - (block $label$19 - (br_if $label$19 + (block $block9 + (br_if $block9 (i32.eqz (local.get $0) ) ) - (block $label$20 - (block $label$21 - (br_if $label$21 + (block $block11 + (block $block10 + (br_if $block10 (i32.ne (local.tee $1 (i32.load offset=8 @@ -4693,7 +4693,7 @@ ) ) ) - (br $label$20) + (br $block11) ) (i32.store offset=12 (local.get $1) @@ -4746,8 +4746,8 @@ ) (local.get $4) ) - (block $label$22 - (br_if $label$22 + (block $block12 + (br_if $block12 (i32.eqz (local.get $6) ) @@ -4771,9 +4771,9 @@ (i32.const 0) ) ) - (block $label$23 - (block $label$24 - (br_if $label$24 + (block $block14 + (block $block13 + (br_if $block13 (i32.and (local.get $2) (local.tee $7 @@ -4794,7 +4794,7 @@ (local.set $7 (local.get $3) ) - (br $label$23) + (br $block14) ) (local.set $7 (i32.load offset=8 @@ -4827,9 +4827,9 @@ (i32.const 0) (local.get $4) ) - (br $label$4) + (br $block7) ) - (br_if $label$14 + (br_if $block8 (i32.eqz (local.tee $8 (i32.load offset=1872 @@ -4951,17 +4951,17 @@ (local.set $4 (local.get $5) ) - (block $label$25 - (loop $label$26 - (block $label$27 - (br_if $label$27 + (block $block16 + (loop $label + (block $block15 + (br_if $block15 (local.tee $0 (i32.load offset=16 (local.get $4) ) ) ) - (br_if $label$25 + (br_if $block16 (i32.eqz (local.tee $0 (i32.load @@ -5006,10 +5006,10 @@ (local.set $4 (local.get $0) ) - (br $label$26) + (br $label) ) ) - (br_if $label$13 + (br_if $block17 (i32.le_u (local.tee $9 (i32.add @@ -5025,8 +5025,8 @@ (local.get $5) ) ) - (block $label$28 - (br_if $label$28 + (block $block18 + (br_if $block18 (i32.eq (local.tee $7 (i32.load offset=12 @@ -5056,10 +5056,10 @@ (local.get $7) (local.get $0) ) - (br $label$5) + (br $block19) ) - (block $label$29 - (br_if $label$29 + (block $block20 + (br_if $block20 (local.tee $0 (i32.load (local.tee $4 @@ -5071,7 +5071,7 @@ ) ) ) - (br_if $label$12 + (br_if $block21 (i32.eqz (local.tee $0 (i32.load offset=16 @@ -5087,11 +5087,11 @@ ) ) ) - (loop $label$30 + (loop $label1 (local.set $11 (local.get $4) ) - (br_if $label$30 + (br_if $label1 (local.tee $0 (i32.load (local.tee $4 @@ -5111,7 +5111,7 @@ (i32.const 16) ) ) - (br_if $label$30 + (br_if $label1 (local.tee $0 (i32.load offset=16 (local.get $7) @@ -5123,12 +5123,12 @@ (local.get $11) (i32.const 0) ) - (br $label$5) + (br $block19) ) (local.set $3 (i32.const -1) ) - (br_if $label$14 + (br_if $block8 (i32.gt_u (local.get $0) (i32.const -65) @@ -5145,7 +5145,7 @@ (i32.const -8) ) ) - (br_if $label$14 + (br_if $block8 (i32.eqz (local.tee $6 (i32.load offset=1872 @@ -5157,8 +5157,8 @@ (local.set $11 (i32.const 31) ) - (block $label$31 - (br_if $label$31 + (block $block22 + (br_if $block22 (i32.gt_u (local.get $3) (i32.const 16777215) @@ -5257,11 +5257,11 @@ (local.get $3) ) ) - (block $label$32 - (block $label$33 - (block $label$34 - (block $label$35 - (br_if $label$35 + (block $block28 + (block $block26 + (block $block24 + (block $block23 + (br_if $block23 (local.tee $4 (i32.load (i32.add @@ -5280,7 +5280,7 @@ (local.set $7 (i32.const 0) ) - (br $label$34) + (br $block24) ) (local.set $0 (i32.const 0) @@ -5307,9 +5307,9 @@ (local.set $7 (i32.const 0) ) - (loop $label$36 - (block $label$37 - (br_if $label$37 + (loop $label2 + (block $block25 + (br_if $block25 (i32.ge_u (local.tee $2 (i32.sub @@ -5331,7 +5331,7 @@ (local.set $7 (local.get $4) ) - (br_if $label$37 + (br_if $block25 (local.get $2) ) (local.set $1 @@ -5343,7 +5343,7 @@ (local.set $0 (local.get $4) ) - (br $label$33) + (br $block26) ) (local.set $0 (select @@ -5388,19 +5388,19 @@ (i32.const 1) ) ) - (br_if $label$36 + (br_if $label2 (local.get $4) ) ) ) - (block $label$38 - (br_if $label$38 + (block $block27 + (br_if $block27 (i32.or (local.get $0) (local.get $7) ) ) - (br_if $label$14 + (br_if $block8 (i32.eqz (local.tee $0 (i32.and @@ -5522,13 +5522,13 @@ ) ) ) - (br_if $label$32 + (br_if $block28 (i32.eqz (local.get $0) ) ) ) - (loop $label$39 + (loop $label3 (local.set $5 (i32.lt_u (local.tee $2 @@ -5545,8 +5545,8 @@ (local.get $1) ) ) - (block $label$40 - (br_if $label$40 + (block $block29 + (br_if $block29 (local.tee $4 (i32.load offset=16 (local.get $0) @@ -5579,17 +5579,17 @@ (local.set $0 (local.get $4) ) - (br_if $label$39 + (br_if $label3 (local.get $4) ) ) ) - (br_if $label$14 + (br_if $block8 (i32.eqz (local.get $7) ) ) - (br_if $label$14 + (br_if $block8 (i32.ge_u (local.get $1) (i32.sub @@ -5600,7 +5600,7 @@ ) ) ) - (br_if $label$13 + (br_if $block17 (i32.le_u (local.tee $11 (i32.add @@ -5616,8 +5616,8 @@ (local.get $7) ) ) - (block $label$41 - (br_if $label$41 + (block $block30 + (br_if $block30 (i32.eq (local.tee $5 (i32.load offset=12 @@ -5647,10 +5647,10 @@ (local.get $5) (local.get $0) ) - (br $label$6) + (br $block31) ) - (block $label$42 - (br_if $label$42 + (block $block32 + (br_if $block32 (local.tee $0 (i32.load (local.tee $4 @@ -5662,7 +5662,7 @@ ) ) ) - (br_if $label$11 + (br_if $block33 (i32.eqz (local.tee $0 (i32.load offset=16 @@ -5678,11 +5678,11 @@ ) ) ) - (loop $label$43 + (loop $label4 (local.set $2 (local.get $4) ) - (br_if $label$43 + (br_if $label4 (local.tee $0 (i32.load (local.tee $4 @@ -5702,7 +5702,7 @@ (i32.const 16) ) ) - (br_if $label$43 + (br_if $label4 (local.tee $0 (i32.load offset=16 (local.get $5) @@ -5714,10 +5714,10 @@ (local.get $2) (i32.const 0) ) - (br $label$6) + (br $block31) ) - (block $label$44 - (br_if $label$44 + (block $block34 + (br_if $block34 (i32.lt_u (local.tee $0 (i32.load offset=1876 @@ -5732,9 +5732,9 @@ (i32.const 0) ) ) - (block $label$45 - (block $label$46 - (br_if $label$46 + (block $block36 + (block $block35 + (br_if $block35 (i32.lt_u (local.tee $4 (i32.sub @@ -5779,7 +5779,7 @@ (i32.const 3) ) ) - (br $label$45) + (br $block36) ) (i32.store offset=1888 (i32.const 0) @@ -5817,10 +5817,10 @@ (i32.const 8) ) ) - (br $label$4) + (br $block7) ) - (block $label$47 - (br_if $label$47 + (block $block37 + (br_if $block37 (i32.le_u (local.tee $0 (i32.load offset=1880 @@ -5872,20 +5872,20 @@ (i32.const 8) ) ) - (br $label$4) + (br $block7) ) (local.set $1 (i32.const 0) ) - (block $label$48 - (br_if $label$48 + (block $block38 + (br_if $block38 (i32.load offset=1844 (i32.const 0) ) ) (call $61) ) - (br_if $label$4 + (br_if $block7 (i32.le_u (local.tee $7 (i32.and @@ -5914,8 +5914,8 @@ (local.set $1 (i32.const 0) ) - (block $label$49 - (br_if $label$49 + (block $block39 + (br_if $block39 (i32.eqz (local.tee $0 (i32.load offset=2308 @@ -5924,7 +5924,7 @@ ) ) ) - (br_if $label$4 + (br_if $block7 (i32.le_u (local.tee $5 (i32.add @@ -5939,7 +5939,7 @@ (local.get $4) ) ) - (br_if $label$4 + (br_if $block7 (i32.gt_u (local.get $5) (local.get $0) @@ -5952,7 +5952,7 @@ (local.set $5 (i32.const -1) ) - (br_if $label$7 + (br_if $block40 (i32.and (i32.load8_u offset=2312 (i32.const 0) @@ -5963,10 +5963,10 @@ (local.set $6 (i32.const 0) ) - (block $label$50 - (block $label$51 - (block $label$52 - (br_if $label$52 + (block $block49 + (block $block43 + (block $block41 + (br_if $block41 (i32.eqz (local.tee $1 (i32.load offset=1892 @@ -5978,9 +5978,9 @@ (local.set $0 (i32.const 2344) ) - (loop $label$53 - (block $label$54 - (br_if $label$54 + (loop $label5 + (block $block42 + (br_if $block42 (i32.gt_u (local.tee $4 (i32.load @@ -5990,7 +5990,7 @@ (local.get $1) ) ) - (br_if $label$51 + (br_if $block43 (i32.gt_u (i32.add (local.get $4) @@ -6002,7 +6002,7 @@ ) ) ) - (br_if $label$53 + (br_if $label5 (local.tee $0 (i32.load offset=8 (local.get $0) @@ -6016,7 +6016,7 @@ (i32.const 2368) ) ) - (br_if $label$9 + (br_if $block44 (i32.eq (local.tee $5 (call $66 @@ -6029,8 +6029,8 @@ (local.set $2 (local.get $7) ) - (block $label$55 - (br_if $label$55 + (block $block45 + (br_if $block45 (i32.eqz (i32.and (local.tee $1 @@ -6066,8 +6066,8 @@ ) ) ) - (block $label$56 - (br_if $label$56 + (block $block46 + (br_if $block46 (i32.gt_u (local.get $2) (local.get $3) @@ -6076,10 +6076,10 @@ (local.set $6 (i32.const 0) ) - (br $label$9) + (br $block44) ) - (block $label$57 - (br_if $label$57 + (block $block47 + (br_if $block47 (i32.le_u (local.get $2) (i32.const 2147483646) @@ -6088,13 +6088,13 @@ (local.set $6 (i32.const 0) ) - (br $label$9) + (br $block44) ) (local.set $6 (i32.const 0) ) - (block $label$58 - (br_if $label$58 + (block $block48 + (br_if $block48 (i32.eqz (local.tee $0 (i32.load offset=2308 @@ -6103,7 +6103,7 @@ ) ) ) - (br_if $label$9 + (br_if $block44 (i32.le_u (local.tee $4 (i32.add @@ -6118,14 +6118,14 @@ (local.get $1) ) ) - (br_if $label$9 + (br_if $block44 (i32.gt_u (local.get $4) (local.get $0) ) ) ) - (br_if $label$50 + (br_if $block49 (i32.ne (local.tee $0 (call $66 @@ -6135,7 +6135,7 @@ (local.get $5) ) ) - (br $label$8) + (br $block50) ) (drop (call $55 @@ -6145,7 +6145,7 @@ (local.set $6 (i32.const 0) ) - (br_if $label$9 + (br_if $block44 (i32.gt_u (local.tee $2 (i32.and @@ -6171,7 +6171,7 @@ (i32.const 2147483646) ) ) - (br_if $label$10 + (br_if $block51 (i32.eq (local.tee $5 (call $66 @@ -6195,8 +6195,8 @@ (local.set $6 (i32.const 0) ) - (block $label$59 - (br_if $label$59 + (block $block52 + (br_if $block52 (i32.le_u (i32.add (local.get $3) @@ -6205,14 +6205,14 @@ (local.get $2) ) ) - (br_if $label$59 + (br_if $block52 (i32.eq (local.get $0) (i32.const -1) ) ) - (block $label$60 - (br_if $label$60 + (block $block53 + (br_if $block53 (i32.le_u (local.tee $1 (i32.and @@ -6239,10 +6239,10 @@ (local.set $5 (local.get $0) ) - (br $label$8) + (br $block50) ) - (block $label$61 - (br_if $label$61 + (block $block54 + (br_if $block54 (i32.eq (call $66 (local.get $1) @@ -6259,7 +6259,7 @@ (local.set $5 (local.get $0) ) - (br $label$8) + (br $block50) ) (drop (call $66 @@ -6272,35 +6272,36 @@ (local.set $6 (i32.const 0) ) - (br $label$9) + (br $block44) ) (local.set $5 (local.get $0) ) - (br_if $label$8 + (br_if $block50 (i32.ne (local.get $0) (i32.const -1) ) ) - (br $label$9) + (br $block44) ) (unreachable) + (unreachable) ) (local.set $7 (i32.const 0) ) - (br $label$5) + (br $block19) ) (local.set $5 (i32.const 0) ) - (br $label$6) + (br $block31) ) (local.set $6 (local.get $2) ) - (br_if $label$8 + (br_if $block50 (i32.ne (local.get $5) (i32.const -1) @@ -6329,16 +6330,16 @@ ) ) ) - (block $label$62 - (block $label$63 - (block $label$64 - (br_if $label$64 + (block $block56 + (block $block57 + (block $block55 + (br_if $block55 (i32.gt_u (local.get $7) (i32.const 2147483646) ) ) - (br_if $label$64 + (br_if $block55 (i32.ne (local.get $5) (i32.const -1) @@ -6364,25 +6365,25 @@ (i32.const 2368) ) ) - (br_if $label$62 + (br_if $block56 (i32.ge_u (local.get $5) (local.get $0) ) ) - (br_if $label$62 + (br_if $block56 (i32.eq (local.get $5) (i32.const -1) ) ) - (br_if $label$62 + (br_if $block56 (i32.eq (local.get $0) (i32.const -1) ) ) - (br_if $label$63 + (br_if $block57 (i32.gt_u (local.tee $2 (i32.sub @@ -6396,9 +6397,9 @@ ) ) ) - (br $label$62) + (br $block56) ) - (br_if $label$62 + (br_if $block56 (i32.eq (local.get $5) (i32.const -1) @@ -6416,8 +6417,8 @@ ) ) ) - (block $label$65 - (br_if $label$65 + (block $block58 + (br_if $block58 (i32.le_u (local.get $0) (i32.load offset=2304 @@ -6430,11 +6431,11 @@ (local.get $0) ) ) - (block $label$66 - (block $label$67 - (block $label$68 - (block $label$69 - (br_if $label$69 + (block $block64 + (block $block61 + (block $block60 + (block $block59 + (br_if $block59 (i32.eqz (local.tee $1 (i32.load offset=1892 @@ -6446,8 +6447,8 @@ (local.set $0 (i32.const 2344) ) - (loop $label$70 - (br_if $label$68 + (loop $label6 + (br_if $block60 (i32.eq (local.get $5) (i32.add @@ -6464,19 +6465,19 @@ ) ) ) - (br_if $label$70 + (br_if $label6 (local.tee $0 (i32.load offset=8 (local.get $0) ) ) ) - (br $label$67) + (br $block61) ) ) - (block $label$71 - (block $label$72 - (br_if $label$72 + (block $block63 + (block $block62 + (br_if $block62 (i32.eqz (local.tee $0 (i32.load offset=1884 @@ -6485,7 +6486,7 @@ ) ) ) - (br_if $label$71 + (br_if $block63 (i32.ge_u (local.get $5) (local.get $0) @@ -6522,7 +6523,7 @@ (i32.const 0) (i32.const 0) ) - (loop $label$73 + (loop $label7 (i32.store (i32.add (local.tee $1 @@ -6547,7 +6548,7 @@ ) (local.get $4) ) - (br_if $label$73 + (br_if $label7 (i32.ne (local.tee $0 (i32.add @@ -6620,21 +6621,21 @@ (i32.const 0) ) ) - (br $label$66) + (br $block64) ) - (br_if $label$67 + (br_if $block61 (i32.le_u (local.get $5) (local.get $1) ) ) - (br_if $label$67 + (br_if $block61 (i32.gt_u (local.get $4) (local.get $1) ) ) - (br_if $label$67 + (br_if $block61 (i32.and (i32.load offset=12 (local.get $0) @@ -6712,10 +6713,10 @@ (i32.const 0) ) ) - (br $label$66) + (br $block64) ) - (block $label$74 - (br_if $label$74 + (block $block65 + (br_if $block65 (i32.ge_u (local.get $5) (local.tee $7 @@ -6742,15 +6743,15 @@ (local.set $0 (i32.const 2344) ) - (block $label$75 - (block $label$76 - (block $label$77 - (block $label$78 - (block $label$79 - (block $label$80 - (block $label$81 - (loop $label$82 - (br_if $label$81 + (block $block97 + (block $block72 + (block $block90 + (block $block70 + (block $block68 + (block $block67 + (block $block66 + (loop $label8 + (br_if $block66 (i32.eq (i32.load (local.get $0) @@ -6758,17 +6759,17 @@ (local.get $4) ) ) - (br_if $label$82 + (br_if $label8 (local.tee $0 (i32.load offset=8 (local.get $0) ) ) ) - (br $label$80) + (br $block67) ) ) - (br_if $label$79 + (br_if $block68 (i32.eqz (i32.and (i32.load8_u offset=12 @@ -6782,9 +6783,9 @@ (local.set $0 (i32.const 2344) ) - (loop $label$83 - (block $label$84 - (br_if $label$84 + (loop $label9 + (block $block69 + (br_if $block69 (i32.gt_u (local.tee $4 (i32.load @@ -6794,7 +6795,7 @@ (local.get $1) ) ) - (br_if $label$78 + (br_if $block70 (i32.gt_u (local.tee $4 (i32.add @@ -6813,7 +6814,7 @@ (local.get $0) ) ) - (br $label$83) + (br $label9) ) ) (i32.store @@ -6893,8 +6894,8 @@ (local.get $3) ) ) - (block $label$85 - (br_if $label$85 + (block $block71 + (br_if $block71 (i32.ne (local.get $1) (local.get $2) @@ -6922,10 +6923,10 @@ (i32.const 1) ) ) - (br $label$76) + (br $block72) ) - (block $label$86 - (br_if $label$86 + (block $block73 + (br_if $block73 (i32.ne (i32.load offset=1888 (i32.const 0) @@ -6962,10 +6963,10 @@ ) (local.get $0) ) - (br $label$76) + (br $block72) ) - (block $label$87 - (br_if $label$87 + (block $block74 + (br_if $block74 (i32.ne (i32.and (local.tee $0 @@ -6984,9 +6985,9 @@ (i32.const -8) ) ) - (block $label$88 - (block $label$89 - (br_if $label$89 + (block $block77 + (block $block75 + (br_if $block75 (i32.gt_u (local.get $0) (i32.const 255) @@ -7015,8 +7016,8 @@ ) ) ) - (block $label$90 - (br_if $label$90 + (block $block76 + (br_if $block76 (i32.ne (local.tee $0 (i32.load offset=12 @@ -7038,7 +7039,7 @@ ) ) ) - (br $label$88) + (br $block77) ) (drop (i32.eq @@ -7054,16 +7055,16 @@ (local.get $0) (local.get $1) ) - (br $label$88) + (br $block77) ) (local.set $8 (i32.load offset=24 (local.get $2) ) ) - (block $label$91 - (block $label$92 - (br_if $label$92 + (block $block79 + (block $block78 + (br_if $block78 (i32.eq (local.tee $5 (i32.load offset=12 @@ -7091,10 +7092,10 @@ (local.get $5) (local.get $0) ) - (br $label$91) + (br $block79) ) - (block $label$93 - (br_if $label$93 + (block $block80 + (br_if $block80 (local.tee $1 (i32.load (local.tee $0 @@ -7106,7 +7107,7 @@ ) ) ) - (br_if $label$93 + (br_if $block80 (local.tee $1 (i32.load (local.tee $0 @@ -7121,13 +7122,13 @@ (local.set $5 (i32.const 0) ) - (br $label$91) + (br $block79) ) - (loop $label$94 + (loop $label10 (local.set $7 (local.get $0) ) - (br_if $label$94 + (br_if $label10 (local.tee $1 (i32.load (local.tee $0 @@ -7147,7 +7148,7 @@ (i32.const 16) ) ) - (br_if $label$94 + (br_if $label10 (local.tee $1 (i32.load offset=16 (local.get $5) @@ -7160,14 +7161,14 @@ (i32.const 0) ) ) - (br_if $label$88 + (br_if $block77 (i32.eqz (local.get $8) ) ) - (block $label$95 - (block $label$96 - (br_if $label$96 + (block $block82 + (block $block81 + (br_if $block81 (i32.ne (i32.load (local.tee $0 @@ -7191,7 +7192,7 @@ (local.get $0) (local.get $5) ) - (br_if $label$95 + (br_if $block82 (local.get $5) ) (i32.store offset=1872 @@ -7206,7 +7207,7 @@ ) ) ) - (br $label$88) + (br $block77) ) (i32.store (i32.add @@ -7224,7 +7225,7 @@ ) (local.get $5) ) - (br_if $label$88 + (br_if $block77 (i32.eqz (local.get $5) ) @@ -7234,8 +7235,8 @@ (local.get $5) (local.get $8) ) - (block $label$97 - (br_if $label$97 + (block $block83 + (br_if $block83 (i32.eqz (local.tee $0 (i32.load offset=16 @@ -7253,7 +7254,7 @@ (local.get $5) ) ) - (br_if $label$88 + (br_if $block77 (i32.eqz (local.tee $0 (i32.load offset=20 @@ -7310,8 +7311,8 @@ ) (local.get $4) ) - (block $label$98 - (br_if $label$98 + (block $block84 + (br_if $block84 (i32.gt_u (local.get $4) (i32.const 255) @@ -7331,9 +7332,9 @@ (i32.const 1908) ) ) - (block $label$99 - (block $label$100 - (br_if $label$100 + (block $block86 + (block $block85 + (br_if $block85 (i32.and (local.tee $4 (i32.load offset=1868 @@ -7358,7 +7359,7 @@ (local.set $1 (local.get $0) ) - (br $label$99) + (br $block86) ) (local.set $1 (i32.load offset=8 @@ -7382,13 +7383,13 @@ (local.get $3) (local.get $1) ) - (br $label$76) + (br $block72) ) (local.set $0 (i32.const 31) ) - (block $label$101 - (br_if $label$101 + (block $block87 + (br_if $block87 (i32.gt_u (local.get $4) (i32.const 16777215) @@ -7498,9 +7499,9 @@ (i32.const 2172) ) ) - (block $label$102 - (block $label$103 - (br_if $label$103 + (block $block89 + (block $block88 + (br_if $block88 (i32.and (local.tee $5 (i32.load offset=1872 @@ -7530,7 +7531,7 @@ (local.get $3) (local.get $1) ) - (br $label$102) + (br $block89) ) (local.set $0 (i32.shl @@ -7556,8 +7557,8 @@ (local.get $1) ) ) - (loop $label$104 - (br_if $label$77 + (loop $label11 + (br_if $block90 (i32.eq (i32.and (i32.load offset=4 @@ -7582,7 +7583,7 @@ (i32.const 1) ) ) - (br_if $label$104 + (br_if $label11 (local.tee $5 (i32.load (local.tee $7 @@ -7618,7 +7619,7 @@ (local.get $3) (local.get $3) ) - (br $label$76) + (br $block72) ) (i32.store offset=1880 (i32.const 0) @@ -7761,7 +7762,7 @@ (i32.const 24) ) ) - (loop $label$105 + (loop $label12 (i32.store offset=4 (local.get $0) (i32.const 7) @@ -7778,14 +7779,14 @@ (i32.const 4) ) ) - (br_if $label$105 + (br_if $label12 (i32.gt_u (local.get $4) (local.get $5) ) ) ) - (br_if $label$66 + (br_if $block64 (i32.eq (local.get $7) (local.get $1) @@ -7816,8 +7817,8 @@ (local.get $7) (local.get $2) ) - (block $label$106 - (br_if $label$106 + (block $block91 + (br_if $block91 (i32.gt_u (local.get $2) (i32.const 255) @@ -7837,9 +7838,9 @@ (i32.const 1908) ) ) - (block $label$107 - (block $label$108 - (br_if $label$108 + (block $block93 + (block $block92 + (br_if $block92 (i32.and (local.tee $5 (i32.load offset=1868 @@ -7864,7 +7865,7 @@ (local.set $4 (local.get $0) ) - (br $label$107) + (br $block93) ) (local.set $4 (i32.load offset=8 @@ -7888,13 +7889,13 @@ (local.get $1) (local.get $4) ) - (br $label$66) + (br $block64) ) (local.set $0 (i32.const 31) ) - (block $label$109 - (br_if $label$109 + (block $block94 + (br_if $block94 (i32.gt_u (local.get $2) (i32.const 16777215) @@ -8007,9 +8008,9 @@ (i32.const 2172) ) ) - (block $label$110 - (block $label$111 - (br_if $label$111 + (block $block96 + (block $block95 + (br_if $block95 (i32.and (local.tee $5 (i32.load offset=1872 @@ -8042,7 +8043,7 @@ ) (local.get $4) ) - (br $label$110) + (br $block96) ) (local.set $0 (i32.shl @@ -8068,8 +8069,8 @@ (local.get $4) ) ) - (loop $label$112 - (br_if $label$75 + (loop $label13 + (br_if $block97 (i32.eq (i32.and (i32.load offset=4 @@ -8094,7 +8095,7 @@ (i32.const 1) ) ) - (br_if $label$112 + (br_if $label13 (local.tee $5 (i32.load (local.tee $7 @@ -8133,7 +8134,7 @@ (local.get $1) (local.get $1) ) - (br $label$66) + (br $block64) ) (i32.store offset=12 (local.tee $0 @@ -8166,7 +8167,7 @@ (i32.const 8) ) ) - (br $label$4) + (br $block7) ) (i32.store offset=12 (local.tee $0 @@ -8196,7 +8197,7 @@ (local.get $0) ) ) - (br_if $label$62 + (br_if $block56 (i32.le_u (local.tee $0 (i32.load offset=1880 @@ -8248,7 +8249,7 @@ (i32.const 8) ) ) - (br $label$4) + (br $block7) ) (i32.store (call $25) @@ -8257,17 +8258,17 @@ (local.set $1 (i32.const 0) ) - (br $label$4) + (br $block7) ) - (block $label$113 - (br_if $label$113 + (block $block98 + (br_if $block98 (i32.eqz (local.get $8) ) ) - (block $label$114 - (block $label$115 - (br_if $label$115 + (block $block100 + (block $block99 + (br_if $block99 (i32.ne (local.get $7) (i32.load @@ -8291,7 +8292,7 @@ (local.get $0) (local.get $5) ) - (br_if $label$114 + (br_if $block100 (local.get $5) ) (i32.store offset=1872 @@ -8306,7 +8307,7 @@ ) ) ) - (br $label$113) + (br $block98) ) (i32.store (i32.add @@ -8324,7 +8325,7 @@ ) (local.get $5) ) - (br_if $label$113 + (br_if $block98 (i32.eqz (local.get $5) ) @@ -8334,8 +8335,8 @@ (local.get $5) (local.get $8) ) - (block $label$116 - (br_if $label$116 + (block $block101 + (br_if $block101 (i32.eqz (local.tee $0 (i32.load offset=16 @@ -8353,7 +8354,7 @@ (local.get $5) ) ) - (br_if $label$113 + (br_if $block98 (i32.eqz (local.tee $0 (i32.load @@ -8377,9 +8378,9 @@ (local.get $5) ) ) - (block $label$117 - (block $label$118 - (br_if $label$118 + (block $block103 + (block $block102 + (br_if $block102 (i32.gt_u (local.get $1) (i32.const 15) @@ -8411,7 +8412,7 @@ (i32.const 1) ) ) - (br $label$117) + (br $block103) ) (i32.store offset=4 (local.get $7) @@ -8434,8 +8435,8 @@ ) (local.get $1) ) - (block $label$119 - (br_if $label$119 + (block $block104 + (br_if $block104 (i32.gt_u (local.get $1) (i32.const 255) @@ -8455,9 +8456,9 @@ (i32.const 1908) ) ) - (block $label$120 - (block $label$121 - (br_if $label$121 + (block $block106 + (block $block105 + (br_if $block105 (i32.and (local.tee $4 (i32.load offset=1868 @@ -8482,7 +8483,7 @@ (local.set $1 (local.get $0) ) - (br $label$120) + (br $block106) ) (local.set $1 (i32.load offset=8 @@ -8506,13 +8507,13 @@ (local.get $11) (local.get $1) ) - (br $label$117) + (br $block103) ) (local.set $0 (i32.const 31) ) - (block $label$122 - (br_if $label$122 + (block $block107 + (br_if $block107 (i32.gt_u (local.get $1) (i32.const 16777215) @@ -8622,10 +8623,10 @@ (i32.const 2172) ) ) - (block $label$123 - (block $label$124 - (block $label$125 - (br_if $label$125 + (block $block110 + (block $block109 + (block $block108 + (br_if $block108 (i32.and (local.get $6) (local.tee $3 @@ -8651,7 +8652,7 @@ (local.get $11) (local.get $4) ) - (br $label$124) + (br $block109) ) (local.set $0 (i32.shl @@ -8677,8 +8678,8 @@ (local.get $4) ) ) - (loop $label$126 - (br_if $label$123 + (loop $label14 + (br_if $block110 (i32.eq (i32.and (i32.load offset=4 @@ -8703,7 +8704,7 @@ (i32.const 1) ) ) - (br_if $label$126 + (br_if $label14 (local.tee $3 (i32.load (local.tee $5 @@ -8739,7 +8740,7 @@ (local.get $11) (local.get $11) ) - (br $label$117) + (br $block103) ) (i32.store offset=12 (local.tee $0 @@ -8772,17 +8773,17 @@ (i32.const 8) ) ) - (br $label$4) + (br $block7) ) - (block $label$127 - (br_if $label$127 + (block $block111 + (br_if $block111 (i32.eqz (local.get $10) ) ) - (block $label$128 - (block $label$129 - (br_if $label$129 + (block $block113 + (block $block112 + (br_if $block112 (i32.ne (local.get $5) (i32.load @@ -8806,7 +8807,7 @@ (local.get $0) (local.get $7) ) - (br_if $label$128 + (br_if $block113 (local.get $7) ) (i32.store offset=1872 @@ -8819,7 +8820,7 @@ ) ) ) - (br $label$127) + (br $block111) ) (i32.store (i32.add @@ -8837,7 +8838,7 @@ ) (local.get $7) ) - (br_if $label$127 + (br_if $block111 (i32.eqz (local.get $7) ) @@ -8847,8 +8848,8 @@ (local.get $7) (local.get $10) ) - (block $label$130 - (br_if $label$130 + (block $block114 + (br_if $block114 (i32.eqz (local.tee $0 (i32.load offset=16 @@ -8866,7 +8867,7 @@ (local.get $7) ) ) - (br_if $label$127 + (br_if $block111 (i32.eqz (local.tee $0 (i32.load @@ -8890,9 +8891,9 @@ (local.get $7) ) ) - (block $label$131 - (block $label$132 - (br_if $label$132 + (block $block116 + (block $block115 + (br_if $block115 (i32.gt_u (local.get $1) (i32.const 15) @@ -8924,7 +8925,7 @@ (i32.const 1) ) ) - (br $label$131) + (br $block116) ) (i32.store offset=4 (local.get $5) @@ -8947,8 +8948,8 @@ ) (local.get $1) ) - (block $label$133 - (br_if $label$133 + (block $block117 + (br_if $block117 (i32.eqz (local.get $6) ) @@ -8972,9 +8973,9 @@ (i32.const 0) ) ) - (block $label$134 - (block $label$135 - (br_if $label$135 + (block $block119 + (block $block118 + (br_if $block118 (i32.and (local.tee $3 (i32.shl @@ -8995,7 +8996,7 @@ (local.set $3 (local.get $4) ) - (br $label$134) + (br $block119) ) (local.set $3 (i32.load offset=8 @@ -9036,7 +9037,7 @@ ) ) ) - (br_if $label$2 + (br_if $block2 (i32.eqz (i32.and (i32.load8_u offset=2312 @@ -9069,8 +9070,8 @@ (i32.const 2368) ) ) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.load offset=1844 (i32.const 0) ) @@ -9091,8 +9092,8 @@ (i32.const 0) (i32.const 2) ) - (block $label$2 - (br_if $label$2 + (block $block1 + (br_if $block1 (call $57 (i32.add (local.get $0) @@ -9100,7 +9101,7 @@ ) ) ) - (br_if $label$2 + (br_if $block1 (call $58 (i32.const 2316) (i32.add @@ -9152,14 +9153,14 @@ (local $5 i32) (local $6 i32) (local $7 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.eqz (local.get $0) ) ) - (block $label$2 - (br_if $label$2 + (block $block1 + (br_if $block1 (i32.eqz (i32.and (i32.load8_u offset=2312 @@ -9169,7 +9170,7 @@ ) ) ) - (br_if $label$1 + (br_if $block (call $55 (i32.const 2316) ) @@ -9198,15 +9199,15 @@ ) ) ) - (block $label$3 - (block $label$4 - (br_if $label$4 + (block $block3 + (block $block2 + (br_if $block2 (i32.and (local.get $2) (i32.const 1) ) ) - (br_if $label$3 + (br_if $block3 (i32.eqz (i32.and (local.get $2) @@ -9214,7 +9215,7 @@ ) ) ) - (br_if $label$3 + (br_if $block3 (i32.lt_u (local.tee $1 (i32.sub @@ -9239,8 +9240,8 @@ (local.get $0) ) ) - (block $label$5 - (br_if $label$5 + (block $block4 + (br_if $block4 (i32.eq (i32.load offset=1888 (i32.const 0) @@ -9248,8 +9249,8 @@ (local.get $1) ) ) - (block $label$6 - (br_if $label$6 + (block $block5 + (br_if $block5 (i32.gt_u (local.get $2) (i32.const 255) @@ -9278,8 +9279,8 @@ ) ) ) - (block $label$7 - (br_if $label$7 + (block $block6 + (br_if $block6 (i32.ne (local.tee $2 (i32.load offset=12 @@ -9301,7 +9302,7 @@ ) ) ) - (br $label$4) + (br $block2) ) (drop (i32.eq @@ -9317,16 +9318,16 @@ (local.get $2) (local.get $4) ) - (br $label$4) + (br $block2) ) (local.set $7 (i32.load offset=24 (local.get $1) ) ) - (block $label$8 - (block $label$9 - (br_if $label$9 + (block $block8 + (block $block7 + (br_if $block7 (i32.eq (local.tee $6 (i32.load offset=12 @@ -9354,10 +9355,10 @@ (local.get $6) (local.get $2) ) - (br $label$8) + (br $block8) ) - (block $label$10 - (br_if $label$10 + (block $block9 + (br_if $block9 (local.tee $4 (i32.load (local.tee $2 @@ -9369,7 +9370,7 @@ ) ) ) - (br_if $label$10 + (br_if $block9 (local.tee $4 (i32.load (local.tee $2 @@ -9384,13 +9385,13 @@ (local.set $6 (i32.const 0) ) - (br $label$8) + (br $block8) ) - (loop $label$11 + (loop $label (local.set $5 (local.get $2) ) - (br_if $label$11 + (br_if $label (local.tee $4 (i32.load (local.tee $2 @@ -9410,7 +9411,7 @@ (i32.const 16) ) ) - (br_if $label$11 + (br_if $label (local.tee $4 (i32.load offset=16 (local.get $6) @@ -9423,14 +9424,14 @@ (i32.const 0) ) ) - (br_if $label$4 + (br_if $block2 (i32.eqz (local.get $7) ) ) - (block $label$12 - (block $label$13 - (br_if $label$13 + (block $block11 + (block $block10 + (br_if $block10 (i32.ne (i32.load (local.tee $2 @@ -9454,7 +9455,7 @@ (local.get $2) (local.get $6) ) - (br_if $label$12 + (br_if $block11 (local.get $6) ) (i32.store offset=1872 @@ -9469,7 +9470,7 @@ ) ) ) - (br $label$4) + (br $block2) ) (i32.store (i32.add @@ -9487,7 +9488,7 @@ ) (local.get $6) ) - (br_if $label$4 + (br_if $block2 (i32.eqz (local.get $6) ) @@ -9497,8 +9498,8 @@ (local.get $6) (local.get $7) ) - (block $label$14 - (br_if $label$14 + (block $block12 + (br_if $block12 (i32.eqz (local.tee $2 (i32.load offset=16 @@ -9516,7 +9517,7 @@ (local.get $6) ) ) - (br_if $label$4 + (br_if $block2 (i32.eqz (local.tee $2 (i32.load offset=20 @@ -9536,9 +9537,9 @@ (local.get $2) (local.get $6) ) - (br $label$4) + (br $block2) ) - (br_if $label$4 + (br_if $block2 (i32.ne (i32.and (local.tee $2 @@ -9576,15 +9577,15 @@ ) (local.get $0) ) - (br $label$3) + (br $block3) ) - (br_if $label$3 + (br_if $block3 (i32.le_u (local.get $3) (local.get $1) ) ) - (br_if $label$3 + (br_if $block3 (i32.eqz (i32.and (local.tee $2 @@ -9596,16 +9597,16 @@ ) ) ) - (block $label$15 - (block $label$16 - (br_if $label$16 + (block $block25 + (block $block13 + (br_if $block13 (i32.and (local.get $2) (i32.const 2) ) ) - (block $label$17 - (br_if $label$17 + (block $block14 + (br_if $block14 (i32.ne (i32.load offset=1892 (i32.const 0) @@ -9635,7 +9636,7 @@ (i32.const 1) ) ) - (br_if $label$3 + (br_if $block3 (i32.ne (local.get $1) (i32.load offset=1888 @@ -9651,10 +9652,10 @@ (i32.const 0) (i32.const 0) ) - (br $label$3) + (br $block3) ) - (block $label$18 - (br_if $label$18 + (block $block15 + (br_if $block15 (i32.ne (i32.load offset=1888 (i32.const 0) @@ -9691,7 +9692,7 @@ ) (local.get $0) ) - (br $label$3) + (br $block3) ) (local.set $0 (i32.add @@ -9702,9 +9703,9 @@ (local.get $0) ) ) - (block $label$19 - (block $label$20 - (br_if $label$20 + (block $block18 + (block $block16 + (br_if $block16 (i32.gt_u (local.get $2) (i32.const 255) @@ -9733,8 +9734,8 @@ ) ) ) - (block $label$21 - (br_if $label$21 + (block $block17 + (br_if $block17 (i32.ne (local.tee $2 (i32.load offset=12 @@ -9756,7 +9757,7 @@ ) ) ) - (br $label$19) + (br $block18) ) (drop (i32.eq @@ -9772,16 +9773,16 @@ (local.get $2) (local.get $4) ) - (br $label$19) + (br $block18) ) (local.set $7 (i32.load offset=24 (local.get $3) ) ) - (block $label$22 - (block $label$23 - (br_if $label$23 + (block $block20 + (block $block19 + (br_if $block19 (i32.eq (local.tee $6 (i32.load offset=12 @@ -9811,10 +9812,10 @@ (local.get $6) (local.get $2) ) - (br $label$22) + (br $block20) ) - (block $label$24 - (br_if $label$24 + (block $block21 + (br_if $block21 (local.tee $2 (i32.load (local.tee $4 @@ -9826,7 +9827,7 @@ ) ) ) - (br_if $label$24 + (br_if $block21 (local.tee $2 (i32.load (local.tee $4 @@ -9841,13 +9842,13 @@ (local.set $6 (i32.const 0) ) - (br $label$22) + (br $block20) ) - (loop $label$25 + (loop $label1 (local.set $5 (local.get $4) ) - (br_if $label$25 + (br_if $label1 (local.tee $2 (i32.load (local.tee $4 @@ -9867,7 +9868,7 @@ (i32.const 16) ) ) - (br_if $label$25 + (br_if $label1 (local.tee $2 (i32.load offset=16 (local.get $6) @@ -9880,14 +9881,14 @@ (i32.const 0) ) ) - (br_if $label$19 + (br_if $block18 (i32.eqz (local.get $7) ) ) - (block $label$26 - (block $label$27 - (br_if $label$27 + (block $block23 + (block $block22 + (br_if $block22 (i32.ne (i32.load (local.tee $2 @@ -9911,7 +9912,7 @@ (local.get $2) (local.get $6) ) - (br_if $label$26 + (br_if $block23 (local.get $6) ) (i32.store offset=1872 @@ -9926,7 +9927,7 @@ ) ) ) - (br $label$19) + (br $block18) ) (i32.store (i32.add @@ -9944,7 +9945,7 @@ ) (local.get $6) ) - (br_if $label$19 + (br_if $block18 (i32.eqz (local.get $6) ) @@ -9954,8 +9955,8 @@ (local.get $6) (local.get $7) ) - (block $label$28 - (br_if $label$28 + (block $block24 + (br_if $block24 (i32.eqz (local.tee $2 (i32.load offset=16 @@ -9973,7 +9974,7 @@ (local.get $6) ) ) - (br_if $label$19 + (br_if $block18 (i32.eqz (local.tee $2 (i32.load offset=20 @@ -10008,7 +10009,7 @@ ) (local.get $0) ) - (br_if $label$15 + (br_if $block25 (i32.ne (local.get $1) (i32.load offset=1888 @@ -10020,7 +10021,7 @@ (i32.const 0) (local.get $0) ) - (br $label$3) + (br $block3) ) (i32.store offset=4 (local.get $3) @@ -10044,8 +10045,8 @@ (local.get $0) ) ) - (block $label$29 - (br_if $label$29 + (block $block26 + (br_if $block26 (i32.gt_u (local.get $0) (i32.const 255) @@ -10065,9 +10066,9 @@ (i32.const 1908) ) ) - (block $label$30 - (block $label$31 - (br_if $label$31 + (block $block28 + (block $block27 + (br_if $block27 (i32.and (local.tee $4 (i32.load offset=1868 @@ -10092,7 +10093,7 @@ (local.set $2 (local.get $0) ) - (br $label$30) + (br $block28) ) (local.set $2 (i32.load offset=8 @@ -10116,13 +10117,13 @@ (local.get $1) (local.get $2) ) - (br $label$3) + (br $block3) ) (local.set $2 (i32.const 31) ) - (block $label$32 - (br_if $label$32 + (block $block29 + (br_if $block29 (i32.gt_u (local.get $0) (i32.const 16777215) @@ -10235,11 +10236,11 @@ (i32.const 2172) ) ) - (block $label$33 - (block $label$34 - (block $label$35 - (block $label$36 - (br_if $label$36 + (block $block33 + (block $block32 + (block $block31 + (block $block30 + (br_if $block30 (i32.and (local.tee $6 (i32.load offset=1872 @@ -10272,7 +10273,7 @@ ) (local.get $4) ) - (br $label$35) + (br $block31) ) (local.set $2 (i32.shl @@ -10298,8 +10299,8 @@ (local.get $4) ) ) - (loop $label$37 - (br_if $label$34 + (loop $label2 + (br_if $block32 (i32.eq (i32.and (i32.load offset=4 @@ -10324,7 +10325,7 @@ (i32.const 1) ) ) - (br_if $label$37 + (br_if $label2 (local.tee $6 (i32.load (local.tee $3 @@ -10363,7 +10364,7 @@ (local.get $1) (local.get $1) ) - (br $label$33) + (br $block33) ) (i32.store offset=12 (local.tee $0 @@ -10409,7 +10410,7 @@ ) ) ) - (br_if $label$1 + (br_if $block (i32.eqz (i32.and (i32.load8_u offset=2312 @@ -10427,8 +10428,8 @@ ) ) (func $63 (param $0 i32) (param $1 i32) (result i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.gt_u (local.get $0) (i32.const 8) @@ -10454,9 +10455,9 @@ (local.set $2 (i32.const 16) ) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (i32.and (local.tee $3 (select @@ -10477,9 +10478,9 @@ (local.set $0 (local.get $3) ) - (br $label$1) + (br $block1) ) - (loop $label$3 + (loop $label (local.set $2 (i32.shl (local.tee $0 @@ -10488,7 +10489,7 @@ (i32.const 1) ) ) - (br_if $label$3 + (br_if $label (i32.lt_u (local.get $0) (local.get $3) @@ -10496,8 +10497,8 @@ ) ) ) - (block $label$4 - (br_if $label$4 + (block $block2 + (br_if $block2 (i32.gt_u (i32.sub (i32.const -64) @@ -10514,8 +10515,8 @@ (i32.const 0) ) ) - (block $label$5 - (br_if $label$5 + (block $block3 + (br_if $block3 (local.tee $3 (call $60 (i32.add @@ -10550,9 +10551,9 @@ (local.set $2 (i32.const 0) ) - (block $label$6 - (block $label$7 - (br_if $label$7 + (block $block5 + (block $block4 + (br_if $block4 (i32.eqz (i32.and (i32.load8_u offset=2312 @@ -10562,7 +10563,7 @@ ) ) ) - (br_if $label$6 + (br_if $block5 (call $55 (i32.const 2316) ) @@ -10574,8 +10575,8 @@ (i32.const -8) ) ) - (block $label$8 - (br_if $label$8 + (block $block6 + (br_if $block6 (i32.eqz (i32.and (i32.add @@ -10641,9 +10642,9 @@ ) ) ) - (block $label$9 - (block $label$10 - (br_if $label$10 + (block $block8 + (block $block7 + (br_if $block7 (i32.and (local.get $5) (i32.const 3) @@ -10665,7 +10666,7 @@ (local.get $3) ) ) - (br $label$9) + (br $block8) ) (i32.store offset=4 (local.get $0) @@ -10734,8 +10735,8 @@ (local.get $0) ) ) - (block $label$11 - (br_if $label$11 + (block $block9 + (br_if $block9 (i32.eqz (i32.and (local.tee $0 @@ -10747,7 +10748,7 @@ ) ) ) - (br_if $label$11 + (br_if $block9 (i32.le_u (local.tee $3 (i32.and @@ -10816,7 +10817,7 @@ (i32.const 8) ) ) - (br_if $label$6 + (br_if $block5 (i32.eqz (i32.and (i32.load8_u offset=2312 @@ -10847,9 +10848,9 @@ (local.get $1) ) ) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (i32.and (local.tee $3 (i32.load offset=4 @@ -10859,7 +10860,7 @@ (i32.const 1) ) ) - (br_if $label$1 + (br_if $block1 (i32.eqz (i32.and (local.get $3) @@ -10877,9 +10878,9 @@ (local.get $1) ) ) - (block $label$3 - (block $label$4 - (br_if $label$4 + (block $block4 + (block $block2 + (br_if $block2 (i32.eq (i32.load offset=1888 (i32.const 0) @@ -10892,8 +10893,8 @@ ) ) ) - (block $label$5 - (br_if $label$5 + (block $block3 + (br_if $block3 (i32.gt_u (local.get $3) (i32.const 255) @@ -10922,7 +10923,7 @@ ) ) ) - (br_if $label$3 + (br_if $block4 (i32.ne (local.tee $3 (i32.load offset=12 @@ -10944,16 +10945,16 @@ ) ) ) - (br $label$2) + (br $block) ) (local.set $7 (i32.load offset=24 (local.get $0) ) ) - (block $label$6 - (block $label$7 - (br_if $label$7 + (block $block6 + (block $block5 + (br_if $block5 (i32.eq (local.tee $6 (i32.load offset=12 @@ -10983,10 +10984,10 @@ (local.get $6) (local.get $3) ) - (br $label$6) + (br $block6) ) - (block $label$8 - (br_if $label$8 + (block $block7 + (br_if $block7 (local.tee $4 (i32.load (local.tee $3 @@ -10998,7 +10999,7 @@ ) ) ) - (br_if $label$8 + (br_if $block7 (local.tee $4 (i32.load (local.tee $3 @@ -11013,13 +11014,13 @@ (local.set $6 (i32.const 0) ) - (br $label$6) + (br $block6) ) - (loop $label$9 + (loop $label (local.set $5 (local.get $3) ) - (br_if $label$9 + (br_if $label (local.tee $4 (i32.load (local.tee $3 @@ -11039,7 +11040,7 @@ (i32.const 16) ) ) - (br_if $label$9 + (br_if $label (local.tee $4 (i32.load offset=16 (local.get $6) @@ -11052,14 +11053,14 @@ (i32.const 0) ) ) - (br_if $label$2 + (br_if $block (i32.eqz (local.get $7) ) ) - (block $label$10 - (block $label$11 - (br_if $label$11 + (block $block9 + (block $block8 + (br_if $block8 (i32.ne (i32.load (local.tee $3 @@ -11083,7 +11084,7 @@ (local.get $3) (local.get $6) ) - (br_if $label$10 + (br_if $block9 (local.get $6) ) (i32.store offset=1872 @@ -11098,7 +11099,7 @@ ) ) ) - (br $label$2) + (br $block) ) (i32.store (i32.add @@ -11116,7 +11117,7 @@ ) (local.get $6) ) - (br_if $label$2 + (br_if $block (i32.eqz (local.get $6) ) @@ -11126,8 +11127,8 @@ (local.get $6) (local.get $7) ) - (block $label$12 - (br_if $label$12 + (block $block10 + (br_if $block10 (i32.eqz (local.tee $3 (i32.load offset=16 @@ -11145,7 +11146,7 @@ (local.get $6) ) ) - (br_if $label$2 + (br_if $block (i32.eqz (local.tee $3 (i32.load offset=20 @@ -11165,9 +11166,9 @@ (local.get $3) (local.get $6) ) - (br $label$2) + (br $block) ) - (br_if $label$2 + (br_if $block (i32.ne (i32.and (local.tee $3 @@ -11219,9 +11220,9 @@ (local.get $4) ) ) - (block $label$13 - (block $label$14 - (br_if $label$14 + (block $block23 + (block $block11 + (br_if $block11 (i32.and (local.tee $3 (i32.load offset=4 @@ -11231,8 +11232,8 @@ (i32.const 2) ) ) - (block $label$15 - (br_if $label$15 + (block $block12 + (br_if $block12 (i32.ne (i32.load offset=1892 (i32.const 0) @@ -11262,7 +11263,7 @@ (i32.const 1) ) ) - (br_if $label$1 + (br_if $block1 (i32.ne (local.get $0) (i32.load offset=1888 @@ -11280,8 +11281,8 @@ ) (return) ) - (block $label$16 - (br_if $label$16 + (block $block13 + (br_if $block13 (i32.ne (i32.load offset=1888 (i32.const 0) @@ -11329,9 +11330,9 @@ (local.get $1) ) ) - (block $label$17 - (block $label$18 - (br_if $label$18 + (block $block16 + (block $block14 + (br_if $block14 (i32.gt_u (local.get $3) (i32.const 255) @@ -11360,8 +11361,8 @@ ) ) ) - (block $label$19 - (br_if $label$19 + (block $block15 + (br_if $block15 (i32.ne (local.tee $3 (i32.load offset=12 @@ -11383,7 +11384,7 @@ ) ) ) - (br $label$17) + (br $block16) ) (drop (i32.eq @@ -11399,16 +11400,16 @@ (local.get $3) (local.get $4) ) - (br $label$17) + (br $block16) ) (local.set $7 (i32.load offset=24 (local.get $2) ) ) - (block $label$20 - (block $label$21 - (br_if $label$21 + (block $block18 + (block $block17 + (br_if $block17 (i32.eq (local.tee $6 (i32.load offset=12 @@ -11438,10 +11439,10 @@ (local.get $6) (local.get $3) ) - (br $label$20) + (br $block18) ) - (block $label$22 - (br_if $label$22 + (block $block19 + (br_if $block19 (local.tee $3 (i32.load (local.tee $4 @@ -11453,7 +11454,7 @@ ) ) ) - (br_if $label$22 + (br_if $block19 (local.tee $3 (i32.load (local.tee $4 @@ -11468,13 +11469,13 @@ (local.set $6 (i32.const 0) ) - (br $label$20) + (br $block18) ) - (loop $label$23 + (loop $label1 (local.set $5 (local.get $4) ) - (br_if $label$23 + (br_if $label1 (local.tee $3 (i32.load (local.tee $4 @@ -11494,7 +11495,7 @@ (i32.const 16) ) ) - (br_if $label$23 + (br_if $label1 (local.tee $3 (i32.load offset=16 (local.get $6) @@ -11507,14 +11508,14 @@ (i32.const 0) ) ) - (br_if $label$17 + (br_if $block16 (i32.eqz (local.get $7) ) ) - (block $label$24 - (block $label$25 - (br_if $label$25 + (block $block21 + (block $block20 + (br_if $block20 (i32.ne (i32.load (local.tee $3 @@ -11538,7 +11539,7 @@ (local.get $3) (local.get $6) ) - (br_if $label$24 + (br_if $block21 (local.get $6) ) (i32.store offset=1872 @@ -11553,7 +11554,7 @@ ) ) ) - (br $label$17) + (br $block16) ) (i32.store (i32.add @@ -11571,7 +11572,7 @@ ) (local.get $6) ) - (br_if $label$17 + (br_if $block16 (i32.eqz (local.get $6) ) @@ -11581,8 +11582,8 @@ (local.get $6) (local.get $7) ) - (block $label$26 - (br_if $label$26 + (block $block22 + (br_if $block22 (i32.eqz (local.tee $3 (i32.load offset=16 @@ -11600,7 +11601,7 @@ (local.get $6) ) ) - (br_if $label$17 + (br_if $block16 (i32.eqz (local.tee $3 (i32.load offset=20 @@ -11635,7 +11636,7 @@ ) (local.get $1) ) - (br_if $label$13 + (br_if $block23 (i32.ne (local.get $0) (i32.load offset=1888 @@ -11671,8 +11672,8 @@ (local.get $1) ) ) - (block $label$27 - (br_if $label$27 + (block $block24 + (br_if $block24 (i32.gt_u (local.get $1) (i32.const 255) @@ -11692,9 +11693,9 @@ (i32.const 1908) ) ) - (block $label$28 - (block $label$29 - (br_if $label$29 + (block $block26 + (block $block25 + (br_if $block25 (i32.and (local.tee $4 (i32.load offset=1868 @@ -11719,7 +11720,7 @@ (local.set $3 (local.get $1) ) - (br $label$28) + (br $block26) ) (local.set $3 (i32.load offset=8 @@ -11748,8 +11749,8 @@ (local.set $3 (i32.const 31) ) - (block $label$30 - (br_if $label$30 + (block $block27 + (br_if $block27 (i32.gt_u (local.get $1) (i32.const 16777215) @@ -11862,10 +11863,10 @@ (i32.const 2172) ) ) - (block $label$31 - (block $label$32 - (block $label$33 - (br_if $label$33 + (block $block30 + (block $block29 + (block $block28 + (br_if $block28 (i32.and (local.tee $6 (i32.load offset=1872 @@ -11898,7 +11899,7 @@ ) (local.get $4) ) - (br $label$32) + (br $block29) ) (local.set $3 (i32.shl @@ -11924,8 +11925,8 @@ (local.get $4) ) ) - (loop $label$34 - (br_if $label$31 + (loop $label2 + (br_if $block30 (i32.eq (i32.and (i32.load offset=4 @@ -11950,7 +11951,7 @@ (i32.const 1) ) ) - (br_if $label$34 + (br_if $label2 (local.tee $6 (i32.load (local.tee $2 @@ -12038,8 +12039,8 @@ (i32.const 1) ) ) - (block $label$1 - (loop $label$2 + (block $block1 + (loop $label (local.set $0 (i32.add (local.tee $3 @@ -12050,19 +12051,19 @@ (local.get $1) ) ) - (block $label$3 - (br_if $label$3 + (block $block + (br_if $block (local.get $2) ) - (br_if $label$1 + (br_if $block1 (i32.le_u (local.get $0) (local.get $3) ) ) ) - (block $label$4 - (br_if $label$4 + (block $block2 + (br_if $block2 (i32.le_u (local.get $0) (i32.shl @@ -12071,7 +12072,7 @@ ) ) ) - (br_if $label$1 + (br_if $block1 (i32.eqz (call $fimport$15 (local.get $0) @@ -12079,7 +12080,7 @@ ) ) ) - (br_if $label$2 + (br_if $label (i32.ne (i32.atomic.rmw.cmpxchg offset=1436 (i32.const 0) @@ -12116,8 +12117,8 @@ ) ) (func $71 (param $0 i32) (result i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (local.get $0) ) (return @@ -12191,11 +12192,11 @@ (i32.const 16) ) ) - (block $label$1 - (block $label$2 - (block $label$3 - (block $label$4 - (br_if $label$4 + (block $block3 + (block $block2 + (block $block1 + (block $block + (br_if $block (call $71 (call $fimport$16 (i32.load offset=60 @@ -12213,8 +12214,8 @@ ) ) ) - (loop $label$5 - (br_if $label$3 + (loop $label + (br_if $block1 (i32.eq (local.get $6) (local.tee $4 @@ -12224,7 +12225,7 @@ ) ) ) - (br_if $label$2 + (br_if $block2 (i32.le_s (local.get $4) (i32.const -1) @@ -12289,7 +12290,7 @@ (local.get $4) ) ) - (br_if $label$5 + (br_if $label (i32.eqz (call $71 (call $fimport$16 @@ -12322,7 +12323,7 @@ ) ) ) - (br_if $label$2 + (br_if $block2 (i32.ne (local.get $6) (i32.const -1) @@ -12353,7 +12354,7 @@ (local.set $4 (local.get $2) ) - (br $label$1) + (br $block3) ) (local.set $4 (i32.const 0) @@ -12375,7 +12376,7 @@ (i32.const 32) ) ) - (br_if $label$1 + (br_if $block3 (i32.eq (local.get $7) (i32.const 2) @@ -12436,15 +12437,15 @@ (func $80 (param $0 i32) (result i32) (local $1 i32) (local $2 i32) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block2 + (block $block + (br_if $block (i32.eqz (local.get $0) ) ) - (block $label$3 - (br_if $label$3 + (block $block1 + (br_if $block1 (i32.gt_s (i32.load offset=76 (local.get $0) @@ -12468,7 +12469,7 @@ (local.get $0) ) ) - (br_if $label$1 + (br_if $block2 (i32.eqz (local.get $1) ) @@ -12483,8 +12484,8 @@ (local.set $2 (i32.const 0) ) - (block $label$4 - (br_if $label$4 + (block $block3 + (br_if $block3 (i32.eqz (i32.load offset=1584 (i32.const 0) @@ -12499,8 +12500,8 @@ ) ) ) - (block $label$5 - (br_if $label$5 + (block $block4 + (br_if $block4 (i32.eqz (local.tee $0 (i32.load @@ -12509,12 +12510,12 @@ ) ) ) - (loop $label$6 + (loop $label (local.set $1 (i32.const 0) ) - (block $label$7 - (br_if $label$7 + (block $block5 + (br_if $block5 (i32.lt_s (i32.load offset=76 (local.get $0) @@ -12528,8 +12529,8 @@ ) ) ) - (block $label$8 - (br_if $label$8 + (block $block6 + (br_if $block6 (i32.le_u (i32.load offset=20 (local.get $0) @@ -12548,8 +12549,8 @@ ) ) ) - (block $label$9 - (br_if $label$9 + (block $block7 + (br_if $block7 (i32.eqz (local.get $1) ) @@ -12558,7 +12559,7 @@ (local.get $0) ) ) - (br_if $label$6 + (br_if $label (local.tee $0 (i32.load offset=56 (local.get $0) @@ -12574,8 +12575,8 @@ (func $81 (param $0 i32) (result i32) (local $1 i32) (local $2 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.le_u (i32.load offset=20 (local.get $0) @@ -12595,7 +12596,7 @@ ) ) ) - (br_if $label$1 + (br_if $block (i32.load offset=20 (local.get $0) ) @@ -12604,8 +12605,8 @@ (i32.const -1) ) ) - (block $label$2 - (br_if $label$2 + (block $block1 + (br_if $block1 (i32.ge_u (local.tee $1 (i32.load offset=4 @@ -12670,8 +12671,8 @@ (local $4 i32) (local $5 i32) (local $6 i32) - (block $label$1 - (br_if $label$1 + (block $block + (br_if $block (i32.eqz (i32.load offset=44 (local.tee $0 @@ -12683,16 +12684,16 @@ (local.set $1 (i32.const 0) ) - (loop $label$2 + (loop $label1 (local.set $2 (i32.const 0) ) (local.set $3 (i32.const 0) ) - (loop $label$3 - (block $label$4 - (br_if $label$4 + (loop $label + (block $block1 + (br_if $block1 (i32.eqz (local.tee $6 (i32.load @@ -12713,7 +12714,7 @@ ) ) ) - (br_if $label$4 + (br_if $block1 (i32.eqz (i32.load (local.tee $4 @@ -12739,7 +12740,7 @@ (i32.const 1) ) ) - (br_if $label$3 + (br_if $label (i32.ne (local.tee $2 (i32.add @@ -12751,7 +12752,7 @@ ) ) ) - (br_if $label$1 + (br_if $block (i32.gt_u (local.get $1) (i32.const 2) @@ -12763,7 +12764,7 @@ (i32.const 1) ) ) - (br_if $label$2 + (br_if $label1 (local.get $3) ) ) diff --git a/test/passes/O.bin.txt b/test/passes/O.bin.txt index 3302ebcc710..fc4724490ce 100644 --- a/test/passes/O.bin.txt +++ b/test/passes/O.bin.txt @@ -55,7 +55,7 @@ (local.set $1 (i64.const 1) ) - (loop $label$3 + (loop $label (if (i32.eqz (i64.eqz @@ -75,7 +75,7 @@ (i64.const 1) ) ) - (br $label$3) + (br $label) ) ) ) @@ -92,14 +92,14 @@ (i64.const 2) ) (then - (loop $label$3 + (loop $label (local.set $1 (i64.mul (local.get $0) (local.get $1) ) ) - (br_if $label$3 + (br_if $label (i64.gt_s (local.tee $0 (i64.sub diff --git a/test/passes/converge_O3_metrics.bin.txt b/test/passes/converge_O3_metrics.bin.txt index 712172cc794..2d9ef8f5713 100644 --- a/test/passes/converge_O3_metrics.bin.txt +++ b/test/passes/converge_O3_metrics.bin.txt @@ -59,8 +59,8 @@ total (local.set $0 (i32.const 10888) ) - (loop $label$3 - (br_if $label$3 + (loop $label + (br_if $label (i32.load8_s (local.tee $0 (i32.add @@ -94,8 +94,8 @@ total ) ) (then - (block $label$2 - (br_if $label$2 + (block $block + (br_if $block (call_indirect (type $0) (local.get $1) (i32.const 10888) @@ -113,8 +113,8 @@ total ) ) ) - (block $label$1 - (br_if $label$1 + (block $block0 + (br_if $block0 (if (result i32) (i32.load (i32.add @@ -291,8 +291,8 @@ total (local.set $0 (i32.const 10888) ) - (loop $label$3 - (br_if $label$3 + (loop $label + (br_if $label (i32.load8_s (local.tee $0 (i32.add @@ -326,8 +326,8 @@ total ) ) (then - (block $label$2 - (br_if $label$2 + (block $block + (br_if $block (call_indirect (type $0) (local.get $1) (i32.const 10888) @@ -345,8 +345,8 @@ total ) ) ) - (block $label$1 - (br_if $label$1 + (block $block0 + (br_if $block0 (if (result i32) (i32.load (i32.add @@ -518,8 +518,8 @@ total (local.set $0 (i32.const 10888) ) - (loop $label$3 - (br_if $label$3 + (loop $label + (br_if $label (i32.load8_s (local.tee $0 (i32.add @@ -553,8 +553,8 @@ total ) ) (then - (block $label$2 - (br_if $label$2 + (block $block + (br_if $block (call_indirect (type $0) (local.get $1) (i32.const 10888) @@ -572,8 +572,8 @@ total ) ) ) - (block $label$1 - (br_if $label$1 + (block $block0 + (br_if $block0 (if (result i32) (i32.load (i32.add diff --git a/test/passes/dce_vacuum_remove-unused-names.bin.txt b/test/passes/dce_vacuum_remove-unused-names.bin.txt index 8a01d2ee58b..72bdb2a9ca9 100644 --- a/test/passes/dce_vacuum_remove-unused-names.bin.txt +++ b/test/passes/dce_vacuum_remove-unused-names.bin.txt @@ -4,8 +4,8 @@ (export "f32.compute_radix" (func $0)) (export "f64.compute_radix" (func $1)) (func $0 (param $0 f32) (param $1 f32) (result f32) - (loop $label$2 - (br_if $label$2 + (loop $label + (br_if $label (f32.eq (f32.add (f32.sub @@ -45,8 +45,8 @@ ) ) (func $1 (param $0 f64) (param $1 f64) (result f64) - (loop $label$2 - (br_if $label$2 + (loop $label + (br_if $label (f64.eq (f64.add (f64.sub @@ -67,8 +67,8 @@ ) ) ) - (loop $label$3 - (br_if $label$3 + (loop $label1 + (br_if $label1 (f64.ne (f64.sub (f64.sub diff --git a/test/passes/dwarf-local-order.bin.txt b/test/passes/dwarf-local-order.bin.txt index 509db04b1d6..8fc6cfbbe82 100644 --- a/test/passes/dwarf-local-order.bin.txt +++ b/test/passes/dwarf-local-order.bin.txt @@ -122,9 +122,9 @@ (local.get $15) ) ) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (local.get $16) ) (local.set $17 @@ -135,7 +135,7 @@ (local.set $18 (local.get $17) ) - (br $label$1) + (br $block1) ) (local.set $19 (i32.const -2147483648) @@ -340,11 +340,11 @@ ) ) ;; code offset: 0xa9 - (block $label$1 + (block $block1 ;; code offset: 0xab - (block $label$2 + (block $block ;; code offset: 0xaf - (br_if $label$2 + (br_if $block ;; code offset: 0xad (local.get $16) ) @@ -362,7 +362,7 @@ (local.get $17) ) ;; code offset: 0xba - (br $label$1) + (br $block1) ) ;; code offset: 0xc3 (local.set $19 @@ -516,9 +516,9 @@ (local.get $7) ) ) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (local.get $8) ) (local.set $9 @@ -529,7 +529,7 @@ (local.set $10 (local.get $9) ) - (br $label$1) + (br $block1) ) (local.set $11 (i32.const -2147483648) @@ -671,9 +671,9 @@ (local.get $7) ) ) - (block $label$1 - (block $label$2 - (br_if $label$2 + (block $block1 + (block $block + (br_if $block (local.get $8) ) (local.set $9 @@ -684,7 +684,7 @@ (local.set $10 (local.get $9) ) - (br $label$1) + (br $block1) ) (local.set $11 (i32.const -2147483648) diff --git a/test/passes/dwarf_with_exceptions.bin.txt b/test/passes/dwarf_with_exceptions.bin.txt index 8a70d511a33..a4e3b5e614d 100644 --- a/test/passes/dwarf_with_exceptions.bin.txt +++ b/test/passes/dwarf_with_exceptions.bin.txt @@ -20,8 +20,8 @@ ;; code offset: 0x8 (global.get $__stack_pointer) ) - ;; code offset: 0x10 - (try $label$9 + ;; code offset: 0x18 + (try (do ;; code offset: 0x12 (call $foo\28\29) @@ -45,8 +45,8 @@ (local.get $1) ) ) - ;; code offset: 0x31 - (try $label$8 + ;; code offset: 0x41 + (try $label (do ;; code offset: 0x33 (call $foo\28\29) @@ -58,8 +58,8 @@ ) ;; code offset: 0x41 (catch_all - ;; code offset: 0x42 - (try $label$7 + ;; code offset: 0x63 + (try (do ;; code offset: 0x44 (call $__cxa_end_catch) @@ -92,7 +92,7 @@ ) ) ;; code offset: 0x6c - (rethrow $label$8) + (rethrow $label) ) ) ;; code offset: 0x6f @@ -274,7 +274,7 @@ DWARF debug info Contains section .debug_info (63 bytes) Contains section .debug_abbrev (41 bytes) -Contains section .debug_line (162 bytes) +Contains section .debug_line (136 bytes) Contains section .debug_str (178 bytes) .debug_abbrev contents: @@ -326,7 +326,7 @@ Abbrev table for offset: 0x00000000 .debug_line contents: debug_line[0x00000000] Line table prologue: - total_length: 0x0000009e + total_length: 0x00000084 version: 4 prologue_length: 0x00000031 min_inst_length: 1 @@ -366,51 +366,36 @@ file_names[ 1]: 0x000000000000000e 4 5 1 0 0 is_stmt prologue_end -0x00000052: 00 DW_LNE_set_address (0x0000000000000012) -0x00000059: 03 DW_LNS_advance_line (5) -0x0000005b: 05 DW_LNS_set_column (3) -0x0000005d: 01 DW_LNS_copy - 0x0000000000000012 5 3 1 0 0 is_stmt - - -0x0000005e: 00 DW_LNE_set_address (0x000000000000001f) -0x00000065: 03 DW_LNS_advance_line (6) -0x00000067: 05 DW_LNS_set_column (5) -0x00000069: 01 DW_LNS_copy +0x00000052: 00 DW_LNE_set_address (0x000000000000001f) +0x00000059: 03 DW_LNS_advance_line (6) +0x0000005b: 01 DW_LNS_copy 0x000000000000001f 6 5 1 0 0 is_stmt -0x0000006a: 00 DW_LNE_set_address (0x000000000000003e) -0x00000071: 03 DW_LNS_advance_line (7) -0x00000073: 05 DW_LNS_set_column (3) -0x00000075: 06 DW_LNS_negate_stmt -0x00000076: 01 DW_LNS_copy +0x0000005c: 00 DW_LNE_set_address (0x000000000000003e) +0x00000063: 03 DW_LNS_advance_line (7) +0x00000065: 05 DW_LNS_set_column (3) +0x00000067: 06 DW_LNS_negate_stmt +0x00000068: 01 DW_LNS_copy 0x000000000000003e 7 3 1 0 0 -0x00000077: 00 DW_LNE_set_address (0x0000000000000041) -0x0000007e: 01 DW_LNS_copy +0x00000069: 00 DW_LNE_set_address (0x0000000000000041) +0x00000070: 01 DW_LNS_copy 0x0000000000000041 7 3 1 0 0 -0x0000007f: 00 DW_LNE_set_address (0x0000000000000044) -0x00000086: 03 DW_LNS_advance_line (8) -0x00000088: 05 DW_LNS_set_column (1) -0x0000008a: 06 DW_LNS_negate_stmt -0x0000008b: 01 DW_LNS_copy +0x00000071: 00 DW_LNE_set_address (0x0000000000000044) +0x00000078: 03 DW_LNS_advance_line (8) +0x0000007a: 05 DW_LNS_set_column (1) +0x0000007c: 06 DW_LNS_negate_stmt +0x0000007d: 01 DW_LNS_copy 0x0000000000000044 8 1 1 0 0 is_stmt -0x0000008c: 00 DW_LNE_set_address (0x0000000000000045) -0x00000093: 01 DW_LNS_copy - 0x0000000000000045 8 1 1 0 0 is_stmt - - -0x00000094: 00 DW_LNE_set_address (0x00000000ffffff64) -0x0000009b: 03 DW_LNS_advance_line (7) -0x0000009d: 05 DW_LNS_set_column (3) -0x0000009f: 00 DW_LNE_end_sequence - 0x00000000ffffff64 7 3 1 0 0 is_stmt end_sequence +0x0000007e: 00 DW_LNE_set_address (0x0000000000000045) +0x00000085: 00 DW_LNE_end_sequence + 0x0000000000000045 8 1 1 0 0 is_stmt end_sequence .debug_str contents: @@ -441,8 +426,8 @@ file_names[ 1]: ;; code offset: 0xa (global.get $__stack_pointer) ) - ;; code offset: 0xe - (try $label$9 + ;; code offset: 0x12 + (try (do ;; code offset: 0x10 (call $foo\28\29) @@ -466,8 +451,8 @@ file_names[ 1]: (local.get $1) ) ) - ;; code offset: 0x1f - (try $label$8 + ;; code offset: 0x27 + (try $label (do ;; code offset: 0x21 (call $foo\28\29) @@ -479,8 +464,8 @@ file_names[ 1]: ) ;; code offset: 0x27 (catch_all - ;; code offset: 0x28 - (try $label$7 + ;; code offset: 0x39 + (try (do ;; code offset: 0x2a (call $__cxa_end_catch) @@ -513,7 +498,7 @@ file_names[ 1]: ) ) ;; code offset: 0x3e - (rethrow $label$8) + (rethrow $label) ) ) ;; code offset: 0x41 @@ -537,7 +522,7 @@ file_names[ 1]: ) ;; custom section ".debug_info", size 63 ;; custom section ".debug_abbrev", size 41 - ;; custom section ".debug_line", size 162 + ;; custom section ".debug_line", size 136 ;; custom section ".debug_str", size 178 ;; custom section "producers", size 134 ;; features section: mutable-globals, sign-ext, exception-handling diff --git a/test/passes/fannkuch0_dwarf.bin.txt b/test/passes/fannkuch0_dwarf.bin.txt index bee23dc186c..9e9bb853743 100644 --- a/test/passes/fannkuch0_dwarf.bin.txt +++ b/test/passes/fannkuch0_dwarf.bin.txt @@ -5658,9 +5658,9 @@ file_names[ 3]: (local.get $4) ) ;; code offset: 0x29d - (block $label$1 + (block $block ;; code offset: 0x29f - (loop $label$2 + (loop $label ;; code offset: 0x2a6 (local.set $20 ;; code offset: 0x2a3 @@ -5713,7 +5713,7 @@ file_names[ 3]: ) ) ;; code offset: 0x2cc - (br_if $label$1 + (br_if $block ;; code offset: 0x2cb (i32.eqz ;; code offset: 0x2c9 @@ -5807,8 +5807,10 @@ file_names[ 3]: (local.get $35) ) ;; code offset: 0x315 - (br $label$2) + (br $label) ) + ;; code offset: 0x318 + (unreachable) ) ;; code offset: 0x31f (local.set $36 @@ -5984,11 +5986,11 @@ file_names[ 3]: (local.get $54) ) ;; code offset: 0x3a8 - (loop $label$3 (result i32) + (loop $label7 (result i32) ;; code offset: 0x3aa - (block $label$4 + (block $block1 ;; code offset: 0x3ac - (loop $label$5 + (loop $label1 ;; code offset: 0x3b0 (local.set $55 ;; code offset: 0x3ae @@ -6038,7 +6040,7 @@ file_names[ 3]: ) ) ;; code offset: 0x3d6 - (br_if $label$4 + (br_if $block1 ;; code offset: 0x3d5 (i32.eqz ;; code offset: 0x3d3 @@ -6147,8 +6149,10 @@ file_names[ 3]: (local.get $72) ) ;; code offset: 0x42a - (br $label$5) + (br $label1) ) + ;; code offset: 0x42d + (unreachable) ) ;; code offset: 0x434 (local.set $73 @@ -6167,9 +6171,9 @@ file_names[ 3]: ) ) ;; code offset: 0x43d - (block $label$6 + (block $block2 ;; code offset: 0x442 - (br_if $label$6 + (br_if $block2 ;; code offset: 0x441 (i32.eqz ;; code offset: 0x43f @@ -6299,7 +6303,7 @@ file_names[ 3]: ) ) ;; code offset: 0x4a5 - (br_if $label$6 + (br_if $block2 ;; code offset: 0x4a4 (i32.eqz ;; code offset: 0x4a2 @@ -6319,9 +6323,9 @@ file_names[ 3]: (local.get $91) ) ;; code offset: 0x4b2 - (block $label$7 + (block $block3 ;; code offset: 0x4b4 - (loop $label$8 + (loop $label2 ;; code offset: 0x4bb (local.set $92 ;; code offset: 0x4b8 @@ -6374,7 +6378,7 @@ file_names[ 3]: ) ) ;; code offset: 0x4e1 - (br_if $label$7 + (br_if $block3 ;; code offset: 0x4e0 (i32.eqz ;; code offset: 0x4de @@ -6509,8 +6513,10 @@ file_names[ 3]: (local.get $112) ) ;; code offset: 0x54a - (br $label$8) + (br $label2) ) + ;; code offset: 0x54d + (unreachable) ) ;; code offset: 0x551 (local.set $113 @@ -6548,7 +6554,7 @@ file_names[ 3]: (local.get $115) ) ;; code offset: 0x56f - (loop $label$9 + (loop $label4 ;; code offset: 0x573 (local.set $116 ;; code offset: 0x571 @@ -6592,9 +6598,9 @@ file_names[ 3]: (local.get $119) ) ;; code offset: 0x595 - (block $label$10 + (block $block4 ;; code offset: 0x597 - (loop $label$11 + (loop $label3 ;; code offset: 0x59e (local.set $120 ;; code offset: 0x59b @@ -6647,7 +6653,7 @@ file_names[ 3]: ) ) ;; code offset: 0x5c4 - (br_if $label$10 + (br_if $block4 ;; code offset: 0x5c3 (i32.eqz ;; code offset: 0x5c1 @@ -6924,8 +6930,10 @@ file_names[ 3]: (local.get $155) ) ;; code offset: 0x6da - (br $label$11) + (br $label3) ) + ;; code offset: 0x6dd + (unreachable) ) ;; code offset: 0x6e4 (local.set $156 @@ -7093,7 +7101,7 @@ file_names[ 3]: ) ) ;; code offset: 0x78d - (br_if $label$9 + (br_if $label4 ;; code offset: 0x78a (local.get $172) ) @@ -7150,9 +7158,9 @@ file_names[ 3]: ) ) ;; code offset: 0x7c5 - (block $label$12 + (block $block5 ;; code offset: 0x7cb - (br_if $label$12 + (br_if $block5 ;; code offset: 0x7ca (i32.eqz ;; code offset: 0x7c7 @@ -7177,7 +7185,7 @@ file_names[ 3]: ) ) ;; code offset: 0x7df - (loop $label$13 + (loop $label6 ;; code offset: 0x7e6 (local.set $181 ;; code offset: 0x7e3 @@ -7245,9 +7253,9 @@ file_names[ 3]: ) ) ;; code offset: 0x825 - (block $label$14 + (block $block6 ;; code offset: 0x82b - (br_if $label$14 + (br_if $block6 ;; code offset: 0x82a (i32.eqz ;; code offset: 0x827 @@ -7363,9 +7371,9 @@ file_names[ 3]: (local.get $196) ) ;; code offset: 0x89b - (block $label$15 + (block $block7 ;; code offset: 0x89d - (loop $label$16 + (loop $label5 ;; code offset: 0x8a4 (local.set $199 ;; code offset: 0x8a1 @@ -7418,7 +7426,7 @@ file_names[ 3]: ) ) ;; code offset: 0x8d8 - (br_if $label$15 + (br_if $block7 ;; code offset: 0x8d7 (i32.eqz ;; code offset: 0x8d4 @@ -7568,8 +7576,10 @@ file_names[ 3]: (local.get $221) ) ;; code offset: 0x96c - (br $label$16) + (br $label5) ) + ;; code offset: 0x96f + (unreachable) ) ;; code offset: 0x973 (local.set $222 @@ -7739,11 +7749,11 @@ file_names[ 3]: ) ) ;; code offset: 0xa1f - (block $label$17 + (block $block9 ;; code offset: 0xa21 - (block $label$18 + (block $block8 ;; code offset: 0xa27 - (br_if $label$18 + (br_if $block8 ;; code offset: 0xa26 (i32.eqz ;; code offset: 0xa23 @@ -7751,7 +7761,7 @@ file_names[ 3]: ) ) ;; code offset: 0xa29 - (br $label$17) + (br $block9) ) ;; code offset: 0xa31 (local.set $242 @@ -7784,11 +7794,11 @@ file_names[ 3]: (local.get $244) ) ;; code offset: 0xa4b - (br $label$13) + (br $label6) ) ) ;; code offset: 0xa4f - (br $label$3) + (br $label7) ) ) (func $main (param $0 i32) (param $1 i32) (result i32) @@ -7927,11 +7937,11 @@ file_names[ 3]: ) ) ;; code offset: 0xaed - (block $label$1 + (block $block1 ;; code offset: 0xaef - (block $label$2 + (block $block ;; code offset: 0xaf4 - (br_if $label$2 + (br_if $block ;; code offset: 0xaf3 (i32.eqz ;; code offset: 0xaf1 @@ -7968,7 +7978,7 @@ file_names[ 3]: (local.get $15) ) ;; code offset: 0xb0e - (br $label$1) + (br $block1) ) ;; code offset: 0xb13 (local.set $17 @@ -8042,11 +8052,11 @@ file_names[ 3]: ) ) ;; code offset: 0xb4a - (block $label$3 + (block $block3 ;; code offset: 0xb4c - (block $label$4 + (block $block2 ;; code offset: 0xb51 - (br_if $label$4 + (br_if $block2 ;; code offset: 0xb50 (i32.eqz ;; code offset: 0xb4e @@ -8086,7 +8096,7 @@ file_names[ 3]: (local.get $28) ) ;; code offset: 0xb6e - (br $label$3) + (br $block3) ) ;; code offset: 0xb76 (local.set $29 @@ -8432,9 +8442,9 @@ file_names[ 3]: (local.get $4) ) ;; code offset: 0xd66 - (block $label$1 + (block $block ;; code offset: 0xd68 - (loop $label$2 + (loop $label ;; code offset: 0xd6f (local.set $6 ;; code offset: 0xd6c @@ -8502,7 +8512,7 @@ file_names[ 3]: ) ) ;; code offset: 0xda0 - (br_if $label$1 + (br_if $block ;; code offset: 0xd9f (i32.eqz ;; code offset: 0xd9d @@ -8644,8 +8654,10 @@ file_names[ 3]: (local.get $26) ) ;; code offset: 0xe19 - (br $label$2) + (br $label) ) + ;; code offset: 0xe1c + (unreachable) ) ;; code offset: 0xe20 (local.set $27 @@ -8736,9 +8748,9 @@ file_names[ 3]: (local.get $27) ) ;; code offset: 0xe67 - (block $label$3 + (block $block1 ;; code offset: 0xe69 - (loop $label$4 + (loop $label1 ;; code offset: 0xe70 (local.set $36 ;; code offset: 0xe6d @@ -8791,7 +8803,7 @@ file_names[ 3]: ) ) ;; code offset: 0xe96 - (br_if $label$3 + (br_if $block1 ;; code offset: 0xe95 (i32.eqz ;; code offset: 0xe93 @@ -8885,8 +8897,10 @@ file_names[ 3]: (local.get $51) ) ;; code offset: 0xedf - (br $label$4) + (br $label1) ) + ;; code offset: 0xee2 + (unreachable) ) ;; code offset: 0xee9 (local.set $52 @@ -8904,9 +8918,9 @@ file_names[ 3]: (local.get $52) ) ;; code offset: 0xef2 - (block $label$5 + (block $block5 ;; code offset: 0xef4 - (loop $label$6 + (loop $label6 ;; code offset: 0xefb (local.set $53 ;; code offset: 0xef8 @@ -8916,11 +8930,11 @@ file_names[ 3]: ) ) ;; code offset: 0xefd - (block $label$7 + (block $block4 ;; code offset: 0xeff - (block $label$8 + (block $block2 ;; code offset: 0xf04 - (br_if $label$8 + (br_if $block2 ;; code offset: 0xf03 (i32.eqz ;; code offset: 0xf01 @@ -8940,9 +8954,9 @@ file_names[ 3]: (local.get $54) ) ;; code offset: 0xf11 - (block $label$9 + (block $block3 ;; code offset: 0xf13 - (loop $label$10 + (loop $label2 ;; code offset: 0xf1a (local.set $55 ;; code offset: 0xf17 @@ -8995,7 +9009,7 @@ file_names[ 3]: ) ) ;; code offset: 0xf40 - (br_if $label$9 + (br_if $block3 ;; code offset: 0xf3f (i32.eqz ;; code offset: 0xf3d @@ -9119,8 +9133,10 @@ file_names[ 3]: (local.get $73) ) ;; code offset: 0xfa0 - (br $label$10) + (br $label2) ) + ;; code offset: 0xfa3 + (unreachable) ) ;; code offset: 0xfa8 (local.set $74 @@ -9173,15 +9189,15 @@ file_names[ 3]: (local.get $78) ) ;; code offset: 0xfce - (br $label$7) + (br $block4) ) ;; code offset: 0xfd1 - (br $label$5) + (br $block5) ) ;; code offset: 0xfd4 - (block $label$11 + (block $block6 ;; code offset: 0xfd6 - (loop $label$12 + (loop $label3 ;; code offset: 0xfda (local.set $79 ;; code offset: 0xfd8 @@ -9231,7 +9247,7 @@ file_names[ 3]: ) ) ;; code offset: 0x1000 - (br_if $label$11 + (br_if $block6 ;; code offset: 0xfff (i32.eqz ;; code offset: 0xffd @@ -9340,11 +9356,13 @@ file_names[ 3]: (local.get $96) ) ;; code offset: 0x1054 - (br $label$12) + (br $label3) ) + ;; code offset: 0x1057 + (unreachable) ) ;; code offset: 0x1059 - (loop $label$13 + (loop $label5 ;; code offset: 0x1060 (local.set $97 ;; code offset: 0x105d @@ -9397,9 +9415,9 @@ file_names[ 3]: ) ) ;; code offset: 0x1083 - (block $label$14 + (block $block7 ;; code offset: 0x1088 - (br_if $label$14 + (br_if $block7 ;; code offset: 0x1087 (i32.eqz ;; code offset: 0x1085 @@ -9407,7 +9425,7 @@ file_names[ 3]: ) ) ;; code offset: 0x108a - (br $label$5) + (br $block5) ) ;; code offset: 0x108f (local.set $104 @@ -9445,9 +9463,9 @@ file_names[ 3]: (local.get $104) ) ;; code offset: 0x10ad - (block $label$15 + (block $block8 ;; code offset: 0x10af - (loop $label$16 + (loop $label4 ;; code offset: 0x10b6 (local.set $107 ;; code offset: 0x10b3 @@ -9500,7 +9518,7 @@ file_names[ 3]: ) ) ;; code offset: 0x10dc - (br_if $label$15 + (br_if $block8 ;; code offset: 0x10db (i32.eqz ;; code offset: 0x10d9 @@ -9650,8 +9668,10 @@ file_names[ 3]: (local.get $129) ) ;; code offset: 0x1154 - (br $label$16) + (br $label4) ) + ;; code offset: 0x1157 + (unreachable) ) ;; code offset: 0x115b (local.set $130 @@ -9821,11 +9841,11 @@ file_names[ 3]: ) ) ;; code offset: 0x1207 - (block $label$17 + (block $block10 ;; code offset: 0x1209 - (block $label$18 + (block $block9 ;; code offset: 0x120f - (br_if $label$18 + (br_if $block9 ;; code offset: 0x120e (i32.eqz ;; code offset: 0x120b @@ -9833,7 +9853,7 @@ file_names[ 3]: ) ) ;; code offset: 0x1211 - (br $label$17) + (br $block10) ) ;; code offset: 0x1219 (local.set $150 @@ -9866,12 +9886,14 @@ file_names[ 3]: (local.get $152) ) ;; code offset: 0x1233 - (br $label$13) + (br $label5) ) ) ;; code offset: 0x1237 - (br $label$6) + (br $label6) ) + ;; code offset: 0x123a + (unreachable) ) ;; code offset: 0x123e (local.set $153 @@ -9912,9 +9934,9 @@ file_names[ 3]: (local.get $153) ) ;; code offset: 0x1263 - (block $label$19 + (block $block11 ;; code offset: 0x1265 - (loop $label$20 + (loop $label7 ;; code offset: 0x1269 (local.set $156 ;; code offset: 0x1267 @@ -9964,7 +9986,7 @@ file_names[ 3]: ) ) ;; code offset: 0x129d - (br_if $label$19 + (br_if $block11 ;; code offset: 0x129c (i32.eqz ;; code offset: 0x1299 @@ -10046,9 +10068,9 @@ file_names[ 3]: ) ) ;; code offset: 0x12ec - (block $label$21 + (block $block12 ;; code offset: 0x12f2 - (br_if $label$21 + (br_if $block12 ;; code offset: 0x12f1 (i32.eqz ;; code offset: 0x12ee @@ -10123,8 +10145,10 @@ file_names[ 3]: (local.get $176) ) ;; code offset: 0x133b - (br $label$20) + (br $label7) ) + ;; code offset: 0x133e + (unreachable) ) ;; code offset: 0x1345 (local.set $177 diff --git a/test/passes/fannkuch3_dwarf.bin.txt b/test/passes/fannkuch3_dwarf.bin.txt index 74b6446a5f3..b2e61acfa7d 100644 --- a/test/passes/fannkuch3_dwarf.bin.txt +++ b/test/passes/fannkuch3_dwarf.bin.txt @@ -4873,13 +4873,13 @@ file_names[ 4]: ) ) ;; code offset: 0x47 - (block $label$1 + (block $block5 ;; code offset: 0x49 - (block $label$2 + (block $block1 ;; code offset: 0x4b - (block $label$3 + (block $block ;; code offset: 0x52 - (br_if $label$3 + (br_if $block ;; code offset: 0x51 (i32.le_s ;; code offset: 0x4d @@ -4889,7 +4889,7 @@ file_names[ 4]: ) ) ;; code offset: 0x54 - (loop $label$4 + (loop $label ;; code offset: 0x60 (i32.store ;; code offset: 0x5d @@ -4908,7 +4908,7 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0x6d - (br_if $label$4 + (br_if $label ;; code offset: 0x6c (i32.ne ;; code offset: 0x68 @@ -4983,7 +4983,7 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x9f - (br_if $label$2 + (br_if $block1 ;; code offset: 0x9e (i32.le_s ;; code offset: 0x9a @@ -4993,11 +4993,11 @@ file_names[ 4]: ) ) ;; code offset: 0xa1 - (loop $label$5 + (loop $label5 ;; code offset: 0xa3 - (block $label$6 + (block $block2 ;; code offset: 0xaa - (br_if $label$6 + (br_if $block2 ;; code offset: 0xa9 (i32.le_s ;; code offset: 0xa5 @@ -5007,7 +5007,7 @@ file_names[ 4]: ) ) ;; code offset: 0xac - (loop $label$7 + (loop $label1 ;; code offset: 0xbd (i32.store ;; code offset: 0xba @@ -5049,16 +5049,16 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0xcd - (br_if $label$7 + (br_if $label1 ;; code offset: 0xcb (local.get $0) ) ) ) ;; code offset: 0xd1 - (block $label$8 + (block $block3 ;; code offset: 0xdb - (br_if $label$8 + (br_if $block3 ;; code offset: 0xda (i32.eqz ;; code offset: 0xd8 @@ -5072,7 +5072,7 @@ file_names[ 4]: ) ) ;; code offset: 0xe5 - (br_if $label$8 + (br_if $block3 ;; code offset: 0xe4 (i32.eq ;; code offset: 0xdf @@ -5108,16 +5108,16 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0xfa - (loop $label$9 + (loop $label3 ;; code offset: 0xfe (local.set $13 ;; code offset: 0xfc (local.get $0) ) ;; code offset: 0x100 - (block $label$10 + (block $block4 ;; code offset: 0x107 - (br_if $label$10 + (br_if $block4 ;; code offset: 0x106 (i32.lt_s ;; code offset: 0x102 @@ -5142,7 +5142,7 @@ file_names[ 4]: (i32.const 1) ) ;; code offset: 0x114 - (loop $label$11 + (loop $label2 ;; code offset: 0x123 (local.set $15 ;; code offset: 0x120 @@ -5195,7 +5195,7 @@ file_names[ 4]: (local.get $15) ) ;; code offset: 0x14d - (br_if $label$11 + (br_if $label2 ;; code offset: 0x14c (i32.lt_s ;; code offset: 0x143 @@ -5266,7 +5266,7 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0x174 - (br_if $label$9 + (br_if $label3 ;; code offset: 0x172 (local.get $1) ) @@ -5290,7 +5290,7 @@ file_names[ 4]: ) ) ;; code offset: 0x189 - (br_if $label$1 + (br_if $block5 ;; code offset: 0x188 (i32.ge_s ;; code offset: 0x184 @@ -5300,16 +5300,16 @@ file_names[ 4]: ) ) ;; code offset: 0x18b - (loop $label$12 + (loop $label6 ;; code offset: 0x18f (local.set $1 ;; code offset: 0x18d (i32.const 0) ) ;; code offset: 0x191 - (block $label$13 + (block $block6 ;; code offset: 0x198 - (br_if $label$13 + (br_if $block6 ;; code offset: 0x197 (i32.le_s ;; code offset: 0x193 @@ -5319,7 +5319,7 @@ file_names[ 4]: ) ) ;; code offset: 0x19a - (loop $label$14 + (loop $label4 ;; code offset: 0x1b4 (i32.store ;; code offset: 0x1a3 @@ -5359,7 +5359,7 @@ file_names[ 4]: ) ) ;; code offset: 0x1bc - (br_if $label$14 + (br_if $label4 ;; code offset: 0x1bb (i32.ne ;; code offset: 0x1b7 @@ -5424,7 +5424,7 @@ file_names[ 4]: ) ) ;; code offset: 0x1ed - (br_if $label$5 + (br_if $label5 ;; code offset: 0x1ec (i32.gt_s ;; code offset: 0x1e8 @@ -5434,7 +5434,7 @@ file_names[ 4]: ) ) ;; code offset: 0x1f9 - (br_if $label$1 + (br_if $block5 ;; code offset: 0x1f8 (i32.eq ;; code offset: 0x1f4 @@ -5460,9 +5460,13 @@ file_names[ 4]: ) ) ;; code offset: 0x202 - (br $label$12) + (br $label6) ) + ;; code offset: 0x205 + (unreachable) ) + ;; code offset: 0x207 + (unreachable) ) ;; code offset: 0x21d (i32.store @@ -5522,11 +5526,11 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x234 - (loop $label$15 + (loop $label11 ;; code offset: 0x236 - (block $label$16 + (block $block7 ;; code offset: 0x23d - (br_if $label$16 + (br_if $block7 ;; code offset: 0x23c (i32.lt_s ;; code offset: 0x238 @@ -5536,7 +5540,7 @@ file_names[ 4]: ) ) ;; code offset: 0x23f - (loop $label$17 + (loop $label7 ;; code offset: 0x250 (i32.store ;; code offset: 0x24d @@ -5578,16 +5582,16 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0x260 - (br_if $label$17 + (br_if $label7 ;; code offset: 0x25e (local.get $0) ) ) ) ;; code offset: 0x264 - (block $label$18 + (block $block8 ;; code offset: 0x26e - (br_if $label$18 + (br_if $block8 ;; code offset: 0x26d (i32.eqz ;; code offset: 0x26b @@ -5601,7 +5605,7 @@ file_names[ 4]: ) ) ;; code offset: 0x278 - (br_if $label$18 + (br_if $block8 ;; code offset: 0x277 (i32.eq ;; code offset: 0x272 @@ -5627,16 +5631,16 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x285 - (loop $label$19 + (loop $label9 ;; code offset: 0x289 (local.set $10 ;; code offset: 0x287 (local.get $0) ) ;; code offset: 0x28b - (block $label$20 + (block $block9 ;; code offset: 0x292 - (br_if $label$20 + (br_if $block9 ;; code offset: 0x291 (i32.lt_s ;; code offset: 0x28d @@ -5661,7 +5665,7 @@ file_names[ 4]: (i32.const 1) ) ;; code offset: 0x29f - (loop $label$21 + (loop $label8 ;; code offset: 0x2ae (local.set $14 ;; code offset: 0x2ab @@ -5714,7 +5718,7 @@ file_names[ 4]: (local.get $14) ) ;; code offset: 0x2d8 - (br_if $label$21 + (br_if $label8 ;; code offset: 0x2d7 (i32.lt_s ;; code offset: 0x2ce @@ -5785,7 +5789,7 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0x2ff - (br_if $label$19 + (br_if $label9 ;; code offset: 0x2fd (local.get $1) ) @@ -5809,7 +5813,7 @@ file_names[ 4]: ) ) ;; code offset: 0x314 - (br_if $label$1 + (br_if $block5 ;; code offset: 0x313 (i32.ge_s ;; code offset: 0x30f @@ -5819,16 +5823,16 @@ file_names[ 4]: ) ) ;; code offset: 0x316 - (loop $label$22 + (loop $label12 ;; code offset: 0x31a (local.set $1 ;; code offset: 0x318 (i32.const 0) ) ;; code offset: 0x31c - (block $label$23 + (block $block10 ;; code offset: 0x323 - (br_if $label$23 + (br_if $block10 ;; code offset: 0x322 (i32.lt_s ;; code offset: 0x31e @@ -5838,7 +5842,7 @@ file_names[ 4]: ) ) ;; code offset: 0x325 - (loop $label$24 + (loop $label10 ;; code offset: 0x33f (i32.store ;; code offset: 0x32e @@ -5878,7 +5882,7 @@ file_names[ 4]: ) ) ;; code offset: 0x347 - (br_if $label$24 + (br_if $label10 ;; code offset: 0x346 (i32.ne ;; code offset: 0x342 @@ -5943,7 +5947,7 @@ file_names[ 4]: ) ) ;; code offset: 0x378 - (br_if $label$15 + (br_if $label11 ;; code offset: 0x377 (i32.gt_s ;; code offset: 0x373 @@ -5953,7 +5957,7 @@ file_names[ 4]: ) ) ;; code offset: 0x384 - (br_if $label$1 + (br_if $block5 ;; code offset: 0x383 (i32.eq ;; code offset: 0x37f @@ -5979,9 +5983,13 @@ file_names[ 4]: ) ) ;; code offset: 0x38d - (br $label$22) + (br $label12) ) + ;; code offset: 0x390 + (unreachable) ) + ;; code offset: 0x392 + (unreachable) ) ;; code offset: 0x396 (call $free @@ -6023,13 +6031,13 @@ file_names[ 4]: ) ) ;; code offset: 0x3bd - (block $label$1 + (block $block2 ;; code offset: 0x3bf - (block $label$2 + (block $block1 ;; code offset: 0x3c1 - (block $label$3 + (block $block ;; code offset: 0x3c8 - (br_if $label$3 + (br_if $block ;; code offset: 0x3c7 (i32.lt_s ;; code offset: 0x3c3 @@ -6044,7 +6052,7 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x3da - (br_if $label$2 + (br_if $block1 ;; code offset: 0x3d9 (i32.gt_s ;; code offset: 0x3d5 @@ -6077,12 +6085,12 @@ file_names[ 4]: (i32.const 1) ) ;; code offset: 0x3e7 - (br $label$1) + (br $block2) ) ;; code offset: 0x3ea - (block $label$4 + (block $block3 ;; code offset: 0x3f1 - (br_if $label$4 + (br_if $block3 ;; code offset: 0x3f0 (i32.eq ;; code offset: 0x3ec @@ -6112,7 +6120,7 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x402 - (loop $label$5 + (loop $label ;; code offset: 0x40c (i32.store offset=8 ;; code offset: 0x408 @@ -6146,7 +6154,7 @@ file_names[ 4]: (local.get $3) ) ;; code offset: 0x42b - (br_if $label$5 + (br_if $label ;; code offset: 0x42a (i32.ne ;; code offset: 0x426 @@ -6195,15 +6203,15 @@ file_names[ 4]: ) ) ;; code offset: 0x444 - (block $label$6 + (block $block8 ;; code offset: 0x446 - (block $label$7 + (block $block6 ;; code offset: 0x448 - (block $label$8 + (block $block5 ;; code offset: 0x44a - (block $label$9 + (block $block4 ;; code offset: 0x451 - (br_if $label$9 + (br_if $block4 ;; code offset: 0x450 (i32.le_s ;; code offset: 0x44c @@ -6213,7 +6221,7 @@ file_names[ 4]: ) ) ;; code offset: 0x453 - (loop $label$10 + (loop $label1 ;; code offset: 0x45f (i32.store ;; code offset: 0x45c @@ -6232,7 +6240,7 @@ file_names[ 4]: (local.get $0) ) ;; code offset: 0x46c - (br_if $label$10 + (br_if $label1 ;; code offset: 0x46b (i32.ne ;; code offset: 0x467 @@ -6261,7 +6269,7 @@ file_names[ 4]: (local.get $4) ) ;; code offset: 0x477 - (br $label$8) + (br $block5) ) ;; code offset: 0x47c (local.set $7 @@ -6274,17 +6282,17 @@ file_names[ 4]: (local.get $4) ) ;; code offset: 0x482 - (br $label$7) + (br $block6) ) ;; code offset: 0x485 - (loop $label$11 + (loop $label6 ;; code offset: 0x489 (local.set $0 ;; code offset: 0x487 (i32.const 0) ) ;; code offset: 0x48b - (loop $label$12 + (loop $label2 ;; code offset: 0x49d (i32.store offset=16 ;; code offset: 0x48d @@ -6326,7 +6334,7 @@ file_names[ 4]: ) ) ;; code offset: 0x4b5 - (br_if $label$12 + (br_if $label2 ;; code offset: 0x4b4 (i32.ne ;; code offset: 0x4b0 @@ -6353,9 +6361,9 @@ file_names[ 4]: ) ) ;; code offset: 0x4bd - (block $label$13 + (block $block7 ;; code offset: 0x4c4 - (br_if $label$13 + (br_if $block7 ;; code offset: 0x4c3 (i32.le_s ;; code offset: 0x4bf @@ -6365,7 +6373,7 @@ file_names[ 4]: ) ) ;; code offset: 0x4c6 - (loop $label$14 + (loop $label3 ;; code offset: 0x4d7 (i32.store ;; code offset: 0x4d4 @@ -6407,14 +6415,14 @@ file_names[ 4]: (local.get $0) ) ;; code offset: 0x4e7 - (br_if $label$14 + (br_if $label3 ;; code offset: 0x4e5 (local.get $8) ) ) ) ;; code offset: 0x4f0 - (br_if $label$6 + (br_if $block8 ;; code offset: 0x4ef (i32.eq ;; code offset: 0x4eb @@ -6434,7 +6442,7 @@ file_names[ 4]: ) ) ;; code offset: 0x4f9 - (loop $label$15 + (loop $label5 ;; code offset: 0x4fd (local.set $0 ;; code offset: 0x4fb @@ -6449,9 +6457,9 @@ file_names[ 4]: ) ) ;; code offset: 0x506 - (block $label$16 + (block $block9 ;; code offset: 0x50d - (br_if $label$16 + (br_if $block9 ;; code offset: 0x50c (i32.le_s ;; code offset: 0x508 @@ -6461,7 +6469,7 @@ file_names[ 4]: ) ) ;; code offset: 0x50f - (loop $label$17 + (loop $label4 ;; code offset: 0x529 (i32.store ;; code offset: 0x518 @@ -6501,7 +6509,7 @@ file_names[ 4]: ) ) ;; code offset: 0x531 - (br_if $label$17 + (br_if $label4 ;; code offset: 0x530 (i32.ne ;; code offset: 0x52c @@ -6566,9 +6574,9 @@ file_names[ 4]: ) ) ;; code offset: 0x55d - (block $label$18 + (block $block10 ;; code offset: 0x564 - (br_if $label$18 + (br_if $block10 ;; code offset: 0x563 (i32.gt_s ;; code offset: 0x55f @@ -6578,7 +6586,7 @@ file_names[ 4]: ) ) ;; code offset: 0x570 - (br_if $label$15 + (br_if $label5 ;; code offset: 0x56f (i32.ne ;; code offset: 0x56b @@ -6596,11 +6604,11 @@ file_names[ 4]: ) ) ;; code offset: 0x572 - (br $label$6) + (br $block8) ) ) ;; code offset: 0x579 - (br_if $label$6 + (br_if $block8 ;; code offset: 0x578 (i32.eqz ;; code offset: 0x576 @@ -6608,11 +6616,13 @@ file_names[ 4]: ) ) ;; code offset: 0x57b - (br $label$11) + (br $label6) ) + ;; code offset: 0x57e + (unreachable) ) ;; code offset: 0x580 - (loop $label$19 + (loop $label10 ;; code offset: 0x586 (drop ;; code offset: 0x584 @@ -6622,9 +6632,9 @@ file_names[ 4]: ) ) ;; code offset: 0x587 - (block $label$20 + (block $block11 ;; code offset: 0x58e - (br_if $label$20 + (br_if $block11 ;; code offset: 0x58d (i32.le_s ;; code offset: 0x589 @@ -6634,7 +6644,7 @@ file_names[ 4]: ) ) ;; code offset: 0x590 - (loop $label$21 + (loop $label7 ;; code offset: 0x5a1 (i32.store ;; code offset: 0x59e @@ -6676,14 +6686,14 @@ file_names[ 4]: (local.get $0) ) ;; code offset: 0x5b1 - (br_if $label$21 + (br_if $label7 ;; code offset: 0x5af (local.get $8) ) ) ) ;; code offset: 0x5ba - (br_if $label$6 + (br_if $block8 ;; code offset: 0x5b9 (i32.eq ;; code offset: 0x5b5 @@ -6703,7 +6713,7 @@ file_names[ 4]: ) ) ;; code offset: 0x5c3 - (loop $label$22 + (loop $label9 ;; code offset: 0x5ca (local.set $8 ;; code offset: 0x5c7 @@ -6718,9 +6728,9 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x5d0 - (block $label$23 + (block $block12 ;; code offset: 0x5d7 - (br_if $label$23 + (br_if $block12 ;; code offset: 0x5d6 (i32.lt_s ;; code offset: 0x5d2 @@ -6730,7 +6740,7 @@ file_names[ 4]: ) ) ;; code offset: 0x5d9 - (loop $label$24 + (loop $label8 ;; code offset: 0x5f3 (i32.store ;; code offset: 0x5e2 @@ -6770,7 +6780,7 @@ file_names[ 4]: ) ) ;; code offset: 0x5fb - (br_if $label$24 + (br_if $label8 ;; code offset: 0x5fa (i32.ne ;; code offset: 0x5f6 @@ -6835,9 +6845,9 @@ file_names[ 4]: ) ) ;; code offset: 0x627 - (block $label$25 + (block $block13 ;; code offset: 0x62e - (br_if $label$25 + (br_if $block13 ;; code offset: 0x62d (i32.gt_s ;; code offset: 0x629 @@ -6847,7 +6857,7 @@ file_names[ 4]: ) ) ;; code offset: 0x63a - (br_if $label$22 + (br_if $label9 ;; code offset: 0x639 (i32.ne ;; code offset: 0x635 @@ -6865,11 +6875,11 @@ file_names[ 4]: ) ) ;; code offset: 0x63c - (br $label$6) + (br $block8) ) ) ;; code offset: 0x642 - (br_if $label$19 + (br_if $label10 ;; code offset: 0x640 (local.get $7) ) @@ -6896,9 +6906,9 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x656 - (block $label$26 + (block $block14 ;; code offset: 0x65b - (br_if $label$26 + (br_if $block14 ;; code offset: 0x65a (i32.eqz ;; code offset: 0x658 @@ -6911,7 +6921,7 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x661 - (loop $label$27 + (loop $label11 ;; code offset: 0x667 (local.set $1 ;; code offset: 0x665 @@ -6956,7 +6966,7 @@ file_names[ 4]: (local.get $6) ) ;; code offset: 0x686 - (br_if $label$27 + (br_if $label11 ;; code offset: 0x684 (local.get $6) ) diff --git a/test/passes/fannkuch3_manyopts_dwarf.bin.txt b/test/passes/fannkuch3_manyopts_dwarf.bin.txt index 3ffb81efeaf..6feceabfffc 100644 --- a/test/passes/fannkuch3_manyopts_dwarf.bin.txt +++ b/test/passes/fannkuch3_manyopts_dwarf.bin.txt @@ -4710,8 +4710,8 @@ file_names[ 4]: (local $14 i32) (local $15 i32) (local $16 i32) - (local $17 i32) - (local $18 i32) + (local $scratch i32) + (local $scratch_18 i32) ;; code offset: 0x35 (local.set $3 ;; code offset: 0x33 @@ -4751,9 +4751,9 @@ file_names[ 4]: ) ) ;; code offset: 0x43 - (block $label$1 + (block $block2 ;; code offset: 0x45 - (block $label$2 + (block $block ;; code offset: 0x4c (if ;; code offset: 0x4b @@ -4765,7 +4765,7 @@ file_names[ 4]: ) (then ;; code offset: 0x4e - (loop $label$4 + (loop $label ;; code offset: 0x5a (i32.store ;; code offset: 0x57 @@ -4784,7 +4784,7 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0x67 - (br_if $label$4 + (br_if $label ;; code offset: 0x66 (i32.ne ;; code offset: 0x62 @@ -4854,7 +4854,7 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0x95 - (br_if $label$2 + (br_if $block ;; code offset: 0x94 (i32.le_s ;; code offset: 0x90 @@ -4864,7 +4864,7 @@ file_names[ 4]: ) ) ;; code offset: 0x97 - (loop $label$5 + (loop $label5 ;; code offset: 0x9e (if ;; code offset: 0x9d @@ -4876,7 +4876,7 @@ file_names[ 4]: ) (then ;; code offset: 0xa0 - (loop $label$7 + (loop $label1 ;; code offset: 0xb1 (i32.store ;; code offset: 0xae @@ -4902,10 +4902,9 @@ file_names[ 4]: ;; code offset: 0xaf (local.get $2) ) - ;; code offset: 0xbd - (br_if $label$7 + (br_if $label1 (block (result i32) - (local.set $17 + (local.set $scratch ;; code offset: 0xb8 (i32.gt_s ;; code offset: 0xb4 @@ -4919,16 +4918,17 @@ file_names[ 4]: ;; code offset: 0xb9 (local.get $1) ) - (local.get $17) + ;; code offset: 0xbd + (local.get $scratch) ) ) ) ) ) ;; code offset: 0xc1 - (block $label$8 + (block $block1 ;; code offset: 0xcb - (br_if $label$8 + (br_if $block1 ;; code offset: 0xca (i32.eqz ;; code offset: 0xc8 @@ -4942,7 +4942,7 @@ file_names[ 4]: ) ) ;; code offset: 0xd5 - (br_if $label$8 + (br_if $block1 ;; code offset: 0xd4 (i32.eq ;; code offset: 0xcf @@ -4978,7 +4978,7 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0xea - (loop $label$9 + (loop $label3 ;; code offset: 0xee (local.set $16 ;; code offset: 0xec @@ -5010,7 +5010,7 @@ file_names[ 4]: (i32.const 1) ) ;; code offset: 0x102 - (loop $label$11 + (loop $label2 ;; code offset: 0x111 (local.set $15 ;; code offset: 0x10e @@ -5063,7 +5063,7 @@ file_names[ 4]: (local.get $15) ) ;; code offset: 0x13b - (br_if $label$11 + (br_if $label2 ;; code offset: 0x13a (i32.lt_s ;; code offset: 0x131 @@ -5135,7 +5135,7 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0x162 - (br_if $label$9 + (br_if $label3 ;; code offset: 0x160 (local.get $1) ) @@ -5159,7 +5159,7 @@ file_names[ 4]: ) ) ;; code offset: 0x177 - (br_if $label$1 + (br_if $block2 ;; code offset: 0x176 (i32.ge_s ;; code offset: 0x172 @@ -5169,7 +5169,7 @@ file_names[ 4]: ) ) ;; code offset: 0x179 - (loop $label$12 + (loop $label6 ;; code offset: 0x17d (local.set $1 ;; code offset: 0x17b @@ -5186,7 +5186,7 @@ file_names[ 4]: ) (then ;; code offset: 0x186 - (loop $label$14 + (loop $label4 ;; code offset: 0x1a0 (i32.store ;; code offset: 0x18f @@ -5226,7 +5226,7 @@ file_names[ 4]: ) ) ;; code offset: 0x1a8 - (br_if $label$14 + (br_if $label4 ;; code offset: 0x1a7 (i32.ne ;; code offset: 0x1a3 @@ -5292,7 +5292,7 @@ file_names[ 4]: ) ) ;; code offset: 0x1d9 - (br_if $label$5 + (br_if $label5 ;; code offset: 0x1d8 (i32.gt_s ;; code offset: 0x1d4 @@ -5302,7 +5302,7 @@ file_names[ 4]: ) ) ;; code offset: 0x1e5 - (br_if $label$1 + (br_if $block2 ;; code offset: 0x1e4 (i32.eq ;; code offset: 0x1e0 @@ -5328,9 +5328,13 @@ file_names[ 4]: ) ) ;; code offset: 0x1ee - (br $label$12) + (br $label6) ) + ;; code offset: 0x1f1 + (unreachable) ) + ;; code offset: 0x1f3 + (unreachable) ) ) ;; code offset: 0x209 @@ -5386,7 +5390,7 @@ file_names[ 4]: ) ) ;; code offset: 0x21c - (loop $label$15 + (loop $label11 ;; code offset: 0x223 (if ;; code offset: 0x222 @@ -5398,7 +5402,7 @@ file_names[ 4]: ) (then ;; code offset: 0x225 - (loop $label$17 + (loop $label7 ;; code offset: 0x236 (i32.store ;; code offset: 0x233 @@ -5424,10 +5428,9 @@ file_names[ 4]: ;; code offset: 0x234 (local.get $2) ) - ;; code offset: 0x242 - (br_if $label$17 + (br_if $label7 (block (result i32) - (local.set $18 + (local.set $scratch_18 ;; code offset: 0x23d (i32.gt_s ;; code offset: 0x239 @@ -5441,16 +5444,17 @@ file_names[ 4]: ;; code offset: 0x23e (local.get $1) ) - (local.get $18) + ;; code offset: 0x242 + (local.get $scratch_18) ) ) ) ) ) ;; code offset: 0x246 - (block $label$18 + (block $block3 ;; code offset: 0x250 - (br_if $label$18 + (br_if $block3 ;; code offset: 0x24f (i32.eqz ;; code offset: 0x24d @@ -5464,7 +5468,7 @@ file_names[ 4]: ) ) ;; code offset: 0x25a - (br_if $label$18 + (br_if $block3 ;; code offset: 0x259 (i32.eq ;; code offset: 0x254 @@ -5490,7 +5494,7 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x267 - (loop $label$19 + (loop $label9 ;; code offset: 0x26b (local.set $10 ;; code offset: 0x269 @@ -5522,7 +5526,7 @@ file_names[ 4]: (i32.const 1) ) ;; code offset: 0x27f - (loop $label$21 + (loop $label8 ;; code offset: 0x28e (local.set $14 ;; code offset: 0x28b @@ -5575,7 +5579,7 @@ file_names[ 4]: (local.get $14) ) ;; code offset: 0x2b8 - (br_if $label$21 + (br_if $label8 ;; code offset: 0x2b7 (i32.lt_s ;; code offset: 0x2ae @@ -5647,7 +5651,7 @@ file_names[ 4]: (local.get $1) ) ;; code offset: 0x2df - (br_if $label$19 + (br_if $label9 ;; code offset: 0x2dd (local.get $1) ) @@ -5671,7 +5675,7 @@ file_names[ 4]: ) ) ;; code offset: 0x2f4 - (br_if $label$1 + (br_if $block2 ;; code offset: 0x2f3 (i32.ge_s ;; code offset: 0x2ef @@ -5681,7 +5685,7 @@ file_names[ 4]: ) ) ;; code offset: 0x2f6 - (loop $label$22 + (loop $label12 ;; code offset: 0x2fa (local.set $1 ;; code offset: 0x2f8 @@ -5698,7 +5702,7 @@ file_names[ 4]: ) (then ;; code offset: 0x303 - (loop $label$24 + (loop $label10 ;; code offset: 0x31d (i32.store ;; code offset: 0x30c @@ -5738,7 +5742,7 @@ file_names[ 4]: ) ) ;; code offset: 0x325 - (br_if $label$24 + (br_if $label10 ;; code offset: 0x324 (i32.ne ;; code offset: 0x320 @@ -5804,7 +5808,7 @@ file_names[ 4]: ) ) ;; code offset: 0x356 - (br_if $label$15 + (br_if $label11 ;; code offset: 0x355 (i32.gt_s ;; code offset: 0x351 @@ -5814,7 +5818,7 @@ file_names[ 4]: ) ) ;; code offset: 0x362 - (br_if $label$1 + (br_if $block2 ;; code offset: 0x361 (i32.eq ;; code offset: 0x35d @@ -5840,9 +5844,13 @@ file_names[ 4]: ) ) ;; code offset: 0x36b - (br $label$22) + (br $label12) ) + ;; code offset: 0x36e + (unreachable) ) + ;; code offset: 0x370 + (unreachable) ) ;; code offset: 0x374 (call $free @@ -5870,8 +5878,8 @@ file_names[ 4]: (local $6 i32) (local $7 i32) (local $8 i32) - (local $9 i32) - (local $10 i32) + (local $scratch i32) + (local $scratch_10 i32) ;; code offset: 0x399 (global.set $global$0 ;; code offset: 0x397 @@ -5886,9 +5894,9 @@ file_names[ 4]: ) ) ;; code offset: 0x39b - (block $label$1 + (block $block1 ;; code offset: 0x39d - (block $label$2 + (block $block ;; code offset: 0x3a4 (if ;; code offset: 0x3a3 @@ -5900,7 +5908,7 @@ file_names[ 4]: ) (then ;; code offset: 0x3b2 - (br_if $label$2 + (br_if $block ;; code offset: 0x3b1 (i32.gt_s ;; code offset: 0x3ad @@ -5934,7 +5942,7 @@ file_names[ 4]: (i32.const 1) ) ;; code offset: 0x3bf - (br $label$1) + (br $block1) ) ;; code offset: 0x3c7 (if @@ -5967,7 +5975,7 @@ file_names[ 4]: (i32.const 0) ) ;; code offset: 0x3d8 - (loop $label$5 + (loop $label ;; code offset: 0x3e2 (i32.store offset=8 ;; code offset: 0x3de @@ -6001,7 +6009,7 @@ file_names[ 4]: (local.get $4) ) ;; code offset: 0x401 - (br_if $label$5 + (br_if $label ;; code offset: 0x400 (i32.ne ;; code offset: 0x3fc @@ -6051,11 +6059,11 @@ file_names[ 4]: ) ) ;; code offset: 0x41a - (block $label$6 + (block $block4 ;; code offset: 0x41c - (block $label$7 + (block $block3 ;; code offset: 0x41e - (block $label$8 + (block $block2 ;; code offset: 0x425 (if ;; code offset: 0x424 @@ -6067,7 +6075,7 @@ file_names[ 4]: ) (then ;; code offset: 0x427 - (loop $label$10 + (loop $label1 ;; code offset: 0x433 (i32.store ;; code offset: 0x430 @@ -6086,7 +6094,7 @@ file_names[ 4]: (local.get $0) ) ;; code offset: 0x440 - (br_if $label$10 + (br_if $label1 ;; code offset: 0x43f (i32.ne ;; code offset: 0x43b @@ -6115,7 +6123,7 @@ file_names[ 4]: (local.get $3) ) ;; code offset: 0x44b - (br $label$8) + (br $block2) ) ) ;; code offset: 0x450 @@ -6129,17 +6137,17 @@ file_names[ 4]: (local.get $3) ) ;; code offset: 0x456 - (br $label$7) + (br $block3) ) ;; code offset: 0x459 - (loop $label$11 + (loop $label6 ;; code offset: 0x45d (local.set $0 ;; code offset: 0x45b (i32.const 0) ) ;; code offset: 0x45f - (loop $label$12 + (loop $label2 ;; code offset: 0x471 (i32.store offset=16 ;; code offset: 0x461 @@ -6181,7 +6189,7 @@ file_names[ 4]: ) ) ;; code offset: 0x489 - (br_if $label$12 + (br_if $label2 ;; code offset: 0x488 (i32.ne ;; code offset: 0x484 @@ -6218,7 +6226,7 @@ file_names[ 4]: ) (then ;; code offset: 0x498 - (loop $label$14 + (loop $label3 ;; code offset: 0x4a9 (i32.store ;; code offset: 0x4a6 @@ -6244,10 +6252,9 @@ file_names[ 4]: ;; code offset: 0x4a7 (local.get $2) ) - ;; code offset: 0x4b5 - (br_if $label$14 + (br_if $label3 (block (result i32) - (local.set $9 + (local.set $scratch ;; code offset: 0x4b0 (i32.gt_s ;; code offset: 0x4ac @@ -6261,14 +6268,15 @@ file_names[ 4]: ;; code offset: 0x4b1 (local.get $0) ) - (local.get $9) + ;; code offset: 0x4b5 + (local.get $scratch) ) ) ) ) ) ;; code offset: 0x4be - (br_if $label$6 + (br_if $block4 ;; code offset: 0x4bd (i32.eq ;; code offset: 0x4b9 @@ -6288,7 +6296,7 @@ file_names[ 4]: ) ) ;; code offset: 0x4c7 - (loop $label$15 + (loop $label5 ;; code offset: 0x4cb (local.set $0 ;; code offset: 0x4c9 @@ -6313,7 +6321,7 @@ file_names[ 4]: ) (then ;; code offset: 0x4db - (loop $label$17 + (loop $label4 ;; code offset: 0x4f5 (i32.store ;; code offset: 0x4e4 @@ -6353,7 +6361,7 @@ file_names[ 4]: ) ) ;; code offset: 0x4fd - (br_if $label$17 + (br_if $label4 ;; code offset: 0x4fc (i32.ne ;; code offset: 0x4f8 @@ -6429,7 +6437,7 @@ file_names[ 4]: ) (then ;; code offset: 0x53a - (br_if $label$15 + (br_if $label5 ;; code offset: 0x539 (i32.ne ;; code offset: 0x535 @@ -6447,21 +6455,21 @@ file_names[ 4]: ) ) ;; code offset: 0x53c - (br $label$6) + (br $block4) ) ) ) ;; code offset: 0x542 - (br_if $label$11 + (br_if $label6 ;; code offset: 0x540 (local.get $6) ) ) ;; code offset: 0x545 - (br $label$6) + (br $block4) ) ;; code offset: 0x548 - (loop $label$19 + (loop $label10 ;; code offset: 0x54e (drop ;; code offset: 0x54c @@ -6481,7 +6489,7 @@ file_names[ 4]: ) (then ;; code offset: 0x556 - (loop $label$21 + (loop $label7 ;; code offset: 0x567 (i32.store ;; code offset: 0x564 @@ -6507,10 +6515,9 @@ file_names[ 4]: ;; code offset: 0x565 (local.get $2) ) - ;; code offset: 0x573 - (br_if $label$21 + (br_if $label7 (block (result i32) - (local.set $10 + (local.set $scratch_10 ;; code offset: 0x56e (i32.gt_s ;; code offset: 0x56a @@ -6524,14 +6531,15 @@ file_names[ 4]: ;; code offset: 0x56f (local.get $0) ) - (local.get $10) + ;; code offset: 0x573 + (local.get $scratch_10) ) ) ) ) ) ;; code offset: 0x57c - (br_if $label$6 + (br_if $block4 ;; code offset: 0x57b (i32.eq ;; code offset: 0x577 @@ -6551,7 +6559,7 @@ file_names[ 4]: ) ) ;; code offset: 0x585 - (loop $label$22 + (loop $label9 ;; code offset: 0x58c (local.set $7 ;; code offset: 0x589 @@ -6576,7 +6584,7 @@ file_names[ 4]: ) (then ;; code offset: 0x599 - (loop $label$24 + (loop $label8 ;; code offset: 0x5b3 (i32.store ;; code offset: 0x5a2 @@ -6616,7 +6624,7 @@ file_names[ 4]: ) ) ;; code offset: 0x5bb - (br_if $label$24 + (br_if $label8 ;; code offset: 0x5ba (i32.ne ;; code offset: 0x5b6 @@ -6692,7 +6700,7 @@ file_names[ 4]: ) (then ;; code offset: 0x5f8 - (br_if $label$22 + (br_if $label9 ;; code offset: 0x5f7 (i32.ne ;; code offset: 0x5f3 @@ -6710,12 +6718,12 @@ file_names[ 4]: ) ) ;; code offset: 0x5fa - (br $label$6) + (br $block4) ) ) ) ;; code offset: 0x600 - (br_if $label$19 + (br_if $label10 ;; code offset: 0x5fe (local.get $6) ) @@ -6747,7 +6755,7 @@ file_names[ 4]: (local.get $4) (then ;; code offset: 0x618 - (loop $label$27 + (loop $label11 ;; code offset: 0x61e (local.set $1 ;; code offset: 0x61c @@ -6792,7 +6800,7 @@ file_names[ 4]: (local.get $2) ) ;; code offset: 0x63d - (br_if $label$27 + (br_if $label11 ;; code offset: 0x63b (local.get $2) ) diff --git a/test/passes/fib2_dwarf.bin.txt b/test/passes/fib2_dwarf.bin.txt index 9d633f78b2d..37c49c336aa 100644 --- a/test/passes/fib2_dwarf.bin.txt +++ b/test/passes/fib2_dwarf.bin.txt @@ -637,9 +637,9 @@ file_names[ 1]: (i32.const 1) ) ;; code offset: 0x12 - (block $label$1 + (block $block ;; code offset: 0x19 - (br_if $label$1 + (br_if $block ;; code offset: 0x18 (i32.lt_s ;; code offset: 0x14 @@ -659,7 +659,7 @@ file_names[ 1]: (i32.const 0) ) ;; code offset: 0x23 - (loop $label$2 + (loop $label ;; code offset: 0x2c (local.set $1 ;; code offset: 0x2b @@ -679,7 +679,7 @@ file_names[ 1]: (local.get $4) ) ;; code offset: 0x3c - (br_if $label$2 + (br_if $label ;; code offset: 0x3b (i32.ne ;; code offset: 0x37 diff --git a/test/passes/fib2_emptylocspan_dwarf.bin.txt b/test/passes/fib2_emptylocspan_dwarf.bin.txt index 6d9078ccc08..e59fe19bb29 100644 --- a/test/passes/fib2_emptylocspan_dwarf.bin.txt +++ b/test/passes/fib2_emptylocspan_dwarf.bin.txt @@ -637,9 +637,9 @@ file_names[ 1]: (i32.const 1) ) ;; code offset: 0x12 - (block $label$1 + (block $block ;; code offset: 0x19 - (br_if $label$1 + (br_if $block ;; code offset: 0x18 (i32.lt_s ;; code offset: 0x14 @@ -659,7 +659,7 @@ file_names[ 1]: (i32.const 0) ) ;; code offset: 0x23 - (loop $label$2 + (loop $label ;; code offset: 0x2c (local.set $1 ;; code offset: 0x2b @@ -679,7 +679,7 @@ file_names[ 1]: (local.get $4) ) ;; code offset: 0x3c - (br_if $label$2 + (br_if $label ;; code offset: 0x3b (i32.ne ;; code offset: 0x37 diff --git a/test/passes/fib_nonzero-low-pc_dwarf.bin.txt b/test/passes/fib_nonzero-low-pc_dwarf.bin.txt index 8d1c7faa89a..a9294f7e1ea 100644 --- a/test/passes/fib_nonzero-low-pc_dwarf.bin.txt +++ b/test/passes/fib_nonzero-low-pc_dwarf.bin.txt @@ -539,9 +539,9 @@ file_names[ 1]: (i32.const 1) ) ;; code offset: 0x19 - (block $label$1 + (block $block ;; code offset: 0x20 - (br_if $label$1 + (br_if $block ;; code offset: 0x1f (i32.le_s ;; code offset: 0x1b @@ -561,7 +561,7 @@ file_names[ 1]: (i32.const 1) ) ;; code offset: 0x2a - (loop $label$2 + (loop $label ;; code offset: 0x33 (local.set $1 ;; code offset: 0x32 @@ -601,7 +601,7 @@ file_names[ 1]: (local.get $4) ) ;; code offset: 0x4a - (br_if $label$2 + (br_if $label ;; code offset: 0x49 (i32.eqz ;; code offset: 0x47 diff --git a/test/passes/flatten.bin.txt b/test/passes/flatten.bin.txt index 53fbcc3977f..680f2c393c5 100644 --- a/test/passes/flatten.bin.txt +++ b/test/passes/flatten.bin.txt @@ -100,10 +100,120 @@ (local $6 i64) (local $7 f32) (local $8 f64) - (block $label$1 + (local $9 f32) + (local $10 f32) + (local $11 f64) + (local $12 f64) + (local $13 i32) + (local $14 i32) + (local $15 i32) + (local $16 i32) + (local $17 f32) + (local $18 f32) + (local $19 i64) + (local $20 i32) + (local $21 i64) + (local $22 i32) + (local $23 f64) + (local $24 f64) + (block (nop) (unreachable) + (i64.eqz + (unreachable) + ) + (drop + (unreachable) + ) (unreachable) + (local.set $9 + (local.get $1) + ) + (local.set $10 + (f32.neg + (local.get $9) + ) + ) + (drop + (local.get $10) + ) + (local.set $11 + (local.get $2) + ) + (local.set $12 + (f64.neg + (local.get $11) + ) + ) + (drop + (local.get $12) + ) + (local.set $13 + (local.get $3) + ) + (local.set $14 + (i32.eqz + (local.get $13) + ) + ) + (drop + (local.get $14) + ) + (local.set $15 + (local.get $4) + ) + (local.set $16 + (i32.eqz + (local.get $15) + ) + ) + (drop + (local.get $16) + ) + (local.set $17 + (local.get $7) + ) + (local.set $18 + (f32.neg + (local.get $17) + ) + ) + (drop + (local.get $18) + ) + (local.set $19 + (local.get $5) + ) + (local.set $20 + (i64.eqz + (local.get $19) + ) + ) + (drop + (local.get $20) + ) + (local.set $21 + (local.get $6) + ) + (local.set $22 + (i64.eqz + (local.get $21) + ) + ) + (drop + (local.get $22) + ) + (local.set $23 + (local.get $8) + ) + (local.set $24 + (f64.neg + (local.get $23) + ) + ) + (drop + (local.get $24) + ) ) (unreachable) ) @@ -138,7 +248,7 @@ (local $32 f64) (local $33 f64) (local $34 f64) - (block $label$1 + (block (local.set $7 (f32.const 5.5) ) diff --git a/test/passes/print.bin.txt b/test/passes/print.bin.txt index 25b2cc38720..f54f883d1e5 100644 --- a/test/passes/print.bin.txt +++ b/test/passes/print.bin.txt @@ -27,8 +27,8 @@ (i32.const 55) ) (then - (loop $label$2 - (br_if $label$2 + (loop $label + (br_if $label (i32.ne (i32.rem_s (local.tee $0 @@ -120,8 +120,8 @@ (i32.const 55) ) (then - (loop $label$2 - (br_if $label$2 + (loop $label + (br_if $label (i32.ne (i32.rem_s (local.tee $0 diff --git a/test/passes/print_g.bin.txt b/test/passes/print_g.bin.txt index 174230388c9..f9a2ac67b39 100644 --- a/test/passes/print_g.bin.txt +++ b/test/passes/print_g.bin.txt @@ -35,9 +35,9 @@ ) (then ;; code offset: 0x12 - (loop $label$2 + (loop $label ;; code offset: 0x2e - (br_if $label$2 + (br_if $label ;; code offset: 0x2d (i32.ne ;; code offset: 0x2a @@ -167,9 +167,9 @@ ) (then ;; code offset: 0x12 - (loop $label$2 + (loop $label ;; code offset: 0x2e - (br_if $label$2 + (br_if $label ;; code offset: 0x2d (i32.ne ;; code offset: 0x2a diff --git a/test/passes/print_g_metrics.bin.txt b/test/passes/print_g_metrics.bin.txt index 2acec546f4a..084dd625686 100644 --- a/test/passes/print_g_metrics.bin.txt +++ b/test/passes/print_g_metrics.bin.txt @@ -30,8 +30,8 @@ (i32.const 55) ) (then - (loop $label$2 - (br_if $label$2 + (loop $label + (br_if $label (i32.ne (i32.rem_s (local.tee $0 @@ -120,8 +120,8 @@ total (i32.const 55) ) (then - (loop $label$2 - (br_if $label$2 + (loop $label + (br_if $label (i32.ne (i32.rem_s (local.tee $0 diff --git a/test/passes/print_g_strip-dwarf.bin.txt b/test/passes/print_g_strip-dwarf.bin.txt index 967e03f3d39..e9b36b9757d 100644 --- a/test/passes/print_g_strip-dwarf.bin.txt +++ b/test/passes/print_g_strip-dwarf.bin.txt @@ -27,8 +27,8 @@ (i32.const 55) ) (then - (loop $label$2 - (br_if $label$2 + (loop $label + (br_if $label (i32.ne (i32.rem_s (local.tee $0 @@ -120,8 +120,8 @@ (i32.const 55) ) (then - (loop $label$2 - (br_if $label$2 + (loop $label + (br_if $label (i32.ne (i32.rem_s (local.tee $0 diff --git a/test/passes/reverse_dwarf_abbrevs.bin.txt b/test/passes/reverse_dwarf_abbrevs.bin.txt index 5427acc6cf0..7b3a5aaad2e 100644 --- a/test/passes/reverse_dwarf_abbrevs.bin.txt +++ b/test/passes/reverse_dwarf_abbrevs.bin.txt @@ -292,11 +292,11 @@ file_names[ 1]: ;; code offset: 0x18d (local.set $4 ;; code offset: 0x89 - (block $label$1 (result i32) + (block $block2 (result i32) ;; code offset: 0x8b - (block $label$2 + (block $block1 ;; code offset: 0x8d - (block $label$3 + (block $block ;; code offset: 0xa5 (if ;; code offset: 0xa4 @@ -331,9 +331,9 @@ file_names[ 1]: ) (then ;; code offset: 0xa7 - (loop $label$5 + (loop $label ;; code offset: 0xb3 - (br_if $label$3 + (br_if $block ;; code offset: 0xb2 (i32.eq ;; code offset: 0xa9 @@ -349,7 +349,7 @@ file_names[ 1]: ) ) ;; code offset: 0xba - (br_if $label$2 + (br_if $block1 ;; code offset: 0xb9 (i32.le_s ;; code offset: 0xb5 @@ -456,7 +456,7 @@ file_names[ 1]: ) ) ;; code offset: 0x125 - (br_if $label$5 + (br_if $label ;; code offset: 0x124 (i32.eqz ;; code offset: 0x122 @@ -517,7 +517,7 @@ file_names[ 1]: (i32.const -1) ) ;; code offset: 0x135 - (br_if $label$2 + (br_if $block1 ;; code offset: 0x134 (i32.ne ;; code offset: 0x130 @@ -563,7 +563,7 @@ file_names[ 1]: ) ) ;; code offset: 0x15a - (br $label$1 + (br $block2 ;; code offset: 0x158 (local.get $2) ) @@ -600,7 +600,7 @@ file_names[ 1]: ;; code offset: 0x183 (drop ;; code offset: 0x181 - (br_if $label$1 + (br_if $block2 ;; code offset: 0x17a (local.tee $4 ;; code offset: 0x178 @@ -797,7 +797,7 @@ file_names[ 1]: ) ) ;; code offset: 0x222 - (block $label$2 + (block $block2 ;; code offset: 0x22d (if ;; code offset: 0x22c @@ -817,7 +817,7 @@ file_names[ 1]: ) (then ;; code offset: 0x22f - (block $label$4 + (block $block ;; code offset: 0x236 (if ;; code offset: 0x235 @@ -834,7 +834,7 @@ file_names[ 1]: (local.get $0) ) ;; code offset: 0x23c - (br $label$4) + (br $block) ) ) ;; code offset: 0x245 @@ -856,7 +856,7 @@ file_names[ 1]: (local.get $0) ) ;; code offset: 0x24b - (br $label$4) + (br $block) ) ) ;; code offset: 0x250 @@ -865,7 +865,7 @@ file_names[ 1]: (local.get $0) ) ;; code offset: 0x252 - (loop $label$7 + (loop $label ;; code offset: 0x25b (i32.store8 ;; code offset: 0x254 @@ -887,7 +887,7 @@ file_names[ 1]: ) ) ;; code offset: 0x26f - (br_if $label$4 + (br_if $block ;; code offset: 0x26e (i32.ge_u ;; code offset: 0x26a @@ -905,7 +905,7 @@ file_names[ 1]: ) ) ;; code offset: 0x276 - (br_if $label$7 + (br_if $label ;; code offset: 0x275 (i32.and ;; code offset: 0x271 @@ -917,9 +917,9 @@ file_names[ 1]: ) ) ;; code offset: 0x27a - (block $label$8 + (block $block1 ;; code offset: 0x287 - (br_if $label$8 + (br_if $block1 ;; code offset: 0x286 (i32.lt_u ;; code offset: 0x281 @@ -937,7 +937,7 @@ file_names[ 1]: ) ) ;; code offset: 0x293 - (br_if $label$8 + (br_if $block1 ;; code offset: 0x292 (i32.gt_u ;; code offset: 0x289 @@ -955,7 +955,7 @@ file_names[ 1]: ) ) ;; code offset: 0x295 - (loop $label$9 + (loop $label1 ;; code offset: 0x29e (i32.store ;; code offset: 0x297 @@ -1127,7 +1127,7 @@ file_names[ 1]: ) ) ;; code offset: 0x348 - (br_if $label$9 + (br_if $label1 ;; code offset: 0x347 (i32.le_u ;; code offset: 0x343 @@ -1147,7 +1147,7 @@ file_names[ 1]: ) ) ;; code offset: 0x351 - (br_if $label$2 + (br_if $block2 ;; code offset: 0x350 (i32.ge_u ;; code offset: 0x34c @@ -1157,7 +1157,7 @@ file_names[ 1]: ) ) ;; code offset: 0x353 - (loop $label$10 + (loop $label2 ;; code offset: 0x35c (i32.store ;; code offset: 0x355 @@ -1179,7 +1179,7 @@ file_names[ 1]: ) ) ;; code offset: 0x370 - (br_if $label$10 + (br_if $label2 ;; code offset: 0x36f (i32.lt_u ;; code offset: 0x36b @@ -1198,7 +1198,7 @@ file_names[ 1]: ) ) ;; code offset: 0x373 - (br $label$2) + (br $block2) ) ) ;; code offset: 0x37b @@ -1217,7 +1217,7 @@ file_names[ 1]: (local.get $0) ) ;; code offset: 0x381 - (br $label$2) + (br $block2) ) ) ;; code offset: 0x38e @@ -1244,7 +1244,7 @@ file_names[ 1]: (local.get $0) ) ;; code offset: 0x394 - (br $label$2) + (br $block2) ) ) ;; code offset: 0x399 @@ -1253,7 +1253,7 @@ file_names[ 1]: (local.get $0) ) ;; code offset: 0x39b - (loop $label$13 + (loop $label3 ;; code offset: 0x3a4 (i32.store8 ;; code offset: 0x39d @@ -1305,7 +1305,7 @@ file_names[ 1]: ) ) ;; code offset: 0x3d6 - (br_if $label$13 + (br_if $label3 ;; code offset: 0x3d5 (i32.le_u ;; code offset: 0x3d1 @@ -1335,7 +1335,7 @@ file_names[ 1]: ) (then ;; code offset: 0x3e1 - (loop $label$15 + (loop $label4 ;; code offset: 0x3ea (i32.store8 ;; code offset: 0x3e3 @@ -1357,7 +1357,7 @@ file_names[ 1]: ) ) ;; code offset: 0x3fe - (br_if $label$15 + (br_if $label4 ;; code offset: 0x3fd (i32.ne ;; code offset: 0x3f9 @@ -1386,380 +1386,377 @@ file_names[ 1]: (local $5 i32) (local $6 i32) ;; code offset: 0x410 - (block $label$1 - ;; code offset: 0x43a + (block $block + ;; code offset: 0x41a (if - ;; code offset: 0x412 - (block $label$2 (result i32) - ;; code offset: 0x41c - (if - ;; code offset: 0x41b - (i32.eqz - ;; code offset: 0x419 - (local.tee $3 - ;; code offset: 0x416 - (i32.load offset=16 - ;; code offset: 0x414 - (local.get $2) - ) - ) + ;; code offset: 0x419 + (i32.eqz + ;; code offset: 0x417 + (local.tee $3 + ;; code offset: 0x414 + (i32.load offset=16 + ;; code offset: 0x412 + (local.get $2) + ) + ) + ) + (then + ;; code offset: 0x420 + (br_if $block + ;; code offset: 0x41e + (call $8 + ;; code offset: 0x41c + (local.get $2) ) - (then + ) + ;; code offset: 0x427 + (local.set $3 + ;; code offset: 0x424 + (i32.load offset=16 ;; code offset: 0x422 - (br_if $label$1 - ;; code offset: 0x420 - (call $8 - ;; code offset: 0x41e - (local.get $2) - ) - ) - ;; code offset: 0x429 - (local.set $3 - ;; code offset: 0x426 - (i32.load offset=16 - ;; code offset: 0x424 - (local.get $2) - ) - ) + (local.get $2) ) ) - ;; code offset: 0x438 - (i32.lt_u - ;; code offset: 0x435 - (i32.sub - ;; code offset: 0x42c - (local.get $3) - ;; code offset: 0x433 - (local.tee $5 - ;; code offset: 0x430 - (i32.load offset=20 - ;; code offset: 0x42e - (local.get $2) - ) + ) + ) + ;; code offset: 0x437 + (if + ;; code offset: 0x436 + (i32.lt_u + ;; code offset: 0x433 + (i32.sub + ;; code offset: 0x42a + (local.get $3) + ;; code offset: 0x431 + (local.tee $5 + ;; code offset: 0x42e + (i32.load offset=20 + ;; code offset: 0x42c + (local.get $2) ) ) - ;; code offset: 0x436 - (local.get $1) ) + ;; code offset: 0x434 + (local.get $1) ) (then - ;; code offset: 0x44a + ;; code offset: 0x447 (return - ;; code offset: 0x447 + ;; code offset: 0x444 (call_indirect (type $1) - ;; code offset: 0x43c + ;; code offset: 0x439 (local.get $2) - ;; code offset: 0x43e + ;; code offset: 0x43b (local.get $0) - ;; code offset: 0x440 + ;; code offset: 0x43d (local.get $1) - ;; code offset: 0x444 + ;; code offset: 0x441 (i32.load offset=36 - ;; code offset: 0x442 + ;; code offset: 0x43f (local.get $2) ) ) ) ) ) - ;; code offset: 0x44c - (block $label$5 - ;; code offset: 0x456 - (br_if $label$5 - ;; code offset: 0x455 + ;; code offset: 0x449 + (block $block1 + ;; code offset: 0x453 + (br_if $block1 + ;; code offset: 0x452 (i32.lt_s - ;; code offset: 0x450 + ;; code offset: 0x44d (i32.load8_s offset=75 - ;; code offset: 0x44e + ;; code offset: 0x44b (local.get $2) ) - ;; code offset: 0x453 + ;; code offset: 0x450 (i32.const 0) ) ) - ;; code offset: 0x45a + ;; code offset: 0x457 (local.set $4 - ;; code offset: 0x458 + ;; code offset: 0x455 (local.get $1) ) - ;; code offset: 0x45c - (loop $label$6 - ;; code offset: 0x463 - (br_if $label$5 - ;; code offset: 0x462 + ;; code offset: 0x459 + (loop $label + ;; code offset: 0x460 + (br_if $block1 + ;; code offset: 0x45f (i32.eqz - ;; code offset: 0x460 + ;; code offset: 0x45d (local.tee $3 - ;; code offset: 0x45e + ;; code offset: 0x45b (local.get $4) ) ) ) - ;; code offset: 0x475 - (br_if $label$6 - ;; code offset: 0x474 + ;; code offset: 0x472 + (br_if $label + ;; code offset: 0x471 (i32.ne - ;; code offset: 0x46f + ;; code offset: 0x46c (i32.load8_u - ;; code offset: 0x46e + ;; code offset: 0x46b (i32.add - ;; code offset: 0x465 + ;; code offset: 0x462 (local.get $0) - ;; code offset: 0x46c + ;; code offset: 0x469 (local.tee $4 - ;; code offset: 0x46b + ;; code offset: 0x468 (i32.add - ;; code offset: 0x467 + ;; code offset: 0x464 (local.get $3) - ;; code offset: 0x469 + ;; code offset: 0x466 (i32.const -1) ) ) ) ) - ;; code offset: 0x472 + ;; code offset: 0x46f (i32.const 10) ) ) ) - ;; code offset: 0x48b - (br_if $label$1 - ;; code offset: 0x48a + ;; code offset: 0x488 + (br_if $block + ;; code offset: 0x487 (i32.lt_u - ;; code offset: 0x486 + ;; code offset: 0x483 (local.tee $4 - ;; code offset: 0x483 + ;; code offset: 0x480 (call_indirect (type $1) - ;; code offset: 0x478 + ;; code offset: 0x475 (local.get $2) - ;; code offset: 0x47a + ;; code offset: 0x477 (local.get $0) - ;; code offset: 0x47c + ;; code offset: 0x479 (local.get $3) - ;; code offset: 0x480 + ;; code offset: 0x47d (i32.load offset=36 - ;; code offset: 0x47e + ;; code offset: 0x47b (local.get $2) ) ) ) - ;; code offset: 0x488 + ;; code offset: 0x485 (local.get $3) ) ) - ;; code offset: 0x492 + ;; code offset: 0x48f (local.set $0 - ;; code offset: 0x491 + ;; code offset: 0x48e (i32.add - ;; code offset: 0x48d + ;; code offset: 0x48a (local.get $0) - ;; code offset: 0x48f + ;; code offset: 0x48c (local.get $3) ) ) - ;; code offset: 0x499 + ;; code offset: 0x496 (local.set $1 - ;; code offset: 0x498 + ;; code offset: 0x495 (i32.sub - ;; code offset: 0x494 + ;; code offset: 0x491 (local.get $1) - ;; code offset: 0x496 + ;; code offset: 0x493 (local.get $3) ) ) - ;; code offset: 0x4a0 + ;; code offset: 0x49d (local.set $5 - ;; code offset: 0x49d + ;; code offset: 0x49a (i32.load offset=20 - ;; code offset: 0x49b + ;; code offset: 0x498 (local.get $2) ) ) - ;; code offset: 0x4a4 + ;; code offset: 0x4a1 (local.set $6 - ;; code offset: 0x4a2 + ;; code offset: 0x49f (local.get $3) ) ) - ;; code offset: 0x4af + ;; code offset: 0x4ac (drop - ;; code offset: 0x4ad + ;; code offset: 0x4aa (call $9 - ;; code offset: 0x4a7 + ;; code offset: 0x4a4 (local.get $5) - ;; code offset: 0x4a9 + ;; code offset: 0x4a6 (local.get $0) - ;; code offset: 0x4ab + ;; code offset: 0x4a8 (local.get $1) ) ) - ;; code offset: 0x4ba + ;; code offset: 0x4b7 (i32.store offset=20 - ;; code offset: 0x4b0 + ;; code offset: 0x4ad (local.get $2) - ;; code offset: 0x4b9 + ;; code offset: 0x4b6 (i32.add - ;; code offset: 0x4b4 + ;; code offset: 0x4b1 (i32.load offset=20 - ;; code offset: 0x4b2 + ;; code offset: 0x4af (local.get $2) ) - ;; code offset: 0x4b7 + ;; code offset: 0x4b4 (local.get $1) ) ) - ;; code offset: 0x4c2 + ;; code offset: 0x4bf (local.set $4 - ;; code offset: 0x4c1 + ;; code offset: 0x4be (i32.add - ;; code offset: 0x4bd + ;; code offset: 0x4ba (local.get $1) - ;; code offset: 0x4bf + ;; code offset: 0x4bc (local.get $6) ) ) ) - ;; code offset: 0x4c5 + ;; code offset: 0x4c2 (local.get $4) ) (func $11 (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (result i32) (local $4 i32) (local $5 i32) - ;; code offset: 0x4d3 + ;; code offset: 0x4d0 (local.set $4 - ;; code offset: 0x4d2 + ;; code offset: 0x4cf (i32.mul - ;; code offset: 0x4ce + ;; code offset: 0x4cb (local.get $1) - ;; code offset: 0x4d0 + ;; code offset: 0x4cd (local.get $2) ) ) - ;; code offset: 0x4d5 - (block $label$1 - ;; code offset: 0x4df + ;; code offset: 0x4d2 + (block $block + ;; code offset: 0x4dc (if - ;; code offset: 0x4de + ;; code offset: 0x4db (i32.le_s - ;; code offset: 0x4d9 + ;; code offset: 0x4d6 (i32.load offset=76 - ;; code offset: 0x4d7 + ;; code offset: 0x4d4 (local.get $3) ) - ;; code offset: 0x4dc + ;; code offset: 0x4d9 (i32.const -1) ) (then - ;; code offset: 0x4e9 + ;; code offset: 0x4e6 (local.set $0 - ;; code offset: 0x4e7 + ;; code offset: 0x4e4 (call $10 - ;; code offset: 0x4e1 + ;; code offset: 0x4de (local.get $0) - ;; code offset: 0x4e3 + ;; code offset: 0x4e0 (local.get $4) - ;; code offset: 0x4e5 + ;; code offset: 0x4e2 (local.get $3) ) ) - ;; code offset: 0x4eb - (br $label$1) + ;; code offset: 0x4e8 + (br $block) ) ) - ;; code offset: 0x4f2 + ;; code offset: 0x4ef (local.set $5 - ;; code offset: 0x4f0 + ;; code offset: 0x4ed (call $15 - ;; code offset: 0x4ee + ;; code offset: 0x4eb (local.get $3) ) ) - ;; code offset: 0x4fc + ;; code offset: 0x4f9 (local.set $0 - ;; code offset: 0x4fa + ;; code offset: 0x4f7 (call $10 - ;; code offset: 0x4f4 + ;; code offset: 0x4f1 (local.get $0) - ;; code offset: 0x4f6 + ;; code offset: 0x4f3 (local.get $4) - ;; code offset: 0x4f8 + ;; code offset: 0x4f5 (local.get $3) ) ) - ;; code offset: 0x501 - (br_if $label$1 - ;; code offset: 0x500 + ;; code offset: 0x4fe + (br_if $block + ;; code offset: 0x4fd (i32.eqz - ;; code offset: 0x4fe + ;; code offset: 0x4fb (local.get $5) ) ) - ;; code offset: 0x505 + ;; code offset: 0x502 (call $16 - ;; code offset: 0x503 + ;; code offset: 0x500 (local.get $3) ) ) - ;; code offset: 0x50d + ;; code offset: 0x50a (if - ;; code offset: 0x50c + ;; code offset: 0x509 (i32.eq - ;; code offset: 0x508 + ;; code offset: 0x505 (local.get $0) - ;; code offset: 0x50a + ;; code offset: 0x507 (local.get $4) ) (then - ;; code offset: 0x516 + ;; code offset: 0x513 (return - ;; code offset: 0x515 + ;; code offset: 0x512 (select - ;; code offset: 0x50f + ;; code offset: 0x50c (local.get $2) - ;; code offset: 0x511 + ;; code offset: 0x50e (i32.const 0) - ;; code offset: 0x513 + ;; code offset: 0x510 (local.get $1) ) ) ) ) - ;; code offset: 0x51c + ;; code offset: 0x519 (i32.div_u - ;; code offset: 0x518 + ;; code offset: 0x515 (local.get $0) - ;; code offset: 0x51a + ;; code offset: 0x517 (local.get $1) ) ) (func $12 (param $0 i32) (param $1 i32) (result i32) (local $2 i32) - ;; code offset: 0x537 + ;; code offset: 0x534 (select - ;; code offset: 0x522 + ;; code offset: 0x51f (i32.const -1) - ;; code offset: 0x524 + ;; code offset: 0x521 (i32.const 0) - ;; code offset: 0x536 + ;; code offset: 0x533 (i32.ne - ;; code offset: 0x532 + ;; code offset: 0x52f (call $11 - ;; code offset: 0x526 + ;; code offset: 0x523 (local.get $0) - ;; code offset: 0x528 + ;; code offset: 0x525 (i32.const 1) - ;; code offset: 0x52e + ;; code offset: 0x52b (local.tee $2 - ;; code offset: 0x52c + ;; code offset: 0x529 (call $17 - ;; code offset: 0x52a + ;; code offset: 0x527 (local.get $0) ) ) - ;; code offset: 0x530 + ;; code offset: 0x52d (local.get $1) ) - ;; code offset: 0x534 + ;; code offset: 0x531 (local.get $2) ) ) @@ -1768,624 +1765,624 @@ file_names[ 1]: (local $2 i32) (local $3 i32) (local $4 i32) - ;; code offset: 0x549 + ;; code offset: 0x546 (global.set $global$0 - ;; code offset: 0x547 + ;; code offset: 0x544 (local.tee $3 - ;; code offset: 0x546 + ;; code offset: 0x543 (i32.sub - ;; code offset: 0x542 + ;; code offset: 0x53f (global.get $global$0) - ;; code offset: 0x544 + ;; code offset: 0x541 (i32.const 16) ) ) ) - ;; code offset: 0x54f + ;; code offset: 0x54c (i32.store8 offset=15 - ;; code offset: 0x54b + ;; code offset: 0x548 (local.get $3) - ;; code offset: 0x54d + ;; code offset: 0x54a (local.get $1) ) - ;; code offset: 0x552 - (block $label$1 - ;; code offset: 0x55c + ;; code offset: 0x54f + (block $block + ;; code offset: 0x559 (if - ;; code offset: 0x55b + ;; code offset: 0x558 (i32.eqz - ;; code offset: 0x559 + ;; code offset: 0x556 (local.tee $2 - ;; code offset: 0x556 + ;; code offset: 0x553 (i32.load offset=16 - ;; code offset: 0x554 + ;; code offset: 0x551 (local.get $0) ) ) ) (then - ;; code offset: 0x560 + ;; code offset: 0x55d (local.set $2 - ;; code offset: 0x55e + ;; code offset: 0x55b (i32.const -1) ) - ;; code offset: 0x566 - (br_if $label$1 - ;; code offset: 0x564 + ;; code offset: 0x563 + (br_if $block + ;; code offset: 0x561 (call $8 - ;; code offset: 0x562 + ;; code offset: 0x55f (local.get $0) ) ) - ;; code offset: 0x56d + ;; code offset: 0x56a (local.set $2 - ;; code offset: 0x56a + ;; code offset: 0x567 (i32.load offset=16 - ;; code offset: 0x568 + ;; code offset: 0x565 (local.get $0) ) ) ) ) - ;; code offset: 0x570 - (block $label$3 - ;; code offset: 0x57c - (br_if $label$3 - ;; code offset: 0x57b + ;; code offset: 0x56d + (block $block1 + ;; code offset: 0x579 + (br_if $block1 + ;; code offset: 0x578 (i32.ge_u - ;; code offset: 0x577 + ;; code offset: 0x574 (local.tee $4 - ;; code offset: 0x574 + ;; code offset: 0x571 (i32.load offset=20 - ;; code offset: 0x572 + ;; code offset: 0x56f (local.get $0) ) ) - ;; code offset: 0x579 + ;; code offset: 0x576 (local.get $2) ) ) - ;; code offset: 0x58c - (br_if $label$3 - ;; code offset: 0x58b + ;; code offset: 0x589 + (br_if $block1 + ;; code offset: 0x588 (i32.eq - ;; code offset: 0x584 + ;; code offset: 0x581 (local.tee $2 - ;; code offset: 0x583 + ;; code offset: 0x580 (i32.and - ;; code offset: 0x57e + ;; code offset: 0x57b (local.get $1) - ;; code offset: 0x580 + ;; code offset: 0x57d (i32.const 255) ) ) - ;; code offset: 0x588 + ;; code offset: 0x585 (i32.load8_s offset=75 - ;; code offset: 0x586 + ;; code offset: 0x583 (local.get $0) ) ) ) - ;; code offset: 0x595 + ;; code offset: 0x592 (i32.store offset=20 - ;; code offset: 0x58e + ;; code offset: 0x58b (local.get $0) - ;; code offset: 0x594 + ;; code offset: 0x591 (i32.add - ;; code offset: 0x590 + ;; code offset: 0x58d (local.get $4) - ;; code offset: 0x592 + ;; code offset: 0x58f (i32.const 1) ) ) - ;; code offset: 0x59c + ;; code offset: 0x599 (i32.store8 - ;; code offset: 0x598 + ;; code offset: 0x595 (local.get $4) - ;; code offset: 0x59a + ;; code offset: 0x597 (local.get $1) ) - ;; code offset: 0x59f - (br $label$1) + ;; code offset: 0x59c + (br $block) ) - ;; code offset: 0x5a4 + ;; code offset: 0x5a1 (local.set $2 - ;; code offset: 0x5a2 + ;; code offset: 0x59f (i32.const -1) ) - ;; code offset: 0x5ba - (br_if $label$1 - ;; code offset: 0x5b9 + ;; code offset: 0x5b7 + (br_if $block + ;; code offset: 0x5b6 (i32.ne - ;; code offset: 0x5b4 + ;; code offset: 0x5b1 (call_indirect (type $1) - ;; code offset: 0x5a6 + ;; code offset: 0x5a3 (local.get $0) - ;; code offset: 0x5ac + ;; code offset: 0x5a9 (i32.add - ;; code offset: 0x5a8 + ;; code offset: 0x5a5 (local.get $3) - ;; code offset: 0x5aa + ;; code offset: 0x5a7 (i32.const 15) ) - ;; code offset: 0x5ad + ;; code offset: 0x5aa (i32.const 1) - ;; code offset: 0x5b1 + ;; code offset: 0x5ae (i32.load offset=36 - ;; code offset: 0x5af + ;; code offset: 0x5ac (local.get $0) ) ) - ;; code offset: 0x5b7 + ;; code offset: 0x5b4 (i32.const 1) ) ) - ;; code offset: 0x5c1 + ;; code offset: 0x5be (local.set $2 - ;; code offset: 0x5be + ;; code offset: 0x5bb (i32.load8_u offset=15 - ;; code offset: 0x5bc + ;; code offset: 0x5b9 (local.get $3) ) ) ) - ;; code offset: 0x5c9 + ;; code offset: 0x5c6 (global.set $global$0 - ;; code offset: 0x5c8 + ;; code offset: 0x5c5 (i32.add - ;; code offset: 0x5c4 + ;; code offset: 0x5c1 (local.get $3) - ;; code offset: 0x5c6 + ;; code offset: 0x5c3 (i32.const 16) ) ) - ;; code offset: 0x5cb + ;; code offset: 0x5c8 (local.get $2) ) (func $14 (param $0 i32) (result i32) (local $1 i32) (local $2 i32) - ;; code offset: 0x5e2 + ;; code offset: 0x5df (if - ;; code offset: 0x5e1 + ;; code offset: 0x5de (i32.ge_s - ;; code offset: 0x5dc + ;; code offset: 0x5d9 (i32.load offset=76 - ;; code offset: 0x5da + ;; code offset: 0x5d7 (local.tee $1 - ;; code offset: 0x5d7 + ;; code offset: 0x5d4 (i32.load - ;; code offset: 0x5d4 + ;; code offset: 0x5d1 (i32.const 1040) ) ) ) - ;; code offset: 0x5df + ;; code offset: 0x5dc (i32.const 0) ) (then - ;; code offset: 0x5e8 + ;; code offset: 0x5e5 (local.set $2 - ;; code offset: 0x5e6 + ;; code offset: 0x5e3 (call $15 - ;; code offset: 0x5e4 + ;; code offset: 0x5e1 (local.get $1) ) ) ) ) - ;; code offset: 0x636 + ;; code offset: 0x633 (local.set $0 - ;; code offset: 0x5eb - (block $label$2 (result i32) - ;; code offset: 0x5fa + ;; code offset: 0x5e8 + (block $block (result i32) + ;; code offset: 0x5f7 (drop - ;; code offset: 0x5f8 - (br_if $label$2 - ;; code offset: 0x5ed + ;; code offset: 0x5f5 + (br_if $block + ;; code offset: 0x5ea (i32.const -1) - ;; code offset: 0x5f7 + ;; code offset: 0x5f4 (i32.lt_s - ;; code offset: 0x5f3 + ;; code offset: 0x5f0 (call $12 - ;; code offset: 0x5ef + ;; code offset: 0x5ec (local.get $0) - ;; code offset: 0x5f1 + ;; code offset: 0x5ee (local.get $1) ) - ;; code offset: 0x5f5 + ;; code offset: 0x5f2 (i32.const 0) ) ) ) - ;; code offset: 0x5fb - (block $label$3 - ;; code offset: 0x605 - (br_if $label$3 - ;; code offset: 0x604 + ;; code offset: 0x5f8 + (block $block1 + ;; code offset: 0x602 + (br_if $block1 + ;; code offset: 0x601 (i32.eq - ;; code offset: 0x5ff + ;; code offset: 0x5fc (i32.load8_u offset=75 - ;; code offset: 0x5fd + ;; code offset: 0x5fa (local.get $1) ) - ;; code offset: 0x602 + ;; code offset: 0x5ff (i32.const 10) ) ) - ;; code offset: 0x614 - (br_if $label$3 - ;; code offset: 0x613 + ;; code offset: 0x611 + (br_if $block1 + ;; code offset: 0x610 (i32.ge_u - ;; code offset: 0x60c + ;; code offset: 0x609 (local.tee $0 - ;; code offset: 0x609 + ;; code offset: 0x606 (i32.load offset=20 - ;; code offset: 0x607 + ;; code offset: 0x604 (local.get $1) ) ) - ;; code offset: 0x610 + ;; code offset: 0x60d (i32.load offset=16 - ;; code offset: 0x60e + ;; code offset: 0x60b (local.get $1) ) ) ) - ;; code offset: 0x61d + ;; code offset: 0x61a (i32.store offset=20 - ;; code offset: 0x616 + ;; code offset: 0x613 (local.get $1) - ;; code offset: 0x61c + ;; code offset: 0x619 (i32.add - ;; code offset: 0x618 + ;; code offset: 0x615 (local.get $0) - ;; code offset: 0x61a + ;; code offset: 0x617 (i32.const 1) ) ) - ;; code offset: 0x624 + ;; code offset: 0x621 (i32.store8 - ;; code offset: 0x620 + ;; code offset: 0x61d (local.get $0) - ;; code offset: 0x622 + ;; code offset: 0x61f (i32.const 10) ) - ;; code offset: 0x629 - (br $label$2 - ;; code offset: 0x627 + ;; code offset: 0x626 + (br $block + ;; code offset: 0x624 (i32.const 0) ) ) - ;; code offset: 0x634 + ;; code offset: 0x631 (i32.shr_s - ;; code offset: 0x630 + ;; code offset: 0x62d (call $13 - ;; code offset: 0x62c + ;; code offset: 0x629 (local.get $1) - ;; code offset: 0x62e + ;; code offset: 0x62b (i32.const 10) ) - ;; code offset: 0x632 + ;; code offset: 0x62f (i32.const 31) ) ) ) - ;; code offset: 0x63a + ;; code offset: 0x637 (if - ;; code offset: 0x638 + ;; code offset: 0x635 (local.get $2) (then - ;; code offset: 0x63e + ;; code offset: 0x63b (call $16 - ;; code offset: 0x63c + ;; code offset: 0x639 (local.get $1) ) ) ) - ;; code offset: 0x641 + ;; code offset: 0x63e (local.get $0) ) (func $15 (param $0 i32) (result i32) - ;; code offset: 0x646 + ;; code offset: 0x643 (i32.const 1) ) (func $16 (param $0 i32) - ;; code offset: 0x64b + ;; code offset: 0x648 (nop) ) (func $17 (param $0 i32) (result i32) (local $1 i32) (local $2 i32) (local $3 i32) - ;; code offset: 0x658 + ;; code offset: 0x655 (local.set $1 - ;; code offset: 0x656 + ;; code offset: 0x653 (local.get $0) ) - ;; code offset: 0x65a - (block $label$1 - ;; code offset: 0x65c - (block $label$2 - ;; code offset: 0x664 - (br_if $label$2 - ;; code offset: 0x663 + ;; code offset: 0x657 + (block $block1 + ;; code offset: 0x659 + (block $block + ;; code offset: 0x661 + (br_if $block + ;; code offset: 0x660 (i32.eqz - ;; code offset: 0x662 + ;; code offset: 0x65f (i32.and - ;; code offset: 0x65e + ;; code offset: 0x65b (local.get $0) - ;; code offset: 0x660 + ;; code offset: 0x65d (i32.const 3) ) ) ) - ;; code offset: 0x66c + ;; code offset: 0x669 (if - ;; code offset: 0x66b + ;; code offset: 0x668 (i32.eqz - ;; code offset: 0x668 + ;; code offset: 0x665 (i32.load8_u - ;; code offset: 0x666 + ;; code offset: 0x663 (local.get $0) ) ) (then - ;; code offset: 0x670 + ;; code offset: 0x66d (return - ;; code offset: 0x66e + ;; code offset: 0x66b (i32.const 0) ) ) ) - ;; code offset: 0x672 - (loop $label$4 - ;; code offset: 0x67f - (br_if $label$2 - ;; code offset: 0x67e + ;; code offset: 0x66f + (loop $label + ;; code offset: 0x67c + (br_if $block + ;; code offset: 0x67b (i32.eqz - ;; code offset: 0x67d + ;; code offset: 0x67a (i32.and - ;; code offset: 0x679 + ;; code offset: 0x676 (local.tee $1 - ;; code offset: 0x678 + ;; code offset: 0x675 (i32.add - ;; code offset: 0x674 + ;; code offset: 0x671 (local.get $1) - ;; code offset: 0x676 + ;; code offset: 0x673 (i32.const 1) ) ) - ;; code offset: 0x67b + ;; code offset: 0x678 (i32.const 3) ) ) ) - ;; code offset: 0x686 - (br_if $label$4 - ;; code offset: 0x683 + ;; code offset: 0x683 + (br_if $label + ;; code offset: 0x680 (i32.load8_u - ;; code offset: 0x681 + ;; code offset: 0x67e (local.get $1) ) ) ) - ;; code offset: 0x689 - (br $label$1) + ;; code offset: 0x686 + (br $block1) ) - ;; code offset: 0x68c - (loop $label$5 - ;; code offset: 0x695 + ;; code offset: 0x689 + (loop $label1 + ;; code offset: 0x692 (local.set $1 - ;; code offset: 0x694 + ;; code offset: 0x691 (i32.add - ;; code offset: 0x690 + ;; code offset: 0x68d (local.tee $2 - ;; code offset: 0x68e + ;; code offset: 0x68b (local.get $1) ) - ;; code offset: 0x692 + ;; code offset: 0x68f (i32.const 4) ) ) - ;; code offset: 0x6b2 - (br_if $label$5 - ;; code offset: 0x6b1 + ;; code offset: 0x6af + (br_if $label1 + ;; code offset: 0x6ae (i32.eqz - ;; code offset: 0x6b0 + ;; code offset: 0x6ad (i32.and - ;; code offset: 0x6a9 + ;; code offset: 0x6a6 (i32.and - ;; code offset: 0x6a0 + ;; code offset: 0x69d (i32.xor - ;; code offset: 0x69c + ;; code offset: 0x699 (local.tee $3 - ;; code offset: 0x699 + ;; code offset: 0x696 (i32.load - ;; code offset: 0x697 + ;; code offset: 0x694 (local.get $2) ) ) - ;; code offset: 0x69e + ;; code offset: 0x69b (i32.const -1) ) - ;; code offset: 0x6a8 + ;; code offset: 0x6a5 (i32.add - ;; code offset: 0x6a1 + ;; code offset: 0x69e (local.get $3) - ;; code offset: 0x6a3 + ;; code offset: 0x6a0 (i32.const -16843009) ) ) - ;; code offset: 0x6aa + ;; code offset: 0x6a7 (i32.const -2139062144) ) ) ) ) - ;; code offset: 0x6bc + ;; code offset: 0x6b9 (if - ;; code offset: 0x6bb + ;; code offset: 0x6b8 (i32.eqz - ;; code offset: 0x6ba + ;; code offset: 0x6b7 (i32.and - ;; code offset: 0x6b5 + ;; code offset: 0x6b2 (local.get $3) - ;; code offset: 0x6b7 + ;; code offset: 0x6b4 (i32.const 255) ) ) (then - ;; code offset: 0x6c3 + ;; code offset: 0x6c0 (return - ;; code offset: 0x6c2 + ;; code offset: 0x6bf (i32.sub - ;; code offset: 0x6be + ;; code offset: 0x6bb (local.get $2) - ;; code offset: 0x6c0 + ;; code offset: 0x6bd (local.get $0) ) ) ) ) - ;; code offset: 0x6c5 - (loop $label$7 - ;; code offset: 0x6cc + ;; code offset: 0x6c2 + (loop $label2 + ;; code offset: 0x6c9 (local.set $3 - ;; code offset: 0x6c9 + ;; code offset: 0x6c6 (i32.load8_u offset=1 - ;; code offset: 0x6c7 + ;; code offset: 0x6c4 (local.get $2) ) ) - ;; code offset: 0x6d5 + ;; code offset: 0x6d2 (local.set $2 - ;; code offset: 0x6d3 + ;; code offset: 0x6d0 (local.tee $1 - ;; code offset: 0x6d2 + ;; code offset: 0x6cf (i32.add - ;; code offset: 0x6ce + ;; code offset: 0x6cb (local.get $2) - ;; code offset: 0x6d0 + ;; code offset: 0x6cd (i32.const 1) ) ) ) - ;; code offset: 0x6d9 - (br_if $label$7 - ;; code offset: 0x6d7 + ;; code offset: 0x6d6 + (br_if $label2 + ;; code offset: 0x6d4 (local.get $3) ) ) ) - ;; code offset: 0x6e1 + ;; code offset: 0x6de (i32.sub - ;; code offset: 0x6dd + ;; code offset: 0x6da (local.get $1) - ;; code offset: 0x6df + ;; code offset: 0x6dc (local.get $0) ) ) (func $18 (result i32) - ;; code offset: 0x6e5 + ;; code offset: 0x6e2 (global.get $global$0) ) (func $19 (param $0 i32) - ;; code offset: 0x6ec + ;; code offset: 0x6e9 (global.set $global$0 - ;; code offset: 0x6ea + ;; code offset: 0x6e7 (local.get $0) ) ) (func $20 (param $0 i32) (result i32) (local $1 i32) - ;; code offset: 0x6fd + ;; code offset: 0x6fa (global.set $global$0 - ;; code offset: 0x6fb + ;; code offset: 0x6f8 (local.tee $1 - ;; code offset: 0x6fa + ;; code offset: 0x6f7 (i32.and - ;; code offset: 0x6f7 + ;; code offset: 0x6f4 (i32.sub - ;; code offset: 0x6f3 + ;; code offset: 0x6f0 (global.get $global$0) - ;; code offset: 0x6f5 + ;; code offset: 0x6f2 (local.get $0) ) - ;; code offset: 0x6f8 + ;; code offset: 0x6f5 (i32.const -16) ) ) ) - ;; code offset: 0x6ff + ;; code offset: 0x6fc (local.get $1) ) (func $21 (param $0 i32) (param $1 i32) (param $2 i64) (param $3 i32) (result i64) - ;; code offset: 0x70c + ;; code offset: 0x709 (call_indirect (type $6) - ;; code offset: 0x704 + ;; code offset: 0x701 (local.get $1) - ;; code offset: 0x706 + ;; code offset: 0x703 (local.get $2) - ;; code offset: 0x708 + ;; code offset: 0x705 (local.get $3) - ;; code offset: 0x70a + ;; code offset: 0x707 (local.get $0) ) ) (func $22 (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (result i32) (local $5 i64) - ;; code offset: 0x72c + ;; code offset: 0x729 (call $fimport$2 - ;; code offset: 0x72b + ;; code offset: 0x728 (i32.wrap_i64 - ;; code offset: 0x72a + ;; code offset: 0x727 (i64.shr_u - ;; code offset: 0x726 + ;; code offset: 0x723 (local.tee $5 - ;; code offset: 0x724 + ;; code offset: 0x721 (call $21 - ;; code offset: 0x714 + ;; code offset: 0x711 (local.get $0) - ;; code offset: 0x716 + ;; code offset: 0x713 (local.get $1) - ;; code offset: 0x721 + ;; code offset: 0x71e (i64.or - ;; code offset: 0x71a + ;; code offset: 0x717 (i64.extend_i32_u - ;; code offset: 0x718 + ;; code offset: 0x715 (local.get $2) ) - ;; code offset: 0x720 + ;; code offset: 0x71d (i64.shl - ;; code offset: 0x71d + ;; code offset: 0x71a (i64.extend_i32_u - ;; code offset: 0x71b + ;; code offset: 0x718 (local.get $3) ) - ;; code offset: 0x71e + ;; code offset: 0x71b (i64.const 32) ) ) - ;; code offset: 0x722 + ;; code offset: 0x71f (local.get $4) ) ) - ;; code offset: 0x728 + ;; code offset: 0x725 (i64.const 32) ) ) ) - ;; code offset: 0x730 + ;; code offset: 0x72d (i32.wrap_i64 - ;; code offset: 0x72e + ;; code offset: 0x72b (local.get $5) ) ) (func $23 (param $0 i32) (result i32) - ;; code offset: 0x736 + ;; code offset: 0x733 (memory.grow - ;; code offset: 0x734 + ;; code offset: 0x731 (local.get $0) ) ) diff --git a/test/stacky.wasm.fromBinary b/test/stacky.wasm.fromBinary index 9891e10716c..d7c85091e1d 100644 --- a/test/stacky.wasm.fromBinary +++ b/test/stacky.wasm.fromBinary @@ -3,16 +3,16 @@ (memory $0 256 256) (export "add" (func $0)) (func $0 (param $0 i32) (param $1 i32) (result i32) - (local $2 i32) + (local $scratch i32) (i32.add (block (result i32) - (local.set $2 + (local.set $scratch (local.get $0) ) (local.set $0 (i32.const 100) ) - (local.get $2) + (local.get $scratch) ) (local.get $1) ) diff --git a/test/try-delegate.wasm.fromBinary b/test/try-delegate.wasm.fromBinary index 07aee56113c..a3e799627d6 100644 --- a/test/try-delegate.wasm.fromBinary +++ b/test/try-delegate.wasm.fromBinary @@ -2,14 +2,12 @@ (type $0 (func)) (tag $tag$0) (func $0 - (try $label$6 + (try $label (do - (block $label$1 - (try $label$4 - (do - ) - (delegate $label$6) + (try + (do ) + (delegate $label) ) ) (catch $tag$0 @@ -17,21 +15,19 @@ ) ) (func $1 - (try $label$9 + (try $label (do - (block $label$1 - (try $label$7 - (do + (try + (do + ) + (catch $tag$0 + (drop + (i32.const 0) ) - (catch $tag$0 - (drop - (i32.const 0) - ) - (try $label$6 - (do - ) - (delegate $label$9) + (try + (do ) + (delegate $label) ) ) ) diff --git a/test/unreachable-pops.wasm.fromBinary b/test/unreachable-pops.wasm.fromBinary index 08af405f262..d4ed2ae703c 100644 --- a/test/unreachable-pops.wasm.fromBinary +++ b/test/unreachable-pops.wasm.fromBinary @@ -1,7 +1,8 @@ (module (type $0 (func (result i32))) (func $0 (result i32) - (block $label$1 (result i32) + (i32.add + (unreachable) (unreachable) ) ) From b1c5a007f3986c11916e8ac4a84c41c01d5e04bb Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 26 Nov 2024 22:41:11 -0800 Subject: [PATCH 169/622] [NFC] Rename {F32,F64}NearestInt to {F32,F64}Nearest (#7089) Rename the opcode values in wasm-binary.h to better match the names of the corresponding instructions. This also makes these names match the scheme used by the rest of the basic unary operations, allowing for more macro use in the binary reader. --- src/wasm-binary.h | 4 ++-- src/wasm/wasm-binary.cpp | 6 +----- src/wasm/wasm-stack.cpp | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 7d928c3e6eb..b9cbba136df 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -563,7 +563,7 @@ enum ASTNodes { F32Ceil = 0x8d, F32Floor = 0x8e, F32Trunc = 0x8f, - F32NearestInt = 0x90, + F32Nearest = 0x90, F32Sqrt = 0x91, F32Add = 0x92, F32Sub = 0x93, @@ -578,7 +578,7 @@ enum ASTNodes { F64Ceil = 0x9b, F64Floor = 0x9c, F64Trunc = 0x9d, - F64NearestInt = 0x9e, + F64Nearest = 0x9e, F64Sqrt = 0x9f, F64Add = 0xa0, F64Sub = 0xa1, diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 7ebb401c1f2..82ac422eaa4 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3097,11 +3097,7 @@ Result<> WasmBinaryReader::readInst() { UNARY_FLOAT(Abs); UNARY_FLOAT(Ceil); UNARY_FLOAT(Floor); - // UNARY_FLOAT(NearestInt); - case BinaryConsts::F32NearestInt: - return builder.makeUnary(NearestFloat32); - case BinaryConsts::F64NearestInt: - return builder.makeUnary(NearestFloat64); + UNARY_FLOAT(Nearest); UNARY_FLOAT(Sqrt); case BinaryConsts::F32UConvertI32: diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 7194229fea0..f86fb58b948 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -902,7 +902,7 @@ void BinaryInstWriter::visitUnary(Unary* curr) { o << int8_t(BinaryConsts::F32Trunc); break; case NearestFloat32: - o << int8_t(BinaryConsts::F32NearestInt); + o << int8_t(BinaryConsts::F32Nearest); break; case SqrtFloat32: o << int8_t(BinaryConsts::F32Sqrt); @@ -923,7 +923,7 @@ void BinaryInstWriter::visitUnary(Unary* curr) { o << int8_t(BinaryConsts::F64Trunc); break; case NearestFloat64: - o << int8_t(BinaryConsts::F64NearestInt); + o << int8_t(BinaryConsts::F64Nearest); break; case SqrtFloat64: o << int8_t(BinaryConsts::F64Sqrt); From 31c988b30556ef000bd2212754d7fc5beebf08d2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 2 Dec 2024 12:20:55 -0800 Subject: [PATCH 170/622] [GC] Fix trapping on array.new_data of dropped segments of offset > 0 (#7124) Even if the size is 0, if the offset is > 0 then we should trap. --- src/wasm-interpreter.h | 15 ++++++++++++--- test/lit/exec/array.wast | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 81531e27c13..937248d811c 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4022,16 +4022,25 @@ class ModuleRunnerBase : public ExpressionRunner { const auto& seg = *wasm.getDataSegment(curr->segment); auto elemBytes = element.getByteSize(); - auto end = offset + size * elemBytes; - if ((size != 0ull && droppedDataSegments.count(curr->segment)) || - end > seg.data.size()) { + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + + uint64_t end; + if (std::ckd_add(&end, offset, size * elemBytes) || end > seg.data.size()) { trap("out of bounds segment access in array.new_data"); } + if (droppedDataSegments.count(curr->segment) && end > 0) { + trap("dropped segment access in array.new_data"); + } contents.reserve(size); for (Index i = offset; i < end; i += elemBytes) { auto addr = (void*)&seg.data[i]; contents.push_back(this->makeFromMemory(addr, element)); } + +#pragma GCC diagnostic pop + return self()->makeGCData(std::move(contents), curr->type); } Flow visitArrayNewElem(ArrayNewElem* curr) { diff --git a/test/lit/exec/array.wast b/test/lit/exec/array.wast index ff10c555ce8..d70af1ddc99 100644 --- a/test/lit/exec/array.wast +++ b/test/lit/exec/array.wast @@ -13,6 +13,8 @@ (elem $passive $func) + (data $data "a") + ;; CHECK: [fuzz-exec] calling func ;; CHECK-NEXT: [fuzz-exec] note result: func => 1 (func $func (export "func") (result i32) @@ -98,6 +100,21 @@ (i32.const 0) ) ) + + ;; CHECK: [fuzz-exec] calling drop_array.new_data + ;; CHECK-NEXT: [trap dropped segment access in array.new_data] + (func $drop_array.new_data (export "drop_array.new_data") + ;; Dropping the data segment causes the next instruction to trap, even though + ;; the size there is 0, because the offset is > 0. + (data.drop $data) + (drop + (array.new_data $array $data + (i32.const 1) + (i32.const 0) + ) + ) + ) + ) ;; CHECK: [fuzz-exec] calling func ;; CHECK-NEXT: [fuzz-exec] note result: func => 1 @@ -115,6 +132,10 @@ ;; CHECK: [fuzz-exec] calling init_active_in_bounds ;; CHECK: [fuzz-exec] calling init_passive + +;; CHECK: [fuzz-exec] calling drop_array.new_data +;; CHECK-NEXT: [trap dropped segment access in array.new_data] +;; CHECK-NEXT: [fuzz-exec] comparing drop_array.new_data ;; CHECK-NEXT: [fuzz-exec] comparing func ;; CHECK-NEXT: [fuzz-exec] comparing init_active ;; CHECK-NEXT: [fuzz-exec] comparing init_active_in_bounds From 74782d217ed15dd73b58b1636c563aa51334a576 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 2 Dec 2024 15:18:57 -0800 Subject: [PATCH 171/622] Do not sink blocks into ifs with unreachable conditions (#7129) RemoveUnusedBrs sinks blocks into If arms when those arms contain branches to the blocks and the other arm and condition do not. Now that we type Ifs with unreachable conditions as unreachable, it is possible for the If arms to have a different type than the block that would be sunk, so sinking the block would produce invalid IR. Fix the problem by never sinking blocks into Ifs with unreachable conditions. Fixes #7128. --- src/passes/RemoveUnusedBrs.cpp | 6 ++++ test/lit/passes/remove-unused-brs.wast | 30 +++++++++++++++++++ .../remove-unused-brs_enable-multivalue.txt | 26 ++++++++-------- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 44549f68dff..32f7bd48a20 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -757,6 +757,12 @@ struct RemoveUnusedBrs : public WalkerPass> { replaceCurrent(loop); worked = true; } else if (auto* iff = curr->list[0]->dynCast()) { + if (iff->condition->type == Type::unreachable) { + // The block result type may not be compatible with the arm result + // types since the unreachable If can satisfy any type of block. + // Just leave this for DCE. + return; + } // The label can't be used in the condition. if (BranchUtils::BranchSeeker::count(iff->condition, curr->name) == 0) { diff --git a/test/lit/passes/remove-unused-brs.wast b/test/lit/passes/remove-unused-brs.wast index 3380db6b70b..2019d37a429 100644 --- a/test/lit/passes/remove-unused-brs.wast +++ b/test/lit/passes/remove-unused-brs.wast @@ -594,4 +594,34 @@ ) ) ) + + ;; CHECK: (func $unreachable-if (type $1) + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (br $block) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-if + ;; Regression test for a problem where blocks were sunk into ifs with + ;; unreachable conditions, causing validation errors when the block type was + ;; incompatible with the if type. + (block $block + (if (result i32) + (unreachable) + (then + (i32.const 0) + ) + (else + (br $block) + ) + ) + ) + ) ) diff --git a/test/passes/remove-unused-brs_enable-multivalue.txt b/test/passes/remove-unused-brs_enable-multivalue.txt index 54780f0d9ee..a20f2905bf0 100644 --- a/test/passes/remove-unused-brs_enable-multivalue.txt +++ b/test/passes/remove-unused-brs_enable-multivalue.txt @@ -2389,12 +2389,12 @@ (loop $label$1 (br_if $label$1 (block $label$2 - (if - (block $label$4 - (unreachable) - ) - (then - (block $label$3 + (block $label$3 + (if + (block $label$4 + (unreachable) + ) + (then (br $label$3) ) ) @@ -2405,15 +2405,15 @@ ) ) (func $if-arm-unreachable - (if - (unreachable) - (then - (block $label$1 + (block $label$1 + (if + (unreachable) + (then (nop) ) - ) - (else - (unreachable) + (else + (unreachable) + ) ) ) ) From f331120e4b942a795d4a6b6d0d5a3d781c1e6a4c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 2 Dec 2024 16:03:21 -0800 Subject: [PATCH 172/622] Fixup block-nested pops even when EH is not enabled (#7130) While parsing a binary file, there may be pops that need to be fixed up even if EH is not (yet) enabled because the target features section has not been parsed yet. Previously `EHUtils::handleBlockNestedPops` did not do anything if EH was not enabled, so the binary parser would fail to fix up pops in that case. Add an optional parameter to override this behavior so the parser can fix up pops unconditionally. Fixes #7127. --- src/ir/eh-utils.cpp | 5 +++-- src/ir/eh-utils.h | 10 ++++++++-- src/wasm/wasm-ir-builder.cpp | 5 ++++- test/lit/binary/stacky-eh-legacy.test | 4 ++-- test/lit/binary/stacky-eh-legacy.test.wasm | Bin 47 -> 86 bytes 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/ir/eh-utils.cpp b/src/ir/eh-utils.cpp index 70b5452a674..6d65de4ad1c 100644 --- a/src/ir/eh-utils.cpp +++ b/src/ir/eh-utils.cpp @@ -148,8 +148,9 @@ void handleBlockNestedPop(Try* try_, Function* func, Module& wasm) { } } -void handleBlockNestedPops(Function* func, Module& wasm) { - if (!wasm.features.hasExceptionHandling()) { +void handleBlockNestedPops(Function* func, Module& wasm, FeaturePolicy policy) { + if (policy == FeaturePolicy::SkipIfNoEH && + !wasm.features.hasExceptionHandling()) { return; } FindAll trys(func->body); diff --git a/src/ir/eh-utils.h b/src/ir/eh-utils.h index 79ccbc50705..ee5ad661d7b 100644 --- a/src/ir/eh-utils.h +++ b/src/ir/eh-utils.h @@ -38,8 +38,14 @@ bool containsValidDanglingPop(Expression* catchBody); // '(local.get $new)' where the 'pop' used to be. void handleBlockNestedPop(Try* try_, Function* func, Module& wasm); -// Calls handleBlockNestedPop for each 'Try's in a given function. -void handleBlockNestedPops(Function* func, Module& wasm); +enum class FeaturePolicy { SkipIfNoEH, RunIfNoEH }; + +// Calls handleBlockNestedPop for each 'Try's in a given function. By default, +// does no work if EH is not enabled, but this can be overridden with the +// RunIfNoEH policy. +void handleBlockNestedPops(Function* func, + Module& wasm, + FeaturePolicy policy = FeaturePolicy::SkipIfNoEH); // Given a catch body, find the pop corresponding to the catch. There might be // pops nested inside a try inside this catch, and we must ignore them, like diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index fcb1fc48d9a..a51337cda0d 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1018,7 +1018,10 @@ Result<> IRBuilder::visitEnd() { func->body = maybeWrapForLabel(*expr); labelDepths.clear(); if (scope.needsPopFixup()) { - EHUtils::handleBlockNestedPops(func, wasm); + // We may be in the binary parser, where pops need to be fixed up before + // we know that EH will be enabled. + EHUtils::handleBlockNestedPops( + func, wasm, EHUtils::FeaturePolicy::RunIfNoEH); } this->func = nullptr; blockHint = 0; diff --git a/test/lit/binary/stacky-eh-legacy.test b/test/lit/binary/stacky-eh-legacy.test index c77435f0b10..792c436d527 100644 --- a/test/lit/binary/stacky-eh-legacy.test +++ b/test/lit/binary/stacky-eh-legacy.test @@ -35,7 +35,7 @@ ;; The fixup will hoist the 'pop' and create another local to store it right ;; after 'catch'. -;; RUN: wasm-opt -all %s.wasm -S -o - | filecheck %s +;; RUN: wasm-opt %s.wasm -S -o - | filecheck %s ;; CHECK: (type $0 (func (param i32))) @@ -43,7 +43,7 @@ ;; CHECK: (tag $tag$0 (param i32)) -;; CHECK: (func $0 (type $1) +;; CHECK: (func $0 ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) diff --git a/test/lit/binary/stacky-eh-legacy.test.wasm b/test/lit/binary/stacky-eh-legacy.test.wasm index 992273cae647284c69847afaa8494c180d6fa19c..8da1d0238ed95419dbb00a29592d135191df7c35 100644 GIT binary patch delta 44 zcmdN7o1m}Gpvqs8Sd^Yx5}%fuSW;S)TFj^|lv Date: Tue, 3 Dec 2024 11:20:36 -0800 Subject: [PATCH 173/622] [NFC] Encapsulate source map reader state (#7132) Move all state relevant to reading source maps out of WasmBinaryReader and into a new utility, SourceMapReader. This is a prerequisite for parallelizing the parsing of function bodies, since the source map reader state is different at the beginning of each function. Also take the opportunity to simplify the way we read source maps, for example by deferring the reading of anything but the position of a debug location until it will be used and by using `std::optional` instead of singleton `std::set`s to store function prologue and epilogue debug locations. --- src/ir/module-utils.cpp | 36 +-- src/parsing.h | 9 - src/passes/DebugLocationPropagation.cpp | 4 +- src/passes/Print.cpp | 8 +- src/source-map.h | 101 +++++++ src/tools/wasm-dis.cpp | 1 + src/wasm-binary.h | 49 +--- src/wasm-stack.h | 8 +- src/wasm.h | 4 +- src/wasm/CMakeLists.txt | 1 + src/wasm/parsing.cpp | 12 - src/wasm/source-map.cpp | 212 ++++++++++++++ src/wasm/wasm-binary.cpp | 259 +----------------- src/wasm/wasm-io.cpp | 19 +- src/wasm/wasm-ir-builder.cpp | 4 +- src/wasm/wasm-stack.cpp | 8 +- src/wasm/wasm.cpp | 4 +- test/gtest/CMakeLists.txt | 2 +- .../{binary-reader.cpp => source-map.cpp} | 36 +-- 19 files changed, 383 insertions(+), 394 deletions(-) create mode 100644 src/source-map.h create mode 100644 src/wasm/source-map.cpp rename test/gtest/{binary-reader.cpp => source-map.cpp} (58%) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 5ebd6edefe5..431e33cc6e7 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -26,32 +26,20 @@ namespace wasm::ModuleUtils { // Update the file name indices when moving a set of debug locations from one // module to another. -static void updateLocationSet(std::set& locations, - std::vector& fileIndexMap) { - std::set updatedLocations; - - for (auto iter : locations) { - iter.fileIndex = fileIndexMap[iter.fileIndex]; - updatedLocations.insert(iter); +static void updateLocation(std::optional& location, + std::vector& fileIndexMap) { + if (location) { + location->fileIndex = fileIndexMap[location->fileIndex]; } - locations.clear(); - std::swap(locations, updatedLocations); } // Update the symbol name indices when moving a set of debug locations from one // module to another. -static void updateSymbolSet(std::set& locations, - std::vector& symbolIndexMap) { - std::set updatedLocations; - - for (auto iter : locations) { - if (iter.symbolNameIndex) { - iter.symbolNameIndex = symbolIndexMap[*iter.symbolNameIndex]; - } - updatedLocations.insert(iter); +static void updateSymbol(std::optional& location, + std::vector& symbolIndexMap) { + if (location && location->symbolNameIndex) { + location->symbolNameIndex = symbolIndexMap[*location->symbolNameIndex]; } - locations.clear(); - std::swap(locations, updatedLocations); } // Copies a function into a module. If newName is provided it is used as the @@ -94,8 +82,8 @@ copyFunctionWithoutAdd(Function* func, iter.second->fileIndex = (*fileIndexMap)[iter.second->fileIndex]; } } - updateLocationSet(ret->prologLocation, *fileIndexMap); - updateLocationSet(ret->epilogLocation, *fileIndexMap); + updateLocation(ret->prologLocation, *fileIndexMap); + updateLocation(ret->epilogLocation, *fileIndexMap); } if (symbolNameIndexMap) { for (auto& iter : ret->debugLocations) { @@ -105,8 +93,8 @@ copyFunctionWithoutAdd(Function* func, (*symbolNameIndexMap)[*(iter.second->symbolNameIndex)]; } } - updateSymbolSet(ret->prologLocation, *symbolNameIndexMap); - updateSymbolSet(ret->epilogLocation, *symbolNameIndexMap); + updateSymbol(ret->prologLocation, *symbolNameIndexMap); + updateSymbol(ret->epilogLocation, *symbolNameIndexMap); } } ret->module = func->module; diff --git a/src/parsing.h b/src/parsing.h index d59b3bd7cb8..80d4db9fb27 100644 --- a/src/parsing.h +++ b/src/parsing.h @@ -43,15 +43,6 @@ struct ParseException { void dump(std::ostream& o) const; }; -struct MapParseException { - std::string text; - - MapParseException() : text("unknown parse error") {} - MapParseException(std::string text) : text(text) {} - - void dump(std::ostream& o) const; -}; - // Helper for parsers that may not have unique label names. This transforms // the names into unique ones, as required by Binaryen IR. struct UniqueNameMapper { diff --git a/src/passes/DebugLocationPropagation.cpp b/src/passes/DebugLocationPropagation.cpp index e2d1ac50fe9..b2eb8fa8303 100644 --- a/src/passes/DebugLocationPropagation.cpp +++ b/src/passes/DebugLocationPropagation.cpp @@ -64,10 +64,10 @@ struct DebugLocationPropagation if (auto it = locs.find(previous); it != locs.end()) { locs[curr] = it->second; } - } else if (self->getFunction()->prologLocation.size()) { + } else if (self->getFunction()->prologLocation) { // Instructions may inherit their locations from the function // prolog. - locs[curr] = *self->getFunction()->prologLocation.begin(); + locs[curr] = *self->getFunction()->prologLocation; } } expressionStack.push_back(curr); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 4ca40f35a30..5f2d1cc3d73 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3060,8 +3060,8 @@ void PrintSExpression::visitDefinedFunction(Function* curr) { currFunction = curr; lastPrintedLocation = std::nullopt; lastPrintIndent = 0; - if (currFunction->prologLocation.size()) { - printDebugLocation(*currFunction->prologLocation.begin()); + if (currFunction->prologLocation) { + printDebugLocation(*currFunction->prologLocation); } handleSignature(curr, true); incIndent(); @@ -3095,14 +3095,14 @@ void PrintSExpression::visitDefinedFunction(Function* curr) { } assert(controlFlowDepth == 0); } - if (currFunction->epilogLocation.size()) { + if (currFunction->epilogLocation) { // Print last debug location: mix of decIndent and printDebugLocation // logic. doIndent(o, indent); if (!minify) { indent--; } - printDebugLocation(*currFunction->epilogLocation.begin()); + printDebugLocation(*currFunction->epilogLocation); o << ')'; } else { decIndent(); diff --git a/src/source-map.h b/src/source-map.h new file mode 100644 index 00000000000..d8c50b5e1fa --- /dev/null +++ b/src/source-map.h @@ -0,0 +1,101 @@ +/* + * Copyright 2024 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_source_map_h +#define wasm_source_map_h + +#include +#include + +#include "wasm.h" + +namespace wasm { + +struct MapParseException { + std::string text; + + MapParseException(std::string text) : text(text) {} + void dump(std::ostream& o) const; +}; + +class SourceMapReader { + const std::vector& buffer; + + // Current position in the source map buffer. + size_t pos = 0; + + // The location in the binary the next debug location will correspond to. 0 + // iff there are no more debug locations. + size_t location = 0; + + // The file index, line, column, and symbol index the next debug location will + // be offset from. + uint32_t file = 0; + uint32_t line = 1; + uint32_t col = 0; + uint32_t symbol = 0; + + // Whether the last read record had position and symbol information. + bool hasInfo = false; + bool hasSymbol = false; + +public: + SourceMapReader(const std::vector& buffer) : buffer(buffer) {} + + void readHeader(Module& wasm); + + std::optional + readDebugLocationAt(size_t currLocation); + + // Do not reuse debug info across function boundaries. + void finishFunction() { hasInfo = false; } + +private: + char peek() { + if (pos >= buffer.size()) { + throw MapParseException("unexpected end of source map"); + } + return buffer[pos]; + } + + char get() { + char c = peek(); + ++pos; + return c; + } + + bool maybeGet(char c) { + if (pos < buffer.size() && peek() == c) { + ++pos; + return true; + } + return false; + } + + void expect(char c) { + using namespace std::string_literals; + char got = get(); + if (got != c) { + throw MapParseException("expected '"s + c + "', got '" + got + "'"); + } + } + + int32_t readBase64VLQ(); +}; + +} // namespace wasm + +#endif // wasm_source_map_h diff --git a/src/tools/wasm-dis.cpp b/src/tools/wasm-dis.cpp index 1603736cea9..cc377e4e2a1 100644 --- a/src/tools/wasm-dis.cpp +++ b/src/tools/wasm-dis.cpp @@ -18,6 +18,7 @@ // wasm2asm console tool // +#include "source-map.h" #include "support/colors.h" #include "support/file.h" #include "wasm-io.h" diff --git a/src/wasm-binary.h b/src/wasm-binary.h index b9cbba136df..7c32e216957 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -28,6 +28,7 @@ #include "ir/import-utils.h" #include "ir/module-utils.h" #include "parsing.h" +#include "source-map.h" #include "wasm-builder.h" #include "wasm-ir-builder.h" #include "wasm-traversal.h" @@ -1403,41 +1404,13 @@ class WasmBinaryWriter { void prepare(); }; +extern std::vector defaultEmptySourceMap; + class WasmBinaryReader { Module& wasm; MixedArena& allocator; const std::vector& input; - // Source map debugging support. - - std::istream* sourceMap; - - // The binary position that the next debug location refers to. That is, this - // is the first item in a source map entry that we have read (the "column", in - // source map terms, which for wasm means the offset in the binary). We have - // read this entry, but have not used it yet (we use it when we read the - // expression at this binary offset). - // - // This is set to 0 as an invalid value if we reach the end of the source map - // and there is nothing left to read. - size_t nextDebugPos; - - // The debug location (file:line:col) corresponding to |nextDebugPos|. That - // is, this is the next 3 fields in a source map entry that we have read, but - // not used yet. - // - // If that location has no debug info (it lacks those 3 fields), then this - // contains the info from the previous one, because in a source map, these - // fields are relative to their last appearance, so we cannot forget them (we - // can't just do something like std::optional or such); for - // example, if we have line number 100, then no debug info, and then line - // number 500, then when we get to 500 we will see "+400" which is relative to - // the last existing line number (we "skip" over a place without debug info). - Function::DebugLocation nextDebugLocation; - - // Whether debug info is present on |nextDebugPos| (see comment there). - bool nextDebugLocationHasDebugInfo; - // Settings. bool debugInfo = true; @@ -1448,17 +1421,20 @@ class WasmBinaryReader { size_t pos = 0; Index startIndex = -1; - std::set debugLocation; size_t codeSectionLocation; std::unordered_set seenSections; + IRBuilder builder; + SourceMapReader sourceMapReader; + // All types defined in the type section std::vector types; public: WasmBinaryReader(Module& wasm, FeatureSet features, - const std::vector& input); + const std::vector& input, + const std::vector& sourceMap = defaultEmptySourceMap); void setDebugInfo(bool value) { debugInfo = value; } void setDWARF(bool value) { DWARF = value; } @@ -1584,8 +1560,6 @@ class WasmBinaryReader { Expression* readExpression(); void readGlobals(); - IRBuilder builder; - // validations that cannot be performed on the Module void validateBinary(); @@ -1607,13 +1581,6 @@ class WasmBinaryReader { void readDylink(size_t); void readDylink0(size_t); - // Debug information reading helpers - void setDebugLocations(std::istream* sourceMap_) { sourceMap = sourceMap_; } - std::unordered_map debugInfoFileIndices; - std::unordered_map debugInfoSymbolNameIndices; - void readNextDebugLocation(); - void readSourceMapHeader(); - Index readMemoryAccess(Address& alignment, Address& offset); std::tuple getMemarg(); diff --git a/src/wasm-stack.h b/src/wasm-stack.h index 85003f9ea0f..f48233333da 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -456,8 +456,8 @@ class BinaryenIRToBinaryWriter void emit(Expression* curr) { writer.visit(curr); } void emitHeader() { - if (func->prologLocation.size()) { - parent.writeDebugLocation(*func->prologLocation.begin()); + if (func->prologLocation) { + parent.writeDebugLocation(*func->prologLocation); } writer.mapLocalsAndEmitHeader(); } @@ -469,8 +469,8 @@ class BinaryenIRToBinaryWriter void emitFunctionEnd() { // Indicate the debug location corresponding to the end opcode // that terminates the function code. - if (func->epilogLocation.size()) { - parent.writeDebugLocation(*func->epilogLocation.begin()); + if (func->epilogLocation) { + parent.writeDebugLocation(*func->epilogLocation); } else { // The end opcode has no debug location. parent.writeNoDebugLocation(); diff --git a/src/wasm.h b/src/wasm.h index a5fc070e611..b3ae82bcfa9 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2095,8 +2095,8 @@ class Function : public Importable { // One can explicitly set the debug location of an expression to // nullopt to stop the propagation of debug locations. std::unordered_map> debugLocations; - std::set prologLocation; - std::set epilogLocation; + std::optional prologLocation; + std::optional epilogLocation; // General debugging info support: track instructions and the function itself. std::unordered_map expressionLocations; diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt index 7a7b26eaddc..64c88c99723 100644 --- a/src/wasm/CMakeLists.txt +++ b/src/wasm/CMakeLists.txt @@ -2,6 +2,7 @@ file(GLOB wasm_HEADERS ../*.h) set(wasm_SOURCES literal.cpp parsing.cpp + source-map.cpp wasm.cpp wasm-binary.cpp wasm-debug.cpp diff --git a/src/wasm/parsing.cpp b/src/wasm/parsing.cpp index 1606a2dd1ff..5d34da78e8a 100644 --- a/src/wasm/parsing.cpp +++ b/src/wasm/parsing.cpp @@ -36,18 +36,6 @@ void ParseException::dump(std::ostream& o) const { Colors::normal(o); } -void MapParseException::dump(std::ostream& o) const { - Colors::magenta(o); - o << "["; - Colors::red(o); - o << "map parse exception: "; - Colors::green(o); - o << text; - Colors::magenta(o); - o << "]"; - Colors::normal(o); -} - // UniqueNameMapper Name UniqueNameMapper::getPrefixedName(Name prefix) { diff --git a/src/wasm/source-map.cpp b/src/wasm/source-map.cpp new file mode 100644 index 00000000000..7ad26e89861 --- /dev/null +++ b/src/wasm/source-map.cpp @@ -0,0 +1,212 @@ +/* + * Copyright 2024 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source-map.h" +#include "support/colors.h" + +namespace wasm { + +std::vector defaultEmptySourceMap; + +void MapParseException::dump(std::ostream& o) const { + Colors::magenta(o); + o << "["; + Colors::red(o); + o << "map parse exception: "; + Colors::green(o); + o << text; + Colors::magenta(o); + o << "]"; + Colors::normal(o); +} + +void SourceMapReader::readHeader(Module& wasm) { + assert(pos == 0); + if (buffer.empty()) { + return; + } + + auto skipWhitespace = [&]() { + while (pos < buffer.size() && (buffer[pos] == ' ' || buffer[pos] == '\n')) { + ++pos; + } + }; + + auto findField = [&](const char* name) { + bool matching = false; + size_t len = strlen(name); + size_t index = 0; + while (1) { + char ch = get(); + if (ch == '\"') { + if (matching) { + if (index == len) { + // We matched a terminating quote. + break; + } + matching = false; + } else { + // Beginning of a new potential match. + matching = true; + index = 0; + } + } else if (matching && name[index] == ch) { + ++index; + } else if (matching) { + matching = false; + } + } + skipWhitespace(); + expect(':'); + skipWhitespace(); + return true; + }; + + auto readString = [&](std::string& str) { + std::vector vec; + skipWhitespace(); + expect('\"'); + while (1) { + if (maybeGet('\"')) { + break; + } + vec.push_back(get()); + } + skipWhitespace(); + str = std::string(vec.begin(), vec.end()); + }; + + if (!findField("sources")) { + throw MapParseException("cannot find the 'sources' field in map"); + } + + skipWhitespace(); + expect('['); + if (!maybeGet(']')) { + do { + std::string file; + readString(file); + wasm.debugInfoFileNames.push_back(file); + } while (maybeGet(',')); + expect(']'); + } + + if (findField("names")) { + skipWhitespace(); + expect('['); + if (!maybeGet(']')) { + do { + std::string symbol; + readString(symbol); + wasm.debugInfoSymbolNames.push_back(symbol); + } while (maybeGet(',')); + expect(']'); + } + } + + if (!findField("mappings")) { + throw MapParseException("cannot find the 'mappings' field in map"); + } + + expect('\"'); + if (maybeGet('\"')) { + // There are no mappings. + location = 0; + return; + } + + // Read the location of the first debug location. + location = readBase64VLQ(); +} + +std::optional +SourceMapReader::readDebugLocationAt(size_t currLocation) { + if (pos >= buffer.size()) { + return std::nullopt; + } + + while (location && location <= currLocation) { + do { + char next = peek(); + if (next == ',' || next == '\"') { + // This is a 1-length entry, so the next location has no debug info. + hasInfo = false; + break; + } + + hasInfo = true; + file += readBase64VLQ(); + line += readBase64VLQ(); + col += readBase64VLQ(); + + next = peek(); + if (next == ',' || next == '\"') { + hasSymbol = false; + break; + } + + hasSymbol = true; + symbol += readBase64VLQ(); + + } while (false); + + // Check whether there is another record to read the position for. + char next = get(); + if (next == '\"') { + // End of records. + location = 0; + break; + } + if (next != ',') { + throw MapParseException("Expected delimiter"); + } + + // Set up for the next record. + location += readBase64VLQ(); + } + + if (!hasInfo) { + return std::nullopt; + } + + auto sym = hasSymbol ? symbol : std::optional{}; + return Function::DebugLocation{file, line, col, sym}; +} + +int32_t SourceMapReader::readBase64VLQ() { + uint32_t value = 0; + uint32_t shift = 0; + while (1) { + auto ch = get(); + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch < 'g')) { + // last number digit + uint32_t digit = ch < 'a' ? ch - 'A' : ch - 'a' + 26; + value |= digit << shift; + break; + } + if (!(ch >= 'g' && ch <= 'z') && !(ch >= '0' && ch <= '9') && ch != '+' && + ch != '/') { + throw MapParseException("invalid VLQ digit"); + } + uint32_t digit = + ch > '9' ? ch - 'g' : (ch >= '0' ? ch - '0' + 20 : (ch == '+' ? 30 : 31)); + value |= digit << shift; + shift += 5; + } + return value & 1 ? -int32_t(value >> 1) : int32_t(value >> 1); +} + +} // namespace wasm diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 82ac422eaa4..86b3ea899c4 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1737,10 +1737,10 @@ void WasmBinaryWriter::writeField(const Field& field) { WasmBinaryReader::WasmBinaryReader(Module& wasm, FeatureSet features, - const std::vector& input) - : wasm(wasm), allocator(wasm.allocator), input(input), sourceMap(nullptr), - nextDebugPos(0), nextDebugLocation{0, 0, 0, std::nullopt}, - nextDebugLocationHasDebugInfo(false), debugLocation(), builder(wasm) { + const std::vector& input, + const std::vector& sourceMap) + : wasm(wasm), allocator(wasm.allocator), input(input), builder(wasm), + sourceMapReader(sourceMap) { wasm.features = features; } @@ -1788,7 +1788,7 @@ void WasmBinaryReader::read() { } readHeader(); - readSourceMapHeader(); + sourceMapReader.readHeader(wasm); // Read sections until the end while (more()) { @@ -2804,12 +2804,10 @@ void WasmBinaryReader::readFunctions() { BinaryLocation(pos - codeSectionLocation + size)}; } - readNextDebugLocation(); + func->prologLocation = sourceMapReader.readDebugLocationAt(pos); readVars(); setLocalNames(*func, numFuncImports + i); - - func->prologLocation = debugLocation; { // Process the function body. Even if we are skipping function bodies we // need to not skip the start function. That contains important code for @@ -2846,11 +2844,9 @@ void WasmBinaryReader::readFunctions() { } } + sourceMapReader.finishFunction(); TypeUpdating::handleNonDefaultableLocals(func.get(), wasm); - - std::swap(func->epilogLocation, debugLocation); currFunction = nullptr; - debugLocation.clear(); } } @@ -2879,9 +2875,8 @@ void WasmBinaryReader::readVars() { } Result<> WasmBinaryReader::readInst() { - readNextDebugLocation(); - if (debugLocation.size()) { - builder.setDebugLocation(*debugLocation.begin()); + if (auto loc = sourceMapReader.readDebugLocationAt(pos)) { + builder.setDebugLocation(loc); } uint8_t code = getInt8(); switch (code) { @@ -4273,242 +4268,6 @@ void WasmBinaryReader::readExports() { } } -static int32_t readBase64VLQ(std::istream& in) { - uint32_t value = 0; - uint32_t shift = 0; - while (1) { - auto ch = in.get(); - if (ch == EOF) { - throw MapParseException("unexpected EOF in the middle of VLQ"); - } - if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch < 'g')) { - // last number digit - uint32_t digit = ch < 'a' ? ch - 'A' : ch - 'a' + 26; - value |= digit << shift; - break; - } - if (!(ch >= 'g' && ch <= 'z') && !(ch >= '0' && ch <= '9') && ch != '+' && - ch != '/') { - throw MapParseException("invalid VLQ digit"); - } - uint32_t digit = - ch > '9' ? ch - 'g' : (ch >= '0' ? ch - '0' + 20 : (ch == '+' ? 30 : 31)); - value |= digit << shift; - shift += 5; - } - return value & 1 ? -int32_t(value >> 1) : int32_t(value >> 1); -} - -void WasmBinaryReader::readSourceMapHeader() { - if (!sourceMap) { - return; - } - - auto skipWhitespace = [&]() { - while (sourceMap->peek() == ' ' || sourceMap->peek() == '\n') { - sourceMap->get(); - } - }; - - auto maybeReadChar = [&](char expected) { - if (sourceMap->peek() != expected) { - return false; - } - sourceMap->get(); - return true; - }; - - auto mustReadChar = [&](char expected) { - char c = sourceMap->get(); - if (c != expected) { - throw MapParseException(std::string("Unexpected char: expected '") + - expected + "' got '" + c + "'"); - } - }; - - auto findField = [&](const char* name) { - bool matching = false; - size_t len = strlen(name); - size_t pos; - while (1) { - int ch = sourceMap->get(); - if (ch == EOF) { - return false; - } - if (ch == '\"') { - if (matching) { - // we matched a terminating quote. - if (pos == len) { - break; - } - matching = false; - } else { - matching = true; - pos = 0; - } - } else if (matching && name[pos] == ch) { - ++pos; - } else if (matching) { - matching = false; - } - } - skipWhitespace(); - mustReadChar(':'); - skipWhitespace(); - return true; - }; - - auto readString = [&](std::string& str) { - std::vector vec; - skipWhitespace(); - mustReadChar('\"'); - if (!maybeReadChar('\"')) { - while (1) { - int ch = sourceMap->get(); - if (ch == EOF) { - throw MapParseException("unexpected EOF in the middle of string"); - } - if (ch == '\"') { - break; - } - vec.push_back(ch); - } - } - skipWhitespace(); - str = std::string(vec.begin(), vec.end()); - }; - - if (!findField("sources")) { - throw MapParseException("cannot find the 'sources' field in map"); - } - - skipWhitespace(); - mustReadChar('['); - if (!maybeReadChar(']')) { - do { - std::string file; - readString(file); - Index index = wasm.debugInfoFileNames.size(); - wasm.debugInfoFileNames.push_back(file); - debugInfoFileIndices[file] = index; - } while (maybeReadChar(',')); - mustReadChar(']'); - } - - if (findField("names")) { - skipWhitespace(); - mustReadChar('['); - if (!maybeReadChar(']')) { - do { - std::string symbol; - readString(symbol); - Index index = wasm.debugInfoSymbolNames.size(); - wasm.debugInfoSymbolNames.push_back(symbol); - debugInfoSymbolNameIndices[symbol] = index; - } while (maybeReadChar(',')); - mustReadChar(']'); - } - } - - if (!findField("mappings")) { - throw MapParseException("cannot find the 'mappings' field in map"); - } - - mustReadChar('\"'); - if (maybeReadChar('\"')) { // empty mappings - nextDebugPos = 0; - return; - } - // read first debug location - // TODO: Handle the case where the very first one has only a position but not - // debug info. In practice that does not happen, which needs - // investigation (if it does, it will assert in readBase64VLQ, so it - // would not be a silent error at least). - uint32_t position = readBase64VLQ(*sourceMap); - nextDebugPos = position; - - auto peek = sourceMap->peek(); - if (peek == ',' || peek == '\"') { - // This is a 1-length entry, so the next location has no debug info. - nextDebugLocationHasDebugInfo = false; - } else { - uint32_t fileIndex = readBase64VLQ(*sourceMap); - uint32_t lineNumber = - readBase64VLQ(*sourceMap) + 1; // adjust zero-based line number - uint32_t columnNumber = readBase64VLQ(*sourceMap); - std::optional symbolNameIndex; - peek = sourceMap->peek(); - if (!(peek == ',' || peek == '\"')) { - symbolNameIndex = readBase64VLQ(*sourceMap); - } - nextDebugLocation = {fileIndex, lineNumber, columnNumber, symbolNameIndex}; - nextDebugLocationHasDebugInfo = true; - } -} - -void WasmBinaryReader::readNextDebugLocation() { - if (!sourceMap) { - return; - } - - if (nextDebugPos == 0) { - // We reached the end of the source map; nothing left to read. - return; - } - - while (nextDebugPos && nextDebugPos <= pos) { - debugLocation.clear(); - // use debugLocation only for function expressions - if (currFunction) { - if (nextDebugLocationHasDebugInfo) { - debugLocation.insert(nextDebugLocation); - } else { - debugLocation.clear(); - } - } - - char ch; - *sourceMap >> ch; - if (ch == '\"') { // end of records - nextDebugPos = 0; - break; - } - if (ch != ',') { - throw MapParseException("Unexpected delimiter"); - } - - int32_t positionDelta = readBase64VLQ(*sourceMap); - uint32_t position = nextDebugPos + positionDelta; - - nextDebugPos = position; - - auto peek = sourceMap->peek(); - if (peek == ',' || peek == '\"') { - // This is a 1-length entry, so the next location has no debug info. - nextDebugLocationHasDebugInfo = false; - break; - } - - int32_t fileIndexDelta = readBase64VLQ(*sourceMap); - uint32_t fileIndex = nextDebugLocation.fileIndex + fileIndexDelta; - int32_t lineNumberDelta = readBase64VLQ(*sourceMap); - uint32_t lineNumber = nextDebugLocation.lineNumber + lineNumberDelta; - int32_t columnNumberDelta = readBase64VLQ(*sourceMap); - uint32_t columnNumber = nextDebugLocation.columnNumber + columnNumberDelta; - - std::optional symbolNameIndex; - peek = sourceMap->peek(); - if (!(peek == ',' || peek == '\"')) { - int32_t symbolNameIndexDelta = readBase64VLQ(*sourceMap); - symbolNameIndex = - nextDebugLocation.symbolNameIndex.value_or(0) + symbolNameIndexDelta; - } - - nextDebugLocation = {fileIndex, lineNumber, columnNumber, symbolNameIndex}; - nextDebugLocationHasDebugInfo = true; - } -} - Expression* WasmBinaryReader::readExpression() { assert(builder.empty()); while (input[pos] != BinaryConsts::End) { diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp index 149216e1a83..e1d036cecce 100644 --- a/src/wasm/wasm-io.cpp +++ b/src/wasm/wasm-io.cpp @@ -50,25 +50,18 @@ void ModuleReader::readText(std::string filename, Module& wasm) { void ModuleReader::readBinaryData(std::vector& input, Module& wasm, std::string sourceMapFilename) { - std::unique_ptr sourceMapStream; + std::vector sourceMapBuffer; + if (sourceMapFilename.size()) { + sourceMapBuffer = + read_file>(sourceMapFilename, Flags::Text); + } // Assume that the wasm has had its initial features applied, and use those // while parsing. - WasmBinaryReader parser(wasm, wasm.features, input); + WasmBinaryReader parser(wasm, wasm.features, input, sourceMapBuffer); parser.setDebugInfo(debugInfo); parser.setDWARF(DWARF); parser.setSkipFunctionBodies(skipFunctionBodies); - if (sourceMapFilename.size()) { - sourceMapStream = std::make_unique(); - sourceMapStream->open(wasm::Path::to_path(sourceMapFilename)); - if (!sourceMapStream->is_open()) { - Fatal() << "Failed opening '" << sourceMapFilename << "'"; - } - parser.setDebugLocations(sourceMapStream.get()); - } parser.read(); - if (sourceMapStream) { - sourceMapStream->close(); - } } void ModuleReader::readBinary(std::string filename, diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index a51337cda0d..96212ccd7fc 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -709,7 +709,7 @@ Result<> IRBuilder::visitFunctionStart(Function* func) { return Err{"unexpected start of function"}; } if (auto* loc = std::get_if(&debugLoc)) { - func->prologLocation.insert(*loc); + func->prologLocation = *loc; } debugLoc = CanReceiveDebug(); scopeStack.push_back(ScopeCtx::makeFunc(func)); @@ -975,7 +975,7 @@ Result<> IRBuilder::visitEnd() { } if (auto* func = scope.getFunction()) { if (auto* loc = std::get_if(&debugLoc)) { - func->epilogLocation.insert(*loc); + func->epilogLocation = *loc; } } debugLoc = CanReceiveDebug(); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index f86fb58b948..61f59c76aa9 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -3096,8 +3096,8 @@ ModuleStackIR::ModuleStackIR(Module& wasm, const PassOptions& options) }) {} void StackIRToBinaryWriter::write() { - if (func->prologLocation.size()) { - parent.writeDebugLocation(*func->prologLocation.begin()); + if (func->prologLocation) { + parent.writeDebugLocation(*func->prologLocation); } writer.mapLocalsAndEmitHeader(); // Stack to track indices of catches within a try @@ -3158,8 +3158,8 @@ void StackIRToBinaryWriter::write() { } // Indicate the debug location corresponding to the end opcode that // terminates the function code. - if (func->epilogLocation.size()) { - parent.writeDebugLocation(*func->epilogLocation.begin()); + if (func->epilogLocation) { + parent.writeDebugLocation(*func->epilogLocation); } else { // The end opcode has no debug location. parent.writeNoDebugLocation(); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 38f35411f52..f5806b184e2 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1503,8 +1503,8 @@ void Function::clearNames() { localNames.clear(); } void Function::clearDebugInfo() { localIndices.clear(); debugLocations.clear(); - prologLocation.clear(); - epilogLocation.clear(); + prologLocation.reset(); + epilogLocation.reset(); } template diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index c3d281f1c0e..102d3ca2a48 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -3,7 +3,7 @@ include_directories(../../src/wasm) set(unittest_SOURCES arena.cpp - binary-reader.cpp + source-map.cpp cfg.cpp dfa_minimization.cpp disjoint_sets.cpp diff --git a/test/gtest/binary-reader.cpp b/test/gtest/source-map.cpp similarity index 58% rename from test/gtest/binary-reader.cpp rename to test/gtest/source-map.cpp index b73fe55bda3..c943be17239 100644 --- a/test/gtest/binary-reader.cpp +++ b/test/gtest/source-map.cpp @@ -14,24 +14,19 @@ * limitations under the License. */ -#include "parser/wat-parser.h" +#include "source-map.h" #include "print-test.h" -#include "wasm-binary.h" #include "gtest/gtest.h" using namespace wasm; -using BinaryReaderTest = PrintTest; +using SourceMapTest = PrintTest; // Check that debug location parsers can handle single-segment mappings. -TEST_F(BinaryReaderTest, SourceMappingSingleSegment) { - auto moduleText = "(module)"; - Module module; - parseWast(module, moduleText); - - BufferWithRandomAccess buffer; - WasmBinaryWriter(&module, buffer, PassOptions()); - auto moduleBytes = buffer.getAsChars(); +TEST_F(SourceMapTest, SourceMappingSingleSegment) { + auto text = "(module)"; + Module wasm; + parseWast(wasm, text); // A single-segment mapping starting at offset 0. std::string sourceMap = R"( @@ -42,22 +37,15 @@ TEST_F(BinaryReaderTest, SourceMappingSingleSegment) { "mappings": "A" } )"; - std::stringstream sourceMapStream(sourceMap); + std::vector buffer(sourceMap.begin(), sourceMap.end()); + + SourceMapReader reader(buffer); // Test `readSourceMapHeader` (only check for errors, as there is no mapping // to print). - { - Module module; - WasmBinaryReader binaryReader(module, FeatureSet::All, moduleBytes); - binaryReader.setDebugLocations(&sourceMapStream); - binaryReader.readSourceMapHeader(); - } + reader.readHeader(wasm); // Test `readNextDebugLocation`. - { - Module module; - WasmBinaryReader binaryReader(module, FeatureSet::All, moduleBytes); - binaryReader.setDebugLocations(&sourceMapStream); - binaryReader.readNextDebugLocation(); - } + // TODO: Actually check the result. + reader.readDebugLocationAt(1); } From 47f9a78e5d423638a3dceeed2cb6449766f6f75e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 4 Dec 2024 12:47:15 -0800 Subject: [PATCH 174/622] Fix GUFA on calls to function refs in open world (#7135) In open world we must assume that a funcref that escapes to the outside might be called. --- src/ir/possible-contents.cpp | 43 ++++++++++++----- test/lit/passes/gufa-closed-open.wast | 68 +++++++++++++++++++++++++++ test/lit/passes/gufa-refs.wast | 5 +- test/lit/passes/gufa.wast | 5 +- 4 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 test/lit/passes/gufa-closed-open.wast diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 17e40f1d805..00a2cb82500 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -501,6 +501,12 @@ struct CollectedFuncInfo { // when we update the child we can find the parent and handle any special // behavior we need there. std::unordered_map childParents; + + // All functions that might be called from the outside. Any RefFunc suggests + // that, in open world. (We could be more precise and use our flow analysis to + // see which, in fact, flow outside, but it is unclear how useful that would + // be. Anyhow, closed-world is more important to optimize, and avoids this.) + std::unordered_set calledFromOutside; }; // Does a walk while maintaining a map of names of branch targets to those @@ -528,8 +534,10 @@ struct BreakTargetWalker : public PostWalker { struct InfoCollector : public BreakTargetWalker> { CollectedFuncInfo& info; + const PassOptions& options; - InfoCollector(CollectedFuncInfo& info) : info(info) {} + InfoCollector(CollectedFuncInfo& info, const PassOptions& options) + : info(info), options(options) {} // Check if a type is relevant for us. If not, we can ignore it entirely. bool isRelevant(Type type) { @@ -665,6 +673,10 @@ struct InfoCollector info.links.push_back( {ResultLocation{func, i}, SignatureResultLocation{func->type, i}}); } + + if (!options.closedWorld) { + info.calledFromOutside.insert(curr->func); + } } void visitRefEq(RefEq* curr) { addRoot(curr); @@ -2092,7 +2104,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) // First, collect information from each function. ModuleUtils::ParallelFunctionAnalysis analysis( wasm, [&](Function* func, CollectedFuncInfo& info) { - InfoCollector finder(info); + InfoCollector finder(info, options); if (func->imported()) { // Imports return unknown values. @@ -2114,7 +2126,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) // Also walk the global module code (for simplicity, also add it to the // function map, using a "function" key of nullptr). auto& globalInfo = analysis.map[nullptr]; - InfoCollector finder(globalInfo); + InfoCollector finder(globalInfo, options); finder.walkModuleCode(&wasm); #ifdef POSSIBLE_CONTENTS_DEBUG @@ -2153,6 +2165,16 @@ Flower::Flower(Module& wasm, const PassOptions& options) // above. InsertOrderedMap roots; + // Any function that may be called from the outside, like an export, is a + // root, since they can be called with unknown parameters. + auto calledFromOutside = [&](Name funcName) { + auto* func = wasm.getFunction(funcName); + auto params = func->getParams(); + for (Index i = 0; i < func->getParams().size(); i++) { + roots[ParamLocation{func, i}] = PossibleContents::fromType(params[i]); + } + }; + for (auto& [func, info] : analysis.map) { for (auto& link : info.links) { links.insert(getIndexes(link)); @@ -2171,6 +2193,10 @@ Flower::Flower(Module& wasm, const PassOptions& options) childParents[getIndex(ExpressionLocation{child, 0})] = getIndex(ExpressionLocation{parent, 0}); } + + for (auto func : info.calledFromOutside) { + calledFromOutside(func); + } } // We no longer need the function-level info. @@ -2180,16 +2206,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) std::cout << "external phase\n"; #endif - // Parameters of exported functions are roots, since exports can have callers - // that we can't see, so anything might arrive there. - auto calledFromOutside = [&](Name funcName) { - auto* func = wasm.getFunction(funcName); - auto params = func->getParams(); - for (Index i = 0; i < func->getParams().size(); i++) { - roots[ParamLocation{func, i}] = PossibleContents::fromType(params[i]); - } - }; - + // Exports can be modified from the outside. for (auto& ex : wasm.exports) { if (ex->kind == ExternalKind::Function) { calledFromOutside(ex->value); diff --git a/test/lit/passes/gufa-closed-open.wast b/test/lit/passes/gufa-closed-open.wast new file mode 100644 index 00000000000..47add9df53b --- /dev/null +++ b/test/lit/passes/gufa-closed-open.wast @@ -0,0 +1,68 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s --check-prefix OPEND +;; RUN: foreach %s %t wasm-opt -all --gufa --closed-world -S -o - | filecheck %s --check-prefix CLOSE + +;; Compare behavior on closed and open world. In open world we must assume that +;; funcrefs, for example, can be called from outside. + +(module + ;; OPEND: (type $0 (func (param funcref))) + + ;; OPEND: (type $1 (func)) + + ;; OPEND: (type $2 (func (param i32))) + + ;; OPEND: (import "fuzzing-support" "call-ref-catch" (func $external-caller (type $0) (param funcref))) + ;; CLOSE: (type $0 (func (param funcref))) + + ;; CLOSE: (type $1 (func)) + + ;; CLOSE: (type $2 (func (param i32))) + + ;; CLOSE: (import "fuzzing-support" "call-ref-catch" (func $external-caller (type $0) (param funcref))) + (import "fuzzing-support" "call-ref-catch" (func $external-caller (param funcref))) + + ;; OPEND: (elem declare func $func) + + ;; OPEND: (export "call-import" (func $call-import)) + + ;; OPEND: (func $call-import (type $1) + ;; OPEND-NEXT: (call $external-caller + ;; OPEND-NEXT: (ref.func $func) + ;; OPEND-NEXT: ) + ;; OPEND-NEXT: ) + ;; CLOSE: (elem declare func $func) + + ;; CLOSE: (export "call-import" (func $call-import)) + + ;; CLOSE: (func $call-import (type $1) + ;; CLOSE-NEXT: (call $external-caller + ;; CLOSE-NEXT: (ref.func $func) + ;; CLOSE-NEXT: ) + ;; CLOSE-NEXT: ) + (func $call-import (export "call-import") + ;; Send a reference to $func to the outside, which may call it. + (call $external-caller + (ref.func $func) + ) + ) + + ;; OPEND: (func $func (type $2) (param $0 i32) + ;; OPEND-NEXT: (drop + ;; OPEND-NEXT: (local.get $0) + ;; OPEND-NEXT: ) + ;; OPEND-NEXT: ) + ;; CLOSE: (func $func (type $2) (param $0 i32) + ;; CLOSE-NEXT: (drop + ;; CLOSE-NEXT: (unreachable) + ;; CLOSE-NEXT: ) + ;; CLOSE-NEXT: ) + (func $func (param $0 i32) + ;; This is called from the outside, so this is not dead code, and nothing + ;; should change here in open world. In closed world, this can become an + ;; unreachable, since nothing can call it. + (drop + (local.get $0) + ) + ) +) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 9772745bc28..80fd323869c 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -1,5 +1,8 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --gufa --closed-world -S -o - | filecheck %s + +;; Closed-world is applied here to avoid treating all ref.funcs as callable +;; from outside (and this is the more important mode to test on). (module ;; CHECK: (type $struct (struct)) diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index fcb60310ad4..721c27eeb66 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1,5 +1,8 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --gufa --closed-world -S -o - | filecheck %s + +;; Closed-world is applied here to avoid treating all ref.funcs as callable +;; from outside (and this is the more important mode to test on). (module ;; CHECK: (type $0 (func (result i32))) From 68963739e56258057a7f0618e0375dd60ae4e124 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 4 Dec 2024 14:49:00 -0800 Subject: [PATCH 175/622] Remove separate Table64Lowering pass (#7131) This pass is now just part of Memory64Lowering. Once this lands we can remove the `--table64-lowering` flag from emscripten. Because I've used an alias here there will be some interim period where emscripten will run this pass twice since it passed both flags. However, this will only be temporary and that second run will be a no-op since the first one will remove the feature. --- src/passes/CMakeLists.txt | 1 - src/passes/Memory64Lowering.cpp | 126 ++++++++++++++++++-- src/passes/Table64Lowering.cpp | 155 ------------------------- src/passes/pass.cpp | 4 +- test/lit/help/wasm-metadce.test | 2 +- test/lit/help/wasm-opt.test | 2 +- test/lit/help/wasm2js.test | 2 +- test/lit/passes/memory64-lowering.wast | 85 +++++++++++++- test/lit/passes/table64-lowering.wast | 83 ------------- 9 files changed, 206 insertions(+), 254 deletions(-) delete mode 100644 src/passes/Table64Lowering.cpp delete mode 100644 test/lit/passes/table64-lowering.wast diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 6b78e487dea..61c1b2f4610 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -112,7 +112,6 @@ set(passes_SOURCES ReorderGlobals.cpp ReorderLocals.cpp ReReloop.cpp - Table64Lowering.cpp TrapMode.cpp TypeGeneralizing.cpp TypeRefining.cpp diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index a3913c7598b..714f3aa5b34 100644 --- a/src/passes/Memory64Lowering.cpp +++ b/src/passes/Memory64Lowering.cpp @@ -31,33 +31,58 @@ namespace wasm { static Name MEMORY_BASE("__memory_base"); static Name MEMORY_BASE32("__memory_base32"); +static Name TABLE_BASE("__table_base"); +static Name TABLE_BASE32("__table_base32"); + struct Memory64Lowering : public WalkerPass> { - void wrapAddress64(Expression*& ptr, Name memoryName) { + void wrapAddress64(Expression*& ptr, + Name memoryOrTableName, + bool isTable = false) { if (ptr->type == Type::unreachable) { return; } auto& module = *getModule(); - auto* memory = module.getMemory(memoryName); - if (memory->is64()) { + bool is64 = false; + if (isTable) { + is64 = module.getTable(memoryOrTableName)->is64(); + } else { + is64 = module.getMemory(memoryOrTableName)->is64(); + } + if (is64) { assert(ptr->type == Type::i64); ptr = Builder(module).makeUnary(UnaryOp::WrapInt64, ptr); } } - void extendAddress64(Expression*& ptr, Name memoryName) { + void extendAddress64(Expression*& ptr, + Name memoryOrTableName, + bool isTable = false) { if (ptr->type == Type::unreachable) { return; } auto& module = *getModule(); - auto* memory = module.getMemory(memoryName); - if (memory->is64()) { + bool is64 = false; + if (isTable) { + is64 = module.getTable(memoryOrTableName)->is64(); + } else { + is64 = module.getMemory(memoryOrTableName)->is64(); + } + if (is64) { assert(ptr->type == Type::i64); ptr->type = Type::i32; ptr = Builder(module).makeUnary(UnaryOp::ExtendUInt32, ptr); } } + void wrapTableAddress64(Expression*& ptr, Name tableName) { + return wrapAddress64(ptr, tableName, true); + } + + void extendTableAddress64(Expression*& ptr, Name tableName) { + return extendAddress64(ptr, tableName, true); + } + void visitLoad(Load* curr) { wrapAddress64(curr->ptr, curr->memory); } void visitStore(Store* curr) { wrapAddress64(curr->ptr, curr->memory); } @@ -177,14 +202,92 @@ struct Memory64Lowering : public WalkerPass> { } } + void visitTableSize(TableSize* curr) { + auto& module = *getModule(); + auto* table = module.getTable(curr->table); + if (table->is64()) { + auto* size = static_cast(curr); + extendTableAddress64(size, curr->table); + replaceCurrent(size); + } + } + + void visitTableGrow(TableGrow* curr) { + auto& module = *getModule(); + auto* table = module.getTable(curr->table); + if (table->is64()) { + wrapTableAddress64(curr->delta, curr->table); + auto* size = static_cast(curr); + extendTableAddress64(size, curr->table); + replaceCurrent(size); + } + } + + void visitTableFill(TableFill* curr) { + wrapTableAddress64(curr->dest, curr->table); + wrapTableAddress64(curr->size, curr->table); + } + + void visitTableCopy(TableCopy* curr) { + wrapTableAddress64(curr->dest, curr->destTable); + wrapTableAddress64(curr->source, curr->sourceTable); + wrapTableAddress64(curr->size, curr->destTable); + } + + void visitTableInit(TableInit* curr) { + wrapTableAddress64(curr->dest, curr->table); + } + + void visitCallIndirect(CallIndirect* curr) { + wrapTableAddress64(curr->target, curr->table); + } + + void visitElementSegment(ElementSegment* segment) { + auto& module = *getModule(); + + // Passive segments don't have any offset to update. + if (segment->table.isNull() || !module.getTable(segment->table)->is64()) { + return; + } + + if (auto* c = segment->offset->dynCast()) { + c->value = Literal(static_cast(c->value.geti64())); + c->type = Type::i32; + } else if (auto* get = segment->offset->dynCast()) { + auto* g = module.getGlobal(get->name); + if (g->imported() && g->base == TABLE_BASE) { + ImportInfo info(module); + auto* memoryBase32 = info.getImportedGlobal(g->module, TABLE_BASE32); + if (!memoryBase32) { + Builder builder(module); + memoryBase32 = builder + .makeGlobal(TABLE_BASE32, + Type::i32, + builder.makeConst(int32_t(0)), + Builder::Immutable) + .release(); + memoryBase32->module = g->module; + memoryBase32->base = TABLE_BASE32; + module.addGlobal(memoryBase32); + } + // Use this alternative import when initializing the segment. + assert(memoryBase32); + get->type = Type::i32; + get->name = memoryBase32->name; + } + } else { + WASM_UNREACHABLE("unexpected elem offset"); + } + } + void run(Module* module) override { if (!module->features.has(FeatureSet::Memory64)) { return; } Super::run(module); - // Don't modify the memories themselves until after the traversal since we - // that would require memories to be the last thing that get visited, and - // we don't want to depend on that specific ordering. + // Don't modify the memories or tables themselves until after the traversal + // since we that would require memories to be the last thing that get + // visited, and we don't want to depend on that specific ordering. for (auto& memory : module->memories) { if (memory->is64()) { memory->addressType = Type::i32; @@ -193,6 +296,11 @@ struct Memory64Lowering : public WalkerPass> { } } } + for (auto& table : module->tables) { + if (table->is64()) { + table->addressType = Type::i32; + } + } module->features.disable(FeatureSet::Memory64); } }; diff --git a/src/passes/Table64Lowering.cpp b/src/passes/Table64Lowering.cpp deleted file mode 100644 index 1167515de17..00000000000 --- a/src/passes/Table64Lowering.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2024 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. - */ - -// -// Lowers a module with a 64-bit table to one with a 32-bit table. -// -// This pass can be deleted once table64 is implemented in Wasm engines: -// https://github.com/WebAssembly/memory64/issues/51 -// - -#include "ir/bits.h" -#include "ir/import-utils.h" -#include "pass.h" -#include "wasm-builder.h" -#include "wasm.h" - -namespace wasm { - -static Name TABLE_BASE("__table_base"); -static Name TABLE_BASE32("__table_base32"); - -struct Table64Lowering : public WalkerPass> { - - void wrapAddress64(Expression*& ptr, Name tableName) { - if (ptr->type == Type::unreachable) { - return; - } - auto& module = *getModule(); - auto* table = module.getTable(tableName); - if (table->is64()) { - assert(ptr->type == Type::i64); - ptr = Builder(module).makeUnary(UnaryOp::WrapInt64, ptr); - } - } - - void extendAddress64(Expression*& ptr, Name tableName) { - if (ptr->type == Type::unreachable) { - return; - } - auto& module = *getModule(); - auto* table = module.getTable(tableName); - if (table->is64()) { - assert(ptr->type == Type::i64); - ptr->type = Type::i32; - ptr = Builder(module).makeUnary(UnaryOp::ExtendUInt32, ptr); - } - } - - void visitTableSize(TableSize* curr) { - auto& module = *getModule(); - auto* table = module.getTable(curr->table); - if (table->is64()) { - auto* size = static_cast(curr); - extendAddress64(size, curr->table); - replaceCurrent(size); - } - } - - void visitTableGrow(TableGrow* curr) { - auto& module = *getModule(); - auto* table = module.getTable(curr->table); - if (table->is64()) { - wrapAddress64(curr->delta, curr->table); - auto* size = static_cast(curr); - extendAddress64(size, curr->table); - replaceCurrent(size); - } - } - - void visitTableFill(TableFill* curr) { - wrapAddress64(curr->dest, curr->table); - wrapAddress64(curr->size, curr->table); - } - - void visitTableCopy(TableCopy* curr) { - wrapAddress64(curr->dest, curr->destTable); - wrapAddress64(curr->source, curr->sourceTable); - wrapAddress64(curr->size, curr->destTable); - } - - void visitTableInit(TableInit* curr) { - wrapAddress64(curr->dest, curr->table); - } - - void visitCallIndirect(CallIndirect* curr) { - wrapAddress64(curr->target, curr->table); - } - - void visitElementSegment(ElementSegment* segment) { - auto& module = *getModule(); - - // Passive segments don't have any offset to update. - if (segment->table.isNull() || !module.getTable(segment->table)->is64()) { - return; - } - - if (auto* c = segment->offset->dynCast()) { - c->value = Literal(static_cast(c->value.geti64())); - c->type = Type::i32; - } else if (auto* get = segment->offset->dynCast()) { - auto* g = module.getGlobal(get->name); - if (g->imported() && g->base == TABLE_BASE) { - ImportInfo info(module); - auto* memoryBase32 = info.getImportedGlobal(g->module, TABLE_BASE32); - if (!memoryBase32) { - Builder builder(module); - memoryBase32 = builder - .makeGlobal(TABLE_BASE32, - Type::i32, - builder.makeConst(int32_t(0)), - Builder::Immutable) - .release(); - memoryBase32->module = g->module; - memoryBase32->base = TABLE_BASE32; - module.addGlobal(memoryBase32); - } - // Use this alternative import when initializing the segment. - assert(memoryBase32); - get->type = Type::i32; - get->name = memoryBase32->name; - } - } else { - WASM_UNREACHABLE("unexpected elem offset"); - } - } - - void run(Module* module) override { - Super::run(module); - // Don't modify the tables themselves until after the traversal since we - // that would require tables to be the last thing that get visited, and - // we don't want to depend on that specific ordering. - for (auto& table : module->tables) { - if (table->is64()) { - table->addressType = Type::i32; - } - } - } -}; - -Pass* createTable64LoweringPass() { return new Table64Lowering(); } - -} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 7f6985e0fd1..ab95300d0cb 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -273,8 +273,8 @@ void PassRegistry::registerPasses() { "32-bit one", createMemory64LoweringPass); registerPass("table64-lowering", - "lower 64-bit tables 32-bit ones", - createTable64LoweringPass); + "alias for memory64-lowering", + createMemory64LoweringPass); registerPass("llvm-memory-copy-fill-lowering", "Lower memory.copy and memory.fill to wasm mvp and disable " "the bulk-memory feature.", diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index e44e51a167b..e9883464133 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -520,7 +520,7 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --symbolmap (alias for print-function-map) ;; CHECK-NEXT: -;; CHECK-NEXT: --table64-lowering lower 64-bit tables 32-bit ones +;; CHECK-NEXT: --table64-lowering alias for memory64-lowering ;; CHECK-NEXT: ;; CHECK-NEXT: --trace-calls instrument the build with code ;; CHECK-NEXT: to intercept specific function diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 5c978ee81e7..06a9e5e8488 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -529,7 +529,7 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --symbolmap (alias for print-function-map) ;; CHECK-NEXT: -;; CHECK-NEXT: --table64-lowering lower 64-bit tables 32-bit ones +;; CHECK-NEXT: --table64-lowering alias for memory64-lowering ;; CHECK-NEXT: ;; CHECK-NEXT: --trace-calls instrument the build with code ;; CHECK-NEXT: to intercept specific function diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index c1dd0401d9e..50e229a1df9 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -483,7 +483,7 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --symbolmap (alias for print-function-map) ;; CHECK-NEXT: -;; CHECK-NEXT: --table64-lowering lower 64-bit tables 32-bit ones +;; CHECK-NEXT: --table64-lowering alias for memory64-lowering ;; CHECK-NEXT: ;; CHECK-NEXT: --trace-calls instrument the build with code ;; CHECK-NEXT: to intercept specific function diff --git a/test/lit/passes/memory64-lowering.wast b/test/lit/passes/memory64-lowering.wast index 4f9d3d050ac..39723c71de8 100644 --- a/test/lit/passes/memory64-lowering.wast +++ b/test/lit/passes/memory64-lowering.wast @@ -1,6 +1,9 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --memory64-lowering --enable-memory64 --enable-threads --enable-bulk-memory -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --memory64-lowering --enable-memory64 --enable-threads --enable-bulk-memory --enable-reference-types -S -o - | filecheck %s + +;; Check the --table64-lowering alias +;; RUN: foreach %s %t wasm-opt --table64-lowering --enable-memory64 --enable-threads --enable-bulk-memory --enable-reference-types -S -o - | filecheck %s (module ;; CHECK: (type $0 (func)) @@ -295,3 +298,83 @@ ;; CHECK: (memory $0 1 65536) (memory $0 i64 1 65537) ) + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (result i64))) + + ;; CHECK: (table $t64 10 100 funcref) + (table $t64 i64 10 100 funcref) + + ;; CHECK: (table $t32 10 100 funcref) + + ;; CHECK: (elem $elem64 (table $t64) (i32.const 0) funcref (item (ref.null nofunc))) + (elem $elem64 (table $t64) (i64.const 0) funcref (ref.null func)) + + (table $t32 10 100 funcref) + ;; CHECK: (elem $elem32 (table $t32) (i32.const 0) funcref (item (ref.null nofunc))) + (elem $elem32 (table $t32) (i32.const 0) funcref (ref.null func)) + + ;; CHECK: (func $test_call_indirect + ;; CHECK-NEXT: (call_indirect $t64 (type $0) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test_call_indirect + (call_indirect 0 (i64.const 0)) + ) + + ;; CHECK: (func $test_table_size (result i64) + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (table.size $t64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test_table_size (result i64) + (table.size $t64) + ) + + ;; CHECK: (func $test_table_grow (result i64) + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (table.grow $t64 + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test_table_grow (result i64) + (table.grow $t64 (ref.null func) (i64.const 10)) + ) + + ;; CHECK: (func $test_table_fill + ;; CHECK-NEXT: (table.fill $t64 + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test_table_fill + (table.fill $t64 (i64.const 0) (ref.null func) (i64.const 10)) + ) + + ;; CHECK: (func $test_table_init + ;; CHECK-NEXT: (table.init $t64 $elem64 + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test_table_init + (table.init $t64 $elem64 (i64.const 0) (i32.const 5) (i32.const 10)) + ) +) diff --git a/test/lit/passes/table64-lowering.wast b/test/lit/passes/table64-lowering.wast deleted file mode 100644 index f3aaf4ef8a0..00000000000 --- a/test/lit/passes/table64-lowering.wast +++ /dev/null @@ -1,83 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: wasm-opt %s --enable-memory64 --enable-reference-types --enable-bulk-memory --table64-lowering -S -o - | filecheck %s - -(module - ;; CHECK: (type $0 (func)) - - ;; CHECK: (type $1 (func (result i64))) - - ;; CHECK: (table $t64 10 100 funcref) - (table $t64 i64 10 100 funcref) - - ;; CHECK: (table $t32 10 100 funcref) - - ;; CHECK: (elem $elem64 (table $t64) (i32.const 0) funcref (item (ref.null nofunc))) - (elem $elem64 (table $t64) (i64.const 0) funcref (ref.null func)) - - (table $t32 10 100 funcref) - ;; CHECK: (elem $elem32 (table $t32) (i32.const 0) funcref (item (ref.null nofunc))) - (elem $elem32 (table $t32) (i32.const 0) funcref (ref.null func)) - - ;; CHECK: (func $test_call_indirect - ;; CHECK-NEXT: (call_indirect $t64 (type $0) - ;; CHECK-NEXT: (i32.wrap_i64 - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test_call_indirect - (call_indirect 0 (i64.const 0)) - ) - - ;; CHECK: (func $test_table_size (result i64) - ;; CHECK-NEXT: (i64.extend_i32_u - ;; CHECK-NEXT: (table.size $t64) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test_table_size (result i64) - (table.size $t64) - ) - - ;; CHECK: (func $test_table_grow (result i64) - ;; CHECK-NEXT: (i64.extend_i32_u - ;; CHECK-NEXT: (table.grow $t64 - ;; CHECK-NEXT: (ref.null nofunc) - ;; CHECK-NEXT: (i32.wrap_i64 - ;; CHECK-NEXT: (i64.const 10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test_table_grow (result i64) - (table.grow $t64 (ref.null func) (i64.const 10)) - ) - - ;; CHECK: (func $test_table_fill - ;; CHECK-NEXT: (table.fill $t64 - ;; CHECK-NEXT: (i32.wrap_i64 - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null nofunc) - ;; CHECK-NEXT: (i32.wrap_i64 - ;; CHECK-NEXT: (i64.const 10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test_table_fill - (table.fill $t64 (i64.const 0) (ref.null func) (i64.const 10)) - ) - - ;; CHECK: (func $test_table_init - ;; CHECK-NEXT: (table.init $t64 $elem64 - ;; CHECK-NEXT: (i32.wrap_i64 - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 5) - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test_table_init - (table.init $t64 $elem64 (i64.const 0) (i32.const 5) (i32.const 10)) - ) -) From 06e06ec86f7d1e91d0fa9e53cf8bb13d0e3e0df4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Dec 2024 09:59:33 -0800 Subject: [PATCH 176/622] [NFC] Send the closed-world flag to TranslateToFuzzReader (#7136) This sends --closed-world to wasm-opt from the fuzzer, when we use that flag (before we just used it on optimizations, but not fuzz generation). And TranslateToFuzzReader now stores a boolean about whether we are in closed- world mode or not. This has no effect so far, and is a refactoring for a later PR, where we must generate code differently based on whether we are in closed-world mode or not. --- scripts/fuzz_opt.py | 10 ++++++---- src/tools/fuzzing.h | 10 ++++++++-- src/tools/fuzzing/fuzzing.cpp | 19 +++++++++++++------ src/tools/wasm-opt.cpp | 3 ++- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 29940383751..b60a3e29ad8 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -225,10 +225,12 @@ def randomize_fuzz_settings(): # optimizations we use to create any other wasm file. FUZZ_OPTS += ['--dce'] - # Enclose the world much of the time when fuzzing closed-world, so that many - # types are private and hence optimizable. - if CLOSED_WORLD and random.random() < 0.5: - GEN_ARGS += ['--enclose-world'] + if CLOSED_WORLD: + GEN_ARGS += [CLOSED_WORLD_FLAG] + # Enclose the world much of the time when fuzzing closed-world, so that + # many types are private and hence optimizable. + if random.random() < 0.5: + GEN_ARGS += ['--enclose-world'] print('randomized settings (NaNs, OOB, legalize):', NANS, OOB, LEGALIZE) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 1afb4bf36f7..a3261ccbe59 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -65,8 +65,12 @@ struct BinaryArgs { class TranslateToFuzzReader { public: - TranslateToFuzzReader(Module& wasm, std::vector&& input); - TranslateToFuzzReader(Module& wasm, std::string& filename); + TranslateToFuzzReader(Module& wasm, + std::vector&& input, + bool closedWorld = false); + TranslateToFuzzReader(Module& wasm, + std::string& filename, + bool closedWorld = false); void pickPasses(OptimizationOptions& options); void setAllowMemory(bool allowMemory_) { allowMemory = allowMemory_; } @@ -77,6 +81,8 @@ class TranslateToFuzzReader { Module& wasm; private: + // Whether the module will be tested in a closed-world environment. + bool closedWorld; Builder builder; Random random; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 7e87f4f5850..ab200a12635 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -32,8 +32,10 @@ namespace { } // anonymous namespace TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, - std::vector&& input) - : wasm(wasm), builder(wasm), random(std::move(input), wasm.features) { + std::vector&& input, + bool closedWorld) + : wasm(wasm), closedWorld(closedWorld), builder(wasm), + random(std::move(input), wasm.features) { // Half the time add no unreachable code so that we'll execute the most code // as possible with no early exits. @@ -50,9 +52,11 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, } TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, - std::string& filename) - : TranslateToFuzzReader( - wasm, read_file>(filename, Flags::Binary)) {} + std::string& filename, + bool closedWorld) + : TranslateToFuzzReader(wasm, + read_file>(filename, Flags::Binary), + closedWorld) {} void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { // Pick random passes to further shape the wasm. This is similar to how we @@ -197,8 +201,11 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { case 41: // GC specific passes. if (wasm.features.hasGC()) { - // Most of these depend on closed world, so just set that. + // Most of these depend on closed world, so just set that. Set it both + // on the global pass options, and in the internal state of this + // TranslateToFuzzReader instance. options.passOptions.closedWorld = true; + closedWorld = true; switch (upTo(16)) { case 0: diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 3e429a976fd..2f8d225802b 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -303,7 +303,8 @@ int main(int argc, const char* argv[]) { } } if (translateToFuzz) { - TranslateToFuzzReader reader(wasm, options.extra["infile"]); + TranslateToFuzzReader reader( + wasm, options.extra["infile"], options.passOptions.closedWorld); if (fuzzPasses) { reader.pickPasses(options); } From 3f82ffc70362bf967d91d3cb56ee4c8c5ebe1161 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Dec 2024 12:21:15 -0800 Subject: [PATCH 177/622] Remove incorrect warning when reading name section (#7140) When we refactored how the name section is read, we accidentally left an old warning about invalid field name indices in place. The old warning code compares the type index from the names section to the size of the parsed type vector to determine if the index is out-of-bounds. Now that we parse the name section before the type section, this is no longer correct. Delete the old warning; we already have a new, correct warning for out-of-bound indices when we parse the type section. --- src/wasm/wasm-binary.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 86b3ea899c4..f710c8bc162 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4730,11 +4730,6 @@ void WasmBinaryReader::findAndReadNames() { auto numTypes = getU32LEB(); for (size_t i = 0; i < numTypes; i++) { auto typeIndex = getU32LEB(); - bool validType = - typeIndex < types.size() && types[typeIndex].isStruct(); - if (!validType) { - std::cerr << "warning: invalid field index in name field section\n"; - } auto numFields = getU32LEB(); NameProcessor processor; for (size_t i = 0; i < numFields; i++) { From 729ea41d145d369b203dca6f70b251ea365cb3d0 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 6 Dec 2024 12:34:19 -0800 Subject: [PATCH 178/622] Add bulk-memory-opt feature and ignore call-indirect-overlong (#7139) LLVM recently split the bulk-memory-opt feature out from bulk-memory, containing just memory.copy and memory.fill. This change follows that, making bulk-memory-opt also enabled when all of bulk-memory is enabled. It also introduces call-indirect-overlong following LLVM, but ignores it, since Binaryen has always allowed the encoding (i.e. command line flags enabling or disabling the feature are accepted but ignored). --- src/passes/LLVMMemoryCopyFillLowering.cpp | 4 +-- src/passes/OptimizeInstructions.cpp | 4 +-- src/tools/tool-options.h | 27 +++++++++++----- src/wasm-binary.h | 2 ++ src/wasm-features.h | 19 ++++++++++- src/wasm/wasm-binary.cpp | 10 ++++++ src/wasm/wasm-validator.cpp | 12 +++---- src/wasm/wasm.cpp | 2 ++ test/binaryen.js/kitchen-sink.js.txt | 2 +- test/example/c-api-kitchen-sink.txt | 2 +- test/lit/help/wasm-as.test | 12 +++++++ test/lit/help/wasm-ctor-eval.test | 12 +++++++ test/lit/help/wasm-dis.test | 12 +++++++ test/lit/help/wasm-emscripten-finalize.test | 12 +++++++ test/lit/help/wasm-merge.test | 12 +++++++ test/lit/help/wasm-metadce.test | 16 ++++++++++ test/lit/help/wasm-opt.test | 16 ++++++++++ test/lit/help/wasm-reduce.test | 12 +++++++ test/lit/help/wasm-split.test | 12 +++++++ test/lit/help/wasm2js.test | 16 ++++++++++ test/lld/em_asm_pthread.wasm.out | 2 +- ..._roundtrip_print-features_all-features.txt | 2 ++ test/unit/input/bulkmem_target_feature.wasm | Bin 219 -> 223 bytes test/unit/test_features.py | 30 ++++++++++++++++-- 24 files changed, 225 insertions(+), 25 deletions(-) diff --git a/src/passes/LLVMMemoryCopyFillLowering.cpp b/src/passes/LLVMMemoryCopyFillLowering.cpp index e5b940a5af2..d2a7e5c482d 100644 --- a/src/passes/LLVMMemoryCopyFillLowering.cpp +++ b/src/passes/LLVMMemoryCopyFillLowering.cpp @@ -49,7 +49,7 @@ struct LLVMMemoryCopyFillLowering } void run(Module* module) override { - if (!module->features.hasBulkMemory()) { + if (!module->features.hasBulkMemoryOpt()) { return; } if (module->features.hasMemory64() || module->features.hasMultiMemory()) { @@ -108,7 +108,7 @@ struct LLVMMemoryCopyFillLowering } else { module->removeFunction(memFillFuncName); } - module->features.disable(FeatureSet::BulkMemory); + module->features.setBulkMemoryOpt(false); } void createMemoryCopyFunc(Module* module) { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 881b7ea1f34..792cf623555 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1275,7 +1275,7 @@ struct OptimizeInstructions if (curr->type == Type::unreachable) { return; } - assert(getModule()->features.hasBulkMemory()); + assert(getModule()->features.hasBulkMemoryOpt()); if (auto* ret = optimizeMemoryCopy(curr)) { return replaceCurrent(ret); } @@ -1285,7 +1285,7 @@ struct OptimizeInstructions if (curr->type == Type::unreachable) { return; } - assert(getModule()->features.hasBulkMemory()); + assert(getModule()->features.hasBulkMemoryOpt()); if (auto* ret = optimizeMemoryFill(curr)) { return replaceCurrent(ret); } diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index f900d76ba48..c7acf1d4618 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -82,7 +82,16 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::MutableGlobals, "mutable globals") .addFeature(FeatureSet::TruncSat, "nontrapping float-to-int operations") .addFeature(FeatureSet::SIMD, "SIMD operations and types") - .addFeature(FeatureSet::BulkMemory, "bulk memory operations") + .addFeature(FeatureSet::BulkMemory, + "bulk memory operations", + FeatureSet(FeatureSet::BulkMemoryOpt)) + .addFeature(FeatureSet::BulkMemoryOpt, + "memory.copy and memory.fill", + FeatureSet::None, + FeatureSet(FeatureSet::BulkMemory)) + .addFeature(FeatureSet::CallIndirectOverlong, + "LEB encoding of call-indirect (Ignored for compatibility as " + "it has no effect on Binaryen)") .addFeature(FeatureSet::ExceptionHandling, "exception handling operations") .addFeature(FeatureSet::TailCall, "tail call operations") @@ -200,16 +209,18 @@ struct ToolOptions : public Options { } ToolOptions& addFeature(FeatureSet::Feature feature, - const std::string& description) { + const std::string& description, + FeatureSet impliedEnable = FeatureSet::None, + FeatureSet impliedDisable = FeatureSet::None) { (*this) .add(std::string("--enable-") + FeatureSet::toString(feature), "", std::string("Enable ") + description, ToolOptionsCategory, Arguments::Zero, - [this, feature](Options*, const std::string&) { - enabledFeatures.set(feature, true); - disabledFeatures.set(feature, false); + [this, feature, impliedEnable](Options*, const std::string&) { + enabledFeatures.set(feature | impliedEnable, true); + disabledFeatures.set(feature | impliedEnable, false); }) .add(std::string("--disable-") + FeatureSet::toString(feature), @@ -217,9 +228,9 @@ struct ToolOptions : public Options { std::string("Disable ") + description, ToolOptionsCategory, Arguments::Zero, - [this, feature](Options*, const std::string&) { - enabledFeatures.set(feature, false); - disabledFeatures.set(feature, true); + [this, feature, impliedDisable](Options*, const std::string&) { + enabledFeatures.set(feature | impliedDisable, false); + disabledFeatures.set(feature | impliedDisable, true); }); return *this; } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 7c32e216957..163a62b1e41 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -396,6 +396,8 @@ extern const char* MultiMemoryFeature; extern const char* TypedContinuationsFeature; extern const char* SharedEverythingFeature; extern const char* FP16Feature; +extern const char* BulkMemoryOptFeature; +extern const char* CallIndirectOverlongFeature; enum Subsection { NameModule = 0, diff --git a/src/wasm-features.h b/src/wasm-features.h index 92b07b5477f..20eec56bbab 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -27,6 +27,8 @@ namespace wasm { struct FeatureSet { enum Feature : uint32_t { + // These features are intended to those documented in tool-conventions: + // https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#target-features-section None = 0, Atomics = 1 << 0, MutableGlobals = 1 << 1, @@ -47,11 +49,16 @@ struct FeatureSet { TypedContinuations = 1 << 16, SharedEverything = 1 << 17, FP16 = 1 << 18, + BulkMemoryOpt = 1 << 19, // Just the memory.copy and fill operations + // This features is a no-op for compatibility. Having it in this list means + // that we can automatically generate tool flags that set it, but otherwise + // it does nothing. Binaryen always accepts LEB call-indirect encodings. + CallIndirectOverlong = 1 << 20, MVP = None, // Keep in sync with llvm default features: // https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153 Default = SignExt | MutableGlobals, - All = (1 << 19) - 1, + All = (1 << 21) - 1, }; static std::string toString(Feature f) { @@ -94,6 +101,10 @@ struct FeatureSet { return "shared-everything"; case FP16: return "fp16"; + case BulkMemoryOpt: + return "bulk-memory-opt"; + case CallIndirectOverlong: + return "call-indirect-overlong"; default: WASM_UNREACHABLE("unexpected feature"); } @@ -145,6 +156,11 @@ struct FeatureSet { return (features & SharedEverything) != 0; } bool hasFP16() const { return (features & FP16) != 0; } + bool hasBulkMemoryOpt() const { + bool has = (features & BulkMemoryOpt) != 0; + assert(has || !hasBulkMemory()); + return has; + } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -169,6 +185,7 @@ struct FeatureSet { void setTypedContinuations(bool v = true) { set(TypedContinuations, v); } void setSharedEverything(bool v = true) { set(SharedEverything, v); } void setFP16(bool v = true) { set(FP16, v); } + void setBulkMemoryOpt(bool v = true) { set(BulkMemoryOpt, v); } void setMVP() { features = MVP; } void setAll() { features = All; } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f710c8bc162..0c931f51864 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1354,6 +1354,10 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::SharedEverythingFeature; case FeatureSet::FP16: return BinaryConsts::CustomSections::FP16Feature; + case FeatureSet::BulkMemoryOpt: + return BinaryConsts::CustomSections::BulkMemoryOptFeature; + case FeatureSet::CallIndirectOverlong: + return BinaryConsts::CustomSections::CallIndirectOverlongFeature; case FeatureSet::None: case FeatureSet::Default: case FeatureSet::All: @@ -4790,6 +4794,12 @@ void WasmBinaryReader::readFeatures(size_t payloadLen) { feature = FeatureSet::Atomics; } else if (name == BinaryConsts::CustomSections::BulkMemoryFeature) { feature = FeatureSet::BulkMemory; + if (used) { + // For backward compatibility, enable this dependent feature. + feature |= FeatureSet::BulkMemoryOpt; + } + } else if (name == BinaryConsts::CustomSections::BulkMemoryOptFeature) { + feature = FeatureSet::BulkMemoryOpt; } else if (name == BinaryConsts::CustomSections::ExceptionHandlingFeature) { feature = FeatureSet::ExceptionHandling; } else if (name == BinaryConsts::CustomSections::MutableGlobalsFeature) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index e295d393179..242e07c4340 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -1526,10 +1526,10 @@ void FunctionValidator::visitDataDrop(DataDrop* curr) { } void FunctionValidator::visitMemoryCopy(MemoryCopy* curr) { - shouldBeTrue( - getModule()->features.hasBulkMemory(), - curr, - "Bulk memory operations require bulk memory [--enable-bulk-memory]"); + shouldBeTrue(getModule()->features.hasBulkMemoryOpt(), + curr, + "memory.copy operations require bulk memory operations " + "[--enable-bulk-memory-opt]"); shouldBeEqualOrFirstIsUnreachable( curr->type, Type(Type::none), curr, "memory.copy must have type none"); auto* destMemory = getModule()->getMemoryOrNull(curr->destMemory); @@ -1561,9 +1561,9 @@ void FunctionValidator::visitMemoryCopy(MemoryCopy* curr) { void FunctionValidator::visitMemoryFill(MemoryFill* curr) { auto* memory = getModule()->getMemoryOrNull(curr->memory); shouldBeTrue( - getModule()->features.hasBulkMemory(), + getModule()->features.hasBulkMemoryOpt(), curr, - "Bulk memory operations require bulk memory [--enable-bulk-memory]"); + "memory.fill operations require bulk memory [--enable-bulk-memory-opt]"); shouldBeEqualOrFirstIsUnreachable( curr->type, Type(Type::none), curr, "memory.fill must have type none"); shouldBeEqualOrFirstIsUnreachable( diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index f5806b184e2..3e04f29ecef 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -57,6 +57,8 @@ const char* MultiMemoryFeature = "multimemory"; const char* TypedContinuationsFeature = "typed-continuations"; const char* SharedEverythingFeature = "shared-everything"; const char* FP16Feature = "fp16"; +const char* BulkMemoryOptFeature = "bulk-memory-opt"; +const char* CallIndirectOverlongFeature = "call-indirect-overlong"; } // namespace CustomSections } // namespace BinaryConsts diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index d38c168571e..637b1ab9549 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -33,7 +33,7 @@ Features.RelaxedSIMD: 4096 Features.ExtendedConst: 8192 Features.Strings: 16384 Features.MultiMemory: 32768 -Features.All: 524287 +Features.All: 2097151 InvalidId: 0 BlockId: 1 IfId: 2 diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 8a5b6dc87b7..d3e6a6767d6 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -47,7 +47,7 @@ BinaryenFeatureMemory64: 2048 BinaryenFeatureRelaxedSIMD: 4096 BinaryenFeatureExtendedConst: 8192 BinaryenFeatureStrings: 16384 -BinaryenFeatureAll: 524287 +BinaryenFeatureAll: 2097151 (f32.neg (f32.const -33.61199951171875) ) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 5dd4b151573..7f2e1bc9452 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -64,6 +64,18 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling operations ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-exception-handling Disable exception handling operations diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index abb80b12eb6..06e2c4a0dfc 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -71,6 +71,18 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling operations ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-exception-handling Disable exception handling operations diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index 90bc12b9918..d698afe86f0 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -57,6 +57,18 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling operations ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-exception-handling Disable exception handling operations diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index cdd378d4082..5f9f17ff286 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -99,6 +99,18 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling operations ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-exception-handling Disable exception handling operations diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index ee1fed13a92..f83fc5c0f1e 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -87,6 +87,18 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling operations ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-exception-handling Disable exception handling operations diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index e9883464133..0be234ac791 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -699,6 +699,22 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and +;; CHECK-NEXT: memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and +;; CHECK-NEXT: memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of +;; CHECK-NEXT: call-indirect (Ignored for +;; CHECK-NEXT: compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of +;; CHECK-NEXT: call-indirect (Ignored for +;; CHECK-NEXT: compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 06a9e5e8488..630a64588a1 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -708,6 +708,22 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and +;; CHECK-NEXT: memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and +;; CHECK-NEXT: memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of +;; CHECK-NEXT: call-indirect (Ignored for +;; CHECK-NEXT: compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of +;; CHECK-NEXT: call-indirect (Ignored for +;; CHECK-NEXT: compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index cc75aed2b11..d02d46fd7a7 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -93,6 +93,18 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling operations ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-exception-handling Disable exception handling operations diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index e5e73656205..04d74142160 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -196,6 +196,18 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of call-indirect +;; CHECK-NEXT: (Ignored for compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling operations ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-exception-handling Disable exception handling operations diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 50e229a1df9..a326c75dbe9 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -662,6 +662,22 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-bulk-memory Disable bulk memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-bulk-memory-opt Enable memory.copy and +;; CHECK-NEXT: memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-bulk-memory-opt Disable memory.copy and +;; CHECK-NEXT: memory.fill +;; CHECK-NEXT: +;; CHECK-NEXT: --enable-call-indirect-overlong Enable LEB encoding of +;; CHECK-NEXT: call-indirect (Ignored for +;; CHECK-NEXT: compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-call-indirect-overlong Disable LEB encoding of +;; CHECK-NEXT: call-indirect (Ignored for +;; CHECK-NEXT: compatibility as it has no +;; CHECK-NEXT: effect on Binaryen) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-exception-handling Enable exception handling ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lld/em_asm_pthread.wasm.out b/test/lld/em_asm_pthread.wasm.out index 8284cc6dc59..aae8441f654 100644 --- a/test/lld/em_asm_pthread.wasm.out +++ b/test/lld/em_asm_pthread.wasm.out @@ -12831,5 +12831,5 @@ ) ) ;; custom section "producers", size 172 - ;; features section: threads, mutable-globals, bulk-memory, sign-ext + ;; features section: threads, mutable-globals, bulk-memory, sign-ext, bulk-memory-opt ) diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt index 901f43bc504..e6b611d4a4e 100644 --- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt +++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt @@ -17,6 +17,8 @@ --enable-typed-continuations --enable-shared-everything --enable-fp16 +--enable-bulk-memory-opt +--enable-call-indirect-overlong (module (type $0 (func (result v128 externref))) (func $foo (type $0) (result v128 externref) diff --git a/test/unit/input/bulkmem_target_feature.wasm b/test/unit/input/bulkmem_target_feature.wasm index 3c69d232368c11928babee38d72aa43b4d45ab3d..7a762820cb8096764ecc791ec5bf0c8f101ec9b3 100755 GIT binary patch delta 42 xcmcc3c%N~?9%Uu|lEk9))ROqL)Wnj~qSRtWZT_UvoNV3P)ZF}{O5OZ|5&&5x577Vs delta 38 tcmcc5c$;y;9(g(blEk9))ROqL)Wnj~qSRtWZSJJfoNV3P)ZF}{N&p*Z4n+U} diff --git a/test/unit/test_features.py b/test/unit/test_features.py index f5c3fabc507..c115719e7c8 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -30,6 +30,9 @@ def check_sign_ext(self, module, error): def check_bulk_mem(self, module, error): self.check_feature(module, error, '--enable-bulk-memory') + def check_bulk_mem_opt(self, module, error): + self.check_feature(module, error, '--enable-bulk-memory-opt') + def check_exception_handling(self, module, error): self.check_feature(module, error, '--enable-exception-handling') @@ -135,8 +138,11 @@ def test_bulk_mem_inst(self): ) ) ''' + self.check_bulk_mem_opt(module, + 'memory.copy operations require bulk memory operations [--enable-bulk-memory-opt]') + # Test that enabling bulk-memory also enables bulk-memory-opt self.check_bulk_mem(module, - 'Bulk memory operations require bulk memory [--enable-bulk-memory]') + 'memory.copy operations require bulk memory operations [--enable-bulk-memory-opt]') def test_bulk_mem_segment(self): module = ''' @@ -306,6 +312,23 @@ def test_cont_type(self): ''' self.check_typed_continuations(module, 'all used types should be allowed') + def test_call_indirect_overlong(self): + # Check that the call-indirect-overlong enable and disable are ignored. + module = ''' + (module) + ''' + + def check_nop(flag): + p = shared.run_process( + shared.WASM_OPT + ['--mvp-features', '--print', '-o', os.devnull] + + [flag], + input=module, + check=False, + capture_output=True) + self.assertEqual(p.returncode, 0) + check_nop('--enable-call-indirect-overlong') + check_nop('--disable-call-indirect-overlong') + class TargetFeaturesSectionTest(utils.BinaryenTestCase): def test_atomics(self): @@ -314,10 +337,10 @@ def test_atomics(self): self.check_features(filename, ['threads']) self.assertIn('i32.atomic.rmw.add', self.disassemble(filename)) - def test_bulk_memory(self): + def test_bulk_memory_opt(self): filename = 'bulkmem_target_feature.wasm' self.roundtrip(filename) - self.check_features(filename, ['bulk-memory']) + self.check_features(filename, ['bulk-memory-opt']) self.assertIn('memory.copy', self.disassemble(filename)) def test_nontrapping_fptoint(self): @@ -427,4 +450,5 @@ def test_emit_all_features(self): '--enable-typed-continuations', '--enable-shared-everything', '--enable-fp16', + '--enable-bulk-memory-opt', ], p2.stdout.splitlines()) From 7f62a423ee4bd908f485d01945b71786176b926a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Dec 2024 14:46:24 -0800 Subject: [PATCH 179/622] Fuzzer: Add call-ref, call-ref-catch imports (#7137) Similar to call-export*, these imports call a wasm function from outside the module. The difference is that we send a function reference for them to call (rather than an export index). This gives more coverage, first by sending a ref from wasm to JS, and also since we will now try to call anything that is sent. Exports, in comparison, are filtered by the fuzzer to things that JS can handle, so this may lead to more traps, but maybe also some new situations. This also leads to adding more logic to execution-results.h to model JS trapping properly. fuzz_shell.js is refactored to allow sharing code between call-export* and call-ref*. --- scripts/fuzz_shell.js | 89 ++++---- src/tools/execution-results.h | 55 ++++- src/tools/fuzzing.h | 6 +- src/tools/fuzzing/fuzzing.cpp | 122 ++++++++--- test/lit/exec/fuzzing-api.wast | 199 +++++++++++++++++- test/lit/passes/gufa-closed-open.wast | 6 +- test/passes/fuzz_metrics_noprint.bin.txt | 52 ++--- .../fuzz_metrics_passes_noprint.bin.txt | 53 +++-- ...e-to-fuzz_all-features_metrics_noprint.txt | 87 ++++---- 9 files changed, 493 insertions(+), 176 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 782040dac84..95176bbe65b 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -160,6 +160,49 @@ function callFunc(func) { return func.apply(null, args); } +// Calls a given function in a try-catch, swallowing JS exceptions, and return 1 +// if we did in fact swallow an exception. Wasm traps are not swallowed (see +// details below). +function tryCall(func) { + try { + func(); + return 0; + } catch (e) { + // We only want to catch exceptions, not wasm traps: traps should still + // halt execution. Handling this requires different code in wasm2js, so + // check for that first (wasm2js does not define RuntimeError, so use + // that for the check - when wasm2js is run, we override the entire + // WebAssembly object with a polyfill, so we know exactly what it + // contains). + var wasm2js = !WebAssembly.RuntimeError; + if (!wasm2js) { + // When running native wasm, we can detect wasm traps. + if (e instanceof WebAssembly.RuntimeError) { + throw e; + } + } + var text = e + ''; + // We must not swallow host limitations here: a host limitation is a + // problem that means we must not compare the outcome here to any other + // VM. + var hostIssues = ['requested new array is too large', + 'out of memory', + 'Maximum call stack size exceeded']; + if (wasm2js) { + // When wasm2js does trap, it just throws an "abort" error. + hostIssues.push('abort'); + } + for (var hostIssue of hostIssues) { + if (text.includes(hostIssue)) { + throw e; + } + } + // Otherwise, this is a normal exception we want to catch (a wasm + // exception, or a conversion error on the wasm/JS boundary, etc.). + return 1; + } +} + // Table get/set operations need a BigInt if the table has 64-bit indexes. This // adds a proper cast as needed. function toAddressType(table, index) { @@ -204,43 +247,15 @@ var imports = { callFunc(exportList[index].value); }, 'call-export-catch': (index) => { - try { - callFunc(exportList[index].value); - return 0; - } catch (e) { - // We only want to catch exceptions, not wasm traps: traps should still - // halt execution. Handling this requires different code in wasm2js, so - // check for that first (wasm2js does not define RuntimeError, so use - // that for the check - when wasm2js is run, we override the entire - // WebAssembly object with a polyfill, so we know exactly what it - // contains). - var wasm2js = !WebAssembly.RuntimeError; - if (!wasm2js) { - // When running native wasm, we can detect wasm traps. - if (e instanceof WebAssembly.RuntimeError) { - throw e; - } - } - var text = e + ''; - // We must not swallow host limitations here: a host limitation is a - // problem that means we must not compare the outcome here to any other - // VM. - var hostIssues = ['requested new array is too large', - 'out of memory', - 'Maximum call stack size exceeded']; - if (wasm2js) { - // When wasm2js does trap, it just throws an "abort" error. - hostIssues.push('abort'); - } - for (var hostIssue of hostIssues) { - if (text.includes(hostIssue)) { - throw e; - } - } - // Otherwise, this is a normal exception we want to catch (a wasm - // exception, or a conversion error on the wasm/JS boundary, etc.). - return 1; - } + return tryCall(() => callFunc(exportList[index].value)); + }, + + // Funcref operations. + 'call-ref': (ref) => { + callFunc(ref); + }, + 'call-ref-catch': (ref) => { + return tryCall(() => callFunc(ref)); }, }, // Emscripten support. diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 25d0c077237..bffc4e8f2b2 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -105,13 +105,25 @@ struct LoggingExternalInterface : public ShellExternalInterface { tableStore(exportedTable, index, arguments[1]); return {}; } else if (import->base == "call-export") { - callExport(arguments[0].geti32()); + callExportAsJS(arguments[0].geti32()); // Return nothing. If we wanted to return a value we'd need to have // multiple such functions, one for each signature. return {}; } else if (import->base == "call-export-catch") { try { - callExport(arguments[0].geti32()); + callExportAsJS(arguments[0].geti32()); + return {Literal(int32_t(0))}; + } catch (const WasmException& e) { + return {Literal(int32_t(1))}; + } + } else if (import->base == "call-ref") { + callRefAsJS(arguments[0]); + // Return nothing. If we wanted to return a value we'd need to have + // multiple such functions, one for each signature. + return {}; + } else if (import->base == "call-ref-catch") { + try { + callRefAsJS(arguments[0]); return {Literal(int32_t(0))}; } catch (const WasmException& e) { return {Literal(int32_t(1))}; @@ -145,7 +157,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { throwException(WasmException{Literal(payload)}); } - Literals callExport(Index index) { + Literals callExportAsJS(Index index) { if (index >= wasm.exports.size()) { // No export. throwEmptyException(); @@ -155,20 +167,47 @@ struct LoggingExternalInterface : public ShellExternalInterface { // No callable export. throwEmptyException(); } - auto* func = wasm.getFunction(exp->value); + return callFunctionAsJS(exp->value); + } + + Literals callRefAsJS(Literal ref) { + if (!ref.isFunction()) { + // Not a callable ref. + throwEmptyException(); + } + return callFunctionAsJS(ref.getFunc()); + } - // TODO JS traps on some types on the boundary, which we should behave the - // same on. For now, this is not needed because the fuzzer will prune all - // non-JS-compatible exports anyhow. + // Call a function in a "JS-ey" manner, adding arguments as needed, and + // throwing if necessary, the same way JS does. + Literals callFunctionAsJS(Name name) { + auto* func = wasm.getFunction(name); - // Send default values as arguments, or trap if we need anything else. + // Send default values as arguments, or error if we need anything else. Literals arguments; for (const auto& param : func->getParams()) { + // An i64 param can work from JS, but fuzz_shell provides 0, which errors + // on attempts to convert it to BigInt. v128 cannot work at all. + if (param == Type::i64 || param == Type::v128) { + throwEmptyException(); + } if (!param.isDefaultable()) { throwEmptyException(); } arguments.push_back(Literal::makeZero(param)); } + + // Error on illegal results. Note that this happens, as per JS semantics, + // *before* the call. + for (const auto& result : func->getResults()) { + // An i64 result is fine: a BigInt will be provided. But v128 still + // errors. + if (result == Type::v128) { + throwEmptyException(); + } + } + + // Call the function. return instance->callFunction(func->name, arguments); } diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index a3261ccbe59..78219045c71 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -115,6 +115,8 @@ class TranslateToFuzzReader { Name tableSetImportName; Name callExportImportName; Name callExportCatchImportName; + Name callRefImportName; + Name callRefCatchImportName; std::unordered_map> globalsByType; std::unordered_map> mutableGlobalsByType; @@ -244,7 +246,9 @@ class TranslateToFuzzReader { Expression* makeImportThrowing(Type type); Expression* makeImportTableGet(); Expression* makeImportTableSet(Type type); - Expression* makeImportCallExport(Type type); + // Call either an export or a ref. We do this from a single function to better + // control the frequency of each. + Expression* makeImportCallCode(Type type); Expression* makeMemoryHashLogging(); // Function creation diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index ab200a12635..a7f5e0d018f 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -771,22 +771,33 @@ void TranslateToFuzzReader::addImportLoggingSupport() { } void TranslateToFuzzReader::addImportCallingSupport() { + if (wasm.features.hasReferenceTypes() && closedWorld) { + // In closed world mode we must *remove* the call-ref* imports, if they + // exist in the initial content. These are not valid to call in closed-world + // mode as they call function references. (Another solution here would be to + // make closed-world issue validation errors on these imports, but that + // would require changes to the general-purpose validator.) + for (auto& func : wasm.functions) { + if (func->imported() && func->module == "fuzzing-support" && + func->base.startsWith("call-ref")) { + // Make it non-imported, and with a simple body. + func->module = func->base = Name(); + auto results = func->getResults(); + func->body = + results.isConcrete() ? makeConst(results) : makeNop(Type::none); + } + } + } + // Only add these some of the time, as they inhibit some fuzzing (things like // wasm-ctor-eval and wasm-merge are sensitive to the wasm being able to call - // its own exports, and to care about the indexes of the exports): - // - // 0 - none - // 1 - call-export - // 2 - call-export-catch - // 3 - call-export & call-export-catch - // 4 - none - // 5 - none - // - auto choice = upTo(6); - if (choice >= 4) { + // its own exports, and to care about the indexes of the exports). + if (oneIn(2)) { return; } + auto choice = upTo(16); + if (choice & 1) { // Given an export index, call it from JS. callExportImportName = Names::getValidFunctionName(wasm, "call-export"); @@ -811,6 +822,34 @@ void TranslateToFuzzReader::addImportCallingSupport() { func->type = Signature(Type::i32, Type::i32); wasm.addFunction(std::move(func)); } + + // If the wasm will be used for closed-world testing, we cannot use the + // call-ref variants, as mentioned before. + if (wasm.features.hasReferenceTypes() && !closedWorld) { + if (choice & 4) { + // Given an funcref, call it from JS. + callRefImportName = Names::getValidFunctionName(wasm, "call-ref"); + auto func = std::make_unique(); + func->name = callRefImportName; + func->module = "fuzzing-support"; + func->base = "call-ref"; + func->type = Signature({Type(HeapType::func, Nullable)}, Type::none); + wasm.addFunction(std::move(func)); + } + + if (choice & 8) { + // Given an funcref, call it from JS and catch all exceptions (similar + // to callExportCatch), return 1 if we caught). + callRefCatchImportName = + Names::getValidFunctionName(wasm, "call-ref-catch"); + auto func = std::make_unique(); + func->name = callRefCatchImportName; + func->module = "fuzzing-support"; + func->base = "call-ref-catch"; + func->type = Signature(Type(HeapType::func, Nullable), Type::i32); + wasm.addFunction(std::move(func)); + } + } } void TranslateToFuzzReader::addImportThrowingSupport() { @@ -998,27 +1037,48 @@ Expression* TranslateToFuzzReader::makeImportTableSet(Type type) { Type::none); } -Expression* TranslateToFuzzReader::makeImportCallExport(Type type) { - // The none-returning variant just does the call. The i32-returning one - // catches any errors and returns 1 when it saw an error. Based on the - // variant, pick which to call, and the maximum index to call. - Name target; +Expression* TranslateToFuzzReader::makeImportCallCode(Type type) { + // Call code: either an export or a ref. Each has a catching and non-catching + // variant. The catching variants return i32, the others none. + assert(type == Type::none || type == Type::i32); + auto catching = type == Type::i32; + auto exportTarget = + catching ? callExportCatchImportName : callExportImportName; + auto refTarget = catching ? callRefCatchImportName : callRefImportName; + + // We want to call a ref less often, as refs are more likely to error (a + // function reference can have arbitrary params and results, including things + // that error on the JS boundary; an export is already filtered for such + // things in some cases - when we legalize the boundary - and even if not, we + // emit lots of void(void) functions - all the invoke_foo functions - that are + // safe to call). + if (refTarget) { + // This matters a lot more in the variants that do *not* catch (in the + // catching ones, we just get a result of 1, but when not caught it halts + // execution). + if ((catching && (!exportTarget || oneIn(2))) || (!catching && oneIn(4))) { + // Most of the time make a non-nullable funcref, to avoid errors. + auto refType = Type(HeapType::func, oneIn(10) ? Nullable : NonNullable); + return builder.makeCall(refTarget, {make(refType)}, type); + } + } + + if (!exportTarget) { + // We decided not to emit a call-ref here, due to fear of erroring, and + // there is no call-export, so just emit something trivial. + return makeTrivial(type); + } + + // Pick the maximum export index to call. Index maxIndex = wasm.exports.size(); - if (type == Type::none) { - target = callExportImportName; - } else if (type == Type::i32) { - target = callExportCatchImportName; - // This never traps, so we can be less careful, but we do still want to - // avoid trapping a lot as executing code is more interesting. (Note that + if (type == Type::i32) { + // This swallows errors, so we can be less careful, but we do still want to + // avoid swallowing a lot as executing code is more interesting. (Note that // even though we double here, the risk is not that great: we are still // adding functions as we go, so the first half of functions/exports can // double here and still end up in bounds by the time we've added them all.) maxIndex = (maxIndex + 1) * 2; - } else { - WASM_UNREACHABLE("bad import.call"); } - // We must have set up the target function. - assert(target); // Most of the time, call a valid export index in the range we picked, but // sometimes allow anything at all. @@ -1027,7 +1087,7 @@ Expression* TranslateToFuzzReader::makeImportCallExport(Type type) { index = builder.makeBinary( RemUInt32, index, builder.makeConst(int32_t(maxIndex))); } - return builder.makeCall(target, {index}, type); + return builder.makeCall(exportTarget, {index}, type); } Expression* TranslateToFuzzReader::makeMemoryHashLogging() { @@ -1705,8 +1765,8 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { options.add(FeatureSet::Atomics, &Self::makeAtomic); } if (type == Type::i32) { - if (callExportCatchImportName) { - options.add(FeatureSet::MVP, &Self::makeImportCallExport); + if (callExportCatchImportName || callRefCatchImportName) { + options.add(FeatureSet::MVP, &Self::makeImportCallCode); } options.add(FeatureSet::ReferenceTypes, &Self::makeRefIsNull); options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, @@ -1787,8 +1847,8 @@ Expression* TranslateToFuzzReader::_makenone() { if (tableSetImportName) { options.add(FeatureSet::ReferenceTypes, &Self::makeImportTableSet); } - if (callExportImportName) { - options.add(FeatureSet::MVP, &Self::makeImportCallExport); + if (callExportImportName || callRefImportName) { + options.add(FeatureSet::MVP, &Self::makeImportCallCode); } return (this->*pick(options))(Type::none); } diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 38a8ce41bbe..eae95fc0a87 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -16,6 +16,9 @@ (import "fuzzing-support" "call-export" (func $call.export (param i32))) (import "fuzzing-support" "call-export-catch" (func $call.export.catch (param i32) (result i32))) + (import "fuzzing-support" "call-ref" (func $call.ref (param funcref))) + (import "fuzzing-support" "call-ref-catch" (func $call.ref.catch (param funcref) (result i32))) + (table $table 10 20 funcref) ;; Note that the exported table appears first here, but in the binary and in @@ -102,7 +105,6 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] - ;; CHECK-NEXT: warning: no passes specified, not doing any work (func $export.calling.catching (export "export.calling.catching") ;; At index 0 in the exports we have $logging, so we will do those loggings, ;; then log a 0 as no exception happens. @@ -118,6 +120,162 @@ ) ) ) + + ;; CHECK: [fuzz-exec] calling ref.calling + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [exception thrown: __private ()] + (func $ref.calling (export "ref.calling") + ;; This will emit some logging. + (call $call.ref + (ref.func $logging) + ) + ;; This will throw. + (call $call.ref + (ref.null func) + ) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.catching + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + (func $ref.calling.catching (export "ref.calling.catching") + ;; This will emit some logging, then log 0 as we do not error. + (call $log-i32 + (call $call.ref.catch + (ref.func $logging) + ) + ) + ;; The exception here is caught, and we'll log 1. + (call $log-i32 + (call $call.ref.catch + (ref.null func) + ) + ) + ) + + (func $legal (param $x i32) (result i32) + ;; Helper for the function below. All types here are legal for JS. + (call $log-i32 + (i32.const 12) + ) + ;; Also log the param to show it is 0, which is what $call.ref does for all + ;; params. + (call $log-i32 + (local.get $x) + ) + (i32.const 34) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.legal + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $ref.calling.legal (export "ref.calling.legal") + ;; It is fine to call-ref a function with params and results. The params get + ;; default values, and the results are ignored. All we will see here is the + ;; logging from the function, "12". + (call $call.ref + (ref.func $legal) + ) + ) + + (func $illegal (param $x i64) + ;; Helper for the function below. The param, an i64, causes a problem: when we + ;; call from JS we provide 0, but 0 throws when it tries to convert to BigInt. + (call $log-i32 + (i32.const 56) + ) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.illegal + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + (func $ref.calling.illegal (export "ref.calling.illegal") + ;; The i64 param causes an error here, so we will only log 1 because we catch an exception. + (call $log-i32 + (call $call.ref.catch + (ref.func $illegal) + ) + ) + ) + + (func $illegal-v128 (param $x v128) + ;; Helper for the function below. + (call $log-i32 + (i32.const 56) + ) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.illegal-v128 + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + (func $ref.calling.illegal-v128 (export "ref.calling.illegal-v128") + ;; As above, we throw on the v128 param, and log 1. + (call $log-i32 + (call $call.ref.catch + (ref.func $illegal-v128) + ) + ) + ) + + (func $illegal-result (result v128) + ;; Helper for the function below. The result is illegal for JS. + (call $log-i32 + (i32.const 910) + ) + (v128.const i32x4 1 2 3 4) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.illegal-result + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + (func $ref.calling.illegal-result (export "ref.calling.illegal-result") + ;; The v128 result causes an error here, so we will log 1 as an exception. The JS + ;; semantics determine that we do that check *before* the call, so the logging + ;; of 910 does not go through. + (call $log-i32 + (call $call.ref.catch + (ref.func $illegal-result) + ) + ) + ) + + (func $legal-result (result i64) + ;; Helper for the function below. + (call $log-i32 + (i32.const 910) + ) + (i64.const 90) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.legal-result + ;; CHECK-NEXT: [LoggingExternalInterface logging 910] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $ref.calling.legal-result (export "ref.calling.legal-result") + ;; Unlike v128, i64 is legal in a result. The JS VM just returns a BigInt. + (call $log-i32 + (call $call.ref.catch + (ref.func $legal-result) + ) + ) + ) + + (func $trap + ;; Helper for the function below. + (unreachable) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.trap + ;; CHECK-NEXT: [trap unreachable] + ;; CHECK-NEXT: warning: no passes specified, not doing any work + (func $ref.calling.trap (export "ref.calling.trap") + ;; We try to catch an exception here, but the target function traps, which is + ;; not something we can catch. We will trap here, and not log at all. + (call $log-i32 + (call $call.ref.catch + (ref.func $trap) + ) + ) + ) ) ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -144,9 +302,48 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + +;; CHECK: [fuzz-exec] calling ref.calling +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [exception thrown: __private ()] + +;; CHECK: [fuzz-exec] calling ref.calling.catching +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] + +;; CHECK: [fuzz-exec] calling ref.calling.legal +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling ref.calling.illegal +;; CHECK-NEXT: [LoggingExternalInterface logging 1] + +;; CHECK: [fuzz-exec] calling ref.calling.illegal-v128 +;; CHECK-NEXT: [LoggingExternalInterface logging 1] + +;; CHECK: [fuzz-exec] calling ref.calling.illegal-result +;; CHECK-NEXT: [LoggingExternalInterface logging 1] + +;; CHECK: [fuzz-exec] calling ref.calling.legal-result +;; CHECK-NEXT: [LoggingExternalInterface logging 910] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling ref.calling.trap +;; CHECK-NEXT: [trap unreachable] ;; CHECK-NEXT: [fuzz-exec] comparing export.calling ;; CHECK-NEXT: [fuzz-exec] comparing export.calling.catching ;; CHECK-NEXT: [fuzz-exec] comparing logging +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.catching +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.illegal +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.illegal-result +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.illegal-v128 +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.legal +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.legal-result +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.trap ;; CHECK-NEXT: [fuzz-exec] comparing table.getting ;; CHECK-NEXT: [fuzz-exec] comparing table.setting ;; CHECK-NEXT: [fuzz-exec] comparing throwing diff --git a/test/lit/passes/gufa-closed-open.wast b/test/lit/passes/gufa-closed-open.wast index 47add9df53b..689ee194f85 100644 --- a/test/lit/passes/gufa-closed-open.wast +++ b/test/lit/passes/gufa-closed-open.wast @@ -12,15 +12,15 @@ ;; OPEND: (type $2 (func (param i32))) - ;; OPEND: (import "fuzzing-support" "call-ref-catch" (func $external-caller (type $0) (param funcref))) + ;; OPEND: (import "outside" "call-ref-catch" (func $external-caller (type $0) (param funcref))) ;; CLOSE: (type $0 (func (param funcref))) ;; CLOSE: (type $1 (func)) ;; CLOSE: (type $2 (func (param i32))) - ;; CLOSE: (import "fuzzing-support" "call-ref-catch" (func $external-caller (type $0) (param funcref))) - (import "fuzzing-support" "call-ref-catch" (func $external-caller (param funcref))) + ;; CLOSE: (import "outside" "call-ref-catch" (func $external-caller (type $0) (param funcref))) + (import "outside" "call-ref-catch" (func $external-caller (param funcref))) ;; OPEND: (elem declare func $func) diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index 0fa206e0a1f..a2f996bcb38 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 45 - [funcs] : 60 + [exports] : 49 + [funcs] : 74 [globals] : 18 - [imports] : 5 + [imports] : 4 [memories] : 1 [memory-data] : 24 - [table-data] : 15 + [table-data] : 19 [tables] : 1 [tags] : 0 - [total] : 5475 - [vars] : 222 - Binary : 410 - Block : 870 - Break : 148 - Call : 271 - CallIndirect : 30 - Const : 915 - Drop : 51 - GlobalGet : 458 - GlobalSet : 323 - If : 293 - Load : 96 - LocalGet : 442 - LocalSet : 284 - Loop : 99 - Nop : 76 - RefFunc : 15 - Return : 78 - Select : 47 - Store : 44 + [total] : 5695 + [vars] : 227 + Binary : 430 + Block : 976 + Break : 188 + Call : 272 + CallIndirect : 20 + Const : 876 + Drop : 99 + GlobalGet : 504 + GlobalSet : 373 + If : 299 + Load : 111 + LocalGet : 365 + LocalSet : 299 + Loop : 108 + Nop : 58 + RefFunc : 19 + Return : 74 + Select : 40 + Store : 34 Switch : 2 Unary : 365 - Unreachable : 158 + Unreachable : 183 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index 9c8c25c1279..c3881104d0d 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,35 +1,34 @@ Metrics total - [exports] : 54 - [funcs] : 84 + [exports] : 30 + [funcs] : 47 [globals] : 17 [imports] : 4 [memories] : 1 [memory-data] : 11 - [table-data] : 22 + [table-data] : 18 [tables] : 1 [tags] : 0 - [total] : 8343 - [vars] : 264 - Binary : 597 - Block : 1335 - Break : 226 - Call : 346 - CallIndirect : 65 - Const : 1375 - Drop : 107 - GlobalGet : 719 - GlobalSet : 522 - If : 458 - Load : 139 - LocalGet : 650 - LocalSet : 441 - Loop : 165 - Nop : 97 - RefFunc : 22 - Return : 120 - Select : 71 - Store : 56 - Switch : 2 - Unary : 574 - Unreachable : 256 + [total] : 4738 + [vars] : 133 + Binary : 338 + Block : 781 + Break : 122 + Call : 249 + CallIndirect : 27 + Const : 780 + Drop : 105 + GlobalGet : 378 + GlobalSet : 288 + If : 216 + Load : 79 + LocalGet : 396 + LocalSet : 252 + Loop : 89 + Nop : 43 + RefFunc : 18 + Return : 70 + Select : 37 + Store : 36 + Unary : 294 + Unreachable : 140 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 8df9d033cc0..e2e9b8053df 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,54 +1,57 @@ Metrics total - [exports] : 9 - [funcs] : 10 + [exports] : 6 + [funcs] : 6 [globals] : 4 [imports] : 8 [memories] : 1 [memory-data] : 112 - [table-data] : 2 + [table-data] : 1 [tables] : 1 [tags] : 1 - [total] : 682 - [vars] : 37 - ArrayLen : 1 - ArrayNew : 7 - ArrayNewFixed : 5 + [total] : 592 + [vars] : 38 + ArrayGet : 2 + ArrayLen : 2 + ArrayNew : 6 + ArrayNewFixed : 4 ArraySet : 1 - AtomicNotify : 1 - Binary : 79 - Block : 72 - BrOn : 4 - Break : 7 - Call : 19 - Const : 149 - Drop : 15 - GlobalGet : 35 - GlobalSet : 32 - If : 20 - Load : 20 - LocalGet : 55 - LocalSet : 26 - Loop : 7 - MemoryFill : 1 - Nop : 9 - Pop : 1 + AtomicCmpxchg : 1 + AtomicFence : 1 + AtomicRMW : 2 + Binary : 81 + Block : 62 + BrOn : 1 + Break : 11 + Call : 13 + CallIndirect : 2 + Const : 123 + Drop : 2 + GlobalGet : 22 + GlobalSet : 22 + If : 17 + Load : 25 + LocalGet : 63 + LocalSet : 35 + Loop : 6 + Nop : 8 + Pop : 3 RefAs : 1 - RefCast : 1 RefEq : 1 - RefFunc : 17 - RefI31 : 2 - RefIsNull : 2 - RefNull : 8 - Return : 5 - SIMDExtract : 3 - Store : 1 - StringConst : 3 + RefFunc : 6 + RefNull : 3 + Return : 4 + SIMDExtract : 2 + Select : 2 + StringConst : 5 + StringEncode : 1 + StringEq : 2 StringMeasure : 1 - StringWTF16Get : 1 - StructNew : 23 - Try : 1 - TupleExtract : 3 - TupleMake : 4 - Unary : 23 - Unreachable : 16 + StringWTF16Get : 2 + StructNew : 9 + Try : 3 + TryTable : 3 + TupleExtract : 1 + TupleMake : 2 + Unary : 18 + Unreachable : 11 From e9f693d40f0479aa218dd5664b22402994d2db29 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Dec 2024 14:48:22 -0800 Subject: [PATCH 180/622] [GC] Fix TypeRefining on StructGets without content but with a reachable ref (#7138) If we see a StructGet with no content (the type it reads from has no writes) then we can make it unreachable. The old code literally just changed the type to unreachable, which would later work out with refinalization - but only if the StructGet's ref was unreachable. But it is possible for this situation to occur without that, and if so, this hit the validation error "can't have an unreachable node without an unreachable child". To fix this, merge all code paths that handle "impossible" situations, which simplifies things, and add this situation. This uncovered an existing bug where we noted default values of refs, but not non-refs (which could lead us to think that a field of a struct that only was ever created by struct.new_default, was never created at all). Fixed as well. --- src/passes/TypeRefining.cpp | 104 ++++++++++------------- test/lit/passes/type-refining.wast | 130 +++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 62 deletions(-) diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index ee12c388df9..c37a105ece7 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -58,11 +58,13 @@ struct FieldInfoScanner void noteDefault(Type fieldType, HeapType type, Index index, FieldInfo& info) { - // Default values do not affect what the heap type of a field can be turned - // into. Note them, however, as they force us to keep the type nullable. + // Default values must be noted, so that we know there is content there. if (fieldType.isRef()) { - info.note(Type(fieldType.getHeapType().getBottom(), Nullable)); + // All we need to note here is nullability (the field must remain + // nullable), but not anything else about the type. + fieldType = Type(fieldType.getHeapType().getBottom(), Nullable); } + info.note(fieldType); } void noteCopy(HeapType type, Index index, FieldInfo& info) { @@ -270,71 +272,49 @@ struct TypeRefining : public Pass { return; } - if (curr->ref->type.isNull()) { - // This get will trap. In theory we could leave this for later - // optimizations to do, but we must actually handle it here, because - // of the situation where this get's type is refined, and the input - // type is the result of a refining: - // - // (struct.get $A ;; should be refined to something - // (struct.get $B ;; just refined to nullref - // - // If the input has become a nullref then we can't just return out of - // this function, as we'd be leaving a struct.get of $A with the - // wrong type. But we can't find the right type since in Binaryen IR - // we use the ref's type to see what is being read, and that just - // turned into nullref. To avoid that corner case, just turn this code - // into unreachable code now, and the later refinalize will turn all - // the parents unreachable, avoiding any type-checking problems. - Builder builder(*getModule()); - replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), - builder.makeUnreachable())); - return; + Type newFieldType; + if (!curr->ref->type.isNull()) { + auto oldType = curr->ref->type.getHeapType(); + newFieldType = parent.finalInfos[oldType][curr->index].getLUB(); } - auto oldType = curr->ref->type.getHeapType(); - auto newFieldType = parent.finalInfos[oldType][curr->index].getLUB(); - if (Type::isSubType(newFieldType, curr->type)) { - // This is the normal situation, where the new type is a refinement of - // the old type. Apply that type so that the type of the struct.get - // matches what is in the refined field. ReFinalize will later - // propagate this to parents. - // - // Note that ReFinalize will also apply the type of the field itself - // to a struct.get, so our doing it here in this pass is usually - // redundant. But ReFinalize also updates other types while doing so, - // which can cause a problem: - // - // (struct.get $A - // (block (result (ref null $A)) - // (ref.null any) - // ) - // ) - // - // Here ReFinalize will turn the block's result into a bottom type, - // which means it won't know a type for the struct.get at that point. - // Doing it in this pass avoids that issue, as we have all the - // necessary information. (ReFinalize will still get into the - // situation where it doesn't know how to update the type of the - // struct.get, but it will just leave the existing type - it assumes - // no update is needed - which will be correct, since we've updated it - // ourselves here, before.) - curr->type = newFieldType; - } else { - // This instruction is invalid, so it must be the result of the - // situation described above: we ignored the read during our - // inference, and optimized accordingly, and so now we must remove it - // to keep the module validating. It doesn't matter what we emit here, - // since there are no struct.new or struct.sets for this type, so this - // code is logically unreachable. - // - // Note that we emit an unreachable here, which changes the type, and - // so we should refinalize. However, we will be refinalizing later - // anyhow in updateTypes, so there is no need. + if (curr->ref->type.isNull() || newFieldType == Type::unreachable || + !Type::isSubType(newFieldType, curr->type)) { + // This get will trap, or cannot be reached: either the ref is null, + // or the field is never written any contents, or the contents we see + // are invalid (they passed through some fallthrough that will trap at + // runtime). Emit unreachable code here. Builder builder(*getModule()); replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), builder.makeUnreachable())); + return; } + + // This is the normal situation, where the new type is a refinement of + // the old type. Apply that type so that the type of the struct.get + // matches what is in the refined field. ReFinalize will later + // propagate this to parents. + // + // Note that ReFinalize will also apply the type of the field itself + // to a struct.get, so our doing it here in this pass is usually + // redundant. But ReFinalize also updates other types while doing so, + // which can cause a problem: + // + // (struct.get $A + // (block (result (ref null $A)) + // (ref.null any) + // ) + // ) + // + // Here ReFinalize will turn the block's result into a bottom type, + // which means it won't know a type for the struct.get at that point. + // Doing it in this pass avoids that issue, as we have all the + // necessary information. (ReFinalize will still get into the + // situation where it doesn't know how to update the type of the + // struct.get, but it will just leave the existing type - it assumes + // no update is needed - which will be correct, since we've updated it + // ourselves here, before.) + curr->type = newFieldType; } }; diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 91f61bc151e..fad016e1d57 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1574,3 +1574,133 @@ (ref.null $8) ) ) + +;; Test for a bug where we made a struct.get unreachable because it was reading +;; a field that had no writes, but in a situation where it is invalid for the +;; struct.get to be unreachable. +(module + ;; CHECK: (type $never (sub (struct (field i32)))) + (type $never (sub (struct (field i32)))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $optimizable (struct (field (mut nullfuncref)))) + (type $optimizable (struct (field (mut (ref null func))))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (global $never (ref null $never) (ref.null none)) + (global $never (export "never") (ref null $never) + ;; Make the type $never public (if it were private, we'd optimize in a + ;; different way that avoids the bug that this tests for). + (ref.null $never) + ) + + ;; CHECK: (export "never" (global $never)) + + ;; CHECK: (func $setup (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $optimizable + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $setup + ;; A struct.new, so that we have a field to refine (which avoids the pass + ;; early-exiting). + (drop + (struct.new $optimizable + (ref.null func) + ) + ) + ;; A struct.get that reads a $never, but the actual fallthrough value is none. + ;; We never create this type, so the field has no possible content, and we can + ;; replace this with an unreachable. + (drop + (struct.get $never 0 + (block (result (ref $never)) + (ref.as_non_null + (ref.null none) + ) + ) + ) + ) + ) +) + +;; Test that we note default values. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (struct (field i32)))) + (type $A (sub (struct (field i32)))) + ;; CHECK: (type $B (sub (struct (field i32)))) + (type $B (sub (struct (field i32)))) + ) + ;; CHECK: (type $2 (func (param (ref null $A) (ref null $B)))) + + ;; CHECK: (type $optimizable (sub (struct (field (ref $2))))) + (type $optimizable (sub (struct (field funcref)))) + + ;; CHECK: (elem declare func $test) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $2) (param $x (ref null $A)) (param $y (ref null $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $optimizable + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (struct.new_default $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (param $x (ref null $A)) (param $y (ref null $B)) + ;; Use $A, $B as params of this export, so they are public. + + ;; Make something for the pass to do, to avoid early-exit. + (drop + (struct.new $optimizable + (ref.func $test) + ) + ) + + ;; Get from a struct.new. We have nothing to optimize here. (In particular, we + ;; cannot make this unreachable, as there is a value in the field, 0.) + (drop + (struct.get $A 0 + (struct.new $A + (i32.const 0) + ) + ) + ) + + ;; As above. Now the value in the field comes from a default value. + (drop + (struct.get $B 0 + (struct.new_default $B) + ) + ) + ) +) From 725f76d6d2f81a1ea558c28f28aa144c65f9bb14 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 10 Dec 2024 12:07:29 -0800 Subject: [PATCH 181/622] [NFC] Simplify TypeGraphWalker in wasm-type.cpp (#7143) Co-locate the declaration and implementation of TypeGraphWalkerBase and its subtypes in wasm-type.cpp and simplify the implementation. Remove the preVisit and postVisit tasks for both Types and HeapTypes since overriding scanType and scanHeapType is sufficient for all users. Stop scanning the HeapTypes in reference types because a follow-on change (#7142) will make that much more complicated, and it turns out that it is not necessary. --- src/wasm/wasm-type.cpp | 352 ++++++++++++++++------------------------- 1 file changed, 139 insertions(+), 213 deletions(-) diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 9bb4f425907..51b77c3ab8a 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -204,82 +204,150 @@ template<> class hash { } }; +template<> class hash { +public: + size_t operator()(const wasm::TypeInfo& info) const; +}; + +template class hash> { +public: + size_t operator()(const reference_wrapper& ref) const { + return hash{}(ref.get()); + } +}; + +template class equal_to> { +public: + bool operator()(const reference_wrapper& a, + const reference_wrapper& b) const { + return equal_to{}(a.get(), b.get()); + } +}; + } // namespace std namespace wasm { namespace { +HeapTypeInfo* getHeapTypeInfo(HeapType ht) { + assert(!ht.isBasic()); + return (HeapTypeInfo*)ht.getID(); +} + +HeapType asHeapType(std::unique_ptr& info) { + return HeapType(uintptr_t(info.get())); +} + +Type markTemp(Type type) { + if (!type.isBasic()) { + Type::getTypeInfo(type)->isTemp = true; + } + return type; +} + +bool isTemp(Type type) { + return !type.isBasic() && Type::getTypeInfo(type)->isTemp; +} + +bool isTemp(HeapType type) { + return !type.isBasic() && getHeapTypeInfo(type)->isTemp; +} + // Generic utility for traversing type graphs. The inserted roots must live as // long as the Walker because they are referenced by address. This base class // only has logic for traversing type graphs; figuring out when to stop // traversing the graph and doing useful work during the traversal is left to -// subclasses. +// subclasses, which should override `scanType` and/or `scanHeapType`. Edges +// from reference types to the referenced heap types are not walked, so +// subclasses should handle referenced heap types when their reference types are +// visited. template struct TypeGraphWalkerBase { - void walkRoot(Type* type); - void walkRoot(HeapType* ht); + void walkRoot(Type* type) { + assert(taskList.empty()); + taskList.push_back(Task::scan(type)); + doWalk(); + } + + void walkRoot(HeapType* ht) { + assert(taskList.empty()); + taskList.push_back(Task::scan(ht)); + doWalk(); + } + +protected: + Self& self() { return *static_cast(this); } - // Override these in subclasses to do useful work. - void preVisitType(Type* type) {} - void preVisitHeapType(HeapType* ht) {} - void postVisitType(Type* type) {} - void postVisitHeapType(HeapType* ht) {} + void scanType(Type* type) { + if (type->isTuple()) { + auto& types = const_cast(type->getTuple()); + for (auto it = types.rbegin(); it != types.rend(); ++it) { + taskList.push_back(Task::scan(&*it)); + } + } + } - // This base walker does not know when to stop scanning, so at least one of - // these needs to be overridden with a method that calls the base scanning - // method only if some end condition isn't met. - void scanType(Type* type); - void scanHeapType(HeapType* ht); + void scanHeapType(HeapType* ht) { + if (ht->isBasic()) { + return; + } + auto* info = getHeapTypeInfo(*ht); + switch (info->kind) { + case HeapTypeKind::Func: + taskList.push_back(Task::scan(&info->signature.results)); + taskList.push_back(Task::scan(&info->signature.params)); + break; + case HeapTypeKind::Cont: + taskList.push_back(Task::scan(&info->continuation.type)); + break; + case HeapTypeKind::Struct: { + auto& fields = info->struct_.fields; + for (auto field = fields.rbegin(); field != fields.rend(); ++field) { + taskList.push_back(Task::scan(&field->type)); + } + break; + } + case HeapTypeKind::Array: + taskList.push_back(Task::scan(&info->array.element.type)); + break; + case HeapTypeKind::Basic: + WASM_UNREACHABLE("unexpected kind"); + } + } private: struct Task { enum Kind { - PreType, - PreHeapType, ScanType, ScanHeapType, - PostType, - PostHeapType, } kind; union { Type* type; HeapType* heapType; }; - static Task preVisit(Type* type) { return Task(type, PreType); } - static Task preVisit(HeapType* ht) { return Task(ht, PreHeapType); } static Task scan(Type* type) { return Task(type, ScanType); } static Task scan(HeapType* ht) { return Task(ht, ScanHeapType); } - static Task postVisit(Type* type) { return Task(type, PostType); } - static Task postVisit(HeapType* ht) { return Task(ht, PostHeapType); } private: Task(Type* type, Kind kind) : kind(kind), type(type) {} Task(HeapType* ht, Kind kind) : kind(kind), heapType(ht) {} }; - void doWalk(); - std::vector taskList; - void push(Type* type); - void push(HeapType* type); - Self& self() { return *static_cast(this); } -}; - -// A type graph walker base class that still does no useful work, but at least -// knows to scan each HeapType only once. -template struct HeapTypeGraphWalker : TypeGraphWalkerBase { - // Override this. - void noteHeapType(HeapType ht) {} - - void scanHeapType(HeapType* ht) { - if (scanned.insert(*ht).second) { - static_cast(this)->noteHeapType(*ht); - TypeGraphWalkerBase::scanHeapType(ht); + void doWalk() { + while (!taskList.empty()) { + auto curr = taskList.back(); + taskList.pop_back(); + switch (curr.kind) { + case Task::ScanType: + self().scanType(curr.type); + break; + case Task::ScanHeapType: + self().scanHeapType(curr.heapType); + break; + } } } - -private: - std::unordered_set scanned; }; // A type graph walker base class that still does no useful work, but at least @@ -307,24 +375,27 @@ template struct TypeGraphWalker : TypeGraphWalkerBase { std::unordered_set scannedTypes; }; -// A type graph walker that only traverses the direct HeapType children of the -// root, looking through child Types. What to do with each child is left to -// subclasses. -template struct HeapTypeChildWalker : HeapTypeGraphWalker { - // Override this. - void noteChild(HeapType* child) {} - +// A type graph walker base class that still does no useful work, but at least +// knows to scan each HeapType and Type only once. +// A type graph walker that calls `noteChild` on each each direct HeapType child +// of the root. +template struct HeapTypeChildWalker : TypeGraphWalkerBase { void scanType(Type* type) { isTopLevel = false; - HeapTypeGraphWalker::scanType(type); + if (type->isRef()) { + this->self().noteChild(type->getHeapType()); + } else { + TypeGraphWalkerBase::scanType(type); + } } - void scanHeapType(HeapType* ht) { + + void scanHeapType(HeapType* type) { if (isTopLevel) { - HeapTypeGraphWalker::scanHeapType(ht); + isTopLevel = false; + TypeGraphWalkerBase::scanHeapType(type); } else { - static_cast(this)->noteChild(ht); + this->self().noteChild(*type); } - isTopLevel = false; } private: @@ -333,63 +404,9 @@ template struct HeapTypeChildWalker : HeapTypeGraphWalker { struct HeapTypeChildCollector : HeapTypeChildWalker { std::vector children; - void noteChild(HeapType* child) { children.push_back(*child); } -}; - -} // anonymous namespace -} // namespace wasm - -namespace std { - -template<> class hash { -public: - size_t operator()(const wasm::TypeInfo& info) const; + void noteChild(HeapType type) { children.push_back(type); } }; -template class hash> { -public: - size_t operator()(const reference_wrapper& ref) const { - return hash{}(ref.get()); - } -}; - -template class equal_to> { -public: - bool operator()(const reference_wrapper& a, - const reference_wrapper& b) const { - return equal_to{}(a.get(), b.get()); - } -}; - -} // namespace std - -namespace wasm { -namespace { - -HeapTypeInfo* getHeapTypeInfo(HeapType ht) { - assert(!ht.isBasic()); - return (HeapTypeInfo*)ht.getID(); -} - -HeapType asHeapType(std::unique_ptr& info) { - return HeapType(uintptr_t(info.get())); -} - -Type markTemp(Type type) { - if (!type.isBasic()) { - Type::getTypeInfo(type)->isTemp = true; - } - return type; -} - -bool isTemp(Type type) { - return !type.isBasic() && Type::getTypeInfo(type)->isTemp; -} - -bool isTemp(HeapType type) { - return !type.isBasic() && getHeapTypeInfo(type)->isTemp; -} - HeapType::BasicHeapType getBasicHeapSupertype(HeapType type) { if (type.isBasic()) { return HeapType::BasicHeapType(type.getID()); @@ -1387,13 +1404,13 @@ FeatureSet HeapType::getFeatures() const { : HeapTypeChildWalker { FeatureSet feats = FeatureSet::None; - void noteChild(HeapType* heapType) { - if (heapType->isShared()) { + void noteChild(HeapType heapType) { + if (heapType.isShared()) { feats |= FeatureSet::SharedEverything; } - if (heapType->isBasic()) { - switch (heapType->getBasic(Unshared)) { + if (heapType.isBasic()) { + switch (heapType.getBasic(Unshared)) { case HeapType::ext: case HeapType::func: feats |= FeatureSet::ReferenceTypes; @@ -1426,14 +1443,14 @@ FeatureSet HeapType::getFeatures() const { } } - if (heapType->getRecGroup().size() > 1 || - heapType->getDeclaredSuperType() || heapType->isOpen()) { + if (heapType.getRecGroup().size() > 1 || + heapType.getDeclaredSuperType() || heapType.isOpen()) { feats |= FeatureSet::ReferenceTypes | FeatureSet::GC; } - if (heapType->isStruct() || heapType->isArray()) { + if (heapType.isStruct() || heapType.isArray()) { feats |= FeatureSet::ReferenceTypes | FeatureSet::GC; - } else if (heapType->isSignature()) { + } else if (heapType.isSignature()) { // This is a function reference, which requires reference types and // possibly also multivalue (if it has multiple returns). Note that // technically typed function references also require GC, however, @@ -1442,17 +1459,17 @@ FeatureSet HeapType::getFeatures() const { // features yet, so we apply the more refined types), so we don't // add that in any case here. feats |= FeatureSet::ReferenceTypes; - auto sig = heapType->getSignature(); + auto sig = heapType.getSignature(); if (sig.results.isTuple()) { feats |= FeatureSet::Multivalue; } - } else if (heapType->isContinuation()) { + } else if (heapType.isContinuation()) { feats |= FeatureSet::TypedContinuations; } // In addition, scan their non-ref children, to add dependencies on // things like SIMD. - for (auto child : heapType->getTypeChildren()) { + for (auto child : heapType.getTypeChildren()) { if (!child.isRef()) { feats |= child.getFeatures(); } @@ -1466,7 +1483,7 @@ FeatureSet HeapType::getFeatures() const { // send |this| there from a |const| method. auto* unconst = const_cast(this); collector.walkRoot(unconst); - collector.noteChild(unconst); + collector.noteChild(*unconst); return collector.feats; } @@ -2248,98 +2265,6 @@ bool RecGroupEquator::eq(const Array& a, const Array& b) const { return eq(a.element, b.element); } -template void TypeGraphWalkerBase::walkRoot(Type* type) { - assert(taskList.empty()); - taskList.push_back(Task::scan(type)); - doWalk(); -} - -template void TypeGraphWalkerBase::walkRoot(HeapType* ht) { - assert(taskList.empty()); - taskList.push_back(Task::scan(ht)); - doWalk(); -} - -template void TypeGraphWalkerBase::doWalk() { - while (!taskList.empty()) { - auto curr = taskList.back(); - taskList.pop_back(); - switch (curr.kind) { - case Task::PreType: - self().preVisitType(curr.type); - break; - case Task::PreHeapType: - self().preVisitHeapType(curr.heapType); - break; - case Task::ScanType: - taskList.push_back(Task::postVisit(curr.type)); - self().scanType(curr.type); - taskList.push_back(Task::preVisit(curr.type)); - break; - case Task::ScanHeapType: - taskList.push_back(Task::postVisit(curr.heapType)); - self().scanHeapType(curr.heapType); - taskList.push_back(Task::preVisit(curr.heapType)); - break; - case Task::PostType: - self().postVisitType(curr.type); - break; - case Task::PostHeapType: - self().postVisitHeapType(curr.heapType); - break; - } - } -} - -template void TypeGraphWalkerBase::scanType(Type* type) { - if (type->isBasic()) { - return; - } - auto* info = Type::getTypeInfo(*type); - switch (info->kind) { - case TypeInfo::TupleKind: { - auto& types = info->tuple; - for (auto it = types.rbegin(); it != types.rend(); ++it) { - taskList.push_back(Task::scan(&*it)); - } - break; - } - case TypeInfo::RefKind: { - taskList.push_back(Task::scan(&info->ref.heapType)); - break; - } - } -} - -template -void TypeGraphWalkerBase::scanHeapType(HeapType* ht) { - if (ht->isBasic()) { - return; - } - auto* info = getHeapTypeInfo(*ht); - switch (info->kind) { - case HeapTypeKind::Func: - taskList.push_back(Task::scan(&info->signature.results)); - taskList.push_back(Task::scan(&info->signature.params)); - break; - case HeapTypeKind::Cont: - taskList.push_back(Task::scan(&info->continuation.type)); - break; - case HeapTypeKind::Struct: { - auto& fields = info->struct_.fields; - for (auto field = fields.rbegin(); field != fields.rend(); ++field) { - taskList.push_back(Task::scan(&field->type)); - } - break; - } - case HeapTypeKind::Array: - taskList.push_back(Task::scan(&info->array.element.type)); - break; - case HeapTypeKind::Basic: - WASM_UNREACHABLE("unexpected kind"); - } -} - } // anonymous namespace struct TypeBuilder::Impl { @@ -2683,10 +2608,11 @@ buildRecGroup(std::unique_ptr&& groupInfo, // replace the old ones. TODO simplify this. struct Locations : TypeGraphWalker { std::unordered_map> types; - void preVisitType(Type* type) { + void scanType(Type* type) { if (isTemp(*type)) { types[*type].insert(type); } + TypeGraphWalker::scanType(type); } }; From 2f6f42ce4ab07ba7d2f73ed7d4c698f2d57f3990 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Dec 2024 14:32:20 -0800 Subject: [PATCH 182/622] Fuzz combinations of two modules (#7144) The new Two fuzz testcase handler generates two wasm files and then runs them in V8, linking them using JS. It then optimizes at least one of the two and runs it again, and checks for differences. This is similar to the Split fuzzer for wasm-split, but the two wasm files are arbitrary and not the result of splitting. Also lower CtorEval priority a little. It is not very important. --- scripts/fuzz_opt.py | 82 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index b60a3e29ad8..87f05905837 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1360,7 +1360,7 @@ def can_run_on_wasm(self, wasm): # Tests wasm-ctor-eval class CtorEval(TestCaseHandler): - frequency = 0.2 + frequency = 0.1 def handle(self, wasm): # get the expected execution results. @@ -1477,7 +1477,9 @@ def can_run_on_wasm(self, wasm): FUNC_NAMES_REGEX = re.compile(r'\n [(]func [$](\S+)') -# Tests wasm-split +# Tests wasm-split. This also tests that fuzz_shell.js properly executes 2 wasm +# files, which adds coverage for ClusterFuzz (which sometimes runs two wasm +# files in that way). class Split(TestCaseHandler): frequency = 1 # TODO: adjust lower when we actually enable this @@ -1670,6 +1672,78 @@ def ensure(self): tar.close() +# Tests linking two wasm files at runtime, and that optimizations do not break +# anything. This is similar to Split(), but rather than split a wasm file into +# two and link them at runtime, this starts with two separate wasm files. +class Two(TestCaseHandler): + frequency = 0.2 + + def handle(self, wasm): + # Generate a second wasm file, unless we were given one (useful during + # reduction). + second_wasm = abspath('second.wasm') + given = os.environ.get('BINARYEN_SECOND_WASM') + if given: + # TODO: should we de-nan this etc. as with the primary? + shutil.copyfile(given, second_wasm) + else: + second_input = abspath('second_input.dat') + make_random_input(random_size(), second_input) + args = [second_input, '-ttf', '-o', second_wasm] + run([in_bin('wasm-opt')] + args + GEN_ARGS + FEATURE_OPTS) + + # The binaryen interpreter only supports a single file, so we run them + # from JS using fuzz_shell.js's support for two files. + # + # Note that we *cannot* run each wasm file separately and compare those + # to the combined output, as fuzz_shell.js intentionally allows calls + # *between* the wasm files, through JS APIs like call-export*. So all we + # do here is see the combined, linked behavior, and then later below we + # see that that behavior remains even after optimizations. + output = run_d8_wasm(wasm, args=[second_wasm]) + + if output == IGNORE: + # There is no point to continue since we can't compare this output + # to anything. + return + + if output.strip() == 'exception thrown: failed to instantiate module': + # We may fail to instantiate the modules for valid reasons, such as + # an active segment being out of bounds. There is no point to + # continue in such cases, as no exports are called. + return + + # Make sure that fuzz_shell.js actually executed all exports from both + # wasm files. + exports = get_exports(wasm, ['func']) + get_exports(second_wasm, ['func']) + assert output.count(FUZZ_EXEC_CALL_PREFIX) == len(exports) + + output = fix_output(output) + + # Optimize at least one of the two. + wasms = [wasm, second_wasm] + for i in range(random.randint(1, 2)): + wasm_index = random.randint(0, 1) + name = wasms[wasm_index] + new_name = name + f'.opt{i}.wasm' + opts = get_random_opts() + run([in_bin('wasm-opt'), name, '-o', new_name] + opts + FEATURE_OPTS) + wasms[wasm_index] = new_name + + # Run again, and compare the output + optimized_output = run_d8_wasm(wasms[0], args=[wasms[1]]) + optimized_output = fix_output(optimized_output) + + compare(output, optimized_output, 'Two') + + def can_run_on_wasm(self, wasm): + # We cannot optimize wasm files we are going to link in closed world + # mode. We also cannot run shared-everything code in d8 yet. We also + # cannot compare if there are NaNs (as optimizations can lead to + # different outputs). + return not CLOSED_WORLD and all_disallowed(['shared-everything']) and not NANS + + # The global list of all test case handlers testcase_handlers = [ FuzzExec(), @@ -1683,6 +1757,7 @@ def ensure(self): # Split(), RoundtripText(), ClusterFuzz(), + Two(), ] @@ -2136,6 +2211,9 @@ def get_random_opts(): # bash %(reduce_sh)s # # You may also need to add --timeout 5 or such if the testcase is a slow one. +# +# If the testcase handler uses a second wasm file, you may be able to reduce it +# using BINARYEN_SECOND_WASM. # ''' % {'wasm_opt': in_bin('wasm-opt'), 'bin': shared.options.binaryen_bin, From 0b54d74c7ae7e81035a41a4710dca82df19b8638 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 10 Dec 2024 15:03:22 -0800 Subject: [PATCH 183/622] [NFC] Encode reference types with bit packing (#7142) Value types were previously represented internally as either enum values for "basic," i.e. non-reference, non-tuple types or pointers to `TypeInfo` structs encoding either references or tuples. Update the representation of reference types to use one bit to encode nullability and the rest of the bits to encode the referenced heap type. This allows canonical reference types to be created with a single logical or rather than by taking a lock on a global type store and doing a hash map lookup to canonicalize. This change is a massive performance improvement and dramatically improves how performance scales with threads because the removed lock was highly contended. Even with a single core, the performance of an O3 optimization pipeline on a WasmGC module improves by 6%. With 8 cores, the improvement increases to 29% and with all 128 threads on my machine, the improvement reaches 46%. The full new encoding of types is as follows: - If the type ID is within the range of the basic types, the type is the corresponding basic type. - Otherwise, if bit 0 is set, the type is a tuple and the rest of the bits are a canonical pointer to the tuple. - Otherwise, the type is a reference type. Bit 1 determines the nullability and the rest of the bits encode the heap type. Also update the encodings of basic heap types so they no longer use the low two bits to avoid conflicts with the use of those bits in the encoding of types. --- src/wasm-type.h | 210 +++++----------- src/wasm/wasm-type.cpp | 364 ++++++---------------------- test/example/c-api-kitchen-sink.txt | 22 +- test/gtest/type-builder.cpp | 8 - 4 files changed, 150 insertions(+), 454 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index d26ba324f04..b48a1071328 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -96,26 +96,27 @@ class HeapType { uintptr_t id; public: - // Bit zero indicates whether the type is `shared`, so we need to leave it - // free. + // Bits 0 and 1 are used by the Type representation, so need to be left free. + // Bit 2 determines whether the basic heap type is shared (1) or unshared (0). enum BasicHeapType : uint32_t { - ext = 0 << 1, - func = 1 << 1, - cont = 2 << 1, - any = 3 << 1, - eq = 4 << 1, - i31 = 5 << 1, - struct_ = 6 << 1, - array = 7 << 1, - exn = 8 << 1, - string = 9 << 1, - none = 10 << 1, - noext = 11 << 1, - nofunc = 12 << 1, - nocont = 13 << 1, - noexn = 14 << 1, + ext = 1 << 3, + func = 2 << 3, + cont = 3 << 3, + any = 4 << 3, + eq = 5 << 3, + i31 = 6 << 3, + struct_ = 7 << 3, + array = 8 << 3, + exn = 9 << 3, + string = 10 << 3, + none = 11 << 3, + noext = 12 << 3, + nofunc = 13 << 3, + nocont = 14 << 3, + noexn = 15 << 3, }; - static constexpr BasicHeapType _last_basic_type = BasicHeapType(noexn + 1); + static constexpr BasicHeapType _last_basic_type = + BasicHeapType(noexn + (1 << 2)); // BasicHeapType can be implicitly upgraded to HeapType constexpr HeapType(BasicHeapType id) : id(id) {} @@ -213,7 +214,7 @@ class HeapType { // Get the shared or unshared version of this basic heap type. constexpr BasicHeapType getBasic(Shareability share) const { assert(isBasic()); - return BasicHeapType(share == Shared ? (id | 1) : (id & ~1)); + return BasicHeapType(share == Shared ? (id | 4) : (id & ~4)); } // (In)equality must be defined for both HeapType and BasicHeapType because it @@ -261,49 +262,15 @@ class HeapType { std::string toString() const; }; -// Internal only. -struct TypeInfo { - using type_t = Type; - // Used in assertions to ensure that temporary types don't leak into the - // global store. - bool isTemp = false; - enum Kind { - TupleKind, - RefKind, - } kind; - struct Ref { - HeapType heapType; - Nullability nullability; - }; - union { - Tuple tuple; - Ref ref; - }; - - TypeInfo(const Tuple& tuple); - TypeInfo(Tuple&& tuple) : kind(TupleKind), tuple(std::move(tuple)) {} - TypeInfo(HeapType heapType, Nullability nullable) - : kind(RefKind), ref{heapType, nullable} {} - TypeInfo(const TypeInfo& other); - ~TypeInfo(); - - constexpr bool isTuple() const { return kind == TupleKind; } - constexpr bool isRef() const { return kind == RefKind; } - - // If this TypeInfo represents a Type that can be represented more simply, - // return that simpler Type. For example, this handles eliminating singleton - // tuple types. - std::optional getCanonical() const; - - bool operator==(const TypeInfo& other) const; - bool operator!=(const TypeInfo& other) const { return !(*this == other); } -}; - class Type { // The `id` uniquely represents each type, so type equality is just a - // comparison of the ids. For basic types the `id` is just the `BasicType` - // enum value below, and for constructed types the `id` is the address of the - // canonical representation of the type, making lookups cheap for all types. + // comparison of the ids. The basic types are packed at the bottom of the + // expressible range, and after that tuple types are distinguished by having + // bit 0 set. When that bit is masked off, they are pointers to the underlying + // vectors of types. Otherwise, the type is a reference type, and is + // represented as a heap type with bit 1 set iff the reference type is + // nullable. + // // Since `Type` is really just a single integer, it should be passed by value. // This is a uintptr_t rather than a TypeID (uint64_t) to save memory on // 32-bit platforms. @@ -311,13 +278,13 @@ class Type { public: enum BasicType : uint32_t { - none, - unreachable, - i32, - i64, - f32, - f64, - v128, + none = 0, + unreachable = 1, + i32 = 2, + i64 = 3, + f32 = 4, + f64 = 5, + v128 = 6, }; static constexpr BasicType _last_basic_type = v128; @@ -338,7 +305,8 @@ class Type { // Construct from a heap type description. Also covers construction from // Signature, Struct or Array via implicit conversion to HeapType. - Type(HeapType, Nullability nullable); + Type(HeapType heapType, Nullability nullable) + : Type(heapType.getID() | (nullable == Nullable ? 2 : 0)) {} // Predicates // Compound Concrete @@ -376,74 +344,37 @@ class Type { // Tuples, refs, etc. are quickly handled using isBasic(), leaving the non- // basic case for the underlying implementation. - bool isTuple() const { - if (isBasic()) { - return false; - } else { - return getTypeInfo(*this)->isTuple(); - } - } - - bool isRef() const { - if (isBasic()) { - return false; - } else { - return getTypeInfo(*this)->isRef(); - } - } - - bool isFunction() const { - if (isBasic()) { - return false; - } else { - auto* info = getTypeInfo(*this); - return info->isRef() && info->ref.heapType.isFunction(); - } - } - - bool isData() const { - if (isBasic()) { - return false; - } else { - auto* info = getTypeInfo(*this); - return info->isRef() && info->ref.heapType.isData(); - } - } - - // Checks whether a type is a reference and is nullable. This returns false - // for a value that is not a reference, that is, for which nullability is - // irrelevant. - bool isNullable() const { - if (isRef()) { - return getTypeInfo(*this)->ref.nullability == Nullable; - } else { - return false; - } + // TODO: Experiment with leaving bit 0 free in basic types. + bool isTuple() const { return !isBasic() && (id & 1); } + const Tuple& getTuple() const { + assert(isTuple()); + return *(Tuple*)(id & ~1); } - // Checks whether a type is a reference and is non-nullable. This returns - // false for a value that is not a reference, that is, for which nullability - // is irrelevant. (For that reason, this is only the negation of isNullable() - // on references, but both return false on non-references.) - bool isNonNullable() const { - if (isRef()) { - return getTypeInfo(*this)->ref.nullability == NonNullable; - } else { - return false; - } + bool isRef() const { return !isBasic() && !(id & 1); } + bool isNullable() const { return isRef() && (id & 2); } + bool isNonNullable() const { return isRef() && !(id & 2); } + HeapType getHeapType() const { + assert(isRef()); + return HeapType(id & ~2); } + bool isFunction() const { return isRef() && getHeapType().isFunction(); } bool isSignature() const { return isRef() && getHeapType().isSignature(); } + bool isData() const { return isRef() && getHeapType().isData(); } // Whether this type is only inhabited by null values. - bool isNull() const; - bool isStruct() const; - bool isArray() const; - bool isExn() const; - bool isString() const; + bool isNull() const { return isRef() && getHeapType().isBottom(); } + bool isStruct() const { return isRef() && getHeapType().isStruct(); } + bool isArray() const { return isRef() && getHeapType().isArray(); } + bool isExn() const { return isRef() && getHeapType().isExn(); } + bool isString() const { return isRef() && getHeapType().isString(); } bool isDefaultable() const; - Nullability getNullability() const; + // TODO: Allow this only for reference types. + Nullability getNullability() const { + return isNullable() ? Nullable : NonNullable; + } private: template bool hasPredicate() { @@ -489,20 +420,6 @@ class Type { // Returns the feature set required to use this type. FeatureSet getFeatures() const; - // Returns the tuple, assuming that this is a tuple type. Note that it is - // normally simpler to use operator[] and size() on the Type directly. - HeapType getHeapType() const { - assert(isRef()); - return getTypeInfo(*this)->ref.heapType; - } - - // Gets the heap type corresponding to this type, assuming that it is a - // reference type. - const Tuple& getTuple() const { - assert(isTuple()); - return getTypeInfo(*this)->tuple; - } - // Returns a number type based on its size in bytes and whether it is a float // type. static Type get(unsigned byteSize, bool float_); @@ -565,7 +482,9 @@ class Type { std::string toString() const; - size_t size() const; + size_t size() const { + return isTuple() ? getTuple().size() : size_t(id != Type::none); + } struct Iterator : ParentIndexIterator { using value_type = Type; @@ -583,15 +502,8 @@ class Type { return std::make_reverse_iterator(begin()); } const Type& operator[](size_t i) const { return *Iterator{{this, i}}; } - - static TypeInfo* getTypeInfo(Type type) { - assert(!type.isBasic()); - return (TypeInfo*)type.getID(); - } }; -inline bool Type::isNull() const { return isRef() && getHeapType().isBottom(); } - namespace HeapTypes { constexpr HeapType ext = HeapType::ext; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 51b77c3ab8a..a6ed6877097 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -143,7 +143,6 @@ struct RecGroupHasher { size_t topLevelHash(HeapType type) const; size_t hash(Type type) const; size_t hash(HeapType type) const; - size_t hash(const TypeInfo& info) const; size_t hash(const HeapTypeInfo& info) const; size_t hash(const Tuple& tuple) const; size_t hash(const Field& field) const; @@ -170,7 +169,6 @@ struct RecGroupEquator { bool topLevelEq(HeapType a, HeapType b) const; bool eq(Type a, Type b) const; bool eq(HeapType a, HeapType b) const; - bool eq(const TypeInfo& a, const TypeInfo& b) const; bool eq(const HeapTypeInfo& a, const HeapTypeInfo& b) const; bool eq(const Tuple& a, const Tuple& b) const; bool eq(const Field& a, const Field& b) const; @@ -204,11 +202,6 @@ template<> class hash { } }; -template<> class hash { -public: - size_t operator()(const wasm::TypeInfo& info) const; -}; - template class hash> { public: size_t operator()(const reference_wrapper& ref) const { @@ -238,17 +231,6 @@ HeapType asHeapType(std::unique_ptr& info) { return HeapType(uintptr_t(info.get())); } -Type markTemp(Type type) { - if (!type.isBasic()) { - Type::getTypeInfo(type)->isTemp = true; - } - return type; -} - -bool isTemp(Type type) { - return !type.isBasic() && Type::getTypeInfo(type)->isTemp; -} - bool isTemp(HeapType type) { return !type.isBasic() && getHeapTypeInfo(type)->isTemp; } @@ -350,35 +332,7 @@ template struct TypeGraphWalkerBase { } }; -// A type graph walker base class that still does no useful work, but at least -// knows to scan each HeapType and Type only once. -template struct TypeGraphWalker : TypeGraphWalkerBase { - // Override these. - void noteType(Type type) {} - void noteHeapType(HeapType ht) {} - - void scanType(Type* type) { - if (scannedTypes.insert(*type).second) { - static_cast(this)->noteType(*type); - TypeGraphWalkerBase::scanType(type); - } - } - void scanHeapType(HeapType* ht) { - if (scannedHeapTypes.insert(*ht).second) { - static_cast(this)->noteHeapType(*ht); - TypeGraphWalkerBase::scanHeapType(ht); - } - } - -private: - std::unordered_set scannedHeapTypes; - std::unordered_set scannedTypes; -}; - -// A type graph walker base class that still does no useful work, but at least -// knows to scan each HeapType and Type only once. -// A type graph walker that calls `noteChild` on each each direct HeapType child -// of the root. +// A type graph walker that scans each each direct HeapType child of the root. template struct HeapTypeChildWalker : TypeGraphWalkerBase { void scanType(Type* type) { isTopLevel = false; @@ -496,59 +450,6 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, } // anonymous namespace -TypeInfo::TypeInfo(const Tuple& tuple) : kind(TupleKind), tuple(tuple) {} - -TypeInfo::TypeInfo(const TypeInfo& other) { - kind = other.kind; - switch (kind) { - case TupleKind: - new (&tuple) auto(other.tuple); - return; - case RefKind: - new (&ref) auto(other.ref); - return; - } - WASM_UNREACHABLE("unexpected kind"); -} - -TypeInfo::~TypeInfo() { - switch (kind) { - case TupleKind: - tuple.~Tuple(); - return; - case RefKind: - ref.~Ref(); - return; - } - WASM_UNREACHABLE("unexpected kind"); -} - -std::optional TypeInfo::getCanonical() const { - if (isTuple()) { - if (tuple.size() == 0) { - return Type::none; - } - if (tuple.size() == 1) { - return tuple[0]; - } - } - return {}; -} - -bool TypeInfo::operator==(const TypeInfo& other) const { - if (kind != other.kind) { - return false; - } - switch (kind) { - case TupleKind: - return tuple == other.tuple; - case RefKind: - return ref.nullability == other.ref.nullability && - ref.heapType == other.ref.heapType; - } - WASM_UNREACHABLE("unexpected kind"); -} - HeapTypeInfo::~HeapTypeInfo() { switch (kind) { case HeapTypeKind::Func: @@ -571,82 +472,75 @@ HeapTypeInfo::~HeapTypeInfo() { namespace { -struct TypeStore { +struct TupleStore { std::recursive_mutex mutex; - // Track unique_ptrs for constructed types to avoid leaks. - std::vector> constructedTypes; + // Track unique_ptrs for constructed tuples to avoid leaks. + std::vector> constructedTuples; - // Maps from constructed types to their canonical Type IDs. - std::unordered_map, uintptr_t> typeIDs; + // Maps from constructed tuples to their canonical Type IDs. + std::unordered_map, uintptr_t> typeIDs; -#ifndef NDEBUG - bool isGlobalStore(); -#endif - - Type insert(const TypeInfo& info) { return doInsert(info); } - Type insert(std::unique_ptr&& info) { return doInsert(info); } - bool hasCanonical(const TypeInfo& info, Type& canonical); + Type insert(const Tuple& info) { return doInsert(info); } + Type insert(std::unique_ptr&& info) { return doInsert(info); } + bool hasCanonical(const Tuple& info, Tuple& canonical); void clear() { typeIDs.clear(); - constructedTypes.clear(); + constructedTuples.clear(); } private: - template Type doInsert(Ref& infoRef) { - const TypeInfo& info = [&]() { - if constexpr (std::is_same_v) { - return infoRef; - } else if constexpr (std::is_same_v>) { - infoRef->isTemp = false; - return *infoRef; + template Type doInsert(Ref& tupleRef) { + const Tuple& tuple = [&]() { + if constexpr (std::is_same_v) { + return tupleRef; + } else if constexpr (std::is_same_v>) { + return *tupleRef; } }(); - auto getPtr = [&]() -> std::unique_ptr { - if constexpr (std::is_same_v) { - return std::make_unique(infoRef); - } else if constexpr (std::is_same_v>) { - return std::move(infoRef); + auto getPtr = [&]() -> std::unique_ptr { + if constexpr (std::is_same_v) { + return std::make_unique(tupleRef); + } else if constexpr (std::is_same_v>) { + return std::move(tupleRef); } }; auto insertNew = [&]() { - assert((!isGlobalStore() || !info.isTemp) && "Leaking temporary type!"); auto ptr = getPtr(); - TypeID id = uintptr_t(ptr.get()); + TypeID id = uintptr_t(ptr.get()) | 1; assert(id > Type::_last_basic_type); typeIDs.insert({*ptr, id}); - constructedTypes.emplace_back(std::move(ptr)); + constructedTuples.emplace_back(std::move(ptr)); return Type(id); }; // Turn e.g. singleton tuple into non-tuple. - if (auto canonical = info.getCanonical()) { - return *canonical; + if (tuple.size() == 0) { + return Type::none; + } + if (tuple.size() == 1) { + return tuple[0]; } std::lock_guard lock(mutex); - // Check whether we already have a type for this structural Info. - auto indexIt = typeIDs.find(std::cref(info)); + // Check whether we already have a type for this tuple. + auto indexIt = typeIDs.find(std::cref(tuple)); if (indexIt != typeIDs.end()) { return Type(indexIt->second); } - // We do not have a type for this Info already. Create one. + // We do not have a type for this tuple already. Create one. return insertNew(); } }; -static TypeStore globalTypeStore; +static TupleStore globalTupleStore; static std::vector> globalHeapTypeStore; static std::recursive_mutex globalHeapTypeStoreMutex; -#ifndef NDEBUG -bool TypeStore::isGlobalStore() { return this == &globalTypeStore; } -#endif - // Keep track of the constructed recursion groups. struct RecGroupStore { std::mutex mutex; @@ -708,7 +602,7 @@ void validateTuple(const Tuple& tuple) { } // anonymous namespace void destroyAllTypesForTestingPurposesOnly() { - globalTypeStore.clear(); + globalTupleStore.clear(); globalHeapTypeStore.clear(); globalRecGroupStore.clear(); } @@ -717,36 +611,13 @@ Type::Type(std::initializer_list types) : Type(Tuple(types)) {} Type::Type(const Tuple& tuple) { validateTuple(tuple); -#ifndef NDEBUG - for (auto type : tuple) { - assert(!isTemp(type) && "Leaking temporary type!"); - } -#endif - new (this) Type(globalTypeStore.insert(tuple)); + new (this) Type(globalTupleStore.insert(tuple)); } Type::Type(Tuple&& tuple) { -#ifndef NDEBUG - for (auto type : tuple) { - assert(!isTemp(type) && "Leaking temporary type!"); - } -#endif - new (this) Type(globalTypeStore.insert(std::move(tuple))); -} - -Type::Type(HeapType heapType, Nullability nullable) { - assert(!isTemp(heapType) && "Leaking temporary type!"); - new (this) Type(globalTypeStore.insert(TypeInfo(heapType, nullable))); + new (this) Type(globalTupleStore.insert(std::move(tuple))); } -bool Type::isStruct() const { return isRef() && getHeapType().isStruct(); } - -bool Type::isArray() const { return isRef() && getHeapType().isArray(); } - -bool Type::isExn() const { return isRef() && getHeapType().isExn(); } - -bool Type::isString() const { return isRef() && getHeapType().isString(); } - bool Type::isDefaultable() const { // A variable can get a default value if its type is concrete (unreachable // and none have no values, hence no default), and if it's a reference, it @@ -762,10 +633,6 @@ bool Type::isDefaultable() const { return isConcrete() && !isNonNullable(); } -Nullability Type::getNullability() const { - return isNullable() ? Nullable : NonNullable; -} - unsigned Type::getByteSize() const { // TODO: alignment? auto getSingleByteSize = [](Type t) { @@ -958,61 +825,36 @@ Type Type::getGreatestLowerBound(Type a, Type b) { return Type(heapType, nullability); } -size_t Type::size() const { - if (isTuple()) { - return getTypeInfo(*this)->tuple.size(); - } else { - // TODO: unreachable is special and expands to {unreachable} currently. - // see also: https://github.com/WebAssembly/binaryen/issues/3062 - return size_t(id != Type::none); - } -} - const Type& Type::Iterator::operator*() const { if (parent->isTuple()) { - return getTypeInfo(*parent)->tuple[index]; + return parent->getTuple()[index]; } else { - // TODO: see comment in Type::size() - assert(index == 0 && parent->id != Type::none && "Index out of bounds"); + assert(index == 0 && *parent != Type::none && "Index out of bounds"); return *parent; } } HeapType::HeapType(Signature sig) { - assert(!isTemp(sig.params) && "Leaking temporary type!"); - assert(!isTemp(sig.results) && "Leaking temporary type!"); new (this) HeapType(globalRecGroupStore.insert(std::make_unique(sig))); } HeapType::HeapType(Continuation continuation) { - assert(!isTemp(continuation.type) && "Leaking temporary type!"); new (this) HeapType( globalRecGroupStore.insert(std::make_unique(continuation))); } HeapType::HeapType(const Struct& struct_) { -#ifndef NDEBUG - for (const auto& field : struct_.fields) { - assert(!isTemp(field.type) && "Leaking temporary type!"); - } -#endif new (this) HeapType( globalRecGroupStore.insert(std::make_unique(struct_))); } HeapType::HeapType(Struct&& struct_) { -#ifndef NDEBUG - for (const auto& field : struct_.fields) { - assert(!isTemp(field.type) && "Leaking temporary type!"); - } -#endif new (this) HeapType(globalRecGroupStore.insert( std::make_unique(std::move(struct_)))); } HeapType::HeapType(Array array) { - assert(!isTemp(array.element.type) && "Leaking temporary type!"); new (this) HeapType(globalRecGroupStore.insert(std::make_unique(array))); } @@ -1059,7 +901,7 @@ bool HeapType::isOpen() const { Shareability HeapType::getShared() const { if (isBasic()) { - return (id & 1) != 0 ? Shared : Unshared; + return (id & 4) != 0 ? Shared : Unshared; } else { return getHeapTypeInfo(*this)->share; } @@ -1764,9 +1606,6 @@ std::ostream& TypePrinter::print(Type type) { #if TRACE_CANONICALIZATION os << "(;" << ((type.getID() >> 4) % 1000) << ";) "; #endif - if (isTemp(type)) { - os << "(; temp ;) "; - } if (type.isTuple()) { print(type.getTuple()); } else if (type.isRef()) { @@ -2029,9 +1868,16 @@ size_t RecGroupHasher::hash(Type type) const { size_t digest = wasm::hash(type.isBasic()); if (type.isBasic()) { wasm::rehash(digest, type.getID()); - } else { - hash_combine(digest, hash(*Type::getTypeInfo(type))); + return digest; } + wasm::rehash(digest, type.isTuple()); + if (type.isTuple()) { + hash_combine(digest, hash(type.getTuple())); + return digest; + } + assert(type.isRef()); + rehash(digest, type.getNullability()); + rehash(digest, hash(type.getHeapType())); return digest; } @@ -2053,20 +1899,6 @@ size_t RecGroupHasher::hash(HeapType type) const { return digest; } -size_t RecGroupHasher::hash(const TypeInfo& info) const { - size_t digest = wasm::hash(info.kind); - switch (info.kind) { - case TypeInfo::TupleKind: - hash_combine(digest, hash(info.tuple)); - return digest; - case TypeInfo::RefKind: - rehash(digest, info.ref.nullability); - hash_combine(digest, hash(info.ref.heapType)); - return digest; - } - WASM_UNREACHABLE("unexpected kind"); -} - size_t RecGroupHasher::hash(const HeapTypeInfo& info) const { size_t digest = wasm::hash(bool(info.supertype)); if (info.supertype) { @@ -2162,7 +1994,14 @@ bool RecGroupEquator::eq(Type a, Type b) const { if (a.isBasic() || b.isBasic()) { return a == b; } - return eq(*Type::getTypeInfo(a), *Type::getTypeInfo(b)); + if (a.isTuple() && b.isTuple()) { + return eq(a.getTuple(), b.getTuple()); + } + if (a.isRef() && b.isRef()) { + return a.getNullability() == b.getNullability() && + eq(a.getHeapType(), b.getHeapType()); + } + return false; } bool RecGroupEquator::eq(HeapType a, HeapType b) const { @@ -2184,20 +2023,6 @@ bool RecGroupEquator::eq(HeapType a, HeapType b) const { return (selfRefA && selfRefB) || (!selfRefA && !selfRefB && groupA == groupB); } -bool RecGroupEquator::eq(const TypeInfo& a, const TypeInfo& b) const { - if (a.kind != b.kind) { - return false; - } - switch (a.kind) { - case TypeInfo::TupleKind: - return eq(a.tuple, b.tuple); - case TypeInfo::RefKind: - return a.ref.nullability == b.ref.nullability && - eq(a.ref.heapType, b.ref.heapType); - } - WASM_UNREACHABLE("unexpected kind"); -} - bool RecGroupEquator::eq(const HeapTypeInfo& a, const HeapTypeInfo& b) const { if (bool(a.supertype) != bool(b.supertype)) { return false; @@ -2268,9 +2093,9 @@ bool RecGroupEquator::eq(const Array& a, const Array& b) const { } // anonymous namespace struct TypeBuilder::Impl { - // Store of temporary Types. Types that need to be canonicalized will be - // copied into the global TypeStore. - TypeStore typeStore; + // Store of temporary tuples. Tuples that need to be canonicalized will be + // copied into the global TupleStore. + TupleStore tupleStore; // Store of temporary recursion groups, which will be moved to the global // collection of recursion groups as part of building. @@ -2361,17 +2186,11 @@ HeapType TypeBuilder::getTempHeapType(size_t i) { } Type TypeBuilder::getTempTupleType(const Tuple& tuple) { - Type ret = impl->typeStore.insert(tuple); - if (tuple.size() > 1) { - return markTemp(ret); - } else { - // No new tuple was created, so the result might not be temporary. - return ret; - } + return impl->tupleStore.insert(tuple); } Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) { - return markTemp(impl->typeStore.insert(TypeInfo(type, nullable))); + return Type(type, nullable); } void TypeBuilder::setSubType(size_t i, std::optional super) { @@ -2490,8 +2309,7 @@ void updateReferencedHeapTypes( std::unique_ptr& info, const std::unordered_map& canonicalized) { // Update the reference types that refer to canonicalized heap types to be - // their canonical versions. Update the Types rather than the HeapTypes so - // that the validation of supertypes sees canonical types. + // their canonical versions. struct ChildUpdater : TypeGraphWalkerBase { const std::unordered_map& canonicalized; bool isTopLevel = true; @@ -2505,8 +2323,6 @@ void updateReferencedHeapTypes( auto ht = type->getHeapType(); if (auto it = canonicalized.find(ht); it != canonicalized.end()) { *type = Type(it->second, type->getNullability()); - } else if (isTemp(*type) && !isTemp(ht)) { - *type = Type(ht, type->getNullability()); } } else if (type->isTuple()) { TypeGraphWalkerBase::scanType(type); @@ -2603,39 +2419,29 @@ buildRecGroup(std::unique_ptr&& groupInfo, std::vector results(group.begin(), group.end()); - // We need to make the Types canonical as well, but right now there is no way - // to move them to their global store, so we have to create new types and - // replace the old ones. TODO simplify this. - struct Locations : TypeGraphWalker { - std::unordered_map> types; + // We need to make the tuples canonical as well, but right now there is no way + // to move them to their global store, so we have to create new tuples and + // replace the old ones. + struct TupleUpdater : TypeGraphWalkerBase { + bool isTopLevel = true; + void scanHeapType(HeapType* type) { + // Only scan top-level heap types. Heap type children will either be + // scanned separately or are already canonical. + if (isTopLevel) { + TypeGraphWalkerBase::scanHeapType(type); + isTopLevel = false; + } + } void scanType(Type* type) { - if (isTemp(*type)) { - types[*type].insert(type); + if (type->isTuple()) { + *type = globalTupleStore.insert(type->getTuple()); } - TypeGraphWalker::scanType(type); } }; - Locations locations; for (auto& type : results) { - locations.walkRoot(&type); - } - - // Canonicalize non-tuple Types (which never directly refer to other Types) - // before tuple Types to avoid canonicalizing a tuple that still contains - // non-canonical Types. - auto canonicalizeTypes = [&](bool tuples) { - for (auto& [original, uses] : locations.types) { - if (original.isTuple() == tuples) { - Type canonical = globalTypeStore.insert(*Type::getTypeInfo(original)); - for (Type* use : uses) { - *use = canonical; - } - } - } - }; - canonicalizeTypes(false); - canonicalizeTypes(true); + TupleUpdater().walkRoot(&type); + } return {results}; } @@ -2795,18 +2601,4 @@ size_t hash::operator()(const wasm::RecGroup& group) const { return wasm::hash(group.getID()); } -size_t hash::operator()(const wasm::TypeInfo& info) const { - auto digest = wasm::hash(info.kind); - switch (info.kind) { - case wasm::TypeInfo::TupleKind: - wasm::rehash(digest, info.tuple); - return digest; - case wasm::TypeInfo::RefKind: - wasm::rehash(digest, info.ref.nullability); - wasm::rehash(digest, info.ref.heapType); - return digest; - } - WASM_UNREACHABLE("unexpected kind"); -} - } // namespace std diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index d3e6a6767d6..032d15db434 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -20,17 +20,17 @@ BinaryenTypeAuto: -1 BinaryenPackedTypeNotPacked: 0 BinaryenPackedTypeInt8: 1 BinaryenPackedTypeInt16: 2 -BinaryenHeapTypeExt: 0 -BinaryenHeapTypeFunc: 2 -BinaryenHeapTypeAny: 6 -BinaryenHeapTypeEq: 8 -BinaryenHeapTypeI31: 10 -BinaryenHeapTypeStruct: 12 -BinaryenHeapTypeArray: 14 -BinaryenHeapTypeString: 18 -BinaryenHeapTypeNone: 20 -BinaryenHeapTypeNoext: 22 -BinaryenHeapTypeNofunc: 24 +BinaryenHeapTypeExt: 8 +BinaryenHeapTypeFunc: 16 +BinaryenHeapTypeAny: 32 +BinaryenHeapTypeEq: 40 +BinaryenHeapTypeI31: 48 +BinaryenHeapTypeStruct: 56 +BinaryenHeapTypeArray: 64 +BinaryenHeapTypeString: 80 +BinaryenHeapTypeNone: 88 +BinaryenHeapTypeNoext: 96 +BinaryenHeapTypeNofunc: 104 BinaryenFeatureMVP: 0 BinaryenFeatureAtomics: 1 BinaryenFeatureBulkMemory: 16 diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 97943551f5b..514df0c59a0 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -164,7 +164,6 @@ TEST_F(TypeTest, Basics) { TypeBuilder builder(3); ASSERT_EQ(builder.size(), size_t{3}); - Type refSig = builder.getTempRefType(builder[0], NonNullable); Type refStruct = builder.getTempRefType(builder[1], NonNullable); Type refArray = builder.getTempRefType(builder[2], NonNullable); Type refNullArray = builder.getTempRefType(builder[2], Nullable); @@ -191,7 +190,6 @@ TEST_F(TypeTest, Basics) { ASSERT_TRUE(built[2].isArray()); // The built types should have the correct structure. - Type newRefSig = Type(built[0], NonNullable); Type newRefStruct = Type(built[1], NonNullable); Type newRefArray = Type(built[2], NonNullable); Type newRefNullArray = Type(built[2], Nullable); @@ -200,12 +198,6 @@ TEST_F(TypeTest, Basics) { Signature(newRefStruct, {newRefArray, Type::i32})); EXPECT_EQ(built[1].getStruct(), Struct({Field(newRefNullArray, Immutable)})); EXPECT_EQ(built[2].getArray(), Array(Field(refNullAny, Mutable))); - - // The built types should be different from the temporary types. - EXPECT_NE(newRefSig, refSig); - EXPECT_NE(newRefStruct, refStruct); - EXPECT_NE(newRefArray, refArray); - EXPECT_NE(newRefNullArray, refNullArray); } TEST_F(TypeTest, DirectSelfSupertype) { From 52bc45fc34ec6868400216074744147e9d922685 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 12 Dec 2024 11:34:26 -0800 Subject: [PATCH 184/622] Execution results: JS traps on exnref on the boundary (#7147) Fixes #7145 --- src/tools/execution-results.h | 10 +++++----- test/lit/exec/fuzzing-api.wast | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index bffc4e8f2b2..b8823c3f0a5 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -187,8 +187,8 @@ struct LoggingExternalInterface : public ShellExternalInterface { Literals arguments; for (const auto& param : func->getParams()) { // An i64 param can work from JS, but fuzz_shell provides 0, which errors - // on attempts to convert it to BigInt. v128 cannot work at all. - if (param == Type::i64 || param == Type::v128) { + // on attempts to convert it to BigInt. v128 and exnref are disalloewd. + if (param == Type::i64 || param == Type::v128 || param.isExn()) { throwEmptyException(); } if (!param.isDefaultable()) { @@ -200,9 +200,9 @@ struct LoggingExternalInterface : public ShellExternalInterface { // Error on illegal results. Note that this happens, as per JS semantics, // *before* the call. for (const auto& result : func->getResults()) { - // An i64 result is fine: a BigInt will be provided. But v128 still - // errors. - if (result == Type::v128) { + // An i64 result is fine: a BigInt will be provided. But v128 and exnref + // still error. + if (result == Type::v128 || result.isExn()) { throwEmptyException(); } } diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index eae95fc0a87..7c975cb7536 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -218,6 +218,24 @@ ) ) + (func $illegal-exnref (param $x exnref) + ;; Helper for the function below. + (call $log-i32 + (i32.const 57) + ) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.illegal-exnref + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + (func $ref.calling.illegal-exnref (export "ref.calling.illegal-exnref") + ;; As above, we throw on the exnref param, and log 1. + (call $log-i32 + (call $call.ref.catch + (ref.func $illegal-exnref) + ) + ) + ) + (func $illegal-result (result v128) ;; Helper for the function below. The result is illegal for JS. (call $log-i32 @@ -324,6 +342,9 @@ ;; CHECK: [fuzz-exec] calling ref.calling.illegal-v128 ;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK: [fuzz-exec] calling ref.calling.illegal-exnref +;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK: [fuzz-exec] calling ref.calling.illegal-result ;; CHECK-NEXT: [LoggingExternalInterface logging 1] @@ -339,6 +360,7 @@ ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.catching ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.illegal +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.illegal-exnref ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.illegal-result ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.illegal-v128 ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.legal From 8a88ba280b2847d4d25d0859a87529e2132ebab8 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 13 Dec 2024 13:23:42 -0800 Subject: [PATCH 185/622] Support control flow inputs in IRBuilder (#7149) Since multivalue was standardized, WebAssembly has supported not only multiple results but also an arbitrary number of inputs on control flow structures, but until now Binaryen did not support control flow input. Binaryen IR still has no way to represent control flow input, so lower it away using scratch locals in IRBuilder. Since both the text and binary parsers use IRBuilder, this gives us full support for parsing control flow inputs. The lowering scheme is mostly simple. A local.set writing the control flow inputs to a scratch local is inserted immediately before the control flow structure begins and a local.get retrieving those inputs is inserted inside the control flow structure before the rest of its body. The only complications come from ifs, in which the inputs must be retrieved at the beginning of both arms, and from loops, where branches to the beginning of the loop must be transformed so their values are written to the scratch local along the way. Resolves #6407. --- CHANGELOG.md | 1 + src/parser/contexts.h | 45 +- src/wasm-binary.h | 4 +- src/wasm-ir-builder.h | 95 ++- src/wasm/wasm-binary.cpp | 40 +- src/wasm/wasm-ir-builder.cpp | 129 ++-- test/lit/binary/bad-multivalue-block.test | 16 - .../lit/binary/bad-multivalue-block.test.wasm | Bin 34 -> 0 bytes test/lit/binary/bad-multivalue-if.test | 22 - test/lit/binary/bad-multivalue-if.test.wasm | Bin 38 -> 0 bytes test/lit/control-flow-input.wast | 623 ++++++++++++++++++ test/lit/control-flow-input.wast.wasm | Bin 0 -> 793 bytes test/lit/parse-bad-block-params.wast | 12 - 13 files changed, 825 insertions(+), 162 deletions(-) delete mode 100644 test/lit/binary/bad-multivalue-block.test delete mode 100644 test/lit/binary/bad-multivalue-block.test.wasm delete mode 100644 test/lit/binary/bad-multivalue-if.test delete mode 100644 test/lit/binary/bad-multivalue-if.test.wasm create mode 100644 test/lit/control-flow-input.wast create mode 100644 test/lit/control-flow-input.wast.wasm delete mode 100644 test/lit/parse-bad-block-params.wast diff --git a/CHANGELOG.md b/CHANGELOG.md index 324c29526f3..52e453ee48f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Current Trunk - BinaryenSelect no longer takes a type parameter. - AutoDrop APIs have been removed. + - Binaryen now supports parsing control flow structures with parameter types. v120 ---- diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 807b6c0030e..3e0bc7c4094 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1447,11 +1447,7 @@ struct ParseDefsCtx : TypeParserCtx { Result getBlockTypeFromTypeUse(Index pos, HeapType type) { assert(type.isSignature()); - if (type.getSignature().params != Type::none) { - return in.err(pos, "block parameters not yet supported"); - } - // TODO: Once we support block parameters, return an error here if any of - // them are named. + // TODO: Error if block parameters are named return type; } @@ -1822,9 +1818,11 @@ struct ParseDefsCtx : TypeParserCtx { HeapType type) { // TODO: validate labels? // TODO: Move error on input types to here? - return withLoc(pos, - irBuilder.makeBlock(label ? *label : Name{}, - type.getSignature().results)); + if (!type.isSignature()) { + return in.err(pos, "expected function type"); + } + return withLoc( + pos, irBuilder.makeBlock(label ? *label : Name{}, type.getSignature())); } Result<> makeIf(Index pos, @@ -1832,10 +1830,11 @@ struct ParseDefsCtx : TypeParserCtx { std::optional label, HeapType type) { // TODO: validate labels? - // TODO: Move error on input types to here? + if (!type.isSignature()) { + return in.err(pos, "expected function type"); + } return withLoc( - pos, - irBuilder.makeIf(label ? *label : Name{}, type.getSignature().results)); + pos, irBuilder.makeIf(label ? *label : Name{}, type.getSignature())); } Result<> visitElse() { return withLoc(irBuilder.visitElse()); } @@ -1845,10 +1844,11 @@ struct ParseDefsCtx : TypeParserCtx { std::optional label, HeapType type) { // TODO: validate labels? - // TODO: Move error on input types to here? + if (!type.isSignature()) { + return in.err(pos, "expected function type"); + } return withLoc( - pos, - irBuilder.makeLoop(label ? *label : Name{}, type.getSignature().results)); + pos, irBuilder.makeLoop(label ? *label : Name{}, type.getSignature())); } Result<> makeTry(Index pos, @@ -1856,10 +1856,11 @@ struct ParseDefsCtx : TypeParserCtx { std::optional label, HeapType type) { // TODO: validate labels? - // TODO: Move error on input types to here? + if (!type.isSignature()) { + return in.err(pos, "expected function type"); + } return withLoc( - pos, - irBuilder.makeTry(label ? *label : Name{}, type.getSignature().results)); + pos, irBuilder.makeTry(label ? *label : Name{}, type.getSignature())); } Result<> makeTryTable(Index pos, @@ -1875,12 +1876,10 @@ struct ParseDefsCtx : TypeParserCtx { labels.push_back(info.label); isRefs.push_back(info.isRef); } - return withLoc(pos, - irBuilder.makeTryTable(label ? *label : Name{}, - type.getSignature().results, - tags, - labels, - isRefs)); + return withLoc( + pos, + irBuilder.makeTryTable( + label ? *label : Name{}, type.getSignature(), tags, labels, isRefs)); } Result<> visitCatch(Index pos, Name tag) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 163a62b1e41..61f4faf69e2 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1467,10 +1467,12 @@ class WasmBinaryReader { bool getBasicType(int32_t code, Type& out); bool getBasicHeapType(int64_t code, HeapType& out); + // Get the signature of control flow structure. + Signature getBlockType(); // Read a value and get a type for it. Type getType(); // Get a type given the initial S32LEB has already been read, and is provided. - Type getType(int initial); + Type getType(int code); HeapType getHeapType(); HeapType getIndexedHeapType(); diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 84ac2697930..250d5d17c18 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -80,15 +80,18 @@ class IRBuilder : public UnifiedExpressionVisitor> { // the corresponding `makeXYZ` function below instead of `visitXYZStart`, but // either way must call `visitEnd` and friends at the appropriate times. Result<> visitFunctionStart(Function* func); - Result<> visitBlockStart(Block* block); - Result<> visitIfStart(If* iff, Name label = {}); + Result<> visitBlockStart(Block* block, Type inputType = Type::none); + Result<> visitIfStart(If* iff, Name label = {}, Type inputType = Type::none); Result<> visitElse(); - Result<> visitLoopStart(Loop* iff); - Result<> visitTryStart(Try* tryy, Name label = {}); + Result<> visitLoopStart(Loop* iff, Type inputType = Type::none); + Result<> + visitTryStart(Try* tryy, Name label = {}, Type inputType = Type::none); Result<> visitCatch(Name tag); Result<> visitCatchAll(); Result<> visitDelegate(Index label); - Result<> visitTryTableStart(TryTable* trytable, Name label = {}); + Result<> visitTryTableStart(TryTable* trytable, + Name label = {}, + Type inputType = Type::none); Result<> visitEnd(); // Used to visit break nodes when traversing a single block without its @@ -113,9 +116,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { // nodes. This is generally safer than calling `visit` because the function // signatures ensure that there are no missing fields. Result<> makeNop(); - Result<> makeBlock(Name label, Type type); - Result<> makeIf(Name label, Type type); - Result<> makeLoop(Name label, Type type); + Result<> makeBlock(Name label, Signature sig); + Result<> makeIf(Name label, Signature sig); + Result<> makeLoop(Name label, Signature sig); Result<> makeBreak(Index label, bool isConditional); Result<> makeSwitch(const std::vector& labels, Index defaultLabel); // Unlike Builder::makeCall, this assumes the function already exists. @@ -180,9 +183,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeTableFill(Name table); Result<> makeTableCopy(Name destTable, Name srcTable); Result<> makeTableInit(Name elem, Name table); - Result<> makeTry(Name label, Type type); + Result<> makeTry(Name label, Signature sig); Result<> makeTryTable(Name label, - Type type, + Signature sig, const std::vector& tags, const std::vector& labels, const std::vector& isRefs); @@ -323,13 +326,21 @@ class IRBuilder : public UnifiedExpressionVisitor> { // The branch label name for this scope. Always fresh, never shadowed. Name label; + // For Try/Catch/CatchAll scopes, we need to separately track a label used // for branches, since the normal label is only used for delegates. Name branchLabel; bool labelUsed = false; + // If the control flow scope has an input type, we need to lower it using a + // scratch local because we cannot represent control flow input in the IR. + Type inputType; + Index inputLocal = -1; + + // The stack of instructions being built in this scope. std::vector exprStack; + // Whether we have seen an unreachable instruction and are in // stack-polymorphic unreachable mode. bool unreachable = false; @@ -338,29 +349,39 @@ class IRBuilder : public UnifiedExpressionVisitor> { size_t startPos = 0; ScopeCtx() : scope(NoScope{}) {} - ScopeCtx(Scope scope) : scope(scope) {} - ScopeCtx(Scope scope, Name label, bool labelUsed) - : scope(scope), label(label), labelUsed(labelUsed) {} + ScopeCtx(Scope scope, Type inputType) + : scope(scope), inputType(inputType) {} + ScopeCtx( + Scope scope, Name label, bool labelUsed, Type inputType, Index inputLocal) + : scope(scope), label(label), labelUsed(labelUsed), inputType(inputType), + inputLocal(inputLocal) {} ScopeCtx(Scope scope, Name label, bool labelUsed, Name branchLabel) : scope(scope), label(label), branchLabel(branchLabel), labelUsed(labelUsed) {} static ScopeCtx makeFunc(Function* func) { - return ScopeCtx(FuncScope{func}); + return ScopeCtx(FuncScope{func}, Type::none); } - static ScopeCtx makeBlock(Block* block) { - return ScopeCtx(BlockScope{block}); + static ScopeCtx makeBlock(Block* block, Type inputType) { + return ScopeCtx(BlockScope{block}, inputType); } - static ScopeCtx makeIf(If* iff, Name originalLabel = {}) { - return ScopeCtx(IfScope{iff, originalLabel}); + static ScopeCtx makeIf(If* iff, Name originalLabel, Type inputType) { + return ScopeCtx(IfScope{iff, originalLabel}, inputType); } - static ScopeCtx - makeElse(If* iff, Name originalLabel, Name label, bool labelUsed) { - return ScopeCtx(ElseScope{iff, originalLabel}, label, labelUsed); + static ScopeCtx makeElse(If* iff, + Name originalLabel, + Name label, + bool labelUsed, + Type inputType, + Index inputLocal) { + return ScopeCtx( + ElseScope{iff, originalLabel}, label, labelUsed, inputType, inputLocal); } - static ScopeCtx makeLoop(Loop* loop) { return ScopeCtx(LoopScope{loop}); } - static ScopeCtx makeTry(Try* tryy, Name originalLabel = {}) { - return ScopeCtx(TryScope{tryy, originalLabel}); + static ScopeCtx makeLoop(Loop* loop, Type inputType) { + return ScopeCtx(LoopScope{loop}, inputType); + } + static ScopeCtx makeTry(Try* tryy, Name originalLabel, Type inputType) { + return ScopeCtx(TryScope{tryy, originalLabel}, inputType); } static ScopeCtx makeCatch(Try* tryy, Name originalLabel, @@ -378,8 +399,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { return ScopeCtx( CatchAllScope{tryy, originalLabel}, label, labelUsed, branchLabel); } - static ScopeCtx makeTryTable(TryTable* trytable, Name originalLabel = {}) { - return ScopeCtx(TryTableScope{trytable, originalLabel}); + static ScopeCtx + makeTryTable(TryTable* trytable, Name originalLabel, Type inputType) { + return ScopeCtx(TryTableScope{trytable, originalLabel}, inputType); } bool isNone() { return std::get_if(&scope); } @@ -518,6 +540,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { } WASM_UNREACHABLE("unexpected scope kind"); } + bool isDelimiter() { return getElse() || getCatch() || getCatchAll(); } }; // The stack of block contexts currently being parsed. @@ -541,7 +564,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Index blockHint = 0; Index labelHint = 0; - void pushScope(ScopeCtx scope) { + Result<> pushScope(ScopeCtx&& scope) { if (auto label = scope.getOriginalLabel()) { // Assign a fresh label to the scope, if necessary. if (!scope.label) { @@ -554,7 +577,21 @@ class IRBuilder : public UnifiedExpressionVisitor> { scope.startPos = lastBinaryPos; lastBinaryPos = *binaryPos; } - scopeStack.push_back(scope); + bool hasInput = scope.inputType != Type::none; + Index inputLocal = scope.inputLocal; + if (hasInput && !scope.isDelimiter()) { + if (inputLocal == Index(-1)) { + auto scratch = addScratchLocal(scope.inputType); + CHECK_ERR(scratch); + inputLocal = scope.inputLocal = *scratch; + } + CHECK_ERR(makeLocalSet(inputLocal)); + } + scopeStack.emplace_back(std::move(scope)); + if (hasInput) { + CHECK_ERR(makeLocalGet(inputLocal)); + } + return Ok{}; } ScopeCtx& getScope() { @@ -610,6 +647,8 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result getLabelType(Index label); Result getLabelType(Name labelName); + void fixLoopWithInput(Loop* loop, Type inputType, Index scratch); + void dump(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 0c931f51864..791dc53d777 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2121,30 +2121,30 @@ bool WasmBinaryReader::getBasicHeapType(int64_t code, HeapType& out) { } } -Type WasmBinaryReader::getType(int initial) { - // Single value types are negative; signature indices are non-negative - if (initial >= 0) { - // TODO: Handle block input types properly. - auto sig = getSignatureByTypeIndex(initial); - if (sig.params != Type::none) { - throwError("control flow inputs are not supported yet"); - } - return sig.results; +Signature WasmBinaryReader::getBlockType() { + // Single value types are negative; signature indices are non-negative. + auto code = getS32LEB(); + if (code >= 0) { + return getSignatureByTypeIndex(code); + } + if (code == BinaryConsts::EncodedType::Empty) { + return Signature(); } + return Signature(Type::none, getType(code)); +} + +Type WasmBinaryReader::getType(int code) { Type type; - if (getBasicType(initial, type)) { + if (getBasicType(code, type)) { return type; } - switch (initial) { - // None only used for block signatures. TODO: Separate out? - case BinaryConsts::EncodedType::Empty: - return Type::none; + switch (code) { case BinaryConsts::EncodedType::nullable: return Type(getHeapType(), Nullable); case BinaryConsts::EncodedType::nonnullable: return Type(getHeapType(), NonNullable); default: - throwError("invalid wasm type: " + std::to_string(initial)); + throwError("invalid wasm type: " + std::to_string(code)); } WASM_UNREACHABLE("unexpected type"); } @@ -2885,11 +2885,11 @@ Result<> WasmBinaryReader::readInst() { uint8_t code = getInt8(); switch (code) { case BinaryConsts::Block: - return builder.makeBlock(Name(), getType()); + return builder.makeBlock(Name(), getBlockType()); case BinaryConsts::If: - return builder.makeIf(Name(), getType()); + return builder.makeIf(Name(), getBlockType()); case BinaryConsts::Loop: - return builder.makeLoop(Name(), getType()); + return builder.makeLoop(Name(), getBlockType()); case BinaryConsts::Br: return builder.makeBreak(getU32LEB(), false); case BinaryConsts::BrIf: @@ -2974,9 +2974,9 @@ Result<> WasmBinaryReader::readInst() { case BinaryConsts::TableSet: return builder.makeTableSet(getTableName(getU32LEB())); case BinaryConsts::Try: - return builder.makeTry(Name(), getType()); + return builder.makeTry(Name(), getBlockType()); case BinaryConsts::TryTable: { - auto type = getType(); + auto type = getBlockType(); std::vector tags; std::vector labels; std::vector isRefs; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 96212ccd7fc..6cd62e43991 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -679,9 +679,8 @@ Result<> IRBuilder::visitExpression(Expression* curr) { Result IRBuilder::getLabelType(Index label) { auto scope = getScope(label); CHECK_ERR(scope); - // Loops would receive their input type rather than their output type, if we - // supported that. - return (*scope)->getLoop() ? Type::none : (*scope)->getResultType(); + // Loops receive their input type rather than their output type. + return (*scope)->getLoop() ? (*scope)->inputType : (*scope)->getResultType(); } Result IRBuilder::getLabelType(Name labelName) { @@ -722,35 +721,31 @@ Result<> IRBuilder::visitFunctionStart(Function* func) { return Ok{}; } -Result<> IRBuilder::visitBlockStart(Block* curr) { +Result<> IRBuilder::visitBlockStart(Block* curr, Type inputType) { applyDebugLoc(curr); - pushScope(ScopeCtx::makeBlock(curr)); - return Ok{}; + return pushScope(ScopeCtx::makeBlock(curr, inputType)); } -Result<> IRBuilder::visitIfStart(If* iff, Name label) { +Result<> IRBuilder::visitIfStart(If* iff, Name label, Type inputType) { applyDebugLoc(iff); CHECK_ERR(visitIf(iff)); - pushScope(ScopeCtx::makeIf(iff, label)); - return Ok{}; + return pushScope(ScopeCtx::makeIf(iff, label, inputType)); } -Result<> IRBuilder::visitLoopStart(Loop* loop) { +Result<> IRBuilder::visitLoopStart(Loop* loop, Type inputType) { applyDebugLoc(loop); - pushScope(ScopeCtx::makeLoop(loop)); - return Ok{}; + return pushScope(ScopeCtx::makeLoop(loop, inputType)); } -Result<> IRBuilder::visitTryStart(Try* tryy, Name label) { +Result<> IRBuilder::visitTryStart(Try* tryy, Name label, Type inputType) { applyDebugLoc(tryy); - pushScope(ScopeCtx::makeTry(tryy, label)); - return Ok{}; + return pushScope(ScopeCtx::makeTry(tryy, label, inputType)); } -Result<> IRBuilder::visitTryTableStart(TryTable* trytable, Name label) { +Result<> +IRBuilder::visitTryTableStart(TryTable* trytable, Name label, Type inputType) { applyDebugLoc(trytable); - pushScope(ScopeCtx::makeTryTable(trytable, label)); - return Ok{}; + return pushScope(ScopeCtx::makeTryTable(trytable, label, inputType)); } Result IRBuilder::finishScope(Block* block) { @@ -849,6 +844,8 @@ Result<> IRBuilder::visitElse() { auto originalLabel = scope.getOriginalLabel(); auto label = scope.label; auto labelUsed = scope.labelUsed; + auto inputType = scope.inputType; + auto inputLocal = scope.inputLocal; auto expr = finishScope(); CHECK_ERR(expr); iff->ifTrue = *expr; @@ -858,8 +855,8 @@ Result<> IRBuilder::visitElse() { lastBinaryPos - codeSectionOffset; } - pushScope(ScopeCtx::makeElse(iff, originalLabel, label, labelUsed)); - return Ok{}; + return pushScope(ScopeCtx::makeElse( + iff, originalLabel, label, labelUsed, inputType, inputLocal)); } Result<> IRBuilder::visitCatch(Name tag) { @@ -891,8 +888,8 @@ Result<> IRBuilder::visitCatch(Name tag) { delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; } - pushScope( - ScopeCtx::makeCatch(tryy, originalLabel, label, labelUsed, branchLabel)); + CHECK_ERR(pushScope( + ScopeCtx::makeCatch(tryy, originalLabel, label, labelUsed, branchLabel))); // Push a pop for the exception payload if necessary. auto params = wasm.getTag(tag)->sig.params; if (params != Type::none) { @@ -933,9 +930,8 @@ Result<> IRBuilder::visitCatchAll() { delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; } - pushScope( + return pushScope( ScopeCtx::makeCatchAll(tryy, originalLabel, label, labelUsed, branchLabel)); - return Ok{}; } Result<> IRBuilder::visitDelegate(Index label) { @@ -1035,11 +1031,24 @@ Result<> IRBuilder::visitEnd() { } else if (auto* loop = scope.getLoop()) { loop->body = *expr; loop->name = scope.label; + if (scope.inputType != Type::none && scope.labelUsed) { + // Branches to this loop carry values, but Binaryen IR does not support + // that. Fix this by trampolining the branches through new code that sets + // the branch value to the appropriate scratch local. + fixLoopWithInput(loop, scope.inputType, scope.inputLocal); + } loop->finalize(loop->type); push(loop); } else if (auto* iff = scope.getIf()) { iff->ifTrue = *expr; - iff->ifFalse = nullptr; + if (scope.inputType != Type::none) { + // Normally an if without an else must have type none, but if there is an + // input parameter, the empty else arm must propagate its value. + // Synthesize an else arm that loads the value from the scratch local. + iff->ifFalse = builder.makeLocalGet(scope.inputLocal, scope.inputType); + } else { + iff->ifFalse = nullptr; + } iff->finalize(iff->type); push(maybeWrapForLabel(iff)); } else if (auto* iff = scope.getElse()) { @@ -1067,6 +1076,46 @@ Result<> IRBuilder::visitEnd() { return Ok{}; } +// Branches to this loop need to be trampolined through code that sets the value +// carried by the branch to the appropriate scratch local before branching to +// the loop. Transform this: +// +// (loop $l (param t1) (result t2) ...) +// +// to this: +// +// (loop $l0 (result t2) +// (block $l1 (result t2) +// (local.set $scratch ;; set the branch values to the scratch local +// (block $l (result t1) +// (br $l1 ;; exit the loop with the fallthrough value, if any. +// ... ;; contains branches to $l +// ) +// ) +// ) +// (br $l0) ;; continue the loop +// ) +// ) +void IRBuilder::fixLoopWithInput(Loop* loop, Type inputType, Index scratch) { + auto l = loop->name; + auto l0 = makeFresh(l, 0); + auto l1 = makeFresh(l, 1); + + Block* inner = + loop->type == Type::none + ? builder.blockifyWithName( + loop->body, l, builder.makeBreak(l1), inputType) + : builder.makeBlock(l, {builder.makeBreak(l1, loop->body)}, inputType); + + Block* outer = builder.makeBlock( + l1, + {builder.makeLocalSet(scratch, inner), builder.makeBreak(l0)}, + loop->type); + + loop->body = outer; + loop->name = l0; +} + Result IRBuilder::getLabelIndex(Name label, bool inDelegate) { auto it = labelDepths.find(label); if (it == labelDepths.end() || it->second.empty()) { @@ -1128,24 +1177,24 @@ Result<> IRBuilder::makeNop() { return Ok{}; } -Result<> IRBuilder::makeBlock(Name label, Type type) { +Result<> IRBuilder::makeBlock(Name label, Signature sig) { auto* block = wasm.allocator.alloc(); block->name = label; - block->type = type; - return visitBlockStart(block); + block->type = sig.results; + return visitBlockStart(block, sig.params); } -Result<> IRBuilder::makeIf(Name label, Type type) { +Result<> IRBuilder::makeIf(Name label, Signature sig) { auto* iff = wasm.allocator.alloc(); - iff->type = type; - return visitIfStart(iff, label); + iff->type = sig.results; + return visitIfStart(iff, label, sig.params); } -Result<> IRBuilder::makeLoop(Name label, Type type) { +Result<> IRBuilder::makeLoop(Name label, Signature sig) { auto* loop = wasm.allocator.alloc(); loop->name = label; - loop->type = type; - return visitLoopStart(loop); + loop->type = sig.results; + return visitLoopStart(loop, sig.params); } Result<> IRBuilder::makeBreak(Index label, bool isConditional) { @@ -1584,19 +1633,19 @@ Result<> IRBuilder::makeTableInit(Name elem, Name table) { return Ok{}; } -Result<> IRBuilder::makeTry(Name label, Type type) { +Result<> IRBuilder::makeTry(Name label, Signature sig) { auto* tryy = wasm.allocator.alloc(); - tryy->type = type; - return visitTryStart(tryy, label); + tryy->type = sig.results; + return visitTryStart(tryy, label, sig.params); } Result<> IRBuilder::makeTryTable(Name label, - Type type, + Signature sig, const std::vector& tags, const std::vector& labels, const std::vector& isRefs) { auto* trytable = wasm.allocator.alloc(); - trytable->type = type; + trytable->type = sig.results; trytable->catchTags.set(tags); trytable->catchRefs.set(isRefs); trytable->catchDests.reserve(labels.size()); @@ -1605,7 +1654,7 @@ Result<> IRBuilder::makeTryTable(Name label, CHECK_ERR(name); trytable->catchDests.push_back(*name); } - return visitTryTableStart(trytable, label); + return visitTryTableStart(trytable, label, sig.params); } Result<> IRBuilder::makeThrow(Name tag) { diff --git a/test/lit/binary/bad-multivalue-block.test b/test/lit/binary/bad-multivalue-block.test deleted file mode 100644 index 8b100fe89ed..00000000000 --- a/test/lit/binary/bad-multivalue-block.test +++ /dev/null @@ -1,16 +0,0 @@ -;; Test that we error properly on a block with a bad multivalue (inputs). - -;; File contents: -;; -;; (module -;; (func $test -;; i32.const 0 -;; (block (param i32) -;; drop -;; ) -;; ) -;; ) - -;; RUN: not wasm-opt -all %s.wasm 2>&1 | filecheck %s - -;; CHECK: control flow inputs are not supported yet diff --git a/test/lit/binary/bad-multivalue-block.test.wasm b/test/lit/binary/bad-multivalue-block.test.wasm deleted file mode 100644 index e44b9033f2001cdaa291bb87f198d97958e2ac00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34 pcmZQbEY4+QU|?Y6U`k+MNMNjIU}j=u;NoKBU~pt$VwB?M1^`B110?_e diff --git a/test/lit/binary/bad-multivalue-if.test b/test/lit/binary/bad-multivalue-if.test deleted file mode 100644 index 8fe20601285..00000000000 --- a/test/lit/binary/bad-multivalue-if.test +++ /dev/null @@ -1,22 +0,0 @@ -;; Test that we error properly on an if with a bad multivalue (inputs). - -;; File contents: -;; -;; (module -;; (func $test -;; i32.const 0 -;; i32.const 1 -;; (if (param i32) -;; (then -;; drop -;; ) -;; (else -;; drop -;; ) -;; ) -;; ) -;; ) - -;; RUN: not wasm-opt -all %s.wasm 2>&1 | filecheck %s - -;; CHECK: control flow inputs are not supported yet diff --git a/test/lit/binary/bad-multivalue-if.test.wasm b/test/lit/binary/bad-multivalue-if.test.wasm deleted file mode 100644 index baddfec4ed77c13dfb6d31fde432c4891f6a7dfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38 tcmZQbEY4+QU|?Y6U`k+MNMNjIU}j=u;NoNCVQ^${WMpBKVwK|N1^`u(1CRg! diff --git a/test/lit/control-flow-input.wast b/test/lit/control-flow-input.wast new file mode 100644 index 00000000000..7baae35f0ef --- /dev/null +++ b/test/lit/control-flow-input.wast @@ -0,0 +1,623 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that control flow input is correctly parsed using scratch locals. The +;; binary input file is generated from this file using WABT's wat2wasm +;; --enable-all --debug-names and should be regenerated when new tests are added +;; here. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s +;; RUN: wasm-opt -all %s.wasm -S -o - | filecheck %s + +(module + (type $id (func (param i32) (result i32))) + + ;; CHECK: (tag $e (param i32)) + (tag $e (param i32)) + + ;; CHECK: (func $block (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block (result i32) + i32.const 0 + block (param i32) (result i32) + end + ) + + ;; CHECK: (func $block-multivalue (type $1) (result i32 i64) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $1) (result i32 i64) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-multivalue (result i32 i64) + i32.const 0 + i64.const 1 + block (param i32 i64) (result i32 i64) + end + ) + + ;; CHECK: (func $block-drop (type $2) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-drop + i32.const 0 + block (param i32) + drop + end + ) + + ;; CHECK: (func $block-multivalue-drop (type $2) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_1 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-multivalue-drop + i32.const 0 + i64.const 1 + block (param i32 i64) + drop + drop + end + ) + + ;; CHECK: (func $block-passthrough-nop (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-passthrough-nop (result i32) + i32.const 0 + block (param i32) (result i32) + nop + end + ) + + ;; CHECK: (func $block-passthrough-type (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-passthrough-type (result i32) + i32.const 0 + block (type $id) + end + ) + + ;; CHECK: (func $loop (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop (result i32) + i32.const 0 + loop (param i32) (result i32) + end + ) + + ;; CHECK: (func $loop-branch (type $2) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 + ;; CHECK-NEXT: (block $label1 + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-branch + i32.const 0 + loop (param i32) + br 0 + end + ) + + ;; CHECK: (func $loop-branch-cond (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 (result i32) + ;; CHECK-NEXT: (block $label1 (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label1 + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-branch-cond (result i32) + i32.const 0 + loop (param i32) (result i32) + i32.const 1 + br_if 0 + end + ) + + ;; CHECK: (func $loop-branch-cond-drop (type $2) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 + ;; CHECK-NEXT: (block $label1 + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-branch-cond-drop + i32.const 0 + loop (param i32) + i32.const 1 + br_if 0 + drop + end + ) + + ;; CHECK: (func $loop-branch-cond-new-val (type $4) (result i64) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 (result i64) + ;; CHECK-NEXT: (block $label1 (result i64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label1 + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-branch-cond-new-val (result i64) + i32.const 0 + loop (param i32) (result i64) + i32.const 1 + br_if 0 + drop + i64.const 2 + end + ) + + ;; CHECK: (func $nested-loops-multivalue (type $1) (result i32 i64) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_1 (tuple i32 i64)) + ;; CHECK-NEXT: (block $label2 (type $1) (result i32 i64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label10 + ;; CHECK-NEXT: (block $label11 + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label1 (type $1) (result i32 i64) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 + ;; CHECK-NEXT: (block $label3 + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (block $label (type $1) (result i32 i64) + ;; CHECK-NEXT: (br_table $label $label1 $label2 + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-loops-multivalue (result i32 i64) + i32.const 0 + i64.const 1 + loop (param i32 i64) + loop (param i32 i64) + i32.const 2 + br_table 0 1 2 + end + end + unreachable + ) + + ;; CHECK: (func $if (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if (result i32) + i32.const 0 + i32.const 1 + if (param i32) (result i32) + end + ) + + ;; CHECK: (func $if-new-val (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-new-val (result i32) + i32.const 0 + i32.const 1 + if (param i32) (result i32) + drop + i32.const 2 + end + ) + + ;; CHECK: (func $if-multivalue (type $1) (result i32 i64) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (type $1) (result i32 i64) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-multivalue (result i32 i64) + i32.const 0 + i64.const 1 + i32.const 2 + if (param i32 i64) (result i32 i64) + end + ) + + ;; CHECK: (func $if-else (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-else (result i32) + i32.const 0 + i32.const 1 + if (param i32) (result i32) + else + end + ) + + ;; CHECK: (func $if-else-drop (type $2) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-else-drop + i32.const 0 + i32.const 1 + if (param i32) + drop + else + drop + end + ) + + ;; CHECK: (func $if-else-multivalue (type $5) (result f32) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_1 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local $scratch_3 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result f32) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_3 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-else-multivalue (result f32) + i32.const 0 + i64.const 1 + i32.const 2 + if (param i32 i64) (result f32) + drop + drop + f32.const 3 + else + drop + drop + f32.const 4 + end + ) + + ;; CHECK: (func $try (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try (result i32) + i32.const 0 + try (param i32) (result i32) + end + ) + + ;; CHECK: (func $try-catch (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-catch (result i32) + i32.const 0 + try (param i32) (result i32) + catch $e + end + ) + + ;; CHECK: (func $try-catch-all (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-catch-all (result i32) + i32.const 0 + try (param i32) (result i32) + catch_all + i32.const 1 + end + ) + + ;; CHECK: (func $try-catch-delegate (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-catch-delegate (result i32) + i32.const 0 + try (param i32) (result i32) + delegate 0 + ) + + ;; CHECK: (func $try-table (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try_table (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-table (result i32) + i32.const 0 + try_table (param i32) (result i32) + end + ) +) diff --git a/test/lit/control-flow-input.wast.wasm b/test/lit/control-flow-input.wast.wasm new file mode 100644 index 0000000000000000000000000000000000000000..664d1be843a4e81a76bd4dc15f445160e49a08c4 GIT binary patch literal 793 zcmZWnOP1O&5G=JU8!Ve)7=H04C-BQEdrU5Z!J2^=WAm^{-YjOw0dkR?D;G&iW&)Wk z++9^&EpfGmweBQ8VeCW*t>64M%S)dW5hH7t$VALIvRGKm zy#q^njx0&*G1H}xzVtI15Cltb*Jr}Jv|5y8Tw1FqMOy{BWSfsf!Keo3#&_eT7t8PvM0H($ljrF4HP+ugUk zJQ)f*r8w3HIeA}At?XUoL*6v)_E?{uwrN!#)UB(1;LYfIAFvj}aa)#spJ%iru?Hd;D_EAL7TOLI3~& literal 0 HcmV?d00001 diff --git a/test/lit/parse-bad-block-params.wast b/test/lit/parse-bad-block-params.wast deleted file mode 100644 index 67e05989ce6..00000000000 --- a/test/lit/parse-bad-block-params.wast +++ /dev/null @@ -1,12 +0,0 @@ -;; RUN: not wasm-opt %s -S -o - 2>&1 | filecheck %s - -;; CHECK: 8:11: error: block parameters not yet supported - -(module - (func - (i32.const 0) - (block (param i32) - (drop) - ) - ) -) From 484adec87c8b988cbb24d0a2fc4718ac69625173 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 13 Dec 2024 13:24:07 -0800 Subject: [PATCH 186/622] [NFC] Move HeapType::isBottom() to header (#7150) This makes Precompute about 5% faster on a WasmGC binary. Inspired by #6931. --- src/wasm-type.h | 27 +++++++++++++++++++++++++++ src/wasm/wasm-type.cpp | 25 ------------------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index b48a1071328..1e7c0a0ba15 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -870,6 +870,33 @@ std::ostream& operator<<(std::ostream&, Struct); std::ostream& operator<<(std::ostream&, Array); std::ostream& operator<<(std::ostream&, TypeBuilder::ErrorReason); +// Inline some nontrivial methods here for performance reasons. + +inline bool HeapType::isBottom() const { + if (isBasic()) { + switch (getBasic(Unshared)) { + case ext: + case func: + case cont: + case any: + case eq: + case i31: + case struct_: + case array: + case exn: + case string: + return false; + case none: + case noext: + case nofunc: + case nocont: + case noexn: + return true; + } + } + return false; +} + } // namespace wasm namespace std { diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index a6ed6877097..4f341588b6a 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -866,31 +866,6 @@ HeapTypeKind HeapType::getKind() const { return getHeapTypeInfo(*this)->kind; } -bool HeapType::isBottom() const { - if (isBasic()) { - switch (getBasic(Unshared)) { - case ext: - case func: - case cont: - case any: - case eq: - case i31: - case struct_: - case array: - case exn: - case string: - return false; - case none: - case noext: - case nofunc: - case nocont: - case noexn: - return true; - } - } - return false; -} - bool HeapType::isOpen() const { if (isBasic()) { return false; From 0272a2745a27ceb5839e933cbb371e33b3d7244d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 13 Dec 2024 14:14:30 -0800 Subject: [PATCH 187/622] Address comments from #7149 (#7152) The PR was accidentally merged without these fixes included. --- CHANGELOG.md | 3 ++- src/wasm-binary.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e453ee48f..b6ceeb8da1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ Current Trunk - BinaryenSelect no longer takes a type parameter. - AutoDrop APIs have been removed. - - Binaryen now supports parsing control flow structures with parameter types. + - Binaryen now supports parsing control flow structures with parameter types by + lowering them away in the parsers.. v120 ---- diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 61f4faf69e2..bece0af8ed9 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1467,7 +1467,7 @@ class WasmBinaryReader { bool getBasicType(int32_t code, Type& out); bool getBasicHeapType(int64_t code, HeapType& out); - // Get the signature of control flow structure. + // Get the signature of a control flow structure. Signature getBlockType(); // Read a value and get a type for it. Type getType(); From 315f7c1f65d67d2d8e008c6973354abac80d8d22 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 16 Dec 2024 12:05:09 -0800 Subject: [PATCH 188/622] Enable upstream spec tests requiring block parameters (#7151) Now that #7149 added support for parsing block parameters, we can run additional spec tests that previously failed. --- CHANGELOG.md | 2 +- scripts/test/shared.py | 6 +----- scripts/test/wasm2js.py | 1 + 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6ceeb8da1c..bea2a14d122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Current Trunk - BinaryenSelect no longer takes a type parameter. - AutoDrop APIs have been removed. - Binaryen now supports parsing control flow structures with parameter types by - lowering them away in the parsers.. + lowering them away in the parsers. v120 ---- diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 624a2f19a2d..8797f50b22f 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -416,7 +416,6 @@ def get_tests(test_dir, extensions=[], recursive=False): 'address.wast', # 64-bit offset allowed by memory64 'align.wast', # Alignment bit 6 used by multi-memory 'binary.wast', # memory.grow reserved byte a LEB in multi-memory - 'block.wast', # Requires block parameters 'bulk.wast', # Requires table.init abbreviation with implicit table 'comments.wast', # Issue with carriage returns being treated as newlines 'const.wast', # Hex float constant not recognized as out of range @@ -425,22 +424,19 @@ def get_tests(test_dir, extensions=[], recursive=False): 'elem.wast', # Requires table.init abbreviation with implicit table 'f32.wast', # Adding -0 and -nan should give a canonical NaN 'f64.wast', # Adding -0 and -nan should give a canonical NaN - 'fac.wast', # Requires block parameters (on a loop) 'float_exprs.wast', # Adding 0 and NaN should give canonical NaN 'float_misc.wast', # Rounding wrong on f64.sqrt 'func.wast', # Duplicate parameter names not properly rejected 'global.wast', # Globals allowed to refer to previous globals by GC - 'if.wast', # Requires block parameters (on an if) + 'if.wast', # Requires more precise unreachable validation 'imports.wast', # Requires wast `register` support 'linking.wast', # Requires wast `register` support - 'loop.wast', # Requires block parameters (on a loop) 'memory.wast', # Multiple memories now allowed 'annotations.wast', # String annotations IDs should be allowed 'id.wast', # Empty IDs should be disallowed 'throw.wast', # Requires try_table interpretation 'try_catch.wast', # Requires wast `register` support 'tag.wast', # Non-empty tag results allowed by stack switching - 'throw_ref.wast', # Requires block parameters (on an if) 'try_table.wast', # Requires try_table interpretation 'br_on_non_null.wast', # Requires sending values on br_on_non_null 'br_on_null.wast', # Requires sending values on br_on_null diff --git a/scripts/test/wasm2js.py b/scripts/test/wasm2js.py index 95fe4fdac94..3c70c061609 100644 --- a/scripts/test/wasm2js.py +++ b/scripts/test/wasm2js.py @@ -30,6 +30,7 @@ wasm2js_skipped_tests = [ 'empty_imported_table.wast', 'br.wast', # depends on multivalue + 'fac.wast', # depends on mutlivalue 'br_table.wast', # needs support for externref in assert_return ] From 353b759b230dff8fb82aeb157aeb6db360d74a49 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 16 Dec 2024 13:37:27 -0800 Subject: [PATCH 189/622] Version 121 (#7153) --- CHANGELOG.md | 13 ++++++++++--- CMakeLists.txt | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bea2a14d122..6685533bd3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,17 @@ full changeset diff at the end of each section. Current Trunk ------------- - - BinaryenSelect no longer takes a type parameter. - - AutoDrop APIs have been removed. +v121 +---- + + - BinaryenSelect no longer takes a type parameter. (#7097) + - AutoDrop APIs have been removed. (#7106) + - bulk-memory-opt and call-indirect-overlong features are added for parity with + LLVM. (#7139) + - WasmGC optimizations now run significantly faster and scale better with + available threads. (#7142) - Binaryen now supports parsing control flow structures with parameter types by - lowering them away in the parsers. + lowering them away in the parsers. (#7149) v120 ---- diff --git a/CMakeLists.txt b/CMakeLists.txt index ed6af9394dd..a6167612542 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.10.2) # to reduce this for compatability with emsdk. set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version") -project(binaryen LANGUAGES C CXX VERSION 120) +project(binaryen LANGUAGES C CXX VERSION 121) include(GNUInstallDirs) # The C++ standard whose features are required to build Binaryen. From aa0550e28002183dd7ea9c2a48ec3533ba70f862 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 16 Dec 2024 15:21:10 -0800 Subject: [PATCH 190/622] Fuzz JSPI (#7148) * Add a new "sleep" fuzzer import, that does a sleep for some ms. * Add JSPI support in fuzz_shell.js. This is in the form of commented-out async/await keywords - commented out so that normal fuzzing is not impacted. When we want to fuzz JSPI, we uncomment them. We also apply the JSPI operations of marking imports and exports as suspending/promising. JSPI fuzzing is added to both fuzz_opt.py and ClusterFuzz's run.py. --- scripts/clusterfuzz/run.py | 9 ++ scripts/fuzz_opt.py | 46 ++++++++-- scripts/fuzz_shell.js | 79 ++++++++++++++---- src/tools/execution-results.h | 3 + src/tools/fuzzing.h | 3 + src/tools/fuzzing/fuzzing.cpp | 29 +++++++ test/lit/exec/fuzzing-api.wast | 19 ++++- test/passes/fuzz_metrics_noprint.bin.txt | 52 ++++++------ .../fuzz_metrics_passes_noprint.bin.txt | 53 ++++++------ ...e-to-fuzz_all-features_metrics_noprint.txt | 83 +++++++++---------- test/unit/test_cluster_fuzz.py | 22 ++++- 11 files changed, 279 insertions(+), 119 deletions(-) diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index 8ac880e0de0..2fedb6510c9 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -200,6 +200,15 @@ def get_js_file_contents(i, output_dir): print(f'Created {bytes} wasm bytes') + # Some of the time, fuzz JSPI (similar to fuzz_opt.py, see details there). + if system_random.random() < 0.25: + # Prepend the flag to enable JSPI. + js = 'var JSPI = 1;\n\n' + js + + # Un-comment the async and await keywords. + js = js.replace('/* async */', 'async') + js = js.replace('/* await */', 'await') + return js diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 87f05905837..ca7c9e355d1 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -232,7 +232,11 @@ def randomize_fuzz_settings(): if random.random() < 0.5: GEN_ARGS += ['--enclose-world'] - print('randomized settings (NaNs, OOB, legalize):', NANS, OOB, LEGALIZE) + # Test JSPI somewhat rarely, as it may be slower. + global JSPI + JSPI = random.random() < 0.25 + + print('randomized settings (NaNs, OOB, legalize, JSPI):', NANS, OOB, LEGALIZE, JSPI) def init_important_initial_contents(): @@ -758,11 +762,39 @@ def run_d8_js(js, args=[], liftoff=True): return run_vm(cmd) -FUZZ_SHELL_JS = in_binaryen('scripts', 'fuzz_shell.js') +# For JSPI, we must customize fuzz_shell.js. We do so the first time we need +# it, and save the filename here. +JSPI_JS_FILE = None + + +def get_fuzz_shell_js(): + js = in_binaryen('scripts', 'fuzz_shell.js') + + if not JSPI: + # Just use the normal fuzz shell script. + return js + + global JSPI_JS_FILE + if JSPI_JS_FILE: + # Use the customized file we've already created. + return JSPI_JS_FILE + + JSPI_JS_FILE = os.path.abspath('jspi_fuzz_shell.js') + with open(JSPI_JS_FILE, 'w') as f: + # Enable JSPI. + f.write('var JSPI = 1;\n\n') + + # Un-comment the async and await keywords. + with open(js) as g: + code = g.read() + code = code.replace('/* async */', 'async') + code = code.replace('/* await */', 'await') + f.write(code) + return JSPI_JS_FILE def run_d8_wasm(wasm, liftoff=True, args=[]): - return run_d8_js(FUZZ_SHELL_JS, [wasm] + args, liftoff=liftoff) + return run_d8_js(get_fuzz_shell_js(), [wasm] + args, liftoff=liftoff) def all_disallowed(features): @@ -850,7 +882,7 @@ class D8: name = 'd8' def run(self, wasm, extra_d8_flags=[]): - return run_vm([shared.V8, FUZZ_SHELL_JS] + shared.V8_OPTS + get_v8_extra_flags() + extra_d8_flags + ['--', wasm]) + return run_vm([shared.V8, get_fuzz_shell_js()] + shared.V8_OPTS + get_v8_extra_flags() + extra_d8_flags + ['--', wasm]) def can_run(self, wasm): # V8 does not support shared memories when running with @@ -1160,7 +1192,7 @@ def fix_number(x): compare_between_vms(before, interpreter, 'Wasm2JS (vs interpreter)') def run(self, wasm): - with open(FUZZ_SHELL_JS) as f: + with open(get_fuzz_shell_js()) as f: wrapper = f.read() cmd = [in_bin('wasm2js'), wasm, '--emscripten'] # avoid optimizations if we have nans, as we don't handle them with @@ -1193,6 +1225,10 @@ def can_run_on_wasm(self, wasm): # specifically for growth here if INITIAL_CONTENTS: return False + # We run in node, which lacks JSPI support, and also we need wasm2js to + # implement wasm suspending using JS async/await. + if JSPI: + return False return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'multimemory', 'memory64']) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 95176bbe65b..6531465179f 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -1,3 +1,17 @@ +// This script can be customized by setting the following variables in code that +// runs before this script. +// +// The binary to be run. (If not set, we get the filename from argv and read +// from it.) +var binary; +// A second binary to be linked in and run as well. (Can also be read from +// argv.) +var secondBinary; +// Whether we are fuzzing JSPI. In addition to this being set, the "async" and +// "await" keywords must be taken out of the /* KEYWORD */ comments (which they +// are normally in, so as not to affect normal fuzzing). +var JSPI; + // Shell integration: find argv and set up readBinary(). var argv; var readBinary; @@ -25,9 +39,6 @@ if (typeof process === 'object' && typeof require === 'function') { }; } -// The binary to be run. This may be set already (by code that runs before this -// script), and if not, we get the filename from argv. -var binary; if (!binary) { binary = readBinary(argv[0]); } @@ -43,7 +54,6 @@ if (argv.length > 0 && argv[argv.length - 1].startsWith('exports:')) { // If a second parameter is given, it is a second binary that we will link in // with it. -var secondBinary; if (argv[1]) { secondBinary = readBinary(argv[1]); } @@ -163,9 +173,9 @@ function callFunc(func) { // Calls a given function in a try-catch, swallowing JS exceptions, and return 1 // if we did in fact swallow an exception. Wasm traps are not swallowed (see // details below). -function tryCall(func) { +/* async */ function tryCall(func) { try { - func(); + /* await */ func(); return 0; } catch (e) { // We only want to catch exceptions, not wasm traps: traps should still @@ -243,19 +253,39 @@ var imports = { }, // Export operations. - 'call-export': (index) => { - callFunc(exportList[index].value); + 'call-export': /* async */ (index) => { + /* await */ callFunc(exportList[index].value); }, - 'call-export-catch': (index) => { - return tryCall(() => callFunc(exportList[index].value)); + 'call-export-catch': /* async */ (index) => { + return tryCall(/* async */ () => /* await */ callFunc(exportList[index].value)); }, // Funcref operations. - 'call-ref': (ref) => { - callFunc(ref); + 'call-ref': /* async */ (ref) => { + // This is a direct function reference, and just like an export, it must + // be wrapped for JSPI. + ref = wrapExportForJSPI(ref); + /* await */ callFunc(ref); + }, + 'call-ref-catch': /* async */ (ref) => { + ref = wrapExportForJSPI(ref); + return tryCall(/* async */ () => /* await */ callFunc(ref)); }, - 'call-ref-catch': (ref) => { - return tryCall(() => callFunc(ref)); + + // Sleep a given amount of ms (when JSPI) and return a given id after that. + 'sleep': (ms, id) => { + if (!JSPI) { + return id; + } + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(id); + }, 0); // TODO: Use the ms in some reasonable, deterministic manner. + // Rather than actually setTimeout on them we could manage + // a queue of pending sleeps manually, and order them based + // on the "ms" (which would not be literal ms, but just + // how many time units to wait). + }); }, }, // Emscripten support. @@ -274,6 +304,22 @@ if (typeof WebAssembly.Tag !== 'undefined') { }; } +// If JSPI is available, wrap the imports and exports. +if (JSPI) { + for (var name of ['sleep', 'call-export', 'call-export-catch', 'call-ref', + 'call-ref-catch']) { + imports['fuzzing-support'][name] = + new WebAssembly.Suspending(imports['fuzzing-support'][name]); + } +} + +function wrapExportForJSPI(value) { + if (JSPI && typeof value === 'function') { + value = WebAssembly.promising(value); + } + return value; +} + // If a second binary will be linked in then set up the imports for // placeholders. Any import like (import "placeholder" "0" (func .. will be // provided by the secondary module, and must be called using an indirection. @@ -312,13 +358,14 @@ function build(binary) { // keep the ability to call anything that was ever exported.) for (var key in instance.exports) { var value = instance.exports[key]; + value = wrapExportForJSPI(value); exports[key] = value; exportList.push({ name: key, value: value }); } } // Run the code by calling exports. -function callExports() { +/* async */ function callExports() { // Call the exports we were told, or if we were not given an explicit list, // call them all. var relevantExports = exportsToCall || exportList; @@ -342,7 +389,7 @@ function callExports() { try { console.log('[fuzz-exec] calling ' + name); - var result = callFunc(value); + var result = /* await */ callFunc(value); if (typeof result !== 'undefined') { console.log('[fuzz-exec] note result: ' + name + ' => ' + printed(result)); } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index b8823c3f0a5..ea822d5477a 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -128,6 +128,9 @@ struct LoggingExternalInterface : public ShellExternalInterface { } catch (const WasmException& e) { return {Literal(int32_t(1))}; } + } else if (import->base == "sleep") { + // Do not actually sleep, just return the id. + return {arguments[1]}; } else { WASM_UNREACHABLE("unknown fuzzer import"); } diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 78219045c71..f76ed62a5bc 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -117,6 +117,7 @@ class TranslateToFuzzReader { Name callExportCatchImportName; Name callRefImportName; Name callRefCatchImportName; + Name sleepImportName; std::unordered_map> globalsByType; std::unordered_map> mutableGlobalsByType; @@ -238,6 +239,7 @@ class TranslateToFuzzReader { void addImportCallingSupport(); void addImportThrowingSupport(); void addImportTableSupport(); + void addImportSleepSupport(); void addHashMemorySupport(); // Special expression makers @@ -249,6 +251,7 @@ class TranslateToFuzzReader { // Call either an export or a ref. We do this from a single function to better // control the frequency of each. Expression* makeImportCallCode(Type type); + Expression* makeImportSleep(Type type); Expression* makeMemoryHashLogging(); // Function creation diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index a7f5e0d018f..2b9286180c3 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -317,6 +317,7 @@ void TranslateToFuzzReader::build() { } addImportLoggingSupport(); addImportCallingSupport(); + addImportSleepSupport(); modifyInitialFunctions(); // keep adding functions until we run out of input while (!random.finished()) { @@ -909,6 +910,24 @@ void TranslateToFuzzReader::addImportTableSupport() { } } +void TranslateToFuzzReader::addImportSleepSupport() { + if (!oneIn(4)) { + // Fuzz this somewhat rarely, as it may be slow. + return; + } + + // An import that sleeps for a given number of milliseconds, and also receives + // an integer id. It returns that integer id (useful for tracking separate + // sleeps). + sleepImportName = Names::getValidFunctionName(wasm, "sleep"); + auto func = std::make_unique(); + func->name = sleepImportName; + func->module = "fuzzing-support"; + func->base = "sleep"; + func->type = Signature({Type::i32, Type::i32}, Type::i32); + wasm.addFunction(std::move(func)); +} + void TranslateToFuzzReader::addHashMemorySupport() { // Add memory hasher helper (for the hash, see hash.h). The function looks // like: @@ -1090,6 +1109,13 @@ Expression* TranslateToFuzzReader::makeImportCallCode(Type type) { return builder.makeCall(exportTarget, {index}, type); } +Expression* TranslateToFuzzReader::makeImportSleep(Type type) { + // Sleep for some ms, and return a given id. + auto* ms = make(Type::i32); + auto id = make(Type::i32); + return builder.makeCall(sleepImportName, {ms, id}, Type::i32); +} + Expression* TranslateToFuzzReader::makeMemoryHashLogging() { auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32); return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none); @@ -1768,6 +1794,9 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { if (callExportCatchImportName || callRefCatchImportName) { options.add(FeatureSet::MVP, &Self::makeImportCallCode); } + if (sleepImportName) { + options.add(FeatureSet::MVP, &Self::makeImportSleep); + } options.add(FeatureSet::ReferenceTypes, &Self::makeRefIsNull); options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeRefEq, diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 7c975cb7536..8e251b2ed4f 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -19,6 +19,8 @@ (import "fuzzing-support" "call-ref" (func $call.ref (param funcref))) (import "fuzzing-support" "call-ref-catch" (func $call.ref.catch (param funcref) (result i32))) + (import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32))) + (table $table 10 20 funcref) ;; Note that the exported table appears first here, but in the binary and in @@ -284,7 +286,6 @@ ;; CHECK: [fuzz-exec] calling ref.calling.trap ;; CHECK-NEXT: [trap unreachable] - ;; CHECK-NEXT: warning: no passes specified, not doing any work (func $ref.calling.trap (export "ref.calling.trap") ;; We try to catch an exception here, but the target function traps, which is ;; not something we can catch. We will trap here, and not log at all. @@ -294,6 +295,18 @@ ) ) ) + + ;; CHECK: [fuzz-exec] calling do-sleep + ;; CHECK-NEXT: [fuzz-exec] note result: do-sleep => 42 + ;; CHECK-NEXT: warning: no passes specified, not doing any work + (func $do-sleep (export "do-sleep") (result i32) + (call $sleep + ;; A ridiculous amount of ms, but in the interpreter it is ignored anyhow. + (i32.const -1) + ;; An id, that is returned back to us. + (i32.const 42) + ) + ) ) ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -354,6 +367,10 @@ ;; CHECK: [fuzz-exec] calling ref.calling.trap ;; CHECK-NEXT: [trap unreachable] + +;; CHECK: [fuzz-exec] calling do-sleep +;; CHECK-NEXT: [fuzz-exec] note result: do-sleep => 42 +;; CHECK-NEXT: [fuzz-exec] comparing do-sleep ;; CHECK-NEXT: [fuzz-exec] comparing export.calling ;; CHECK-NEXT: [fuzz-exec] comparing export.calling.catching ;; CHECK-NEXT: [fuzz-exec] comparing logging diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index a2f996bcb38..bf7e517da49 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 49 - [funcs] : 74 + [exports] : 46 + [funcs] : 68 [globals] : 18 [imports] : 4 [memories] : 1 [memory-data] : 24 - [table-data] : 19 + [table-data] : 22 [tables] : 1 [tags] : 0 - [total] : 5695 - [vars] : 227 - Binary : 430 - Block : 976 - Break : 188 - Call : 272 - CallIndirect : 20 - Const : 876 - Drop : 99 - GlobalGet : 504 - GlobalSet : 373 - If : 299 - Load : 111 - LocalGet : 365 - LocalSet : 299 - Loop : 108 - Nop : 58 - RefFunc : 19 - Return : 74 - Select : 40 - Store : 34 + [total] : 9465 + [vars] : 215 + Binary : 671 + Block : 1531 + Break : 370 + Call : 366 + CallIndirect : 67 + Const : 1478 + Drop : 111 + GlobalGet : 766 + GlobalSet : 558 + If : 514 + Load : 173 + LocalGet : 729 + LocalSet : 550 + Loop : 202 + Nop : 133 + RefFunc : 22 + Return : 99 + Select : 84 + Store : 83 Switch : 2 - Unary : 365 - Unreachable : 183 + Unary : 682 + Unreachable : 274 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index c3881104d0d..814fdb1323d 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,34 +1,35 @@ Metrics total - [exports] : 30 - [funcs] : 47 + [exports] : 43 + [funcs] : 56 [globals] : 17 [imports] : 4 [memories] : 1 [memory-data] : 11 - [table-data] : 18 + [table-data] : 16 [tables] : 1 [tags] : 0 - [total] : 4738 - [vars] : 133 - Binary : 338 - Block : 781 - Break : 122 - Call : 249 - CallIndirect : 27 - Const : 780 - Drop : 105 - GlobalGet : 378 - GlobalSet : 288 - If : 216 - Load : 79 - LocalGet : 396 - LocalSet : 252 - Loop : 89 - Nop : 43 - RefFunc : 18 - Return : 70 - Select : 37 - Store : 36 - Unary : 294 - Unreachable : 140 + [total] : 10611 + [vars] : 184 + Binary : 754 + Block : 1699 + Break : 397 + Call : 325 + CallIndirect : 112 + Const : 1783 + Drop : 101 + GlobalGet : 869 + GlobalSet : 657 + If : 549 + Load : 195 + LocalGet : 893 + LocalSet : 609 + Loop : 251 + Nop : 123 + RefFunc : 16 + Return : 78 + Select : 74 + Store : 84 + Switch : 3 + Unary : 730 + Unreachable : 309 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index e2e9b8053df..3c2b7bfa285 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,57 +1,52 @@ Metrics total - [exports] : 6 - [funcs] : 6 + [exports] : 3 + [funcs] : 3 [globals] : 4 [imports] : 8 [memories] : 1 [memory-data] : 112 - [table-data] : 1 + [table-data] : 0 [tables] : 1 [tags] : 1 - [total] : 592 - [vars] : 38 - ArrayGet : 2 - ArrayLen : 2 - ArrayNew : 6 - ArrayNewFixed : 4 - ArraySet : 1 + [total] : 630 + [vars] : 23 + ArrayNewFixed : 3 AtomicCmpxchg : 1 AtomicFence : 1 - AtomicRMW : 2 - Binary : 81 - Block : 62 - BrOn : 1 - Break : 11 - Call : 13 - CallIndirect : 2 - Const : 123 - Drop : 2 - GlobalGet : 22 - GlobalSet : 22 - If : 17 - Load : 25 - LocalGet : 63 - LocalSet : 35 - Loop : 6 - Nop : 8 - Pop : 3 - RefAs : 1 - RefEq : 1 - RefFunc : 6 - RefNull : 3 - Return : 4 - SIMDExtract : 2 - Select : 2 - StringConst : 5 + AtomicNotify : 1 + Binary : 63 + Block : 60 + BrOn : 3 + Break : 8 + Call : 4 + CallRef : 3 + Const : 129 + DataDrop : 1 + Drop : 8 + GlobalGet : 21 + GlobalSet : 20 + I31Get : 1 + If : 12 + Load : 17 + LocalGet : 74 + LocalSet : 52 + Loop : 8 + MemoryFill : 1 + Nop : 4 + RefAs : 19 + RefFunc : 26 + RefI31 : 1 + RefIsNull : 1 + RefNull : 11 + Return : 2 + Select : 5 StringEncode : 1 - StringEq : 2 - StringMeasure : 1 - StringWTF16Get : 2 - StructNew : 9 - Try : 3 - TryTable : 3 + StructGet : 3 + StructNew : 26 + Try : 1 + TryTable : 6 TupleExtract : 1 TupleMake : 2 - Unary : 18 - Unreachable : 11 + Unary : 20 + Unreachable : 10 diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 56250d46aed..8f1d1810463 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -274,10 +274,11 @@ def test_file_contents(self): print() # To check for interesting JS file contents, we'll note how many times - # we build and run the wasm. + # we build and run the wasm, and other things like JSPI. seen_builds = [] seen_calls = [] seen_second_builds = [] + seen_JSPIs = [] for i in range(1, N + 1): fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') @@ -287,6 +288,17 @@ def test_file_contents(self): seen_calls.append(js.count('callExports();')) seen_second_builds.append(js.count('build(secondBinary);')) + # If JSPI is enabled, the async and await keywords should be + # enabled (uncommented). + if 'JSPI = 1' in js: + seen_JSPIs.append(1) + assert '/* async */' not in js + assert '/* await */' not in js + else: + seen_JSPIs.append(0) + assert '/* async */' in js + assert '/* await */' in js + # There is always one build and one call (those are in the default # fuzz_shell.js), and we add a couple of operations, each with equal # probability to be a build or a call, so over the 100 testcases here we @@ -323,6 +335,14 @@ def test_file_contents(self): print() + # JSPI is done 1/4 of the time or so. + print('JSPIs are distributed as ~ mean 0.25') + print(f'mean JSPIs: {statistics.mean(seen_JSPIs)}') + self.assertEqual(min(seen_JSPIs), 0) + self.assertEqual(max(seen_JSPIs), 1) + + print() + # "zzz" in test name so that this runs last. If it runs first, it can be # confusing as it appears next to the logging of which bundle we use (see # setUpClass). From c93009422574e54797736ce4b346804943a14d32 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 16 Dec 2024 16:13:17 -0800 Subject: [PATCH 191/622] [wasm-reduce] Add an option to save all interim working files as we reduce (#7154) With this option, each time we reduce we save a file w.wasm.17 or such, incrementing that counter. This is useful when debugging the reducer, but might have more uses. --- src/tools/wasm-reduce.cpp | 31 +++++++++++++++++++++++++++++-- test/lit/help/wasm-reduce.test | 3 +++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 026825118f3..3b922946232 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -83,6 +83,9 @@ static size_t timeout = 2; // default of enabling all features should work in most cases. static std::string extraFlags = "-all"; +// Whether to save all intermediate working files as we go. +static bool saveAllWorkingFiles = false; + struct ProgramResult { int code; std::string output; @@ -231,6 +234,11 @@ ProgramResult expected; // case we may try again but much later. static std::unordered_set functionsWeTriedToRemove; +// The index of the working file we save, when saveAllWorkingFiles. We must +// store this globally so that the difference instances of Reducer do not +// overlap. +static size_t workingFileIndex = 0; + struct Reducer : public WalkerPass>> { std::string command, test, working; @@ -322,7 +330,7 @@ struct Reducer if (ProgramResult(command) == expected) { std::cerr << "| command \"" << currCommand << "\" succeeded, reduced size to " << newSize << '\n'; - copy_file(test, working); + applyTestToWorking(); more = true; oldSize = newSize; } @@ -335,6 +343,16 @@ struct Reducer } } + // Apply the test file to the working file, after we saw that it successfully + // reduced the testcase. + void applyTestToWorking() { + copy_file(test, working); + + if (saveAllWorkingFiles) { + copy_file(working, working + '.' + std::to_string(workingFileIndex++)); + } + } + // does one pass of slow and destructive reduction. returns whether it // succeeded or not // the criterion here is a logical change in the program. this may actually @@ -471,7 +489,7 @@ struct Reducer void noteReduction(size_t amount = 1) { reduced += amount; - copy_file(test, working); + applyTestToWorking(); } // tests a reduction on an arbitrary child @@ -1302,6 +1320,15 @@ int main(int argc, const char* argv[]) { extraFlags = argument; std::cout << "|applying extraFlags: " << extraFlags << "\n"; }) + .add("--save-all-working", + "-saw", + "Save all intermediate working files, as $WORKING.0, .1, .2 etc", + WasmReduceOption, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { + saveAllWorkingFiles = true; + std::cout << "|saving all intermediate working files\n"; + }) .add_positional( "INFILE", Options::Arguments::One, diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index d02d46fd7a7..5116b7bea12 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -51,6 +51,9 @@ ;; CHECK-NEXT: wasm-opt while reducing. (default: ;; CHECK-NEXT: --enable-all) ;; CHECK-NEXT: +;; CHECK-NEXT: --save-all-working,-saw Save all intermediate working files, as +;; CHECK-NEXT: $WORKING.0, .1, .2 etc +;; CHECK-NEXT: ;; CHECK-NEXT: ;; CHECK-NEXT: Tool options: ;; CHECK-NEXT: ------------- From 9f5f8dd2ffe0b89ea071aea3d2b3efad42dada4f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 17 Dec 2024 14:30:46 -0800 Subject: [PATCH 192/622] RemoveUnusedBrs: Avoid an error on loops with unreachable ifs (#7156) We normally like to move brs after ifs into the if, when in a loop: (loop $loop (if .. (unreachable) (code) ) (br $loop) ) => (loop $loop (if .. (unreachable) (block (code) (br $loop) ;; moved in ) ) ) However this may be invalid to do if the if condition is unreachable, as then one arm may be concrete (`code` in the example could be an `i32`, for example). As this is dead code anyhow, leave it for DCE. --- src/passes/RemoveUnusedBrs.cpp | 10 ++-- test/lit/passes/remove-unused-brs.wast | 66 +++++++++++++++++++------- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 32f7bd48a20..47a1f1c6553 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -621,9 +621,13 @@ struct RemoveUnusedBrs : public WalkerPass> { block->finalize(); return true; } - } else { - // this is already an if-else. if one side is a dead end, we can - // append to the other, if there is no returned value to concern us + } else if (iff->condition->type != Type::unreachable) { + // This is already an if-else. If one side is a dead end, we can + // append to the other, if there is no returned value to concern us. + // Note that we skip ifs with unreachable conditions, as they are dead + // code that DCE can remove, and modifying them can lead to errors + // (one of the arms may still be concrete, in which case appending to + // it would be invalid). // can't be, since in the middle of a block assert(!iff->type.isConcrete()); diff --git a/test/lit/passes/remove-unused-brs.wast b/test/lit/passes/remove-unused-brs.wast index 2019d37a429..c61320602a8 100644 --- a/test/lit/passes/remove-unused-brs.wast +++ b/test/lit/passes/remove-unused-brs.wast @@ -30,7 +30,7 @@ ) ) - ;; CHECK: (func $selectify-simple (type $0) (param $0 i32) (result i32) + ;; CHECK: (func $selectify-simple (type $1) (param $0 i32) (result i32) ;; CHECK-NEXT: (select ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.lt_u @@ -73,7 +73,7 @@ ) ) - ;; CHECK: (func $restructure-br_if (type $0) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if (type $1) (param $x i32) (result i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then @@ -104,12 +104,12 @@ ) ) - ;; CHECK: (func $nothing (type $1) + ;; CHECK: (func $nothing (type $0) ;; CHECK-NEXT: ) (func $nothing) - ;; CHECK: (func $restructure-br_if-condition-reorderable (type $0) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-condition-reorderable (type $1) (param $x i32) (result i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $nothing) @@ -146,7 +146,7 @@ ) ) - ;; CHECK: (func $restructure-br_if-value-effectful (type $0) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful (type $1) (param $x i32) (result i32) ;; CHECK-NEXT: (select ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $nothing) @@ -188,7 +188,7 @@ ) ) - ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-1 (type $0) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-1 (type $1) (param $x i32) (result i32) ;; CHECK-NEXT: (block $x (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $x @@ -233,7 +233,7 @@ (i32.const 400) ) - ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-2 (type $0) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-2 (type $1) (param $x i32) (result i32) ;; CHECK-NEXT: (block $x (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $x @@ -272,7 +272,7 @@ (call $get-i32) ) ) - ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-3 (type $0) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-3 (type $1) (param $x i32) (result i32) ;; CHECK-NEXT: (block $x (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $x @@ -305,7 +305,7 @@ ) ) - ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-4 (type $0) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-4 (type $1) (param $x i32) (result i32) ;; CHECK-NEXT: (block $x (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $x @@ -340,7 +340,7 @@ ) ) - ;; CHECK: (func $restructure-select-no-multivalue (type $1) + ;; CHECK: (func $restructure-select-no-multivalue (type $0) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (block $block (type $2) (result i32 i32) ;; CHECK-NEXT: (tuple.drop 2 @@ -387,7 +387,7 @@ ) ) - ;; CHECK: (func $if-of-if (type $1) + ;; CHECK: (func $if-of-if (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (select @@ -421,7 +421,7 @@ ) ) - ;; CHECK: (func $if-of-if-but-side-effects (type $1) + ;; CHECK: (func $if-of-if-but-side-effects (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.tee $x @@ -460,7 +460,7 @@ ) ) - ;; CHECK: (func $if-of-if-but-too-costly (type $1) + ;; CHECK: (func $if-of-if-but-too-costly (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.tee $x @@ -515,7 +515,7 @@ ) ) - ;; CHECK: (func $if-of-if-but-inner-else (type $1) + ;; CHECK: (func $if-of-if-but-inner-else (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.tee $x @@ -555,7 +555,7 @@ ) ) - ;; CHECK: (func $if-of-if-but-outer-else (type $1) + ;; CHECK: (func $if-of-if-but-outer-else (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.tee $x @@ -595,7 +595,7 @@ ) ) - ;; CHECK: (func $unreachable-if (type $1) + ;; CHECK: (func $unreachable-if (type $0) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (unreachable) @@ -624,4 +624,38 @@ ) ) ) + + ;; CHECK: (func $loop-with-unreachable-if (type $0) + ;; CHECK-NEXT: (loop $label + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-with-unreachable-if + ;; We normally move brs right after an if into one of the if arms, when + ;; possible. That is almost possible here, but the if condition is + ;; unreachable, which allows one of the arms to have a concrete type. It is + ;; invalid to append to such an arm, so we should do nothing (leaving this + ;; for DCE). + (loop $label + (if (result i32) + (unreachable) + (then + (unreachable) + ) + (else + (i32.const 0) + ) + ) + (br $label) + ) + ) ) From 7c8cd2f4a51213964f6f1ec11e54d2b6e721cdaf Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Dec 2024 20:01:23 -0800 Subject: [PATCH 193/622] Support atomic struct accessors (#7155) Implement support for both sequentially consistent and acquire-release variants of `struct.atomic.get` and `struct.atomic.set`, as proposed by shared-everything-threads. Introduce a new `MemoryOrdering` enum for describing different levels of atomicity (or the lack thereof). This new enum should eventually be adopted by linear memory atomic accessors as well to support acquire-release semantics, but for now just use it in `StructGet` and `StructSet`. In addition to implementing parsing and emitting for the instructions, validate that shared-everything is enabled to use them, mark them as having synchronization side effects, and lightly optimize them by relaxing acquire-release accesses to non-shared structs to normal, unordered accesses. This is valid because such accesses cannot possibly synchronize with other threads. Also update Precompute to avoid optimizing out synchronization points. There are probably other passes that need to be updated to avoid incorrectly optimizing synchronizing accesses, but identifying and fixing them is left as future work. --- scripts/gen-s-parser.py | 4 + src/gen-s-parser.inc | 39 +++++ src/ir/effects.h | 15 ++ src/parser/contexts.h | 25 ++- src/parser/parsers.h | 46 +++++ src/passes/OptimizeInstructions.cpp | 13 ++ src/passes/Precompute.cpp | 46 +++-- src/passes/Print.cpp | 31 +++- src/wasm-binary.h | 12 ++ src/wasm-builder.h | 14 +- src/wasm-delegations-fields.def | 2 + src/wasm-ir-builder.h | 9 +- src/wasm.h | 8 + src/wasm/wasm-binary.cpp | 40 +++++ src/wasm/wasm-ir-builder.cpp | 13 +- src/wasm/wasm-stack.cpp | 21 ++- src/wasm/wasm-validator.cpp | 10 ++ test/lit/basic/gc-atomics.wast | 149 +++++++++++++++++ .../optimize-instructions-gc-atomics.wast | 157 ++++++++++++++++++ test/lit/passes/precompute-gc-atomics.wast | 72 ++++++++ test/lit/passes/vacuum-gc-atomics.wast | 91 ++++++++++ test/lit/validation/gc-atomics.wast | 38 +++++ 22 files changed, 813 insertions(+), 42 deletions(-) create mode 100644 test/lit/basic/gc-atomics.wast create mode 100644 test/lit/passes/optimize-instructions-gc-atomics.wast create mode 100644 test/lit/passes/precompute-gc-atomics.wast create mode 100644 test/lit/passes/vacuum-gc-atomics.wast create mode 100644 test/lit/validation/gc-atomics.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index d15c07e8eca..b5592d433be 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -617,7 +617,11 @@ ("struct.get", "makeStructGet()"), ("struct.get_s", "makeStructGet(true)"), ("struct.get_u", "makeStructGet(false)"), + ("struct.atomic.get", "makeAtomicStructGet()"), + ("struct.atomic.get_s", "makeAtomicStructGet(true)"), + ("struct.atomic.get_u", "makeAtomicStructGet(false)"), ("struct.set", "makeStructSet()"), + ("struct.atomic.set", "makeAtomicStructSet()"), ("array.new", "makeArrayNew(false)"), ("array.new_default", "makeArrayNew(true)"), ("array.new_data", "makeArrayNewData()"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 75fda4f7a6a..a96ee265973 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -5013,6 +5013,45 @@ switch (buf[0]) { } case 'u': { switch (buf[7]) { + case 'a': { + switch (buf[14]) { + case 'g': { + switch (buf[17]) { + case '\0': + if (op == "struct.atomic.get"sv) { + CHECK_ERR(makeAtomicStructGet(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case '_': { + switch (buf[18]) { + case 's': + if (op == "struct.atomic.get_s"sv) { + CHECK_ERR(makeAtomicStructGet(ctx, pos, annotations, true)); + return Ok{}; + } + goto parse_error; + case 'u': + if (op == "struct.atomic.get_u"sv) { + CHECK_ERR(makeAtomicStructGet(ctx, pos, annotations, false)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + default: goto parse_error; + } + } + case 's': + if (op == "struct.atomic.set"sv) { + CHECK_ERR(makeAtomicStructSet(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } case 'g': { switch (buf[10]) { case '\0': diff --git a/src/ir/effects.h b/src/ir/effects.h index 716624d6455..ee596f67bfa 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -872,6 +872,18 @@ class EffectAnalyzer { if (curr->ref->type.isNullable()) { parent.implicitTrap = true; } + switch (curr->order) { + case MemoryOrder::Unordered: + break; + case MemoryOrder::SeqCst: + // Synchronizes with other threads. + parent.isAtomic = true; + break; + case MemoryOrder::AcqRel: + // Only synchronizes if other threads can read the field. + parent.isAtomic = curr->ref->type.getHeapType().isShared(); + break; + } } void visitStructSet(StructSet* curr) { if (curr->ref->type.isNull()) { @@ -883,6 +895,9 @@ class EffectAnalyzer { if (curr->ref->type.isNullable()) { parent.implicitTrap = true; } + if (curr->order != MemoryOrder::Unordered) { + parent.isAtomic = true; + } } void visitArrayNew(ArrayNew* curr) {} void visitArrayNewData(ArrayNewData* curr) { diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 3e0bc7c4094..a65299eac4c 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -735,13 +735,20 @@ struct NullInstrParserCtx { return Ok{}; } template - Result<> makeStructGet( - Index, const std::vector&, HeapTypeT, FieldIdxT, bool) { + Result<> makeStructGet(Index, + const std::vector&, + HeapTypeT, + FieldIdxT, + bool, + MemoryOrder = MemoryOrder::Unordered) { return Ok{}; } template - Result<> - makeStructSet(Index, const std::vector&, HeapTypeT, FieldIdxT) { + Result<> makeStructSet(Index, + const std::vector&, + HeapTypeT, + FieldIdxT, + MemoryOrder = MemoryOrder::Unordered) { return Ok{}; } template @@ -2448,15 +2455,17 @@ struct ParseDefsCtx : TypeParserCtx { const std::vector& annotations, HeapType type, Index field, - bool signed_) { - return withLoc(pos, irBuilder.makeStructGet(type, field, signed_)); + bool signed_, + MemoryOrder order = MemoryOrder::Unordered) { + return withLoc(pos, irBuilder.makeStructGet(type, field, signed_, order)); } Result<> makeStructSet(Index pos, const std::vector& annotations, HeapType type, - Index field) { - return withLoc(pos, irBuilder.makeStructSet(type, field)); + Index field, + MemoryOrder order = MemoryOrder::Unordered) { + return withLoc(pos, irBuilder.makeStructSet(type, field, order)); } Result<> makeArrayNew(Index pos, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 2d3321dcdd4..1f723640393 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -48,6 +48,7 @@ template Result limits64(Ctx&); template Result memtype(Ctx&); template Result memtypeContinued(Ctx&, Type addressType); +template Result memorder(Ctx&); template Result tabletype(Ctx&); template Result tabletypeContinued(Ctx&, Type addressType); @@ -246,8 +247,15 @@ Result<> makeStructGet(Ctx&, const std::vector&, bool signed_ = false); template +Result<> makeAtomicStructGet(Ctx&, + Index, + const std::vector&, + bool signed_ = false); +template Result<> makeStructSet(Ctx&, Index, const std::vector&); template +Result<> makeAtomicStructSet(Ctx&, Index, const std::vector&); +template Result<> makeArrayNew(Ctx&, Index, const std::vector&, bool default_); template @@ -801,6 +809,17 @@ Result memtypeContinued(Ctx& ctx, Type addressType) { return ctx.makeMemType(addressType, *limits, shared); } +// memorder ::= '' | 'seqcst' | 'acqrel' +template Result memorder(Ctx& ctx) { + if (ctx.in.takeKeyword("seqcst"sv)) { + return MemoryOrder::SeqCst; + } + if (ctx.in.takeKeyword("acqrel"sv)) { + return MemoryOrder::AcqRel; + } + return MemoryOrder::SeqCst; +} + // tabletype ::= (limits32 | 'i32' limits32 | 'i64' limit64) reftype template Result tabletype(Ctx& ctx) { Type addressType = Type::i32; @@ -2224,6 +2243,20 @@ Result<> makeStructGet(Ctx& ctx, return ctx.makeStructGet(pos, annotations, *type, *field, signed_); } +template +Result<> makeAtomicStructGet(Ctx& ctx, + Index pos, + const std::vector& annotations, + bool signed_) { + auto order = memorder(ctx); + CHECK_ERR(order); + auto type = typeidx(ctx); + CHECK_ERR(type); + auto field = fieldidx(ctx, *type); + CHECK_ERR(field); + return ctx.makeStructGet(pos, annotations, *type, *field, signed_, *order); +} + template Result<> makeStructSet(Ctx& ctx, Index pos, const std::vector& annotations) { @@ -2234,6 +2267,19 @@ makeStructSet(Ctx& ctx, Index pos, const std::vector& annotations) { return ctx.makeStructSet(pos, annotations, *type, *field); } +template +Result<> makeAtomicStructSet(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto order = memorder(ctx); + CHECK_ERR(order); + auto type = typeidx(ctx); + CHECK_ERR(type); + auto field = fieldidx(ctx, *type); + CHECK_ERR(field); + return ctx.makeStructSet(pos, annotations, *type, *field, *order); +} + template Result<> makeArrayNew(Ctx& ctx, Index pos, diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 792cf623555..6a528d74fa9 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1831,6 +1831,12 @@ struct OptimizeInstructions void visitStructGet(StructGet* curr) { skipNonNullCast(curr->ref, curr); trapOnNull(curr, curr->ref); + // Relax acquire loads of unshared fields to unordered because they cannot + // synchronize with other threads. + if (curr->order == MemoryOrder::AcqRel && curr->ref->type.isRef() && + !curr->ref->type.getHeapType().isShared()) { + curr->order = MemoryOrder::Unordered; + } } void visitStructSet(StructSet* curr) { @@ -1847,6 +1853,13 @@ struct OptimizeInstructions optimizeStoredValue(curr->value, fields[curr->index].getByteSize()); } } + + // Relax release stores of unshared fields to unordered because they cannot + // synchronize with other threads. + if (curr->order == MemoryOrder::AcqRel && curr->ref->type.isRef() && + !curr->ref->type.getHeapType().isShared()) { + curr->order = MemoryOrder::Unordered; + } } void visitArrayNew(ArrayNew* curr) { diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 0fc0753ae7c..93f2f1d6947 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -134,23 +134,37 @@ class PrecomputingExpressionRunner } Flow visitStructSet(StructSet* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStructGet(StructGet* curr) { - if (curr->ref->type != Type::unreachable && !curr->ref->type.isNull()) { - // If this field is immutable then we may be able to precompute this, as - // if we also created the data in this function (or it was created in an - // immutable global) then we know the value in the field. If it is - // immutable, call the super method which will do the rest here. That - // includes checking for the data being properly created, as if it was - // not then we will not have a constant value for it, which means the - // local.get of that value will stop us. - auto& field = - curr->ref->type.getHeapType().getStruct().fields[curr->index]; - if (field.mutable_ == Immutable) { - return Super::visitStructGet(curr); - } + if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) { + return Flow(NONCONSTANT_FLOW); } - - // Otherwise, we've failed to precompute. - return Flow(NONCONSTANT_FLOW); + switch (curr->order) { + case MemoryOrder::Unordered: + // This can always be precomputed. + break; + case MemoryOrder::SeqCst: + // This can never be precomputed away because it synchronizes with other + // threads. + return Flow(NONCONSTANT_FLOW); + case MemoryOrder::AcqRel: + // This synchronizes only with writes to the same data, so it can still + // be precomputed if the data is not shared with other threads. + if (curr->ref->type.getHeapType().isShared()) { + return Flow(NONCONSTANT_FLOW); + } + break; + } + // If this field is immutable then we may be able to precompute this, as + // if we also created the data in this function (or it was created in an + // immutable global) then we know the value in the field. If it is + // immutable, call the super method which will do the rest here. That + // includes checking for the data being properly created, as if it was + // not then we will not have a constant value for it, which means the + // local.get of that value will stop us. + auto& field = curr->ref->type.getHeapType().getStruct().fields[curr->index]; + if (field.mutable_ == Mutable) { + return Flow(NONCONSTANT_FLOW); + } + return Super::visitStructGet(curr); } Flow visitArrayNew(ArrayNew* curr) { auto flow = Super::visitArrayNew(curr); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 5f2d1cc3d73..d70034c859b 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2276,24 +2276,47 @@ struct PrintExpressionContents o << index; } } + void printMemoryOrder(MemoryOrder order) { + switch (order) { + // Unordered should have a different base instruction, so there is nothing + // to print. We could be explicit and print seqcst, but we choose not to + // for more concise output. + case MemoryOrder::Unordered: + case MemoryOrder::SeqCst: + break; + case MemoryOrder::AcqRel: + o << "acqrel "; + break; + } + } void visitStructGet(StructGet* curr) { auto heapType = curr->ref->type.getHeapType(); const auto& field = heapType.getStruct().fields[curr->index]; + printMedium(o, "struct"); + if (curr->order != MemoryOrder::Unordered) { + printMedium(o, ".atomic"); + } if (field.type == Type::i32 && field.packedType != Field::not_packed) { if (curr->signed_) { - printMedium(o, "struct.get_s "); + printMedium(o, ".get_s "); } else { - printMedium(o, "struct.get_u "); + printMedium(o, ".get_u "); } } else { - printMedium(o, "struct.get "); + printMedium(o, ".get "); } + printMemoryOrder(curr->order); printHeapType(heapType); o << ' '; printFieldName(heapType, curr->index); } void visitStructSet(StructSet* curr) { - printMedium(o, "struct.set "); + if (curr->order == MemoryOrder::Unordered) { + printMedium(o, "struct.set "); + } else { + printMedium(o, "struct.atomic.set "); + } + printMemoryOrder(curr->order); auto heapType = curr->ref->type.getHeapType(); printHeapType(heapType); o << ' '; diff --git a/src/wasm-binary.h b/src/wasm-binary.h index bece0af8ed9..7d98302baae 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1125,6 +1125,15 @@ enum ASTNodes { I31GetU = 0x1e, RefI31Shared = 0x1f, + // Shared GC Opcodes + + OrderSeqCst = 0x0, + OrderAcqRel = 0x1, + StructAtomicGet = 0x5c, + StructAtomicGetS = 0x5d, + StructAtomicGetU = 0x5e, + StructAtomicSet = 0x5f, + // stringref opcodes StringConst = 0x82, @@ -1352,6 +1361,8 @@ class WasmBinaryWriter { void writeField(const Field& field); + void writeMemoryOrder(MemoryOrder order); + private: Module* wasm; BufferWithRandomAccess& o; @@ -1587,6 +1598,7 @@ class WasmBinaryReader { Index readMemoryAccess(Address& alignment, Address& offset); std::tuple getMemarg(); + MemoryOrder getMemoryOrder(); [[noreturn]] void throwError(std::string text) { throw ParseException(text, 0, pos); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 20485f14d10..4396bc6df73 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -936,21 +936,29 @@ class Builder { ret->finalize(); return ret; } - StructGet* - makeStructGet(Index index, Expression* ref, Type type, bool signed_ = false) { + StructGet* makeStructGet(Index index, + Expression* ref, + Type type, + bool signed_ = false, + MemoryOrder order = MemoryOrder::Unordered) { auto* ret = wasm.allocator.alloc(); ret->index = index; ret->ref = ref; ret->type = type; ret->signed_ = signed_; + ret->order = order; ret->finalize(); return ret; } - StructSet* makeStructSet(Index index, Expression* ref, Expression* value) { + StructSet* makeStructSet(Index index, + Expression* ref, + Expression* value, + MemoryOrder order = MemoryOrder::Unordered) { auto* ret = wasm.allocator.alloc(); ret->index = index; ret->ref = ref; ret->value = value; + ret->order = order; ret->finalize(); return ret; } diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 3be04022094..e883763a44b 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -639,12 +639,14 @@ DELEGATE_FIELD_CASE_START(StructGet) DELEGATE_FIELD_INT(StructGet, index) DELEGATE_FIELD_CHILD(StructGet, ref) DELEGATE_FIELD_INT(StructGet, signed_) +DELEGATE_FIELD_INT(StructGet, order) DELEGATE_FIELD_CASE_END(StructGet) DELEGATE_FIELD_CASE_START(StructSet) DELEGATE_FIELD_INT(StructSet, index) DELEGATE_FIELD_CHILD(StructSet, value) DELEGATE_FIELD_CHILD(StructSet, ref) +DELEGATE_FIELD_INT(StructSet, order) DELEGATE_FIELD_CASE_END(StructSet) DELEGATE_FIELD_CASE_START(ArrayNew) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 250d5d17c18..a40e8df8248 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -204,8 +204,13 @@ class IRBuilder : public UnifiedExpressionVisitor> { makeBrOn(Index label, BrOnOp op, Type in = Type::none, Type out = Type::none); Result<> makeStructNew(HeapType type); Result<> makeStructNewDefault(HeapType type); - Result<> makeStructGet(HeapType type, Index field, bool signed_); - Result<> makeStructSet(HeapType type, Index field); + Result<> makeStructGet(HeapType type, + Index field, + bool signed_, + MemoryOrder order = MemoryOrder::Unordered); + Result<> makeStructSet(HeapType type, + Index field, + MemoryOrder order = MemoryOrder::Unordered); Result<> makeArrayNew(HeapType type); Result<> makeArrayNewDefault(HeapType type); Result<> makeArrayNewData(HeapType type, Name data); diff --git a/src/wasm.h b/src/wasm.h index b3ae82bcfa9..3f60a67d231 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -65,6 +65,12 @@ struct Address { } }; +enum class MemoryOrder { + Unordered, + SeqCst, + AcqRel, +}; + enum class IRProfile { Normal, Poppy }; // Operators @@ -1652,6 +1658,7 @@ class StructGet : public SpecificExpression { Expression* ref; // Packed fields have a sign. bool signed_ = false; + MemoryOrder order = MemoryOrder::Unordered; void finalize(); }; @@ -1664,6 +1671,7 @@ class StructSet : public SpecificExpression { Index index; Expression* ref; Expression* value; + MemoryOrder order = MemoryOrder::Unordered; void finalize(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 791dc53d777..b0c5a54acc9 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1737,6 +1737,20 @@ void WasmBinaryWriter::writeField(const Field& field) { o << U32LEB(field.mutable_); } +void WasmBinaryWriter::writeMemoryOrder(MemoryOrder order) { + switch (order) { + case MemoryOrder::Unordered: + break; + case MemoryOrder::SeqCst: + o << uint8_t(BinaryConsts::OrderSeqCst); + return; + case MemoryOrder::AcqRel: + o << uint8_t(BinaryConsts::OrderAcqRel); + return; + } + WASM_UNREACHABLE("unexpected memory order"); +} + // reader WasmBinaryReader::WasmBinaryReader(Module& wasm, @@ -3406,6 +3420,21 @@ Result<> WasmBinaryReader::readInst() { return Err{"expected 0x00 byte immediate on atomic.fence"}; } return builder.makeAtomicFence(); + case BinaryConsts::StructAtomicGet: + case BinaryConsts::StructAtomicGetS: + case BinaryConsts::StructAtomicGetU: { + auto order = getMemoryOrder(); + auto type = getIndexedHeapType(); + auto field = getU32LEB(); + bool signed_ = op == BinaryConsts::StructAtomicGetS; + return builder.makeStructGet(type, field, signed_, order); + } + case BinaryConsts::StructAtomicSet: { + auto order = getMemoryOrder(); + auto type = getIndexedHeapType(); + auto field = getU32LEB(); + return builder.makeStructSet(type, field, order); + } } return Err{"unknown atomic operation"}; } @@ -4952,4 +4981,15 @@ std::tuple WasmBinaryReader::getMemarg() { return {getMemoryName(memIdx), alignment, offset}; } +MemoryOrder WasmBinaryReader::getMemoryOrder() { + auto code = getInt8(); + switch (code) { + case BinaryConsts::OrderSeqCst: + return MemoryOrder::SeqCst; + case BinaryConsts::OrderAcqRel: + return MemoryOrder::AcqRel; + } + throwError("Unrecognized memory order code " + std::to_string(code)); +} + } // namespace wasm diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 6cd62e43991..4b034241072 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1792,21 +1792,26 @@ Result<> IRBuilder::makeStructNewDefault(HeapType type) { return Ok{}; } -Result<> IRBuilder::makeStructGet(HeapType type, Index field, bool signed_) { +Result<> IRBuilder::makeStructGet(HeapType type, + Index field, + bool signed_, + MemoryOrder order) { const auto& fields = type.getStruct().fields; StructGet curr; CHECK_ERR(ChildPopper{*this}.visitStructGet(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - push(builder.makeStructGet(field, curr.ref, fields[field].type, signed_)); + push( + builder.makeStructGet(field, curr.ref, fields[field].type, signed_, order)); return Ok{}; } -Result<> IRBuilder::makeStructSet(HeapType type, Index field) { +Result<> +IRBuilder::makeStructSet(HeapType type, Index field, MemoryOrder order) { StructSet curr; curr.index = field; CHECK_ERR(ChildPopper{*this}.visitStructSet(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - push(builder.makeStructSet(field, curr.ref, curr.value)); + push(builder.makeStructSet(field, curr.ref, curr.value, order)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 61f59c76aa9..08043b27ff4 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2327,15 +2327,20 @@ void BinaryInstWriter::visitStructGet(StructGet* curr) { } const auto& heapType = curr->ref->type.getHeapType(); const auto& field = heapType.getStruct().fields[curr->index]; + bool atomic = curr->order != MemoryOrder::Unordered; int8_t op; if (field.type != Type::i32 || field.packedType == Field::not_packed) { - op = BinaryConsts::StructGet; + op = atomic ? BinaryConsts::StructAtomicGet : BinaryConsts::StructGet; } else if (curr->signed_) { - op = BinaryConsts::StructGetS; + op = atomic ? BinaryConsts::StructAtomicGetS : BinaryConsts::StructGetS; } else { - op = BinaryConsts::StructGetU; + op = atomic ? BinaryConsts::StructAtomicGetU : BinaryConsts::StructGetU; + } + auto prefix = atomic ? BinaryConsts::AtomicPrefix : BinaryConsts::GCPrefix; + o << int8_t(prefix) << U32LEB(op); + if (atomic) { + parent.writeMemoryOrder(curr->order); } - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(op); parent.writeIndexedHeapType(heapType); o << U32LEB(curr->index); } @@ -2345,7 +2350,13 @@ void BinaryInstWriter::visitStructSet(StructSet* curr) { emitUnreachable(); return; } - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StructSet); + if (curr->order == MemoryOrder::Unordered) { + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StructSet); + } else { + o << int8_t(BinaryConsts::AtomicPrefix) + << U32LEB(BinaryConsts::StructAtomicSet); + parent.writeMemoryOrder(curr->order); + } parent.writeIndexedHeapType(curr->ref->type.getHeapType()); o << U32LEB(curr->index); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 242e07c4340..7de69a1fffe 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2989,6 +2989,11 @@ void FunctionValidator::visitStructGet(StructGet* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, "struct.get requires gc [--enable-gc]"); + shouldBeTrue(curr->order == MemoryOrder::Unordered || + getModule()->features.hasSharedEverything(), + curr, + "struct.atomic.get requires shared-everything " + "[--enable-shared-everything]"); if (curr->type == Type::unreachable || curr->ref->type.isNull()) { return; } @@ -3016,6 +3021,11 @@ void FunctionValidator::visitStructSet(StructSet* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, "struct.set requires gc [--enable-gc]"); + shouldBeTrue(curr->order == MemoryOrder::Unordered || + getModule()->features.hasSharedEverything(), + curr, + "struct.atomic.set requires shared-everything " + "[--enable-shared-everything]"); if (curr->ref->type == Type::unreachable) { return; } diff --git a/test/lit/basic/gc-atomics.wast b/test/lit/basic/gc-atomics.wast new file mode 100644 index 00000000000..c454b4c99fa --- /dev/null +++ b/test/lit/basic/gc-atomics.wast @@ -0,0 +1,149 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s +;; RUN: wasm-opt -all %s --roundtrip -S -o - | filecheck %s + +(module + ;; CHECK: (type $struct (struct (field (mut i32)))) + (type $struct (struct (field (mut i32)))) + ;; CHECK: (type $packed (struct (field (mut i8)))) + (type $packed (struct (field (mut i8)))) + + ;; CHECK: (func $get (type $3) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param (ref null $struct)) (result i32) + (struct.atomic.get $struct 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $get-seqcst (type $3) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-seqcst (param (ref null $struct)) (result i32) + (struct.atomic.get seqcst $struct 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $get-acqrel (type $3) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-acqrel (param (ref null $struct)) (result i32) + (struct.atomic.get acqrel $struct 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $get-s (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get_s $packed 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-s (param (ref null $packed)) (result i32) + (struct.atomic.get_s $packed 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $get-s-seqcst (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get_s $packed 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-s-seqcst (param (ref null $packed)) (result i32) + (struct.atomic.get_s seqcst $packed 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $get-s-acqrel (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get_s acqrel $packed 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-s-acqrel (param (ref null $packed)) (result i32) + (struct.atomic.get_s acqrel $packed 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $get-u (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get_u $packed 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-u (param (ref null $packed)) (result i32) + (struct.atomic.get_u $packed 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $get-u-seqcst (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get_u $packed 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-u-seqcst (param (ref null $packed)) (result i32) + (struct.atomic.get_u seqcst $packed 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $get-u-acqrel (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get_u acqrel $packed 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-u-acqrel (param (ref null $packed)) (result i32) + (struct.atomic.get_u acqrel $packed 0 + (local.get 0) + ) + ) + + ;; CHECK: (func $set (type $4) (param $0 (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.set $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set (param (ref null $struct)) + (struct.atomic.set $struct 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $set-seqcst (type $4) (param $0 (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.set $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-seqcst (param (ref null $struct)) + (struct.atomic.set seqcst $struct 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $set-acqrel (type $4) (param $0 (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.set acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-acqrel (param (ref null $struct)) + (struct.atomic.set acqrel $struct 0 + (local.get 0) + (i32.const 0) + ) + ) +) diff --git a/test/lit/passes/optimize-instructions-gc-atomics.wast b/test/lit/passes/optimize-instructions-gc-atomics.wast new file mode 100644 index 00000000000..a0283390c9a --- /dev/null +++ b/test/lit/passes/optimize-instructions-gc-atomics.wast @@ -0,0 +1,157 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (type $unshared (struct (field (mut i32)))) + + ;; CHECK: (type $shared (shared (struct (field (mut i32))))) + (type $shared (shared (struct (field (mut i32))))) + (type $unshared (struct (field (mut i32)))) + + ;; CHECK: (func $get-unordered-unshared (type $2) (result i32) + ;; CHECK-NEXT: (struct.get $unshared 0 + ;; CHECK-NEXT: (struct.new_default $unshared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-unordered-unshared (result i32) + (struct.get $unshared 0 + (struct.new_default $unshared) + ) + ) + + ;; CHECK: (func $get-unordered-shared (type $2) (result i32) + ;; CHECK-NEXT: (struct.get $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-unordered-shared (result i32) + (struct.get $shared 0 + (struct.new_default $shared) + ) + ) + + ;; CHECK: (func $get-seqcst-unshared (type $2) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $unshared 0 + ;; CHECK-NEXT: (struct.new_default $unshared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-seqcst-unshared (result i32) + (struct.atomic.get seqcst $unshared 0 + (struct.new_default $unshared) + ) + ) + + ;; CHECK: (func $get-seqcst-shared (type $2) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-seqcst-shared (result i32) + (struct.atomic.get seqcst $shared 0 + (struct.new_default $shared) + ) + ) + + ;; CHECK: (func $get-acqrel-unshared (type $2) (result i32) + ;; CHECK-NEXT: (struct.get $unshared 0 + ;; CHECK-NEXT: (struct.new_default $unshared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-acqrel-unshared (result i32) + ;; This can be relaxed to unordered + (struct.atomic.get acqrel $unshared 0 + (struct.new_default $unshared) + ) + ) + + ;; CHECK: (func $get-acqrel-shared (type $2) (result i32) + ;; CHECK-NEXT: (struct.atomic.get acqrel $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-acqrel-shared (result i32) + (struct.atomic.get acqrel $shared 0 + (struct.new_default $shared) + ) + ) + + ;; CHECK: (func $set-unordered-unshared (type $3) + ;; CHECK-NEXT: (struct.set $unshared 0 + ;; CHECK-NEXT: (struct.new_default $unshared) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-unordered-unshared + (struct.set $unshared 0 + (struct.new_default $unshared) + (i32.const 0) + ) + ) + + ;; CHECK: (func $set-unordered-shared (type $3) + ;; CHECK-NEXT: (struct.set $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-unordered-shared + (struct.set $shared 0 + (struct.new_default $shared) + (i32.const 0) + ) + ) + + ;; CHECK: (func $set-seqcst-unshared (type $3) + ;; CHECK-NEXT: (struct.atomic.set $unshared 0 + ;; CHECK-NEXT: (struct.new_default $unshared) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-seqcst-unshared + (struct.atomic.set seqcst $unshared 0 + (struct.new_default $unshared) + (i32.const 0) + ) + ) + + ;; CHECK: (func $set-seqcst-shared (type $3) + ;; CHECK-NEXT: (struct.atomic.set $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-seqcst-shared + (struct.atomic.set seqcst $shared 0 + (struct.new_default $shared) + (i32.const 0) + ) + ) + + ;; CHECK: (func $set-acqrel-unshared (type $3) + ;; CHECK-NEXT: (struct.set $unshared 0 + ;; CHECK-NEXT: (struct.new_default $unshared) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-acqrel-unshared + ;; This can be relaxed to unordered. + (struct.atomic.set acqrel $unshared 0 + (struct.new_default $unshared) + (i32.const 0) + ) + ) + + ;; CHECK: (func $set-acqrel-shared (type $3) + ;; CHECK-NEXT: (struct.atomic.set acqrel $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-acqrel-shared + (struct.atomic.set acqrel $shared 0 + (struct.new_default $shared) + (i32.const 0) + ) + ) +) diff --git a/test/lit/passes/precompute-gc-atomics.wast b/test/lit/passes/precompute-gc-atomics.wast new file mode 100644 index 00000000000..1f2d07753b4 --- /dev/null +++ b/test/lit/passes/precompute-gc-atomics.wast @@ -0,0 +1,72 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --precompute-propagate -S -o - | filecheck %s + +(module + ;; CHECK: (type $shared (shared (struct (field i32)))) + (type $shared (shared (struct (field i32)))) + ;; CHECK: (type $unshared (struct (field i32))) + (type $unshared (struct (field i32))) + + ;; CHECK: (func $get-unordered-unshared (type $0) (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $get-unordered-unshared (result i32) + (struct.get $unshared 0 + (struct.new_default $unshared) + ) + ) + + ;; CHECK: (func $get-unordered-shared (type $0) (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $get-unordered-shared (result i32) + (struct.get $shared 0 + (struct.new_default $shared) + ) + ) + + ;; CHECK: (func $get-seqcst-unshared (type $0) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $unshared 0 + ;; CHECK-NEXT: (struct.new_default $unshared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-seqcst-unshared (result i32) + (struct.atomic.get seqcst $unshared 0 + (struct.new_default $unshared) + ) + ) + + ;; CHECK: (func $get-seqcst-shared (type $0) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-seqcst-shared (result i32) + (struct.atomic.get seqcst $shared 0 + (struct.new_default $shared) + ) + ) + + ;; CHECK: (func $get-acqrel-unshared (type $0) (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $get-acqrel-unshared (result i32) + ;; We can optimize this because acquire-release on unshared data does not + ;; synchronize with anything. + (struct.atomic.get acqrel $unshared 0 + (struct.new_default $unshared) + ) + ) + + ;; CHECK: (func $get-acqrel-shared (type $0) (result i32) + ;; CHECK-NEXT: (struct.atomic.get acqrel $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-acqrel-shared (result i32) + (struct.atomic.get acqrel $shared 0 + (struct.new_default $shared) + ) + ) +) diff --git a/test/lit/passes/vacuum-gc-atomics.wast b/test/lit/passes/vacuum-gc-atomics.wast new file mode 100644 index 00000000000..49a8a8a6f90 --- /dev/null +++ b/test/lit/passes/vacuum-gc-atomics.wast @@ -0,0 +1,91 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that synchronizing operations are considered to have side effects that +;; prevent them from being dropped. + +;; RUN: wasm-opt %s -all --vacuum -S -o - | filecheck %s + +(module + ;; CHECK: (type $shared (shared (struct (field i32)))) + (type $shared (shared (struct (field i32)))) + ;; CHECK: (type $unshared (struct (field i32))) + (type $unshared (struct (field i32))) + + ;; CHECK: (func $get-unordered-unshared (type $0) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $get-unordered-unshared + (drop + (struct.get $unshared 0 + (struct.new_default $unshared) + ) + ) + ) + + ;; CHECK: (func $get-unordered-shared (type $0) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $get-unordered-shared + (drop + (struct.get $shared 0 + (struct.new_default $shared) + ) + ) + ) + + ;; CHECK: (func $get-seqcst-unshared (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.get $unshared 0 + ;; CHECK-NEXT: (struct.new_default $unshared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-seqcst-unshared + (drop + (struct.atomic.get seqcst $unshared 0 + (struct.new_default $unshared) + ) + ) + ) + + ;; CHECK: (func $get-seqcst-shared (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.get $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-seqcst-shared + (drop + (struct.atomic.get seqcst $shared 0 + (struct.new_default $shared) + ) + ) + ) + + ;; CHECK: (func $get-acqrel-unshared (type $0) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $get-acqrel-unshared + (drop + (struct.atomic.get acqrel $unshared 0 + (struct.new_default $unshared) + ) + ) + ) + + ;; CHECK: (func $get-acqrel-shared (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.get acqrel $shared 0 + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-acqrel-shared + (drop + (struct.atomic.get acqrel $shared 0 + (struct.new_default $shared) + ) + ) + ) +) diff --git a/test/lit/validation/gc-atomics.wast b/test/lit/validation/gc-atomics.wast new file mode 100644 index 00000000000..28e98b9fe33 --- /dev/null +++ b/test/lit/validation/gc-atomics.wast @@ -0,0 +1,38 @@ +;; Test that shared-everything GC instructions require the shared-everything +;; feature. + +;; RUN: not wasm-opt -all --disable-shared-everything %s 2>&1 | filecheck %s + +(module + (type $struct (struct (field (mut i32)))) + + ;; CHECK: struct.atomic.get requires shared-everything [--enable-shared-everything] + (func $get-seqcst (result i32) + (struct.atomic.get seqcst $struct 0 + (struct.new_default $struct) + ) + ) + + ;; CHECK: struct.atomic.get requires shared-everything [--enable-shared-everything] + (func $get-acqrel (result i32) + (struct.atomic.get acqrel $struct 0 + (struct.new_default $struct) + ) + ) + + ;; CHECK: struct.atomic.set requires shared-everything [--enable-shared-everything] + (func $set-seqcst + (struct.atomic.set seqcst $struct 0 + (struct.new_default $struct) + (i32.const 0) + ) + ) + + ;; CHECK: struct.atomic.set requires shared-everything [--enable-shared-everything] + (func $set-acqrel + (struct.atomic.set acqrel $struct 0 + (struct.new_default $struct) + (i32.const 0) + ) + ) +) \ No newline at end of file From d444abdc9fcee98715813f03e28aa7070879c414 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Dec 2024 23:06:03 -0800 Subject: [PATCH 194/622] Handle atomic accesses in Heap2Local (#7158) Heap2Local replaces gets and sets of non-escaping heap allocations with gets and sets of locals. Since the accessed data does not escape, it cannot be used to directly synchronize with other threads, so this optimization is generally safe even in the presence of shared structs and atomic struct accesses. The only caveat is that sequentially consistent accesses additionally participate in the global ordering of sequentially consistent operations, and that effect on the global ordering cannot be removed. Insert seqcst fences to maintain this global synchronization when removing sequentially consistent gets and sets. --- src/passes/Heap2Local.cpp | 22 ++++++- test/lit/passes/heap2local.wast | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index c31bc8436b1..a6223d4fcbf 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -839,9 +839,19 @@ struct Struct2Local : PostWalker { // Drop the ref (leaving it to other opts to remove, when possible), and // write the data to the local instead of the heap allocation. - replaceCurrent(builder.makeSequence( + auto* replacement = builder.makeSequence( builder.makeDrop(curr->ref), - builder.makeLocalSet(localIndexes[curr->index], curr->value))); + builder.makeLocalSet(localIndexes[curr->index], curr->value)); + + // This struct.set cannot possibly synchronize with other threads via the + // read value, since the struct never escapes this function. But if the set + // is sequentially consistent, it still participates in the global order of + // sequentially consistent operations. Preserve this effect on the global + // ordering by inserting a fence. + if (curr->order == MemoryOrder::SeqCst) { + replacement = builder.blockify(replacement, builder.makeAtomicFence()); + } + replaceCurrent(replacement); } void visitStructGet(StructGet* curr) { @@ -873,7 +883,13 @@ struct Struct2Local : PostWalker { // general. However, signed gets make that more complicated, so leave this // for other opts to handle. value = Bits::makePackedFieldGet(value, field, curr->signed_, wasm); - replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), value)); + auto* replacement = builder.blockify(builder.makeDrop(curr->ref)); + // See the note on seqcst struct.set. It is ok to insert the fence before + // the value here since we know the value is just a local.get. + if (curr->order == MemoryOrder::SeqCst) { + replacement = builder.blockify(replacement, builder.makeAtomicFence()); + } + replaceCurrent(builder.blockify(replacement, value)); } }; diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index bad4a33bf30..0af7e7bb4ea 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -4608,3 +4608,106 @@ ) ) ) + +;; Atomic accesses need special handling +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $struct (shared (struct (field (mut i32))))) + (type $struct (shared (struct (field (mut i32))))) + + ;; CHECK: (func $acqrel (type $0) + ;; CHECK-NEXT: (local $0 (ref null $struct)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $acqrel + (local (ref null $struct)) + (local.set 0 + (struct.new_default $struct) + ) + ;; acqrel accesses to non-escaping structs cannot synchronize with other + ;; threads, so we can optimize normally. + (drop + (struct.atomic.get acqrel $struct 0 + (local.get 0) + ) + ) + (struct.atomic.set acqrel $struct 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $seqcst (type $0) + ;; CHECK-NEXT: (local $0 (ref null $struct)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $seqcst + (local (ref null $struct)) + (local.set 0 + (struct.new_default $struct) + ) + ;; seqcst accesses participate in the global ordering of seqcst operations, + ;; so they need to be replaced with a seqcst fence to maintain that + ;; ordering. + (drop + (struct.atomic.get $struct 0 + (local.get 0) + ) + ) + (struct.atomic.set $struct 0 + (local.get 0) + (i32.const 0) + ) + ) +) From c744bd18ce73b82cf23e64728a8167c61ae4e24a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 18 Dec 2024 09:18:35 -0800 Subject: [PATCH 195/622] [NFC] Improve ClusterFuzz testing (#7157) 1. Error on retrying due to a wasm-opt issue, locally (in production, we don't want to error on ClusterFuzz). 2. Move some asserts from test_run_py to the helper generate_testcases (so that the asserts happen in all callers). --- scripts/fuzz_opt.py | 13 +++++++++---- test/unit/test_cluster_fuzz.py | 25 ++++++++++++++----------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index ca7c9e355d1..2fb64941f12 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1654,10 +1654,15 @@ def handle(self, wasm): os.unlink(f) # Call run.py(), similarly to how ClusterFuzz does. - run([sys.executable, - os.path.join(self.clusterfuzz_dir, 'run.py'), - '--output_dir=' + os.getcwd(), - '--no_of_files=1']) + out = run([sys.executable, + os.path.join(self.clusterfuzz_dir, 'run.py'), + '--output_dir=' + os.getcwd(), + '--no_of_files=1']) + + # We should not see any mention of a wasm-opt error that caused a + # retry. On production ClusterFuzz this is not an error, but we do want + # to know about such issues, as they may be real bugs in wasm-opt. + assert 'retry' not in out, out # We should see the two files. assert os.path.exists(fuzz_file) diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 8f1d1810463..5455d632053 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -74,22 +74,14 @@ def generate_testcases(self, N, testcase_dir): stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.assertEqual(proc.returncode, 0) - return proc - - # Test the bundled run.py script. - def test_run_py(self): - temp_dir = tempfile.TemporaryDirectory() - - N = 10 - proc = self.generate_testcases(N, temp_dir.name) # We should have logged the creation of N testcases. self.assertEqual(proc.stdout.count('Created testcase:'), N) # We should have actually created them. for i in range(0, N + 2): - fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') - flags_file = os.path.join(temp_dir.name, f'flags-binaryen-{i}.js') + fuzz_file = os.path.join(testcase_dir, f'fuzz-binaryen-{i}.js') + flags_file = os.path.join(testcase_dir, f'flags-binaryen-{i}.js') # We actually emit the range [1, N], so 0 or N+1 should not exist. if i >= 1 and i <= N: self.assertTrue(os.path.exists(fuzz_file)) @@ -98,8 +90,19 @@ def test_run_py(self): self.assertTrue(not os.path.exists(fuzz_file)) self.assertTrue(not os.path.exists(flags_file)) + return proc + + # Test the bundled run.py script. + def test_run_py(self): + temp_dir = tempfile.TemporaryDirectory() + + N = 10 + proc = self.generate_testcases(N, temp_dir.name) + # Run.py should report no errors or warnings to stderr, except from - # those we know are safe. + # those we know are safe (we cannot test this in generate_testcases, + # because the caller could do something like set BINARYEN_PASS_DEBUG, + # which generates intentional stderr warnings). SAFE_WARNINGS = [ # When we randomly pick no passes to run, this is shown. 'warning: no passes specified, not doing any work', From 009078979a26b7f4ec99fdfe1929b26176575805 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 18 Dec 2024 13:40:28 -0800 Subject: [PATCH 196/622] Handle atomic accesses in ConstantFieldPropagation (#7159) Sequentially consistent gets that are optimized out need to have seqcst fences inserted in their place to keep the same effect on global ordering of sequentially consistent operations. In principle, acquire gets could be similarly optimized with an acquire fence in their place, but acquire fences synchronize more strongly than acquire gets, so this may have a negative performance impact. For now, inhibit optimization of acquire gets. --- src/passes/ConstantFieldPropagation.cpp | 35 +++++-- test/lit/passes/cfp.wast | 123 ++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 8 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index a3dd6aa6f5b..36aebf3a49f 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -139,18 +139,28 @@ struct FunctionOptimizer : public WalkerPass> { if (!info.hasNoted()) { // This field is never written at all. That means that we do not even // construct any data of this type, and so it is a logic error to reach - // this location in the code. (Unless we are in an open-world - // situation, which we assume we are not in.) Replace this get with a - // trap. Note that we do not need to care about the nullability of the - // reference, as if it should have trapped, we are replacing it with - // another trap, which we allow to reorder (but we do need to care about - // side effects in the reference, so keep it around). + // this location in the code. (Unless we are in an open-world situation, + // which we assume we are not in.) Replace this get with a trap. Note that + // we do not need to care about the nullability of the reference, as if it + // should have trapped, we are replacing it with another trap, which we + // allow to reorder (but we do need to care about side effects in the + // reference, so keep it around). We also do not need to care about + // synchronization since trapping accesses do not synchronize with other + // accesses. replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), builder.makeUnreachable())); changed = true; return; } + if (curr->order == MemoryOrder::AcqRel) { + // Removing an acquire get and preserving its synchronization properties + // would require inserting an acquire fence, but the fence would have + // stronger synchronization properties so might be more expensive. + // Instead, just skip the optimization. + return; + } + // If the value is not a constant, then it is unknown and we must give up // on simply applying a constant. However, we can try to use a ref.test, if // that is allowed. @@ -166,8 +176,17 @@ struct FunctionOptimizer : public WalkerPass> { // constant value. (Leave it to further optimizations to get rid of the // ref.) auto* value = makeExpression(info, heapType, curr); - replaceCurrent(builder.makeSequence( - builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)), value)); + auto* replacement = builder.blockify( + builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref))); + // If this get is sequentially consistent, then it synchronizes with other + // threads at least by participating in the global order of sequentially + // consistent operations. Preserve that effect by replacing the access with + // a fence. + assert(curr->order != MemoryOrder::AcqRel); + if (curr->order == MemoryOrder::SeqCst) { + replacement = builder.blockify(replacement, builder.makeAtomicFence()); + } + replaceCurrent(builder.blockify(replacement, value)); changed = true; } diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index e70cfc639e5..e674fdc4b19 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2830,3 +2830,126 @@ ) ) ) + +;; Atomic accesses require special handling +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $shared (shared (struct (field (mut i32))))) + (type $shared (shared (struct (mut i32)))) + ;; CHECK: (type $unwritten (shared (struct (field (mut i32))))) + (type $unwritten (shared (struct (mut i32)))) + ) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref $shared)))) + + ;; CHECK: (type $4 (func (param (ref $unwritten)))) + + ;; CHECK: (func $init (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $shared) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new_default $shared) + ) + ) + + ;; CHECK: (func $gets (type $3) (param $0 (ref $shared)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.get acqrel $shared 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets (param (ref $shared)) + (drop + (struct.get $shared 0 + (local.get 0) + ) + ) + (drop + ;; This is not optimized because we wouldn't want to replace it with a + ;; stronger acquire fence. + (struct.atomic.get acqrel $shared 0 + (local.get 0) + ) + ) + (drop + ;; This can be optimized, but requires a seqcst fence. + (struct.atomic.get $shared 0 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $traps (type $4) (param $0 (ref $unwritten)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $traps (param (ref $unwritten)) + ;; This are all optimizable because they are known to trap. No fences are + ;; necessary. + (drop + (struct.get $unwritten 0 + (local.get 0) + ) + ) + (drop + (struct.atomic.get acqrel $unwritten 0 + (local.get 0) + ) + ) + (drop + (struct.atomic.get $unwritten 0 + (local.get 0) + ) + ) + ) +) From 0b378312d74f93f65a9f54efd8b5baeab33c7074 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 18 Dec 2024 15:09:24 -0800 Subject: [PATCH 197/622] Handle atomics in GTO (#7160) GTO removes fields that are never read and also removes sets to those fields. Update the pass to add a seqcst fence when removing a seqcst set to preserve its effect on the global order of seqcst operations. --- src/passes/GlobalTypeOptimization.cpp | 24 +++++++--- test/lit/passes/gto-removals.wast | 63 +++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 0baecdf43aa..b2ba23b0feb 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -514,13 +514,23 @@ struct GlobalTypeOptimization : public Pass { // operations here: the trap on a null ref happens after the value, // which might have side effects. Builder builder(*getModule()); - auto flipped = getResultOfFirst(curr->ref, - builder.makeDrop(curr->value), - getFunction(), - getModule(), - getPassOptions()); - replaceCurrent( - builder.makeDrop(builder.makeRefAs(RefAsNonNull, flipped))); + auto* flipped = getResultOfFirst(curr->ref, + builder.makeDrop(curr->value), + getFunction(), + getModule(), + getPassOptions()); + Expression* replacement = + builder.makeDrop(builder.makeRefAs(RefAsNonNull, flipped)); + if (curr->order == MemoryOrder::SeqCst) { + // If the removed set is sequentially consistent, we must insert a + // seqcst fence to preserve the effect on the global order of seqcst + // operations. No fence is necessary for release sets because there + // are no reads for them to synchronize with given that we are + // removing the field. + replacement = + builder.makeSequence(replacement, builder.makeAtomicFence()); + } + replaceCurrent(replacement); } } diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index a6fd9c22857..6ab126611ea 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -1564,3 +1564,66 @@ (export "globalB" (global $globalB)) ) +;; Removed atomic sets needs special handling. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (shared (struct))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A)))) + + ;; CHECK: (func $sets (type $1) (param $0 (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $sets (param (ref $A)) + ;; Normal set is optimizable. + (struct.set $A 0 + (local.get 0) + (i32.const 1) + ) + ;; Release set is optimizable without a fence because there is no get to + ;; synchronize with. + (struct.atomic.set acqrel $A 0 + (local.get 0) + (i32.const 1) + ) + ;; This requires a fence to keep the effect on the global order of seqcst + ;; operations. + (struct.atomic.set $A 0 + (local.get 0) + (i32.const 1) + ) + ) +) From dcec348ded6d7ab7bf4b758bdb92107e4262dbf4 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 19 Dec 2024 12:12:42 -0800 Subject: [PATCH 198/622] Explicitly disable WASM_BIGINT in emcc build when the CMake flag is off (#7162) This handles the case where WASM_BIGINT is enabled by default by emcc. Binaryen's JS bindings do not currently work with bigint (see #7163) --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a6167612542..d02096fa06e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,6 +337,8 @@ if(EMSCRIPTEN) option(ENABLE_BIGINT "Enable wasm BigInt support" OFF) if(ENABLE_BIGINT) add_link_flag("-sWASM_BIGINT") + else() + add_link_flag("-sWASM_BIGINT=0") endif() if("${CMAKE_BUILD_TYPE}" MATCHES "Release") From 4e1ae4aa5815fb474c829d15d7c93abee0ce14e3 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Thu, 19 Dec 2024 14:49:16 -0800 Subject: [PATCH 199/622] [Outlining] Sort sequences by order appeared in function (#7164) During function reconstruction, a walker iterates thru each instruction of a function, incrementing a counter to find matching sequences. As a result, the sequences of a function must be sorted by smallest start index, otherwise reconstruction will miss outlining a repeat sequence. I considered making a test for this commit, but the sort wasn't needed until the tests started running on GitHub infra. I'm not sure what specific architecture is causing the discrepancy in vector ordering, but let's introduce the sort to be safe. --- src/passes/Outlining.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index b86e6cd17e1..0c5c22f1865 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -364,11 +364,20 @@ struct Outlining : public Pass { seqByFunc.end(), keys.begin(), [](auto pair) { return pair.first; }); - for (auto funcName : keys) { - auto* func = module->getFunction(funcName); - ReconstructStringifyWalker reconstruct(module, func); - reconstruct.sequences = std::move(seqByFunc[funcName]); - reconstruct.doWalkFunction(func); + for (auto func : keys) { + // During function reconstruction, a walker iterates thru each instruction + // of a function, incrementing a counter to find matching sequences. As a + // result, the sequences of a function must be sorted by + // smallest start index, otherwise reconstruction will miss outlining a + // repeat sequence. + std::sort(seqByFunc[func].begin(), + seqByFunc[func].end(), + [](OutliningSequence a, OutliningSequence b) { + return a.startIdx < b.startIdx; + }); + ReconstructStringifyWalker reconstruct(module, module->getFunction(func)); + reconstruct.sequences = std::move(seqByFunc[func]); + reconstruct.doWalkFunction(module->getFunction(func)); } } From 74b2b064e59beee84e88afaa952a8c51cf9309a4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 19 Dec 2024 16:37:49 -0800 Subject: [PATCH 200/622] [NFC] Move code from fuzz_opt.py to a shared place (#7165) The list of tests, and which tests cannot be fuzzed, will be useful in a future PR that uses it for ClusterFuzz as well. This just moves things out so they are reusable. --- scripts/fuzz_opt.py | 78 +++-------------------------------------- scripts/test/fuzzing.py | 76 +++++++++++++++++++++++++++++++++++++++ scripts/test/shared.py | 16 +++++++++ 3 files changed, 97 insertions(+), 73 deletions(-) create mode 100644 scripts/test/fuzzing.py diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 2fb64941f12..58d1a022a53 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -41,6 +41,7 @@ import traceback from os.path import abspath +from test import fuzzing from test import shared from test import support @@ -242,7 +243,7 @@ def randomize_fuzz_settings(): def init_important_initial_contents(): # Fuzz dir contents are always important to us. fuzz_dir = os.path.join(shared.options.binaryen_root, 'fuzz') - fuzz_cases = shared.get_tests(fuzz_dir, test_suffixes, recursive=True) + fuzz_cases = shared.get_tests(fuzz_dir, shared.test_suffixes, recursive=True) FIXED_IMPORTANT_INITIAL_CONTENTS = fuzz_cases # If auto_initial_contents is set we'll also grab all test files that are @@ -306,66 +307,9 @@ def is_git_repo(): IMPORTANT_INITIAL_CONTENTS = [os.path.join(shared.get_test_dir('.'), t) for t in initial_contents] -INITIAL_CONTENTS_IGNORE = [ - # Float16 is still experimental. - 'f16.wast', - # not all relaxed SIMD instructions are implemented in the interpreter - 'relaxed-simd.wast', - # TODO: fuzzer and interpreter support for strings - 'strings.wast', - 'simplify-locals-strings.wast', - 'string-lowering-instructions.wast', - # TODO: fuzzer and interpreter support for extern conversions - 'extern-conversions.wast', - # ignore DWARF because it is incompatible with multivalue atm - 'zlib.wasm', - 'cubescript.wasm', - 'class_with_dwarf_noprint.wasm', - 'fib2_dwarf.wasm', - 'fib_nonzero-low-pc_dwarf.wasm', - 'inlined_to_start_dwarf.wasm', - 'fannkuch3_manyopts_dwarf.wasm', - 'fib2_emptylocspan_dwarf.wasm', - 'fannkuch3_dwarf.wasm', - 'dwarf-local-order.wasm', - 'strip-producers.wasm', - 'multi_unit_abbrev_noprint.wasm', - 'reverse_dwarf_abbrevs.wasm', - 'print_g.wasm', - 'print_g_strip-dwarf.wasm', - 'fannkuch0_dwarf.wasm', - 'dwarfdump_roundtrip_dwarfdump.wasm', - 'dwarfdump.wasm', - 'fannkuch3_dwarf.wasm', - 'dwarf-local-order.wasm', - 'dwarf_unit_with_no_abbrevs_noprint.wasm', - 'strip-debug.wasm', - 'multi_line_table_dwarf.wasm', - 'dwarf_with_exceptions.wasm', - 'strip-dwarf.wasm', - 'ignore_missing_func_dwarf.wasm', - 'print.wasm', - # TODO fuzzer support for multimemory - 'multi-memories-atomics64.wast', - 'multi-memories-basics.wast', - 'multi-memories-simd.wast', - 'multi-memories-atomics64.wasm', - 'multi-memories-basics.wasm', - 'multi-memories-simd.wasm', - 'multi-memories_size.wast', - # TODO: fuzzer support for internalize/externalize - 'optimize-instructions-gc-extern.wast', - 'gufa-extern.wast', - # the fuzzer does not support imported memories - 'multi-memory-lowering-import.wast', - 'multi-memory-lowering-import-error.wast', - # the fuzzer does not support typed continuations - 'typed_continuations.wast', - 'typed_continuations_resume.wast', - 'typed_continuations_contnew.wast', - 'typed_continuations_contbind.wast', - 'typed_continuations_suspend.wast', -] +all_tests = shared.get_all_tests() + +INITIAL_CONTENTS_IGNORE = fuzzing.unfuzzable_tests def pick_initial_contents(): @@ -1802,18 +1746,6 @@ def can_run_on_wasm(self, wasm): ] -test_suffixes = ['*.wasm', '*.wast', '*.wat'] - -core_tests = shared.get_tests(shared.get_test_dir('.'), test_suffixes) -passes_tests = shared.get_tests(shared.get_test_dir('passes'), test_suffixes) -spec_tests = shared.get_tests(shared.get_test_dir('spec'), test_suffixes) -wasm2js_tests = shared.get_tests(shared.get_test_dir('wasm2js'), test_suffixes) -lld_tests = shared.get_tests(shared.get_test_dir('lld'), test_suffixes) -unit_tests = shared.get_tests(shared.get_test_dir(os.path.join('unit', 'input')), test_suffixes) -lit_tests = shared.get_tests(shared.get_test_dir('lit'), test_suffixes, recursive=True) -all_tests = core_tests + passes_tests + spec_tests + wasm2js_tests + lld_tests + unit_tests + lit_tests - - # Do one test, given an input file for -ttf and some optimizations to run def test_one(random_input, given_wasm): randomize_pass_debug() diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py new file mode 100644 index 00000000000..c1022b6abb2 --- /dev/null +++ b/scripts/test/fuzzing.py @@ -0,0 +1,76 @@ +# Copyright 2024 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. + + +# Tests that the fuzzers should not operate on. +unfuzzable_tests = [ + # Float16 is still experimental. + 'f16.wast', + # not all relaxed SIMD instructions are implemented in the interpreter + 'relaxed-simd.wast', + # TODO: fuzzer and interpreter support for strings + 'strings.wast', + 'simplify-locals-strings.wast', + 'string-lowering-instructions.wast', + # TODO: fuzzer and interpreter support for extern conversions + 'extern-conversions.wast', + # ignore DWARF because it is incompatible with multivalue atm + 'zlib.wasm', + 'cubescript.wasm', + 'class_with_dwarf_noprint.wasm', + 'fib2_dwarf.wasm', + 'fib_nonzero-low-pc_dwarf.wasm', + 'inlined_to_start_dwarf.wasm', + 'fannkuch3_manyopts_dwarf.wasm', + 'fib2_emptylocspan_dwarf.wasm', + 'fannkuch3_dwarf.wasm', + 'dwarf-local-order.wasm', + 'strip-producers.wasm', + 'multi_unit_abbrev_noprint.wasm', + 'reverse_dwarf_abbrevs.wasm', + 'print_g.wasm', + 'print_g_strip-dwarf.wasm', + 'fannkuch0_dwarf.wasm', + 'dwarfdump_roundtrip_dwarfdump.wasm', + 'dwarfdump.wasm', + 'fannkuch3_dwarf.wasm', + 'dwarf-local-order.wasm', + 'dwarf_unit_with_no_abbrevs_noprint.wasm', + 'strip-debug.wasm', + 'multi_line_table_dwarf.wasm', + 'dwarf_with_exceptions.wasm', + 'strip-dwarf.wasm', + 'ignore_missing_func_dwarf.wasm', + 'print.wasm', + # TODO fuzzer support for multimemory + 'multi-memories-atomics64.wast', + 'multi-memories-basics.wast', + 'multi-memories-simd.wast', + 'multi-memories-atomics64.wasm', + 'multi-memories-basics.wasm', + 'multi-memories-simd.wasm', + 'multi-memories_size.wast', + # TODO: fuzzer support for internalize/externalize + 'optimize-instructions-gc-extern.wast', + 'gufa-extern.wast', + # the fuzzer does not support imported memories + 'multi-memory-lowering-import.wast', + 'multi-memory-lowering-import-error.wast', + # the fuzzer does not support typed continuations + 'typed_continuations.wast', + 'typed_continuations_resume.wast', + 'typed_continuations_contnew.wast', + 'typed_continuations_contbind.wast', + 'typed_continuations_suspend.wast', +] diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 8797f50b22f..e0a51a73a69 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -561,3 +561,19 @@ def skip_if_on_windows(name): print('skipping test "%s" on windows' % name) return True return False + + +test_suffixes = ['*.wasm', '*.wast', '*.wat'] + + +# return a list of all the tests in the entire test suite +def get_all_tests(): + core_tests = get_tests(get_test_dir('.'), test_suffixes) + passes_tests = get_tests(get_test_dir('passes'), test_suffixes) + spec_tests = get_tests(get_test_dir('spec'), test_suffixes) + wasm2js_tests = get_tests(get_test_dir('wasm2js'), test_suffixes) + lld_tests = get_tests(get_test_dir('lld'), test_suffixes) + unit_tests = get_tests(get_test_dir(os.path.join('unit', 'input')), test_suffixes) + lit_tests = get_tests(get_test_dir('lit'), test_suffixes, recursive=True) + + return core_tests + passes_tests + spec_tests + wasm2js_tests + lld_tests + unit_tests + lit_tests From 7c42700738fa9f9aff29dd930e9fd7f6cf71fc29 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 19 Dec 2024 17:45:05 -0800 Subject: [PATCH 201/622] Do not optimize atomic gets in GUFA (#7161) Conservatively avoid introducing synchronization bugs by not optimizing atomic struct.gets at all in GUFA. It is possible that we could be more precise in the future. Also remove obsolete logic dealing with the types of null values as a drive-by. All null values now have bottom types, so the type mismatch this code checked for is impossible. --- src/ir/properties.h | 24 +++++++++++++++ src/passes/GUFA.cpp | 22 +++++--------- test/lit/passes/gufa-refs.wast | 53 ++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index 09486ee34ca..70f18c2762f 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -486,6 +486,30 @@ inline bool canEmitSelectWithArms(Expression* ifTrue, Expression* ifFalse) { return ifTrue->type.isSingle() && ifFalse->type.isSingle(); } +// If this instruction accesses memory or the heap, or otherwise participates in +// shared memory synchronization, return the memory order corresponding to the +// kind of synchronization it does. Return MemoryOrder::Unordered if there is no +// synchronization. Does not look at children. +inline MemoryOrder getMemoryOrder(Expression* curr) { + if (auto* get = curr->dynCast()) { + return get->order; + } + if (auto* set = curr->dynCast()) { + return set->order; + } + if (auto* load = curr->dynCast()) { + return load->isAtomic ? MemoryOrder::SeqCst : MemoryOrder::Unordered; + } + if (auto* store = curr->dynCast()) { + return store->isAtomic ? MemoryOrder::SeqCst : MemoryOrder::Unordered; + } + if (curr->is() || curr->is() || + curr->is() || curr->is()) { + return MemoryOrder::SeqCst; + } + return MemoryOrder::Unordered; +} + // A "generative" expression is one that can generate different results for the // same inputs, and that difference is *not* explained by other expressions that // interact with this one. This is an intrinsic/internal property of the diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 513b2cf3dc2..2ca1ac6ef59 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -150,20 +150,14 @@ struct GUFAOptimizer return; } - if (contents.isNull() && curr->type.isNullable()) { - // Null values are all identical, so just fix up the type here if we need - // to (the null's type might not fit in this expression, if it passed - // through casts). - if (!Type::isSubType(contents.getType(), curr->type)) { - contents = PossibleContents::literal( - Literal::makeNull(curr->type.getHeapType())); - } - - // Note that if curr's type is *not* nullable, then the code will trap at - // runtime (the null must arrive through a cast that will trap). We handle - // that below, so we don't need to think about it here. - - // TODO: would emitting a more specific null be useful when valid? + if (Properties::getMemoryOrder(curr) != MemoryOrder::Unordered) { + // This load might synchronize with some store, and if we replaced the + // load with a constant or with a load from a global, it would not + // synchronize with that store anymore. Since we know what value the store + // must write, and we know it is the same as every other store to the same + // location, it's possible that optimizing here would be allowable, but + // for now be conservative and do not optimize. + return; } auto* c = contents.makeExpression(wasm); diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 80fd323869c..ec67cf40cfd 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -6137,3 +6137,56 @@ ) ) ) + +;; Atomic accesses require special handling +(module + ;; CHECK: (type $A (shared (struct (field i32)))) + (type $A (shared (struct (field i32)))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (func $gets (type $1) + ;; CHECK-NEXT: (local $0 (ref $A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.get acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local (ref $A)) + (local.set 0 + (struct.new_default $A) + ) + (drop + ;; This is optimizable. It reads from shared memory, but there is only one + ;; possible value that can be read. + (struct.get $A 0 + (local.get 0) + ) + ) + (drop + ;; We do not (yet) optimize atomic gets. + (struct.atomic.get acqrel $A 0 + (local.get 0) + ) + ) + (drop + ;; We do not (yet) optimize atomic gets. + (struct.atomic.get $A 0 + (local.get 0) + ) + ) + ) +) From 793e07369902026b727c5c4bc16a72ad3e4950e4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Dec 2024 12:08:35 -0800 Subject: [PATCH 202/622] Update GlobalStructInference to handle atomics (#7168) GlobalStructInference optimizes gets of immutable fields of structs that are only ever instantiated to initialize immutable globals. Due to all the immutability, it's not possible for the optimized reads to synchronize with any writes via the accessed memory, so we just need to be careful to replace removed seqcst gets with seqcst fences. As a drive-by, fix some stale comments in gsi.wast. --- src/passes/GlobalStructInference.cpp | 35 +++- test/lit/passes/gsi.wast | 228 ++++++++++++++++++++++++++- 2 files changed, 251 insertions(+), 12 deletions(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 4158db05104..f27df3a3c94 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -359,6 +359,11 @@ struct GlobalStructInference : public Pass { // refined, which could change the struct.get's type. refinalize = true; } + // No need to worry about atomic gets here. We will still read from + // the same memory location as before and preserve all side effects + // (including synchronization) that were previously present. The + // memory location is immutable anyway, so there cannot be any writes + // to synchronize with in the first place. curr->ref = builder.makeSequence( builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)), builder.makeGlobalGet(global, globalType)); @@ -457,10 +462,18 @@ struct GlobalStructInference : public Pass { // the early return above) so that only leaves 1 and 2. if (values.size() == 1) { // The case of 1 value is simple: trap if the ref is null, and - // otherwise return the value. - replaceCurrent(builder.makeSequence( - builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)), - getReadValue(values[0]))); + // otherwise return the value. We must also fence if the get was + // seqcst. No additional work is necessary for an acquire get because + // there cannot have been any writes to this immutable field that it + // would synchronize with. + Expression* replacement = + builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)); + if (curr->order == MemoryOrder::SeqCst) { + replacement = + builder.blockify(replacement, builder.makeAtomicFence()); + } + replaceCurrent( + builder.blockify(replacement, getReadValue(values[0]))); return; } assert(values.size() == 2); @@ -486,11 +499,19 @@ struct GlobalStructInference : public Pass { // of their execution matters (they may note globals for un-nesting). auto* left = getReadValue(values[0]); auto* right = getReadValue(values[1]); - // Note that we must trap on null, so add a ref.as_non_null here. + // Note that we must trap on null, so add a ref.as_non_null here. We + // must also add a fence if this get is seqcst. As before, no extra work + // is necessary for an acquire get because there cannot be a write it + // synchronizes with. + Expression* getGlobal = + builder.makeGlobalGet(checkGlobal, wasm.getGlobal(checkGlobal)->type); + if (curr->order == MemoryOrder::SeqCst) { + getGlobal = + builder.makeSequence(builder.makeAtomicFence(), getGlobal); + } replaceCurrent(builder.makeSelect( builder.makeRefEq(builder.makeRefAs(RefAsNonNull, curr->ref), - builder.makeGlobalGet( - checkGlobal, wasm.getGlobal(checkGlobal)->type)), + getGlobal), left, right)); } diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast index 299b19e00f2..7bc83efd40e 100644 --- a/test/lit/passes/gsi.wast +++ b/test/lit/passes/gsi.wast @@ -144,16 +144,16 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test1 (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) - ;; We can infer that this get must reference $global1 and make the reference - ;; point to that. Note that we do not infer the value of 42 here, but leave - ;; it for other passes to do. + ;; Even though the value here is not known at compile time - it reads an + ;; imported global - we can still infer that we are reading from $global1. (drop (struct.get $struct1 0 (local.get $struct1) ) ) - ;; Even though the value here is not known at compile time - it reads an - ;; imported global - we can still infer that we are reading from $global2. + ;; We can infer that this get must reference $global2 and make the reference + ;; point to that. Note that we do not infer the value of 42 here, but leave + ;; it for other passes to do. (drop (struct.get $struct2 0 (local.get $struct2) @@ -1944,3 +1944,221 @@ ) ) ) + +;; Test atomic gets. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $one (shared (struct (field i32)))) + (type $one (shared (struct (field i32)))) + ;; CHECK: (type $two (shared (struct (field i32)))) + (type $two (shared (struct (field i32)))) + ;; CHECK: (type $two-same (shared (struct (field i32)))) + (type $two-same (shared (struct (field i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $one)))) + + ;; CHECK: (type $4 (func (param (ref $two)))) + + ;; CHECK: (type $5 (func (param (ref $two-same)))) + + ;; CHECK: (global $one (ref $one) (struct.new $one + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + (global $one (ref $one) (struct.new $one (i32.const 42))) + + ;; CHECK: (global $two-a (ref $two) (struct.new $two + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + (global $two-a (ref $two) (struct.new $two (i32.const 42))) + + ;; CHECK: (global $two-b (ref $two) (struct.new $two + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + (global $two-b (ref $two) (struct.new $two (i32.const 1337))) + + ;; CHECK: (global $two-same-a (ref $two-same) (struct.new $two-same + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + (global $two-same-a (ref $two-same) (struct.new $two-same (i32.const 42))) + + ;; CHECK: (global $two-same-b (ref $two-same) (struct.new $two-same + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + (global $two-same-b (ref $two-same) (struct.new $two-same (i32.const 42))) + + ;; CHECK: (func $one (type $3) (param $0 (ref $one)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $one 0 + ;; CHECK-NEXT: (block (result (ref $one)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $one) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.get acqrel $one 0 + ;; CHECK-NEXT: (block (result (ref $one)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $one) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.get $one 0 + ;; CHECK-NEXT: (block (result (ref $one)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $one) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $one (param (ref $one)) + (drop + (struct.get $one 0 + (local.get 0) + ) + ) + (drop + (struct.atomic.get acqrel $one 0 + (local.get 0) + ) + ) + (drop + (struct.atomic.get $one 0 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $two (type $4) (param $0 (ref $two)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $two-a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $two-a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref $two)) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (global.get $two-a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $two (param (ref $two)) + (drop + (struct.get $two 0 + (local.get 0) + ) + ) + (drop + ;; This is optimized normally because there cannot be any writes it + ;; synchronizes with. + (struct.atomic.get acqrel $two 0 + (local.get 0) + ) + ) + (drop + ;; This requires a fence to maintain its effect on the global order of + ;; seqcst operations. + (struct.atomic.get $two 0 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $two-same (type $5) (param $0 (ref $two-same)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $two-same (param (ref $two-same)) + (drop + (struct.get $two-same 0 + (local.get 0) + ) + ) + (drop + ;; This is optimized normally because there cannot be any writes it + ;; synchronizes with. + (struct.atomic.get acqrel $two-same 0 + (local.get 0) + ) + ) + (drop + ;; This requires a fence to maintain its effect on the global order of + ;; seqcst operations. + (struct.atomic.get $two-same 0 + (local.get 0) + ) + ) + ) +) From ac7cae5ba45f2995b045927ed1d7c03f1fded227 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Dec 2024 12:09:45 -0800 Subject: [PATCH 203/622] [NFC] Fix spurious uninitialized variable warning (#7174) The coverage CI builder was failing because of a spurious error about a variable being possibly used uninitialized. Fix the warning by initializing the variable to 0. --- src/support/string.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/support/string.cpp b/src/support/string.cpp index 7dc9ba89c55..96056d90a0e 100644 --- a/src/support/string.cpp +++ b/src/support/string.cpp @@ -152,7 +152,8 @@ std::optional takeWTF8CodePoint(std::string_view& str) { uint8_t leading = str[0]; size_t trailingBytes; - uint32_t u; + // Initialized only to avoid spurious compiler warnings. + uint32_t u = 0; if ((leading & 0b10000000) == 0b00000000) { // 0xxxxxxx trailingBytes = 0; From 43da2c7ff5069a4cb5733b4efcdbd573977e3c46 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Dec 2024 12:10:18 -0800 Subject: [PATCH 204/622] Update flake8 and fix errors (#7172) The flake8 we were running on CI was too old and began giving spurious errors about the uninterpreted contents of f-strings. Update to the latest flake8 and fix all the new errors, including the previously incorrect comment syntax in the .flake8 file. Also remove scripts/storage.py, since it didn't seem to be used for anything we currently need. --- .flake8 | 9 +++-- .github/workflows/ci.yml | 2 +- check.py | 4 +-- requirements-dev.txt | 2 +- scripts/fuzz_opt.py | 2 +- scripts/port_passes_tests_to_lit.py | 2 +- scripts/storage.py | 55 ----------------------------- scripts/test/shared.py | 4 +-- scripts/test/support.py | 4 +-- scripts/update_lit_checks.py | 2 +- 10 files changed, 17 insertions(+), 69 deletions(-) delete mode 100755 scripts/storage.py diff --git a/.flake8 b/.flake8 index 83735a2f0ec..4e967e90899 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,9 @@ [flake8] ignore = - E501, # line too long - E241, # space after comma (ignored for list in gen-s-parser.py) - W504 # line break after binary operator + ; line too long + E501, + ; space after comma (ignored for list in gen-s-parser.py) + E241, + ; line break after binary operator + W504 exclude = third_party,./test/emscripten,./test/spec,./test/wasm-install,./test/lit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01b125a7a17..b826c840bac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: fetch-depth: 0 - name: install tools run: | - sudo pip3 install -r requirements-dev.txt + pip3 install -r requirements-dev.txt sudo apt install lsb-release wget software-properties-common gnupg wget https://apt.llvm.org/llvm.sh sudo chmod +x llvm.sh diff --git a/check.py b/check.py index 66493dd3c6f..dfaada0ab93 100755 --- a/check.py +++ b/check.py @@ -32,8 +32,8 @@ def get_changelog_version(): with open(os.path.join(shared.options.binaryen_root, 'CHANGELOG.md')) as f: lines = f.readlines() - lines = [l for l in lines if len(l.split()) == 1] - lines = [l for l in lines if l.startswith('v')] + lines = [line for line in lines if len(line.split()) == 1] + lines = [line for line in lines if line.startswith('v')] version = lines[0][1:] print("Parsed CHANGELOG.md version: %s" % version) return int(version) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8bffeb2a926..511ad43bd36 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,6 +3,6 @@ # Install with `pip3 install -r requirements-dev.txt` -flake8==3.7.8 +flake8==7.1.1 filecheck==0.0.22 lit==0.11.0.post1 diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 58d1a022a53..27f85e7a08a 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -990,7 +990,7 @@ def run_vms(self, wasm): print('(ignored, so not running other VMs)') # the ignoring should have been noted during run_vms() - assert(ignored_vm_runs > ignored_before) + assert ignored_vm_runs > ignored_before return vm_results diff --git a/scripts/port_passes_tests_to_lit.py b/scripts/port_passes_tests_to_lit.py index 659cc20d2a5..6e82c455136 100755 --- a/scripts/port_passes_tests_to_lit.py +++ b/scripts/port_passes_tests_to_lit.py @@ -67,7 +67,7 @@ def port_test(args, test): run_line = (f';; RUN: foreach %s %t wasm-opt {" ".join(opts)} -S -o -' ' | filecheck %s') - notice = (f';; NOTE: This test was ported using' + notice = (';; NOTE: This test was ported using' ' port_passes_tests_to_lit.py and could be cleaned up.') with open(test, 'r') as src_file: diff --git a/scripts/storage.py b/scripts/storage.py deleted file mode 100755 index d7ae42e13d3..00000000000 --- a/scripts/storage.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2016 WebAssembly Community Group participants -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function - -import glob -import json -import os -import urllib2 - - -STORAGE_BASE = 'https://storage.googleapis.com/wasm-llvm/builds/git/' - - -def download_revision(force_latest): - name = 'latest' if force_latest else 'lkgr' - downloaded = urllib2.urlopen(STORAGE_BASE + name).read().strip() - # TODO: for now try opening as JSON, if that doesn't work then the content is - # just a hash. The waterfall is in the process of migrating to JSON. - info = None - try: - info = json.loads(downloaded) - except ValueError: - pass - return info['build'] if type(info) == dict else downloaded - - -def download_tar(tar_pattern, directory, revision): - tar_path = os.path.join(directory, tar_pattern) - revision_tar_path = tar_path % revision - if os.path.isfile(revision_tar_path): - print('Already have `%s`' % revision_tar_path) - else: - print('Downloading `%s`' % revision_tar_path) - with open(revision_tar_path, 'w+') as f: - f.write(urllib2.urlopen(STORAGE_BASE + tar_pattern % revision).read()) - # Remove any previous tarfiles. - for older_tar in glob.glob(tar_path % '*'): - if older_tar != revision_tar_path: - print('Removing older tar file `%s`' % older_tar) - os.remove(older_tar) - return revision_tar_path diff --git a/scripts/test/shared.py b/scripts/test/shared.py index e0a51a73a69..ac9a408b613 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -315,7 +315,7 @@ def __init__(self, returncode, cmd, output=None, stderr=None): def run_process(cmd, check=True, input=None, capture_output=False, decode_output=True, *args, **kw): - if input and type(input) == str: + if input and type(input) is str: input = bytes(input, 'utf-8') if capture_output: kw['stdout'] = subprocess.PIPE @@ -358,7 +358,7 @@ def fail_if_not_contained(actual, expected): def fail_if_not_identical_to_file(actual, expected_file): - binary = expected_file.endswith(".wasm") or type(actual) == bytes + binary = expected_file.endswith(".wasm") or type(actual) is bytes with open(expected_file, 'rb' if binary else 'r') as f: fail_if_not_identical(actual, f.read(), fromfile=expected_file) diff --git a/scripts/test/support.py b/scripts/test/support.py index 43bd00fe8a0..a41fee22793 100644 --- a/scripts/test/support.py +++ b/scripts/test/support.py @@ -170,7 +170,7 @@ def to_end(j): # write a split wast from split_wast. the wast may be binary if the original # file was binary def write_wast(filename, wast, asserts=[]): - if type(wast) == bytes: + if type(wast) is bytes: assert not asserts with open(filename, 'wb') as o: o.write(wast) @@ -182,7 +182,7 @@ def write_wast(filename, wast, asserts=[]): def run_command(cmd, expected_status=0, stderr=None, expected_err=None, err_contains=False, err_ignore=None): if expected_err is not None: - assert stderr == subprocess.PIPE or stderr is None,\ + assert stderr == subprocess.PIPE or stderr is None, \ "Can't redirect stderr if using expected_err" stderr = subprocess.PIPE print('executing: ', ' '.join(cmd)) diff --git a/scripts/update_lit_checks.py b/scripts/update_lit_checks.py index 345ff39d0c6..b6bb213a8f7 100755 --- a/scripts/update_lit_checks.py +++ b/scripts/update_lit_checks.py @@ -88,7 +88,7 @@ def itertests(args): def find_run_lines(test, lines): - line_matches = [RUN_LINE_RE.match(l) for l in lines] + line_matches = [RUN_LINE_RE.match(line) for line in lines] matches = [match.group(1) for match in line_matches if match] if not matches: warn(f'No RUN lines found in {test}. Ignoring.') From edfd9a17202ae76933f64bf4e171f9a6ebe94b0e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Dec 2024 12:16:56 -0800 Subject: [PATCH 205/622] [NFC] Remove unused `useNewWATParser` bool (#7170) We always use the new WAT parser now. --- src/wasm-io.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/wasm-io.h b/src/wasm-io.h index e5cad9a23ff..a082c88cee8 100644 --- a/src/wasm-io.h +++ b/src/wasm-io.h @@ -28,10 +28,6 @@ namespace wasm { -// TODO: Remove this after switching to the new WAT parser by default and -// removing the old one. -extern bool useNewWATParser; - class ModuleIOBase { protected: bool debugInfo; From 5ed6cf191aa88b424f6784ba27ac2ab069234fd7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 20 Dec 2024 14:50:20 -0800 Subject: [PATCH 206/622] [NFC] Move more logic about unfuzzable tests to a shared location (#7175) It turns out that #7165 was not enough because we had a second (!?) list of tests to ignore, and also a condition. Move all that to the shared location as well, continuing that PR. Also remove simd.wast from the list, as that issue has been fixed. --- scripts/fuzz_opt.py | 20 +------------------- scripts/test/fuzzing.py | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 27f85e7a08a..70c5a437c94 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -309,8 +309,6 @@ def is_git_repo(): all_tests = shared.get_all_tests() -INITIAL_CONTENTS_IGNORE = fuzzing.unfuzzable_tests - def pick_initial_contents(): # if we use an initial wasm file's contents as the basis for the @@ -334,25 +332,9 @@ def pick_initial_contents(): # no longer exist, and we should just skip it. if not os.path.exists(test_name): return - if os.path.basename(test_name) in INITIAL_CONTENTS_IGNORE: + if not fuzzing.is_fuzzable(test_name): return assert os.path.exists(test_name) - # tests that check validation errors are not helpful for us - if '.fail.' in test_name: - print('initial contents is just a .fail test') - return - if os.path.basename(test_name) in [ - # contains too many segments to run in a wasm VM - 'limit-segments_disable-bulk-memory.wast', - # https://github.com/WebAssembly/binaryen/issues/3203 - 'simd.wast', - # corner cases of escaping of names is not interesting - 'names.wast', - # huge amount of locals that make it extremely slow - 'too_much_for_liveness.wasm' - ]: - print('initial contents is disallowed') - return if test_name.endswith('.wast'): # this can contain multiple modules, pick one diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index c1022b6abb2..be99af74631 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os + # Tests that the fuzzers should not operate on. -unfuzzable_tests = [ +unfuzzable = [ # Float16 is still experimental. 'f16.wast', # not all relaxed SIMD instructions are implemented in the interpreter @@ -73,4 +75,20 @@ 'typed_continuations_contnew.wast', 'typed_continuations_contbind.wast', 'typed_continuations_suspend.wast', + # contains too many segments to run in a wasm VM + 'limit-segments_disable-bulk-memory.wast', + # https://github.com/WebAssembly/binaryen/issues/7176 + 'names.wast', + # huge amount of locals that make it extremely slow + 'too_much_for_liveness.wasm', ] + + +def is_fuzzable(name): + name = os.path.basename(name) + + # It makes no sense to fuzz things that check validation errors. + if '.fail.' in name: + return False + + return name not in unfuzzable From 4d8a933e1136159160f2b45ad3a9a1c82021a75b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Dec 2024 16:43:02 -0800 Subject: [PATCH 207/622] Fix UBSan on CI (#7173) The UBSan builder started failing with an error about a misaligned store in wasm-ctor-eval.cpp. The store was already done via `memcpy` to avoid alignment issues, but apparently this is no longer enough. Use `void*` as the destination type to further avoid giving the impression of guaranteed alignment. Also fix UB when executing std::abs on minimum negative integers in literal.cpp. --- src/tools/wasm-ctor-eval.cpp | 14 +++++++------- src/wasm/literal.cpp | 6 ++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 89727d01220..17927f5a693 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -462,30 +462,30 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { const size_t MaximumMemory = 100 * 1024 * 1024; // TODO: handle unaligned too, see shell-interface - template T* getMemory(Address address, Name memoryName) { + void* getMemory(Address address, Name memoryName, size_t size) { auto it = memories.find(memoryName); assert(it != memories.end()); auto& memory = it->second; // resize the memory buffer as needed. - auto max = address + sizeof(T); + auto max = address + size; if (max > memory.size()) { if (max > MaximumMemory) { throw FailToEvalException("excessively high memory address accessed"); } memory.resize(max); } - return (T*)(&memory[address]); + return &memory[address]; } template void doStore(Address address, T value, Name memoryName) { - // do a memcpy to avoid undefined behavior if unaligned - memcpy(getMemory(address, memoryName), &value, sizeof(T)); + // Use memcpy to avoid UB if unaligned. + memcpy(getMemory(address, memoryName, sizeof(T)), &value, sizeof(T)); } template T doLoad(Address address, Name memoryName) { - // do a memcpy to avoid undefined behavior if unaligned + // Use memcpy to avoid UB if unaligned. T ret; - memcpy(&ret, getMemory(address, memoryName), sizeof(T)); + memcpy(&ret, getMemory(address, memoryName, sizeof(T)), sizeof(T)); return ret; } diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index b53378cfa44..05027ee6bd6 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -978,8 +978,14 @@ Literal Literal::neg() const { Literal Literal::abs() const { switch (type.getBasic()) { case Type::i32: + if (i32 == std::numeric_limits::min()) { + return *this; + } return Literal(std::abs(i32)); case Type::i64: + if (i64 == std::numeric_limits::min()) { + return *this; + } return Literal(std::abs(i64)); case Type::f32: return Literal(i32 & 0x7fffffff).castToF32(); From 6ddacde514af7cc546caa07fede4baa3e429c33c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Dec 2024 17:45:47 -0800 Subject: [PATCH 208/622] [NFC] Make MemoryOrder parameters non-optional (#7171) Update Builder and IRBuilder makeStructGet and makeStructSet functions to require the memory order to be explicitly supplied. This is slightly more verbose, but will reduce the chances that we forget to properly consider synchronization when implementing new features in the future. --- src/binaryen-c.cpp | 6 ++++-- src/parser/contexts.h | 9 +++------ src/parser/parsers.h | 6 ++++-- src/passes/Heap2Local.cpp | 9 ++++++--- src/passes/J2CLItableMerging.cpp | 3 ++- src/tools/fuzzing/fuzzing.cpp | 5 +++-- src/tools/wasm-ctor-eval.cpp | 3 ++- src/wasm-builder.h | 6 +++--- src/wasm-ir-builder.h | 10 +++------- src/wasm/wasm-binary.cpp | 8 +++++--- src/wasm/wasm-ir-builder.cpp | 2 +- 11 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 4294d56ea64..fa834d2686c 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1752,7 +1752,8 @@ BinaryenExpressionRef BinaryenStructGet(BinaryenModuleRef module, bool signed_) { return static_cast( Builder(*(Module*)module) - .makeStructGet(index, (Expression*)ref, Type(type), signed_)); + .makeStructGet( + index, (Expression*)ref, MemoryOrder::Unordered, Type(type), signed_)); } BinaryenExpressionRef BinaryenStructSet(BinaryenModuleRef module, BinaryenIndex index, @@ -1760,7 +1761,8 @@ BinaryenExpressionRef BinaryenStructSet(BinaryenModuleRef module, BinaryenExpressionRef value) { return static_cast( Builder(*(Module*)module) - .makeStructSet(index, (Expression*)ref, (Expression*)value)); + .makeStructSet( + index, (Expression*)ref, (Expression*)value, MemoryOrder::Unordered)); } BinaryenExpressionRef BinaryenArrayNew(BinaryenModuleRef module, BinaryenHeapType type, diff --git a/src/parser/contexts.h b/src/parser/contexts.h index a65299eac4c..2f008c01940 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -740,15 +740,12 @@ struct NullInstrParserCtx { HeapTypeT, FieldIdxT, bool, - MemoryOrder = MemoryOrder::Unordered) { + MemoryOrder) { return Ok{}; } template - Result<> makeStructSet(Index, - const std::vector&, - HeapTypeT, - FieldIdxT, - MemoryOrder = MemoryOrder::Unordered) { + Result<> makeStructSet( + Index, const std::vector&, HeapTypeT, FieldIdxT, MemoryOrder) { return Ok{}; } template diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 1f723640393..59f34038d09 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -2240,7 +2240,8 @@ Result<> makeStructGet(Ctx& ctx, CHECK_ERR(type); auto field = fieldidx(ctx, *type); CHECK_ERR(field); - return ctx.makeStructGet(pos, annotations, *type, *field, signed_); + return ctx.makeStructGet( + pos, annotations, *type, *field, signed_, MemoryOrder::Unordered); } template @@ -2264,7 +2265,8 @@ makeStructSet(Ctx& ctx, Index pos, const std::vector& annotations) { CHECK_ERR(type); auto field = fieldidx(ctx, *type); CHECK_ERR(field); - return ctx.makeStructSet(pos, annotations, *type, *field); + return ctx.makeStructSet( + pos, annotations, *type, *field, MemoryOrder::Unordered); } template diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index a6223d4fcbf..b050c4a762b 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -1066,7 +1066,9 @@ struct Array2Struct : PostWalker { } // Convert the ArraySet into a StructSet. - replaceCurrent(builder.makeStructSet(index, curr->ref, curr->value)); + // TODO: Handle atomic array accesses. + replaceCurrent(builder.makeStructSet( + index, curr->ref, curr->value, MemoryOrder::Unordered)); } void visitArrayGet(ArrayGet* curr) { @@ -1085,8 +1087,9 @@ struct Array2Struct : PostWalker { } // Convert the ArrayGet into a StructGet. - replaceCurrent( - builder.makeStructGet(index, curr->ref, curr->type, curr->signed_)); + // TODO: Handle atomic array accesses. + replaceCurrent(builder.makeStructGet( + index, curr->ref, MemoryOrder::Unordered, curr->type, curr->signed_)); } // Some additional operations need special handling diff --git a/src/passes/J2CLItableMerging.cpp b/src/passes/J2CLItableMerging.cpp index 472d18b7e86..be8da22cefa 100644 --- a/src/passes/J2CLItableMerging.cpp +++ b/src/passes/J2CLItableMerging.cpp @@ -280,6 +280,7 @@ struct J2CLItableMerging : public Pass { replaceCurrent(builder.makeStructGet( 0, curr->ref, + MemoryOrder::Unordered, parent.structInfoByITableType[curr->type.getHeapType()] ->javaClass.getStruct() .fields[0] @@ -341,4 +342,4 @@ struct J2CLItableMerging : public Pass { } // anonymous namespace Pass* createJ2CLItableMergingPass() { return new J2CLItableMerging(); } -} // namespace wasm \ No newline at end of file +} // namespace wasm diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 2b9286180c3..9a342c553f2 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -4493,7 +4493,8 @@ Expression* TranslateToFuzzReader::makeStructGet(Type type) { auto [structType, fieldIndex] = pick(structFields); auto* ref = makeTrappingRefUse(structType); auto signed_ = maybeSignedGet(structType.getStruct().fields[fieldIndex]); - return builder.makeStructGet(fieldIndex, ref, type, signed_); + return builder.makeStructGet( + fieldIndex, ref, MemoryOrder::Unordered, type, signed_); } Expression* TranslateToFuzzReader::makeStructSet(Type type) { @@ -4505,7 +4506,7 @@ Expression* TranslateToFuzzReader::makeStructSet(Type type) { auto fieldType = structType.getStruct().fields[fieldIndex].type; auto* ref = makeTrappingRefUse(structType); auto* value = make(fieldType); - return builder.makeStructSet(fieldIndex, ref, value); + return builder.makeStructSet(fieldIndex, ref, value, MemoryOrder::Unordered); } // Make a bounds check for an array operation, given a ref + index. An optional diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 17927f5a693..a940d928412 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -957,7 +957,8 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { Expression* set; if (global.type.isStruct()) { - set = builder.makeStructSet(index, getGlobal, value); + set = + builder.makeStructSet(index, getGlobal, value, MemoryOrder::Unordered); } else { set = builder.makeArraySet( getGlobal, builder.makeConst(int32_t(index)), value); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 4396bc6df73..03d0e2da037 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -938,9 +938,9 @@ class Builder { } StructGet* makeStructGet(Index index, Expression* ref, + MemoryOrder order, Type type, - bool signed_ = false, - MemoryOrder order = MemoryOrder::Unordered) { + bool signed_ = false) { auto* ret = wasm.allocator.alloc(); ret->index = index; ret->ref = ref; @@ -953,7 +953,7 @@ class Builder { StructSet* makeStructSet(Index index, Expression* ref, Expression* value, - MemoryOrder order = MemoryOrder::Unordered) { + MemoryOrder order) { auto* ret = wasm.allocator.alloc(); ret->index = index; ret->ref = ref; diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index a40e8df8248..c46f9f2ca76 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -204,13 +204,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { makeBrOn(Index label, BrOnOp op, Type in = Type::none, Type out = Type::none); Result<> makeStructNew(HeapType type); Result<> makeStructNewDefault(HeapType type); - Result<> makeStructGet(HeapType type, - Index field, - bool signed_, - MemoryOrder order = MemoryOrder::Unordered); - Result<> makeStructSet(HeapType type, - Index field, - MemoryOrder order = MemoryOrder::Unordered); + Result<> + makeStructGet(HeapType type, Index field, bool signed_, MemoryOrder order); + Result<> makeStructSet(HeapType type, Index field, MemoryOrder order); Result<> makeArrayNew(HeapType type); Result<> makeArrayNewDefault(HeapType type); Result<> makeArrayNewData(HeapType type, Name data); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index b0c5a54acc9..2bcf3d44614 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4172,13 +4172,15 @@ Result<> WasmBinaryReader::readInst() { case BinaryConsts::StructGetU: { auto type = getIndexedHeapType(); auto field = getU32LEB(); - return builder.makeStructGet( - type, field, op == BinaryConsts::StructGetS); + return builder.makeStructGet(type, + field, + op == BinaryConsts::StructGetS, + MemoryOrder::Unordered); } case BinaryConsts::StructSet: { auto type = getIndexedHeapType(); auto field = getU32LEB(); - return builder.makeStructSet(type, field); + return builder.makeStructSet(type, field, MemoryOrder::Unordered); } case BinaryConsts::ArrayNew: return builder.makeArrayNew(getIndexedHeapType()); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 4b034241072..3c966769c6d 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1801,7 +1801,7 @@ Result<> IRBuilder::makeStructGet(HeapType type, CHECK_ERR(ChildPopper{*this}.visitStructGet(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); push( - builder.makeStructGet(field, curr.ref, fields[field].type, signed_, order)); + builder.makeStructGet(field, curr.ref, order, fields[field].type, signed_)); return Ok{}; } From 5611a528b2a3bcd37aedd863c6058f99095c9a35 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 30 Dec 2024 13:14:59 -0800 Subject: [PATCH 209/622] [wasm-reduce] Reduce struct.new arguments away when possible (#7118) If all the fields of a struct.new are defaultable, see if replacing it with a struct.new_default preserves the behavior, and reduce that way if so. Also add a missing --closed-world to the --remove-unused-types invocation. Without that, it was erroring and not working, which I noticed when testing this. The test also checks that. --- src/tools/wasm-reduce.cpp | 21 ++++++++++++++++++++- test/reduce/gc.wast | 28 ++++++++++++++++++++++++++++ test/reduce/gc.wast.txt | 16 ++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 test/reduce/gc.wast create mode 100644 test/reduce/gc.wast.txt diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 3b922946232..6613c8899bf 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -304,7 +304,7 @@ struct Reducer "--simplify-globals", "--simplify-locals --vacuum", "--strip", - "--remove-unused-types", + "--remove-unused-types --closed-world", "--vacuum"}; auto oldSize = file_size(working); bool more = true; @@ -627,6 +627,25 @@ struct Reducer // children (which would recreate the current state). return; } + } else if (auto* structNew = curr->dynCast()) { + // If all the fields are defaultable, try to replace this with a + // struct.new_with_default. + if (!structNew->isWithDefault() && structNew->type != Type::unreachable) { + auto& fields = structNew->type.getHeapType().getStruct().fields; + if (std::all_of(fields.begin(), fields.end(), [&](auto& field) { + return field.type.isDefaultable(); + })) { + ExpressionList operands(getModule()->allocator); + operands.swap(structNew->operands); + assert(structNew->isWithDefault()); + if (tryToReplaceCurrent(structNew)) { + return; + } else { + structNew->operands.swap(operands); + assert(!structNew->isWithDefault()); + } + } + } } // Finally, try to replace with a child. for (auto* child : ChildIterator(curr)) { diff --git a/test/reduce/gc.wast b/test/reduce/gc.wast new file mode 100644 index 00000000000..98d1cd07545 --- /dev/null +++ b/test/reduce/gc.wast @@ -0,0 +1,28 @@ +(module + (rec + (type $A (struct (field (mut i32)) (field funcref))) + ;; This type can be optimized away. + (type $unused (struct)) + ) + + (global $A (ref null $A) (struct.new $A + ;; These particular values are not used, and can be removed, leaving the + ;; struct.new as struct.new_default. + (i32.const 0) + (ref.func $use-global) + )) + + (func $use-global (export "use-global") (result i32) + ;; This function stores 42 in the global struct, then reads and returns + ;; that. We don't manage to optimize away anything in this function, which + ;; only serves to keep alive the type and the global for the above testing. + (struct.set $A 0 + (global.get $A) + (i32.const 42) + ) + (struct.get $A 0 + (global.get $A) + ) + ) +) + diff --git a/test/reduce/gc.wast.txt b/test/reduce/gc.wast.txt new file mode 100644 index 00000000000..3af5287ce99 --- /dev/null +++ b/test/reduce/gc.wast.txt @@ -0,0 +1,16 @@ +(module + (type $0 (struct (field (mut i32)) (field funcref))) + (type $1 (func (result i32))) + (global $global$0 (ref null $0) (struct.new_default $0)) + (export "use-global" (func $0)) + (func $0 (result i32) + (struct.set $0 0 + (global.get $global$0) + (i32.const 42) + ) + (struct.get $0 0 + (global.get $global$0) + ) + ) +) + From 0d7ef684a8fe20efd755c70a7343cd5cba4a9ba7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Jan 2025 13:58:33 -0800 Subject: [PATCH 210/622] [NFC] Remove some unused code (#7186) This may have been used in the past somehow, but no longer is. --- src/passes/CoalesceLocals.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp index 78b8264955a..f813d9f78af 100644 --- a/src/passes/CoalesceLocals.cpp +++ b/src/passes/CoalesceLocals.cpp @@ -98,10 +98,6 @@ struct CoalesceLocals interferences.set(low, high, true); } - void unInterfere(Index i, Index j) { - interferences.set(std::min(i, j), std::max(i, j), false); - } - bool interferes(Index i, Index j) { return interferences.get(std::min(i, j), std::max(i, j)); } From 1911e0b0b0e1a14e1d2be41ebfab3c43b747eae1 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 3 Jan 2025 18:10:26 -0800 Subject: [PATCH 211/622] Also remove BulkMemory feature when running LLVMMemCopyFillLowering (#7189) This is safe because we have already asserted that there are no passive segments. This causes Binaryen to encode the resulting binary without a DataCount section, making it compatible with engines that don't support bulk memory. --- src/passes/LLVMMemoryCopyFillLowering.cpp | 3 +++ test/lit/passes/memory-copy-fill-lowering.wast | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/passes/LLVMMemoryCopyFillLowering.cpp b/src/passes/LLVMMemoryCopyFillLowering.cpp index d2a7e5c482d..bf0225daca6 100644 --- a/src/passes/LLVMMemoryCopyFillLowering.cpp +++ b/src/passes/LLVMMemoryCopyFillLowering.cpp @@ -70,6 +70,9 @@ struct LLVMMemoryCopyFillLowering " no passive segments"; } } + // Since there are no passive segments, we can remove the feature. This also + // causes Binaryen to not encode a DataCount section. + module->features.setBulkMemory(false); // In order to introduce a call to a function, it must first exist, so // create an empty stub. diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast index 551f5a4e6b5..ea5198f893a 100644 --- a/test/lit/passes/memory-copy-fill-lowering.wast +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -1,6 +1,8 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; NOTE: These assertions have been manually generated, and cannot be updated by update_lit_checks.py +;; because of the assertion at the end (update_lit_checks.py ignores the features section because it's +;; not semantically part of the module.) -;; RUN: wasm-opt --enable-bulk-memory %s --llvm-memory-copy-fill-lowering -S -o - | filecheck %s +;; RUN: wasm-opt --enable-bulk-memory %s --llvm-memory-copy-fill-lowering --emit-target-features -S -o - | filecheck %s (module (memory 0) @@ -166,3 +168,4 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) +;; CHECK-NEXT: ;; features section: mutable-globals, sign-ext From 059aa205cbeff5c14839ac8014665e8614029dfa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 6 Jan 2025 10:08:09 -0800 Subject: [PATCH 212/622] Fix IRBuilder on local operations outside of a function context (#7183) IRBuilder needs to check for a function context before doing func->getLocalType(). Without such a check, a bad binary with a local operation in say a global init would lead to a crash. We don't actually need this for local.set, unlike tee and get, since it doesn't call getLocalType, but this PR adds that too for consistency of errors. No other operations in IRBuilder were missing this check aside from local operations. Fixes #7178 --- src/wasm/wasm-ir-builder.cpp | 9 +++++++++ test/lit/binary/global-local-get.test | 10 ++++++++++ test/lit/binary/global-local-get.test.wasm | Bin 0 -> 16 bytes test/lit/binary/global-local-set.test | 10 ++++++++++ test/lit/binary/global-local-set.test.wasm | Bin 0 -> 18 bytes test/lit/binary/global-local-tee.test | 10 ++++++++++ test/lit/binary/global-local-tee.test.wasm | Bin 0 -> 18 bytes 7 files changed, 39 insertions(+) create mode 100644 test/lit/binary/global-local-get.test create mode 100644 test/lit/binary/global-local-get.test.wasm create mode 100644 test/lit/binary/global-local-set.test create mode 100644 test/lit/binary/global-local-set.test.wasm create mode 100644 test/lit/binary/global-local-tee.test create mode 100644 test/lit/binary/global-local-tee.test.wasm diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 3c966769c6d..2d86f2262c3 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1259,11 +1259,17 @@ Result<> IRBuilder::makeCallIndirect(Name table, HeapType type, bool isReturn) { } Result<> IRBuilder::makeLocalGet(Index local) { + if (!func) { + return Err{"local.get is only valid in a function context"}; + } push(builder.makeLocalGet(local, func->getLocalType(local))); return Ok{}; } Result<> IRBuilder::makeLocalSet(Index local) { + if (!func) { + return Err{"local.set is only valid in a function context"}; + } LocalSet curr; curr.index = local; CHECK_ERR(visitLocalSet(&curr)); @@ -1272,6 +1278,9 @@ Result<> IRBuilder::makeLocalSet(Index local) { } Result<> IRBuilder::makeLocalTee(Index local) { + if (!func) { + return Err{"local.tee is only valid in a function context"}; + } LocalSet curr; curr.index = local; CHECK_ERR(visitLocalSet(&curr)); diff --git a/test/lit/binary/global-local-get.test b/test/lit/binary/global-local-get.test new file mode 100644 index 00000000000..4253a8bc303 --- /dev/null +++ b/test/lit/binary/global-local-get.test @@ -0,0 +1,10 @@ +RUN: not wasm-opt --debug %s.wasm 2>&1 | filecheck %s + +;; Check that we get the expected error for an input binary that looks like +;; this: +;; +;; (module +;; (global $g i32 (local.get 0)) +;; ) + +;; CHECK: local.get is only valid in a function context diff --git a/test/lit/binary/global-local-get.test.wasm b/test/lit/binary/global-local-get.test.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f975882aedee7a845bfba7bc743b794b7a6077c3 GIT binary patch literal 16 XcmZQbEY4+QU|?WlW2|RTVBiJ-6yyQ= literal 0 HcmV?d00001 diff --git a/test/lit/binary/global-local-set.test b/test/lit/binary/global-local-set.test new file mode 100644 index 00000000000..68775eb9a4d --- /dev/null +++ b/test/lit/binary/global-local-set.test @@ -0,0 +1,10 @@ +RUN: not wasm-opt --debug %s.wasm 2>&1 | filecheck %s + +;; Check that we get the expected error for an input binary that looks like +;; this: +;; +;; (module +;; (global $g i32 (local.set 0 (i32.const 1))) +;; ) + +;; CHECK: local.set is only valid in a function context diff --git a/test/lit/binary/global-local-set.test.wasm b/test/lit/binary/global-local-set.test.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2e1cd0b54d7ea1d6a11412febd2431409dce430b GIT binary patch literal 18 ZcmZQbEY4+QU|?Y5V610wWK?9}1^^l00zUu% literal 0 HcmV?d00001 diff --git a/test/lit/binary/global-local-tee.test b/test/lit/binary/global-local-tee.test new file mode 100644 index 00000000000..e3dabde80c9 --- /dev/null +++ b/test/lit/binary/global-local-tee.test @@ -0,0 +1,10 @@ +RUN: not wasm-opt --debug %s.wasm 2>&1 | filecheck %s + +;; Check that we get the expected error for an input binary that looks like +;; this: +;; +;; (module +;; (global $g i32 (local.tee 0 (i32.const 1))) +;; ) + +;; CHECK: local.tee is only valid in a function context diff --git a/test/lit/binary/global-local-tee.test.wasm b/test/lit/binary/global-local-tee.test.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d4a8d7f30461d4faf71935c31cc7a014921051c6 GIT binary patch literal 18 ZcmZQbEY4+QU|?Y5V610wWK?3{1^^l30zd!& literal 0 HcmV?d00001 From 1faa606a525f5c41a2e49935a7d4572e1701a1ee Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 6 Jan 2025 11:42:06 -0800 Subject: [PATCH 213/622] Fuzzing: Do not emit RefAsNonNull in non-function contexts (#7188) Emitting that in e.g. a global init is not going to validate, so avoid doing so, and continue to the code below, which may manage to emit something in this case. --- src/tools/fuzzing/fuzzing.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 9a342c553f2..6e4807af8d3 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3243,14 +3243,19 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { // which is not ideal. if (type.isNonNullable() && (random.finished() || nesting >= LIMIT)) { // If we have a function context then we can at least emit a local.get, - // perhaps, which is less bad. Note that we need to check typeLocals + // sometimes, which is less bad. Note that we need to check typeLocals // manually here to avoid infinite recursion (as makeLocalGet will fall back // to us, if there is no local). // TODO: we could also look for locals containing subtypes - if (funcContext && !funcContext->typeLocals[type].empty()) { - return makeLocalGet(type); + if (funcContext) { + if (!funcContext->typeLocals[type].empty()) { + return makeLocalGet(type); + } + // No local, but we are in a function context so RefAsNonNull is valid. + return builder.makeRefAs(RefAsNonNull, builder.makeRefNull(heapType)); } - return builder.makeRefAs(RefAsNonNull, builder.makeRefNull(heapType)); + // No function context, so we are in quite the pickle. Continue onwards, as + // we may succeed to emit something more complex (like a struct.new). } // When we make children, they must be trivial if we are not in a function From fcea5933fdd58869758e582c70c07e131a21d3f8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 6 Jan 2025 11:42:29 -0800 Subject: [PATCH 214/622] [GC][Threads] Atomic GC operations require --enable-threads (#7185) Without this, it is invalid to lower them to simpler atomic operations like atomic.fence (as some passes do) or linear memory atomics (as a future lowering pass might do). Fixes #7184 --- CHANGELOG.md | 3 +++ src/wasm/wasm-validator.cpp | 28 ++++++++++++++++++---------- test/lit/validation/gc-atomics.wast | 8 ++++++-- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6685533bd3b..f05140491e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ full changeset diff at the end of each section. Current Trunk ------------- + - `struct.atomic.get`/`struct.atomic.set` now require the threads feature, + `--enable-threads`. (#7185) + v121 ---- diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 7de69a1fffe..4f9976bdb0b 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2989,11 +2989,15 @@ void FunctionValidator::visitStructGet(StructGet* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, "struct.get requires gc [--enable-gc]"); - shouldBeTrue(curr->order == MemoryOrder::Unordered || - getModule()->features.hasSharedEverything(), - curr, - "struct.atomic.get requires shared-everything " - "[--enable-shared-everything]"); + if (curr->order != MemoryOrder::Unordered) { + shouldBeTrue(getModule()->features.hasSharedEverything(), + curr, + "struct.atomic.get requires shared-everything " + "[--enable-shared-everything]"); + shouldBeTrue(getModule()->features.hasAtomics(), + curr, + "struct.atomic.get requires threads [--enable-threads]"); + } if (curr->type == Type::unreachable || curr->ref->type.isNull()) { return; } @@ -3021,11 +3025,15 @@ void FunctionValidator::visitStructSet(StructSet* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, "struct.set requires gc [--enable-gc]"); - shouldBeTrue(curr->order == MemoryOrder::Unordered || - getModule()->features.hasSharedEverything(), - curr, - "struct.atomic.set requires shared-everything " - "[--enable-shared-everything]"); + if (curr->order != MemoryOrder::Unordered) { + shouldBeTrue(getModule()->features.hasSharedEverything(), + curr, + "struct.atomic.set requires shared-everything " + "[--enable-shared-everything]"); + shouldBeTrue(getModule()->features.hasAtomics(), + curr, + "struct.atomic.set requires threads [--enable-threads]"); + } if (curr->ref->type == Type::unreachable) { return; } diff --git a/test/lit/validation/gc-atomics.wast b/test/lit/validation/gc-atomics.wast index 28e98b9fe33..7c13b5230e6 100644 --- a/test/lit/validation/gc-atomics.wast +++ b/test/lit/validation/gc-atomics.wast @@ -1,12 +1,13 @@ ;; Test that shared-everything GC instructions require the shared-everything ;; feature. -;; RUN: not wasm-opt -all --disable-shared-everything %s 2>&1 | filecheck %s +;; RUN: not wasm-opt -all --disable-shared-everything --disable-threads %s 2>&1 | filecheck %s (module (type $struct (struct (field (mut i32)))) ;; CHECK: struct.atomic.get requires shared-everything [--enable-shared-everything] + ;; CHECK: struct.atomic.get requires threads [--enable-threads] (func $get-seqcst (result i32) (struct.atomic.get seqcst $struct 0 (struct.new_default $struct) @@ -14,6 +15,7 @@ ) ;; CHECK: struct.atomic.get requires shared-everything [--enable-shared-everything] + ;; CHECK: struct.atomic.get requires threads [--enable-threads] (func $get-acqrel (result i32) (struct.atomic.get acqrel $struct 0 (struct.new_default $struct) @@ -21,6 +23,7 @@ ) ;; CHECK: struct.atomic.set requires shared-everything [--enable-shared-everything] + ;; CHECK: struct.atomic.set requires threads [--enable-threads] (func $set-seqcst (struct.atomic.set seqcst $struct 0 (struct.new_default $struct) @@ -29,10 +32,11 @@ ) ;; CHECK: struct.atomic.set requires shared-everything [--enable-shared-everything] + ;; CHECK: struct.atomic.set requires threads [--enable-threads] (func $set-acqrel (struct.atomic.set acqrel $struct 0 (struct.new_default $struct) (i32.const 0) ) ) -) \ No newline at end of file +) From 01be8403a3935d4984c6deb0faff32a597d32db4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 6 Jan 2025 12:54:41 -0800 Subject: [PATCH 215/622] ClusterFuzz: Add a --no_retry option for local debugging (#7187) When we run ClusterFuzz's run.py locally, we need to investigate any errors in creating fuzz testcases (as those are real bugs in wasm-opt). Previously we just checked for a "retry" message in the output of run.py (as the script retries automatically - we don't want a wasm-opt bug to stop ClusterFuzz), but that makes it impossible to debug the problem, since the retry tramples the data. The new option, used only locally, stops at the error so it can be investigated. --- scripts/clusterfuzz/run.py | 16 +++++++++++++++- scripts/fuzz_opt.py | 16 +++++++--------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index 2fedb6510c9..c6e2585cf29 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -92,6 +92,12 @@ def get_file_name(prefix, index): # (We also use urandom below, which uses this under the hood.) system_random = random.SystemRandom() +# In production ClusterFuzz we retry whenever we see a wasm-opt error. We are +# not looking for wasm-opt issues there, and just use it to generate testcases +# for VMs. For local testing, however, we may want to disable retrying, which +# allows us to debug any such failures that we run into. +retry = True + # Generate a random wasm file, and return a string that creates a typed array of # those bytes, suitable for use in a JS file, in the form @@ -117,6 +123,11 @@ def get_wasm_contents(i, output_dir): try: subprocess.check_call(cmd) except subprocess.CalledProcessError: + if not retry: + print('error in running wasm-opt') + print(' '.join(cmd)) + raise + # Try again. print('(oops, retrying wasm-opt)') attempt += 1 @@ -217,13 +228,16 @@ def main(argv): # https://google.github.io/clusterfuzz/setting-up-fuzzing/blackbox-fuzzing/#uploading-a-fuzzer output_dir = '.' num = 100 - expected_flags = ['input_dir=', 'output_dir=', 'no_of_files='] + expected_flags = ['input_dir=', 'output_dir=', 'no_of_files=', 'no_retry'] optlist, _ = getopt.getopt(argv[1:], '', expected_flags) for option, value in optlist: if option == '--output_dir': output_dir = value elif option == '--no_of_files': num = int(value) + elif option == '--no_retry': + global retry + retry = False for i in range(1, num + 1): testcase_file_path = os.path.join(output_dir, diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 70c5a437c94..beee6b25f39 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1580,15 +1580,13 @@ def handle(self, wasm): os.unlink(f) # Call run.py(), similarly to how ClusterFuzz does. - out = run([sys.executable, - os.path.join(self.clusterfuzz_dir, 'run.py'), - '--output_dir=' + os.getcwd(), - '--no_of_files=1']) - - # We should not see any mention of a wasm-opt error that caused a - # retry. On production ClusterFuzz this is not an error, but we do want - # to know about such issues, as they may be real bugs in wasm-opt. - assert 'retry' not in out, out + run([sys.executable, + os.path.join(self.clusterfuzz_dir, 'run.py'), + '--output_dir=' + os.getcwd(), + '--no_of_files=1', + # Do not retry on wasm-opt errors: we want to investigate + # them. + '--no_retry']) # We should see the two files. assert os.path.exists(fuzz_file) From f9d78d8ce4ef4332b77843b3e1b47e0fd5adf525 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 7 Jan 2025 11:05:29 -0800 Subject: [PATCH 216/622] Fuzzing: Handle instantiation errors in ClusterFuzz (#7166) If the module has an invalid segment offset, for example, it will error. We should not error in the fuzzer on such rare cases. Also add logging of the actual error, which matches what we do for errors elsewhere, and makes debugging easier. As a result another place needs to look for the prefix now, and not the entire string (since we append the error contents). --- scripts/fuzz_opt.py | 12 +++++++++--- scripts/fuzz_shell.js | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index beee6b25f39..e4265e7f011 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1556,6 +1556,10 @@ def handle(self, wasm): run([in_bin('wasm-opt'), abspath('a.wast')] + FEATURE_OPTS) +# The error shown when a module fails to instantiate. +INSTANTIATE_ERROR = 'exception thrown: failed to instantiate module' + + # Fuzz in a near-identical manner to how we fuzz on ClusterFuzz. This is mainly # to see that fuzzing that way works properly (it likely won't catch anything # the other fuzzers here catch, though it is possible). That is, running this @@ -1611,8 +1615,10 @@ def handle(self, wasm): # Verify that we called something. The fuzzer should always emit at # least one exported function (unless we've decided to ignore the entire - # run). - if output != IGNORE: + # run, or if the wasm errored during instantiation, which can happen due + # to a testcase with a segment out of bounds, say). + if output != IGNORE and not output.startswith(INSTANTIATE_ERROR): + assert FUZZ_EXEC_CALL_PREFIX in output def ensure(self): @@ -1672,7 +1678,7 @@ def handle(self, wasm): # to anything. return - if output.strip() == 'exception thrown: failed to instantiate module': + if output.startswith(INSTANTIATE_ERROR): # We may fail to instantiate the modules for valid reasons, such as # an active segment being out of bounds. There is no point to # continue in such cases, as no exports are called. diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 6531465179f..3aa9013e623 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -346,7 +346,7 @@ function build(binary) { try { instance = new WebAssembly.Instance(module, imports); } catch (e) { - console.log('exception thrown: failed to instantiate module'); + console.log('exception thrown: failed to instantiate module: ' + e); quit(); } From 8d0f662a054b97c0aaa02943d97219882a3d585b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 7 Jan 2025 14:36:28 -0800 Subject: [PATCH 217/622] [Fuzzing] Use initial contents in ClusterFuzz (#7192) The ClusterFuzz bundler now looks through all of our test suites and packages all testcases that are suitable for ClusterFuzz to use. This adds more variety to the wasm files we fuzz there, as the test suite has corner cases that the main fuzzer is unlikely to generate. This adds a comment in the JS whenever it uses initial content, to make debugging easier, something like [10, 20, 30] /* using initial content 17.wasm */ (this is the reason for the change to extract_wasms.py) --- scripts/bundle_clusterfuzz.py | 45 +++++++++++++++++++++ scripts/clusterfuzz/extract_wasms.py | 2 +- scripts/clusterfuzz/run.py | 37 +++++++++++++++-- test/unit/test_cluster_fuzz.py | 60 ++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 4 deletions(-) diff --git a/scripts/bundle_clusterfuzz.py b/scripts/bundle_clusterfuzz.py index a035538377c..4220c3c51c2 100755 --- a/scripts/bundle_clusterfuzz.py +++ b/scripts/bundle_clusterfuzz.py @@ -71,6 +71,7 @@ ''' import os +import subprocess import sys import tarfile @@ -87,7 +88,9 @@ # Delete the argument, as importing |shared| scans it. sys.argv.pop() +from test import fuzzing # noqa from test import shared # noqa +from test import support # noqa # Pick where to get the builds if build_dir: @@ -97,6 +100,14 @@ binaryen_bin = shared.options.binaryen_bin binaryen_lib = shared.options.binaryen_lib +# ClusterFuzz's run.py uses these features. Keep this in sync with that, so that +# we only bundle initial content that makes sense for it. +features = [ + '-all', + '--disable-shared-everything', + '--disable-fp16', +] + with tarfile.open(output_file, "w:gz") as tar: # run.py run = os.path.join(shared.options.binaryen_root, 'scripts', 'clusterfuzz', 'run.py') @@ -128,6 +139,40 @@ print(f' ......... : {path}') tar.add(path, arcname=f'lib/{name}') + # Add tests we will use as initial content under initial/. We put all the + # tests from the test suite there. + print(' .. initial content: ') + temp_wasm = 'temp.wasm' + index = 0 + all_tests = shared.get_all_tests() + for i, test in enumerate(all_tests): + if not fuzzing.is_fuzzable(test): + continue + for wast, asserts in support.split_wast(test): + if not wast: + continue + support.write_wast(temp_wasm, wast) + # If the file is not valid for our features, skip it. In the same + # operation, also convert to binary if this was text (binary is more + # compact). + cmd = shared.WASM_OPT + ['-q', temp_wasm, '-o', temp_wasm] + features + if subprocess.run(cmd, stderr=subprocess.PIPE).returncode: + continue + + # Looks good. + tar.add(temp_wasm, arcname=f'initial/{index}.wasm') + index += 1 + print(f'\r {100 * i / len(all_tests):.2f}%', end='', flush=True) + print(f' (num: {index})') + + # Write initial/num.txt which contains the number of testcases in that + # directory (saves run.py from needing to listdir each time). + num_txt = 'num.txt' + with open(num_txt, 'w') as f: + f.write(f'{index}') + tar.add(num_txt, arcname='initial/num.txt') + + print('Done.') print('To run the tests on this bundle, do:') print() diff --git a/scripts/clusterfuzz/extract_wasms.py b/scripts/clusterfuzz/extract_wasms.py index bb727810d75..9f364d7cc34 100644 --- a/scripts/clusterfuzz/extract_wasms.py +++ b/scripts/clusterfuzz/extract_wasms.py @@ -67,7 +67,7 @@ def repl(text): # Replace the wasm files and write them out. -js = re.sub(r'var \w+ = new Uint8Array\(\[([\d,]+)\]\);', repl, js) +js = re.sub(r'var \w+ = new Uint8Array\(\[([\d,]+)\]\)', repl, js) # Write out the new JS. with open(f'{out}.js', 'w') as f: diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index c6e2585cf29..313e01ac734 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -68,6 +68,12 @@ # testcase. JS_SHELL_PATH = os.path.join(ROOT_DIR, 'scripts', 'fuzz_shell.js') +# The path to the directory with initial contents. +INITIAL_CONTENT_PATH = os.path.join(ROOT_DIR, 'initial') + +# The file that contains the number of initial contents +INITIAL_CONTENT_NUM_PATH = os.path.join(ROOT_DIR, 'initial', 'num.txt') + # The arguments we provide to wasm-opt to generate wasm files. FUZZER_ARGS = [ # Generate a wasm from random data. @@ -76,7 +82,8 @@ '--fuzz-passes', # Enable all features but disable ones not yet ready for fuzzing. This may # be a smaller set than fuzz_opt.py, as that enables a few experimental - # flags, while here we just fuzz with d8's --wasm-staging. + # flags, while here we just fuzz with d8's --wasm-staging. This should be + # synchonized with bundle_clusterfuzz. '-all', '--disable-shared-everything', '--disable-fp16', @@ -92,6 +99,17 @@ def get_file_name(prefix, index): # (We also use urandom below, which uses this under the hood.) system_random = random.SystemRandom() +# The number of initial content testcases that were bundled for us, in the +# "initial/" subdir. +with open(INITIAL_CONTENT_NUM_PATH) as f: + num_initial_contents = int(f.read()) + + +def get_random_initial_content(): + index = system_random.randint(0, num_initial_contents - 1) + return os.path.join(INITIAL_CONTENT_PATH, f'{index}.wasm') + + # In production ClusterFuzz we retry whenever we see a wasm-opt error. We are # not looking for wasm-opt issues there, and just use it to generate testcases # for VMs. For local testing, however, we may want to disable retrying, which @@ -117,9 +135,19 @@ def get_wasm_contents(i, output_dir): with open(input_data_file_path, 'wb') as file: file.write(os.urandom(random_size)) - # Generate wasm from the random data. + # Generate a command to use wasm-opt with the proper args to generate + # wasm content from the input data. cmd = [FUZZER_BINARY_PATH] + FUZZER_ARGS cmd += ['-o', wasm_file_path, input_data_file_path] + + # Sometimes use a file from the initial content testcases. + if system_random.random() < 0.5: + initial_content = get_random_initial_content() + cmd += ['--initial-fuzz=' + initial_content] + else: + initial_content = None + + # Generate wasm from the random data. try: subprocess.check_call(cmd) except subprocess.CalledProcessError: @@ -148,7 +176,10 @@ def get_wasm_contents(i, output_dir): # Convert to a string, and wrap into a typed array. wasm_contents = ','.join([str(c) for c in wasm_contents]) - return f'new Uint8Array([{wasm_contents}])' + js = f'new Uint8Array([{wasm_contents}])' + if initial_content: + js = f'{js} /* using initial content {os.path.basename(initial_content)} */' + return js # Returns the contents of a .js fuzz file, given the index of the testcase and diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 5455d632053..497484f6950 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -282,6 +282,15 @@ def test_file_contents(self): seen_calls = [] seen_second_builds = [] seen_JSPIs = [] + seen_initial_contents = [] + + # Initial contents are noted in comments like this: + # + # /* using initial content 42.wasm */ + # + # Note that we may see more than one in a file, as we may have more than + # one wasm in each testcase: each wasm has a chance. + initial_content_regex = re.compile(r'[/][*] using initial content ([^ ]+) [*][/]') for i in range(1, N + 1): fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') @@ -302,6 +311,8 @@ def test_file_contents(self): assert '/* async */' in js assert '/* await */' in js + seen_initial_contents.append(re.findall(initial_content_regex, js)) + # There is always one build and one call (those are in the default # fuzz_shell.js), and we add a couple of operations, each with equal # probability to be a build or a call, so over the 100 testcases here we @@ -346,6 +357,55 @@ def test_file_contents(self): print() + # Flatten the data to help some of the below, from + # [['a.wasm', 'b.wasm'], ['c.wasm']] + # into + # ['a.wasm', 'b.wasm', 'c.wasm'] + flat_initial_contents = [item for items in seen_initial_contents for item in items] + + # Initial content appear 50% of the time for each wasm file. Each + # testcase has 1.333 wasm files on average. + print('Initial contents are distributed as ~ mean 0.68') + print(f'mean initial contents: {len(flat_initial_contents) / N}') + # Initial contents should be mostly unique (we have many, many testcases + # and we pick just 100 or so). And we must see more than one unique one. + unique_initial_contents = set(flat_initial_contents) + print(f'unique initial contents: {len(unique_initial_contents)} should be almost equal to {len(flat_initial_contents)}') + self.assertGreater(len(unique_initial_contents), 1) + # Not all testcases have initial contents. + num_initial_contents = [len(items) for items in seen_initial_contents] + self.assertEqual(min(num_initial_contents), 0) + # Some do (this is redundant given that the set of unique initial + # contents was asserted on before, so this just confirms/checks that). + self.assertGreaterEqual(max(num_initial_contents), 1) + + print() + + # Execute the files in V8. Almost all should execute properly (some + # small number may trap during startup, say on a segment out of bounds). + if shared.V8: + valid_executions = 0 + for i in range(1, N + 1): + fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') + + cmd = [shared.V8, '--wasm-staging', fuzz_file] + proc = subprocess.run(cmd, stdout=subprocess.PIPE) + + # An execution is valid if we exited without error, and if we + # managed to run some code before exiting (modules with no + # exports will be considered "invalid" here, but that is very + # rare, and in a sense they are actually unuseful). + if proc.returncode == 0 and b'[fuzz-exec] calling ' in proc.stdout: + valid_executions += 1 + + print('Valid executions are distributed as ~ mean 0.99') + print(f'mean valid executions: {valid_executions / N}') + # Assert on having at least half execute properly. Given the true mean + # is 0.9, for half of 100 to fail is incredibly unlikely. + self.assertGreater(valid_executions, N / 2) + + print() + # "zzz" in test name so that this runs last. If it runs first, it can be # confusing as it appears next to the logging of which bundle we use (see # setUpClass). From 47195f151ac4a208a0e982b63f6f7695df0f184b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 7 Jan 2025 16:05:32 -0800 Subject: [PATCH 218/622] Model constraints on StringNew children more precisely (#7193) The stringref spec has a bug where the string.new... instructions that take array reference parameters do not have the proper type annotations, which means IRBuilder does not have a single type that it knows the array reference must have. We previously fudged this by using `arrayref` as the type constraint, but the fuzzer was able to generate a valid test case that was parsed into invalid IR due to this imprecision. Fix the bug by adding special constraint types for "any i8 array reference" and "any i16 array reference" just for use with the offending string.new instructions. Fixes #7191. --- src/ir/child-typer.h | 24 +- src/wasm/wasm-ir-builder.cpp | 58 ++++- test/lit/basic/unreachable-string-new.wast | 263 +++++++++++++++++++++ 3 files changed, 326 insertions(+), 19 deletions(-) create mode 100644 test/lit/basic/unreachable-string-new.wast diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 154da0e455c..3d45df32bf1 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -91,6 +91,18 @@ template struct ChildTyper : OverriddenVisitor { self().noteAnyTupleType(childp, arity); } + // Used only for string.new_lossy_utf8_array to work around a missing type + // annotation in the stringref spec. + void noteAnyI8ArrayReferenceType(Expression** childp) { + self().noteAnyI8ArrayReferenceType(childp); + } + + // Used only for string.new_wtf16_array to work around a missing type + // annotation in the stringref spec. + void noteAnyI16ArrayReferenceType(Expression** childp) { + self().noteAnyI16ArrayReferenceType(childp); + } + Type getLabelType(Name label) { return self().getLabelType(label); } void visitNop(Nop* curr) {} @@ -992,15 +1004,15 @@ template struct ChildTyper : OverriddenVisitor { WASM_UNREACHABLE("unexpected op"); } - void visitStringNew(StringNew* curr, - std::optional ht = std::nullopt) { + void visitStringNew(StringNew* curr) { switch (curr->op) { case StringNewLossyUTF8Array: + noteAnyI8ArrayReferenceType(&curr->ref); + note(&curr->start, Type::i32); + note(&curr->end, Type::i32); + return; case StringNewWTF16Array: - if (!ht) { - ht = curr->ref->type.getHeapType(); - } - note(&curr->ref, Type(*ht, Nullable)); + noteAnyI16ArrayReferenceType(&curr->ref); note(&curr->start, Type::i32); note(&curr->end, Type::i32); return; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 2d86f2262c3..f5ffac99a42 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -292,7 +292,16 @@ struct IRBuilder::ChildPopper size_t arity; }; - struct Constraint : std::variant { + struct AnyI8ArrayReference {}; + + struct AnyI16ArrayReference {}; + + struct Constraint : std::variant { std::optional getSubtype() const { if (auto* subtype = std::get_if(this)) { return subtype->bound; @@ -301,6 +310,12 @@ struct IRBuilder::ChildPopper } bool isAnyType() const { return std::get_if(this); } bool isAnyReference() const { return std::get_if(this); } + bool isAnyI8ArrayReference() const { + return std::get_if(this); + } + bool isAnyI16ArrayReference() const { + return std::get_if(this); + } std::optional getAnyTuple() const { if (auto* tuple = std::get_if(this)) { return tuple->arity; @@ -356,6 +371,14 @@ struct IRBuilder::ChildPopper children.push_back({childp, {AnyTuple{arity}}}); } + void noteAnyI8ArrayReferenceType(Expression** childp) { + children.push_back({childp, {AnyI8ArrayReference{}}}); + } + + void noteAnyI16ArrayReferenceType(Expression** childp) { + children.push_back({childp, {AnyI16ArrayReference{}}}); + } + Type getLabelType(Name label) { WASM_UNREACHABLE("labels should be explicitly provided"); }; @@ -455,6 +478,26 @@ struct IRBuilder::ChildPopper needUnreachableFallback = true; break; } + } else if (constraint.isAnyI8ArrayReference()) { + bool isI8Array = + type.isRef() && type.getHeapType().isArray() && + type.getHeapType().getArray().element.packedType == Field::i8; + bool isNone = + type.isRef() && type.getHeapType().isMaybeShared(HeapType::none); + if (!isI8Array && !isNone && type != Type::unreachable) { + needUnreachableFallback = true; + break; + } + } else if (constraint.isAnyI16ArrayReference()) { + bool isI16Array = + type.isRef() && type.getHeapType().isArray() && + type.getHeapType().getArray().element.packedType == Field::i16; + bool isNone = + type.isRef() && type.getHeapType().isMaybeShared(HeapType::none); + if (!isI16Array && !isNone && type != Type::unreachable) { + needUnreachableFallback = true; + break; + } } else { WASM_UNREACHABLE("unexpected constraint"); } @@ -601,13 +644,6 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } - Result<> visitStringNew(StringNew* curr, - std::optional ht = std::nullopt) { - std::vector children; - ConstraintCollector{builder, children}.visitStringNew(curr, ht); - return popConstrainedChildren(children); - } - Result<> visitStringEncode(StringEncode* curr, std::optional ht = std::nullopt) { std::vector children; @@ -1951,11 +1987,7 @@ Result<> IRBuilder::makeStringNew(StringNewOp op) { push(builder.makeStringNew(op, curr.ref)); return Ok{}; } - // There's no type annotation on these instructions due to a bug in the - // stringref proposal, so we just fudge it and pass `array` instead of a - // defined heap type. This will allow us to pop a child with an invalid - // array type, but that's just too bad. - CHECK_ERR(ChildPopper{*this}.visitStringNew(&curr, HeapType::array)); + CHECK_ERR(visitStringNew(&curr)); push(builder.makeStringNew(op, curr.ref, curr.start, curr.end)); return Ok{}; } diff --git a/test/lit/basic/unreachable-string-new.wast b/test/lit/basic/unreachable-string-new.wast new file mode 100644 index 00000000000..89310e20c6c --- /dev/null +++ b/test/lit/basic/unreachable-string-new.wast @@ -0,0 +1,263 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt -all --preserve-type-order %s -S -o - | filecheck %s + +(module + ;; CHECK: (type $array8 (array i8)) + (type $array8 (array i8)) + ;; CHECK: (type $array16 (array i16)) + (type $array16 (array i16)) + ;; CHECK: (type $shared-array8 (shared (array i8))) + (type $shared-array8 (shared (array i8))) + ;; CHECK: (type $shared-array16 (shared (array i16))) + (type $shared-array16 (shared (array i16))) + + ;; CHECK: (func $i8-bad-array (type $4) (result (ref string)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref array)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (string.new_lossy_utf8_array + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i8-bad-array (result (ref string)) + ;; This is not a specific array type, so it must not be popped as the first + ;; child of the string.new. + block (result (ref array)) + unreachable + end + i32.const 0 + unreachable + string.new_lossy_utf8_array + ) + + ;; CHECK: (func $i8-bad-array16 (type $4) (result (ref string)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $array16)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (string.new_lossy_utf8_array + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i8-bad-array16 (result (ref string)) + ;; As above, with an array with the wrong element type. + block (result (ref $array16)) + unreachable + end + i32.const 0 + unreachable + string.new_lossy_utf8_array + ) + + ;; CHECK: (func $i8-ok (type $4) (result (ref string)) + ;; CHECK-NEXT: (string.new_lossy_utf8_array + ;; CHECK-NEXT: (block (result (ref $array8)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i8-ok (result (ref string)) + ;; Now we have a valid type, so it can be popped as the first child of the + ;; string.new. + block (result (ref $array8)) + unreachable + end + i32.const 0 + unreachable + string.new_lossy_utf8_array + ) + + ;; CHECK: (func $i8-ok-none (type $4) (result (ref string)) + ;; CHECK-NEXT: (string.new_lossy_utf8_array + ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i8-ok-none (result (ref string)) + ;; Due to subsumption, bottom references must also be valid. + block (result (ref none)) + unreachable + end + i32.const 0 + unreachable + string.new_lossy_utf8_array + ) + + ;; CHECK: (func $i8-ok-shared (type $4) (result (ref string)) + ;; CHECK-NEXT: (string.new_lossy_utf8_array + ;; CHECK-NEXT: (block (result (ref $shared-array8)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i8-ok-shared (result (ref string)) + ;; Shared arrays work as well. TODO: Should the result be shared in this + ;; case? + block (result (ref $shared-array8)) + unreachable + end + i32.const 0 + unreachable + string.new_lossy_utf8_array + ) + + ;; CHECK: (func $i8-ok-shared-none (type $4) (result (ref string)) + ;; CHECK-NEXT: (string.new_lossy_utf8_array + ;; CHECK-NEXT: (block (result (ref (shared none))) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i8-ok-shared-none (result (ref string)) + ;; Shared bottom references are also ok. + block (result (ref (shared none))) + unreachable + end + i32.const 0 + unreachable + string.new_lossy_utf8_array + ) + + ;; CHECK: (func $i16-bad-array (type $4) (result (ref string)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref array)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i16-bad-array (result (ref string)) + block (result (ref array)) + unreachable + end + i32.const 0 + unreachable + string.new_wtf16_array + ) + + ;; CHECK: (func $i16-bad-array8 (type $4) (result (ref string)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $array8)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i16-bad-array8 (result (ref string)) + block (result (ref $array8)) + unreachable + end + i32.const 0 + unreachable + string.new_wtf16_array + ) + + ;; CHECK: (func $i16-ok (type $4) (result (ref string)) + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (block (result (ref $array16)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i16-ok (result (ref string)) + block (result (ref $array16)) + unreachable + end + i32.const 0 + unreachable + string.new_wtf16_array + ) + + ;; CHECK: (func $i16-ok-none (type $4) (result (ref string)) + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i16-ok-none (result (ref string)) + block (result (ref none)) + unreachable + end + i32.const 0 + unreachable + string.new_wtf16_array + ) + + ;; CHECK: (func $i16-ok-shared (type $4) (result (ref string)) + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (block (result (ref $shared-array16)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i16-ok-shared (result (ref string)) + block (result (ref $shared-array16)) + unreachable + end + i32.const 0 + unreachable + string.new_wtf16_array + ) + + ;; CHECK: (func $i16-ok-shared-none (type $4) (result (ref string)) + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (block (result (ref (shared none))) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i16-ok-shared-none (result (ref string)) + block (result (ref (shared none))) + unreachable + end + i32.const 0 + unreachable + string.new_wtf16_array + ) +) From 6804a90b2defb2fece7bb12087080fb1bee64409 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 7 Jan 2025 17:14:28 -0800 Subject: [PATCH 219/622] [NFC] Ignore MemoryPacking warning in ClusterFuzz test (#7195) Noticed by chance in #7193 --- test/unit/test_cluster_fuzz.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 497484f6950..cb5f7b81011 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -106,6 +106,8 @@ def test_run_py(self): SAFE_WARNINGS = [ # When we randomly pick no passes to run, this is shown. 'warning: no passes specified, not doing any work', + # MemoryPacking warns on some things. + 'warning: active memory segments have overlap, which prevents some optimizations.', ] stderr = proc.stderr for safe in SAFE_WARNINGS: From e2764536b9a7c0dd2c3c2fbde17a03c7296b34fb Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Wed, 8 Jan 2025 09:36:49 -0800 Subject: [PATCH 220/622] Make RemoveMemory pass also remove the start function (#7196) Also rename it to RemoveMemoryInit to make it more clear what it's for. For now, keep the legacy pass name, to be deleted after rolling into Emscripten. --- src/passes/CMakeLists.txt | 2 +- ...{RemoveMemory.cpp => RemoveMemoryInit.cpp} | 13 +++++++++---- src/passes/pass.cpp | 8 ++++++-- src/passes/passes.h | 2 +- src/tools/wasm-reduce.cpp | 2 +- test/lit/help/wasm-metadce.test | 5 ++++- test/lit/help/wasm-opt.test | 5 ++++- test/lit/help/wasm2js.test | 5 ++++- test/lit/passes/remove-memory-init.wast | 19 +++++++++++++++++++ test/passes/remove-memory.txt | 3 --- test/passes/remove-memory.wast | 5 ----- 11 files changed, 49 insertions(+), 20 deletions(-) rename src/passes/{RemoveMemory.cpp => RemoveMemoryInit.cpp} (68%) create mode 100644 test/lit/passes/remove-memory-init.wast delete mode 100644 test/passes/remove-memory.txt delete mode 100644 test/passes/remove-memory.wast diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 61c1b2f4610..ed816ba09d4 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -102,7 +102,7 @@ set(passes_SOURCES TraceCalls.cpp RedundantSetElimination.cpp RemoveImports.cpp - RemoveMemory.cpp + RemoveMemoryInit.cpp RemoveNonJSOps.cpp RemoveUnusedBrs.cpp RemoveUnusedNames.cpp diff --git a/src/passes/RemoveMemory.cpp b/src/passes/RemoveMemoryInit.cpp similarity index 68% rename from src/passes/RemoveMemory.cpp rename to src/passes/RemoveMemoryInit.cpp index 1520be1e8db..6c4cc78c67a 100644 --- a/src/passes/RemoveMemory.cpp +++ b/src/passes/RemoveMemoryInit.cpp @@ -15,20 +15,25 @@ */ // -// Removeds memory segments, leaving only code in the module. -// +// Removes memory segments, leaving only code in the module. It also removes +// the start function, which (in LLVM use cases) is only used for initializing +// the memory. #include #include namespace wasm { -struct RemoveMemory : public Pass { +struct RemoveMemoryInit : public Pass { void run(Module* module) override { module->removeDataSegments([&](DataSegment* curr) { return true; }); + if (module->start) { + module->removeFunction(module->start); + module->start = Name(); + } } }; -Pass* createRemoveMemoryPass() { return new RemoveMemory(); } +Pass* createRemoveMemoryInitPass() { return new RemoveMemoryInit(); } } // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index ab95300d0cb..5e690790bb4 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -411,8 +411,12 @@ void PassRegistry::registerPasses() { registerPass("remove-imports", "removes imports and replaces them with nops", createRemoveImportsPass); - registerPass( - "remove-memory", "removes memory segments", createRemoveMemoryPass); + registerPass("remove-memory-init", + "removes memory initialization", + createRemoveMemoryInitPass); + registerPass("remove-memory", + "removes memory init (legacy name)", + createRemoveMemoryInitPass); registerPass("remove-unused-brs", "removes breaks from locations that are not needed", createRemoveUnusedBrsPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index b313b343165..81d7cdcc7ff 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -132,7 +132,7 @@ Pass* createPrintFunctionMapPass(); Pass* createPropagateGlobalsGloballyPass(); Pass* createRemoveNonJSOpsPass(); Pass* createRemoveImportsPass(); -Pass* createRemoveMemoryPass(); +Pass* createRemoveMemoryInitPass(); Pass* createRemoveUnusedBrsPass(); Pass* createRemoveUnusedModuleElementsPass(); Pass* createRemoveUnusedNonFunctionModuleElementsPass(); diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 6613c8899bf..6b89103a3ac 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -294,7 +294,7 @@ struct Reducer "--optimize-instructions", "--precompute", "--remove-imports", - "--remove-memory", + "--remove-memory-init", "--remove-unused-names --remove-unused-brs", "--remove-unused-module-elements", "--remove-unused-nonfunction-module-elements", diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 0be234ac791..fa68453a76d 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -388,7 +388,10 @@ ;; CHECK-NEXT: --remove-imports removes imports and replaces ;; CHECK-NEXT: them with nops ;; CHECK-NEXT: -;; CHECK-NEXT: --remove-memory removes memory segments +;; CHECK-NEXT: --remove-memory removes memory init (legacy +;; CHECK-NEXT: name) +;; CHECK-NEXT: +;; CHECK-NEXT: --remove-memory-init removes memory initialization ;; CHECK-NEXT: ;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible ;; CHECK-NEXT: with js diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 630a64588a1..49130d2a9eb 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -397,7 +397,10 @@ ;; CHECK-NEXT: --remove-imports removes imports and replaces ;; CHECK-NEXT: them with nops ;; CHECK-NEXT: -;; CHECK-NEXT: --remove-memory removes memory segments +;; CHECK-NEXT: --remove-memory removes memory init (legacy +;; CHECK-NEXT: name) +;; CHECK-NEXT: +;; CHECK-NEXT: --remove-memory-init removes memory initialization ;; CHECK-NEXT: ;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible ;; CHECK-NEXT: with js diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index a326c75dbe9..34a42d2f4a6 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -351,7 +351,10 @@ ;; CHECK-NEXT: --remove-imports removes imports and replaces ;; CHECK-NEXT: them with nops ;; CHECK-NEXT: -;; CHECK-NEXT: --remove-memory removes memory segments +;; CHECK-NEXT: --remove-memory removes memory init (legacy +;; CHECK-NEXT: name) +;; CHECK-NEXT: +;; CHECK-NEXT: --remove-memory-init removes memory initialization ;; CHECK-NEXT: ;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible ;; CHECK-NEXT: with js diff --git a/test/lit/passes/remove-memory-init.wast b/test/lit/passes/remove-memory-init.wast new file mode 100644 index 00000000000..dd746ff8b58 --- /dev/null +++ b/test/lit/passes/remove-memory-init.wast @@ -0,0 +1,19 @@ +;; RUN: wasm-opt %s --remove-memory-init -all -S -o - | filecheck %s + +(module + (type $0 (func)) + ;; CHECK: (memory $mem 2) + (memory $mem 2) + (data (memory $mem) (i32.const 0) "Hello, world\00") + (func $__wasm_init_memory (type $0) + (memory.fill 0 + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + (start $__wasm_init_memory) +) + +;; CHECK-NOT: data +;; CHECK-NOT: __wasm_init_memory diff --git a/test/passes/remove-memory.txt b/test/passes/remove-memory.txt deleted file mode 100644 index ddfdde49329..00000000000 --- a/test/passes/remove-memory.txt +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory $mem 1024 1024) -) diff --git a/test/passes/remove-memory.wast b/test/passes/remove-memory.wast deleted file mode 100644 index 26b4949a81f..00000000000 --- a/test/passes/remove-memory.wast +++ /dev/null @@ -1,5 +0,0 @@ -(module - (memory $mem 1024 1024) - (data (memory $mem) (i32.const 10) "123") - (data (memory $mem) (i32.const 20) "149") -) From 5a17afca6bd66387e2c365c547a39c025e70cefd Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 8 Jan 2025 09:54:41 -0800 Subject: [PATCH 221/622] Validate features used by block return types (#7197) We previously checked only for the multivalue feature when used by blocks, but now check for all used features. This will fix a current fuzzer error where the new unreachable-string-new.wast test validates with shared-everything disabled even though it uses shared heap types, allowing the fuzzer to use those shared types in other places that are checked by the validator. --- src/wasm/wasm-validator.cpp | 10 +++++----- test/unit/test_features.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 4f9976bdb0b..faae35472a2 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -675,11 +675,11 @@ void FunctionValidator::validatePoppyExpression(Expression* curr) { } void FunctionValidator::visitBlock(Block* curr) { - if (!getModule()->features.hasMultivalue()) { - shouldBeTrue( - !curr->type.isTuple(), - curr, - "Multivalue block type require multivalue [--enable-multivalue]"); + auto feats = curr->type.getFeatures(); + if (!shouldBeTrue(feats <= getModule()->features, + curr, + "Block type requires additional features")) { + getStream() << getMissingFeaturesList(*getModule(), feats) << '\n'; } // if we are break'ed to, then the value must be right for us if (curr->name.is()) { diff --git a/test/unit/test_features.py b/test/unit/test_features.py index c115719e7c8..1b91eb255f8 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -252,7 +252,7 @@ def test_multivalue_block(self): ) ) ''' - self.check_multivalue(module, 'Multivalue block type require multivalue [--enable-multivalue]') + self.check_multivalue(module, 'Block type requires additional features') def test_i31_global(self): module = ''' From 5dc0e8ca7dd15af9934c2c223a5faa47b1eaf03d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 8 Jan 2025 16:51:03 -0800 Subject: [PATCH 222/622] Fuzzing: Ignore differences in names that wasm2js fixes up (#7200) wasm2js renames exports to js-valid names. That difference can be noticeable when we compare it to the original names, so fix that up. This is needed for #7198: that PR adds more invokes, including invokes of imports, whose names can be modified by wasm2js. --- scripts/fuzz_opt.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index e4265e7f011..5396d435b02 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1086,6 +1086,10 @@ def fix_output_for_js(x): # fuzzer can notice. x = re.sub(r' null', ' 0', x) + # wasm2js converts exports to valid JS forms, which affects some of + # the names in the test suite. Fix those up. + x = x.replace('log-', 'log_') + # check if a number is 0 or a subnormal, which is basically zero def is_basically_zero(x): # to check if something is a subnormal, compare it to the largest one From 3b838f70d637bb89cc0218d26429cb192da0c615 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 8 Jan 2025 16:51:17 -0800 Subject: [PATCH 223/622] [NFC] Remove some dead code in wasm2js (#7201) This code wrote to `export_name` but it was never used. I guess we forgot to remove it when we updated the name mangling there. --- src/wasm2js.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/wasm2js.h b/src/wasm2js.h index 7ed089448e2..6aa718e3917 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2772,14 +2772,6 @@ void Wasm2JSGlue::emitPostES6() { default: continue; } - std::ostringstream export_name; - for (char c : exp->name.str) { - if (c == '-') { - export_name << '_'; - } else { - export_name << c; - } - } out << "export var " << asmangle(exp->name.toString()) << " = ret" << moduleName << "." << asmangle(exp->name.toString()) << ";\n"; } From 666c9df201c82aa4d718de8e8c85fd8f7aa1c1f8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 8 Jan 2025 16:51:57 -0800 Subject: [PATCH 224/622] Fuzzer: Add invokes for initial functions (#7198) This adds more of a chance to call code from initial content. This includes invokes of function imports, which we didn't do before. --- src/tools/fuzzing/fuzzing.cpp | 14 ++++ test/passes/fuzz_metrics_noprint.bin.txt | 54 ++++++------- .../fuzz_metrics_passes_noprint.bin.txt | 54 ++++++------- ...e-to-fuzz_all-features_metrics_noprint.txt | 80 +++++++++---------- 4 files changed, 108 insertions(+), 94 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 6e4807af8d3..460a4e21775 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1648,6 +1648,20 @@ void TranslateToFuzzReader::modifyInitialFunctions() { // them. } } + + // Add invocations, which can help execute the code here even if the function + // was not exported (or was exported but with a signature that traps + // immediately, like receiving a non-nullable ref, that the fuzzer can't + // provide from JS). Note we need to use a temp vector for iteration, as + // addInvocations modifies wasm.functions. + std::vector funcs; + for (auto& func : wasm.functions) { + funcs.push_back(func.get()); + } + for (auto* func : funcs) { + addInvocations(func); + } + // Remove a start function - the fuzzing harness expects code to run only // from exports. wasm.start = Name(); diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index bf7e517da49..e1ade8ab17c 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 46 - [funcs] : 68 + [exports] : 65 + [funcs] : 85 [globals] : 18 [imports] : 4 [memories] : 1 [memory-data] : 24 - [table-data] : 22 + [table-data] : 24 [tables] : 1 [tags] : 0 - [total] : 9465 - [vars] : 215 - Binary : 671 - Block : 1531 - Break : 370 - Call : 366 - CallIndirect : 67 - Const : 1478 - Drop : 111 - GlobalGet : 766 - GlobalSet : 558 - If : 514 - Load : 173 - LocalGet : 729 - LocalSet : 550 - Loop : 202 - Nop : 133 - RefFunc : 22 - Return : 99 - Select : 84 - Store : 83 - Switch : 2 - Unary : 682 - Unreachable : 274 + [total] : 8396 + [vars] : 239 + Binary : 573 + Block : 1420 + Break : 276 + Call : 382 + CallIndirect : 56 + Const : 1258 + Drop : 124 + GlobalGet : 731 + GlobalSet : 531 + If : 468 + Load : 129 + LocalGet : 578 + LocalSet : 422 + Loop : 173 + Nop : 134 + RefFunc : 24 + Return : 122 + Select : 58 + Store : 57 + Switch : 6 + Unary : 605 + Unreachable : 269 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index 814fdb1323d..ce183485c1e 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 43 - [funcs] : 56 + [exports] : 84 + [funcs] : 112 [globals] : 17 [imports] : 4 [memories] : 1 [memory-data] : 11 - [table-data] : 16 + [table-data] : 36 [tables] : 1 [tags] : 0 - [total] : 10611 - [vars] : 184 - Binary : 754 - Block : 1699 - Break : 397 - Call : 325 - CallIndirect : 112 - Const : 1783 - Drop : 101 - GlobalGet : 869 - GlobalSet : 657 - If : 549 - Load : 195 - LocalGet : 893 - LocalSet : 609 - Loop : 251 - Nop : 123 - RefFunc : 16 - Return : 78 - Select : 74 - Store : 84 - Switch : 3 - Unary : 730 - Unreachable : 309 + [total] : 7833 + [vars] : 292 + Binary : 578 + Block : 1340 + Break : 204 + Call : 414 + CallIndirect : 35 + Const : 1256 + Drop : 107 + GlobalGet : 698 + GlobalSet : 535 + If : 418 + Load : 134 + LocalGet : 502 + LocalSet : 331 + Loop : 149 + Nop : 87 + RefFunc : 36 + Return : 103 + Select : 49 + Store : 70 + Switch : 6 + Unary : 508 + Unreachable : 273 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 3c2b7bfa285..a702187b204 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,7 +1,7 @@ Metrics total - [exports] : 3 - [funcs] : 3 + [exports] : 10 + [funcs] : 9 [globals] : 4 [imports] : 8 [memories] : 1 @@ -9,44 +9,44 @@ total [table-data] : 0 [tables] : 1 [tags] : 1 - [total] : 630 - [vars] : 23 - ArrayNewFixed : 3 - AtomicCmpxchg : 1 - AtomicFence : 1 - AtomicNotify : 1 - Binary : 63 - Block : 60 - BrOn : 3 - Break : 8 - Call : 4 - CallRef : 3 - Const : 129 - DataDrop : 1 - Drop : 8 - GlobalGet : 21 - GlobalSet : 20 - I31Get : 1 - If : 12 + [total] : 553 + [vars] : 38 + ArrayCopy : 2 + ArrayLen : 4 + ArrayNew : 1 + ArrayNewFixed : 7 + Binary : 69 + Block : 55 + BrOn : 1 + Break : 1 + Call : 23 + Const : 107 + Drop : 7 + GlobalGet : 19 + GlobalSet : 18 + If : 17 Load : 17 - LocalGet : 74 - LocalSet : 52 - Loop : 8 - MemoryFill : 1 - Nop : 4 - RefAs : 19 - RefFunc : 26 - RefI31 : 1 - RefIsNull : 1 - RefNull : 11 - Return : 2 - Select : 5 + LocalGet : 66 + LocalSet : 39 + Loop : 1 + Nop : 3 + RefAs : 13 + RefFunc : 9 + RefI31 : 2 + RefIsNull : 2 + RefNull : 7 + RefTest : 1 + Return : 5 + SIMDReplace : 1 + Select : 3 + Store : 1 + StringConst : 3 StringEncode : 1 - StructGet : 3 - StructNew : 26 - Try : 1 - TryTable : 6 + StringMeasure : 1 + StructGet : 1 + StructNew : 15 + StructSet : 2 TupleExtract : 1 - TupleMake : 2 - Unary : 20 - Unreachable : 10 + TupleMake : 3 + Unary : 16 + Unreachable : 9 From b41327ebe761efffe8ac0b9ba3308fdceb6b96d0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 9 Jan 2025 08:58:29 -0800 Subject: [PATCH 225/622] Fuzzer: Shuffle exports (#7199) This adds more variety in some situations. --- src/tools/fuzzing.h | 1 + src/tools/fuzzing/fuzzing.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index f76ed62a5bc..b878b0ae615 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -233,6 +233,7 @@ class TranslateToFuzzReader { void addTag(); void finalizeMemory(); void finalizeTable(); + void shuffleExports(); void prepareHangLimitSupport(); void addHangLimitSupport(); void addImportLoggingSupport(); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 460a4e21775..b3c9497f996 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -332,6 +332,7 @@ void TranslateToFuzzReader::build() { addHashMemorySupport(); } finalizeTable(); + shuffleExports(); } void TranslateToFuzzReader::setupMemory() { @@ -746,6 +747,37 @@ void TranslateToFuzzReader::finalizeTable() { } } +void TranslateToFuzzReader::shuffleExports() { + // Randomly ordering the exports is useful for a few reasons. First, initial + // content may have a natural order in which to execute things (an "init" + // export first, for example), and changing that order may lead to very + // different execution. Second, even in the fuzzer's own random content there + // is a "direction", since we generate as we go (e.g. no function calls a + // later function that does not exist yet / will be created later), and also + // we emit invokes for a function right after it (so we end up calling the + // same code several times in succession, but interleaving it with others may + // find more things). But we also keep a good chance for the natural order + // here, as it may help some initial content. + if (wasm.exports.empty() || oneIn(2)) { + return; + } + + // Sort the exports in the simple Fisher-Yates manner. + // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm + for (Index i = 0; i < wasm.exports.size() - 1; i++) { + // Pick the index of the item to place at index |i|. The number of items to + // pick from begins at the full length, then decreases with i. + auto j = i + upTo(wasm.exports.size() - i); + + // Swap the item over here. + if (j != i) { + std::swap(wasm.exports[i], wasm.exports[j]); + } + } + + wasm.updateMaps(); +} + void TranslateToFuzzReader::prepareHangLimitSupport() { HANG_LIMIT_GLOBAL = Names::getValidGlobalName(wasm, "hangLimit"); } From 1c3876658a600ce2628a6e8eb5cf4e6d3da55322 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 9 Jan 2025 14:10:18 -0800 Subject: [PATCH 226/622] wasm2js fuzzing: Fix up call-* import names (#7203) call-*, call-export-* are needed for the same reasons as log-*. --- scripts/fuzz_opt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 5396d435b02..adbb6ac83a2 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1089,6 +1089,8 @@ def fix_output_for_js(x): # wasm2js converts exports to valid JS forms, which affects some of # the names in the test suite. Fix those up. x = x.replace('log-', 'log_') + x = x.replace('call-', 'call_') + x = x.replace('export-', 'export_') # check if a number is 0 or a subnormal, which is basically zero def is_basically_zero(x): From 9dfd3d9f5e992f5c9117649261129e28c7a2e682 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jan 2025 09:01:22 -0800 Subject: [PATCH 227/622] Fuzzing: Fix fuzz_shell.js on exports whose name is an integer (#7205) JS sorts such indexes to the start of wasm.exports, but we want the order in the wasm, so get the proper ordering using WebAssembly.Module.exports. --- scripts/fuzz_shell.js | 11 ++++++-- scripts/wasm2js.js | 18 ++++++++++++ test/lit/node/fuzz_shell_numeric_export.wast | 29 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/lit/node/fuzz_shell_numeric_export.wast diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 3aa9013e623..d96a9d60fdc 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -350,13 +350,20 @@ function build(binary) { quit(); } - // Update the exports. Note that this adds onto |exports|, |exportList|, + // Update the exports. Note that this adds onto |exports| and |exportList|, // which is intentional: if we build another wasm, or build this one more // than once, we want to be able to call them all, so we unify all their // exports. (We do trample in |exports| when keys are equal - basically this // is a single global namespace - but |exportList| is appended to, so we do // keep the ability to call anything that was ever exported.) - for (var key in instance.exports) { + // + // Note we do not iterate on instance.exports: the order there is not + // necessarily the order in the wasm (which is what wasm-opt --fuzz-exec uses, + // for example), because of JS object iteration rules (numbers are considered + // "array indexes" and appear first, + // https://tc39.es/ecma262/#sec-ordinaryownpropertykeys). + for (var e of WebAssembly.Module.exports(module)) { + var key = e.name; var value = instance.exports[key]; value = wrapExportForJSPI(value); exports[key] = value; diff --git a/scripts/wasm2js.js b/scripts/wasm2js.js index d76e37280f7..2a09eb91221 100644 --- a/scripts/wasm2js.js +++ b/scripts/wasm2js.js @@ -67,6 +67,15 @@ var WebAssembly = { info['env']['__tempMemory__'] = 0; // risky! // This will be replaced by the actual wasm2js code. var exports = instantiate(info, wasmMemory); + + // Save the exports on the module object, so that + // WebAssembly.Module.exports() works. Ideally we would do this in + // WebAssembly.Module(), but we don't know the exports at that point - all + // the actual work happens in Instance(), here. In practice this is enough + // as calls to WebAssembly.Module.exports() from the fuzzer happen after + // instantiation. + module.exports = exports; + return { 'exports': exports }; @@ -83,6 +92,15 @@ var WebAssembly = { } }; +// Add the exports() API on top of the Module constructor. +WebAssembly.Module.exports = (module) => { + var ret = []; + for (var key in module.exports) { + ret.push({ name: key }); + } + return ret; +}; + var tempRet0 = 0; var env = { diff --git a/test/lit/node/fuzz_shell_numeric_export.wast b/test/lit/node/fuzz_shell_numeric_export.wast new file mode 100644 index 00000000000..4072bef3468 --- /dev/null +++ b/test/lit/node/fuzz_shell_numeric_export.wast @@ -0,0 +1,29 @@ +;; Test that numeric exports appear in the right position. The JS rules +;; affecting the order of instance.exports can be confusing, and we want the +;; actual order in the wasm. + +(module + (func $a (export "a") (result i32) + (i32.const 10) + ) + + (func $0 (export "0") (result i32) + (i32.const 20) + ) + + (func $c (export "c") (result i32) + (i32.const 30) + ) +) + +;; Run in wasm-opt and in node to see they agree. +;; RUN: wasm-opt %s -o %t.wasm -q --fuzz-exec-before | filecheck %s +;; RUN: node %S/../../../scripts/fuzz_shell.js %t.wasm | filecheck %s +;; +;; CHECK: [fuzz-exec] calling a +;; CHECK: [fuzz-exec] note result: a => 10 +;; CHECK: [fuzz-exec] calling 0 +;; CHECK: [fuzz-exec] note result: 0 => 20 +;; CHECK: [fuzz-exec] calling c +;; CHECK: [fuzz-exec] note result: c => 30 + From 444682c6c5738e25c408e794123659a9e09d29aa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jan 2025 09:01:43 -0800 Subject: [PATCH 228/622] Fuzzing: Allow running wasm exports in different orders in fuzz_shell.js (#7204) Normally fuzz_shell.js runs the exports in the natural order, when callExports is called. There is benefit to running them in different orders, just to get more variety at runtime. To allow that, it now receives a random seed that, if provided, it uses to determine the order at runtime. This is primarily useful for ClusterFuzz, which adds more executions of callExports. We now add such a seed to those. --- scripts/clusterfuzz/run.py | 17 ++++++--- scripts/fuzz_shell.js | 37 +++++++++++++++++-- test/lit/node/fuzz_shell_orders.wast | 53 ++++++++++++++++++++++++++++ test/unit/test_cluster_fuzz.py | 45 +++++++++++++++++++---- 4 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 test/lit/node/fuzz_shell_orders.wast diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index 313e01ac734..ccde7435955 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -228,17 +228,24 @@ def get_js_file_contents(i, output_dir): extra_js_operations = [ # Compile and link the wasm again. Each link adds more to the total # exports that we can call. - 'build(binary);\n', - # Run all the exports we've accumulated. - 'callExports();\n', + 'build(binary)', + # Run all the exports we've accumulated. This is a placeholder, as we + # must pick a random seed for each (the placeholder would cause a JS + # error at runtime if we had a bug and did not replace it properly). + 'CALL_EXPORTS', ] if has_second: extra_js_operations += [ - 'build(secondBinary);\n', + 'build(secondBinary)', ] for i in range(num): - js += system_random.choice(extra_js_operations) + choice = system_random.choice(extra_js_operations) + if choice == 'CALL_EXPORTS': + # The random seed can be any unsigned 32-bit number. + seed = system_random.randint(0, 0xffffffff) + choice = f'callExports({seed})' + js += choice + ';\n' print(f'Created {bytes} wasm bytes') diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index d96a9d60fdc..2c7681cb501 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -371,12 +371,41 @@ function build(binary) { } } -// Run the code by calling exports. -/* async */ function callExports() { +// Simple deterministic hashing, on an unsigned 32-bit seed. See e.g. +// https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine +function hashCombine(seed, value) { + seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >>> 2); + return seed >>> 0; +} + +// Run the code by calling exports. The optional |ordering| parameter indicates +// howe we should order the calls to the exports: if it is not provided, we call +// them in the natural order, which allows our output to be compared to other +// executions of the wasm (e.g. from wasm-opt --fuzz-exec). If |ordering| is +// provided, it is a random seed we use to make deterministic choices on +// the order of calls. +/* async */ function callExports(ordering) { // Call the exports we were told, or if we were not given an explicit list, // call them all. var relevantExports = exportsToCall || exportList; + if (ordering !== undefined) { + // Copy the list, and sort it in the simple Fisher-Yates manner. + // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm + relevantExports = relevantExports.slice(0); + for (var i = 0; i < relevantExports.length - 1; i++) { + // Pick the index of the item to place at index |i|. + ordering = hashCombine(ordering, i); + // The number of items to pick from begins at the full length, then + // decreases with i. + var j = i + (ordering % (relevantExports.length - i)); + // Swap the item over here. + var t = relevantExports[j]; + relevantExports[j] = relevantExports[i]; + relevantExports[i] = t; + } + } + for (var e of relevantExports) { var name, value; if (typeof e === 'string') { @@ -384,7 +413,7 @@ function build(binary) { name = e; value = exports[e]; } else { - // We are given an object form exportList, which bas both a name and a + // We are given an object form exportList, which has both a name and a // value. name = e.name; value = e.value; @@ -396,6 +425,8 @@ function build(binary) { try { console.log('[fuzz-exec] calling ' + name); + // TODO: Based on |ordering|, do not always await, leaving a promise + // for later, so we interleave stacks. var result = /* await */ callFunc(value); if (typeof result !== 'undefined') { console.log('[fuzz-exec] note result: ' + name + ' => ' + printed(result)); diff --git a/test/lit/node/fuzz_shell_orders.wast b/test/lit/node/fuzz_shell_orders.wast new file mode 100644 index 00000000000..6d51c4e1b2c --- /dev/null +++ b/test/lit/node/fuzz_shell_orders.wast @@ -0,0 +1,53 @@ +;; Test that appending a run operation with a seed can lead to a different +;; order of export calls. + +(module + (import "fuzzing-support" "log-i32" (func $log (param i32))) + + (func $a (export "a") (result i32) + (i32.const 10) + ) + + (func $b (export "b") (result i32) + (i32.const 20) + ) + + (func $c (export "c") (result i32) + (i32.const 30) + ) +) + +;; Run normally: we should see a,b,c called in order. +;; +;; RUN: wasm-opt %s -o %t.wasm -q +;; RUN: node %S/../../../scripts/fuzz_shell.js %t.wasm | filecheck %s +;; +;; CHECK: [fuzz-exec] calling a +;; CHECK: [fuzz-exec] note result: a => 10 +;; CHECK: [fuzz-exec] calling b +;; CHECK: [fuzz-exec] note result: b => 20 +;; CHECK: [fuzz-exec] calling c +;; CHECK: [fuzz-exec] note result: c => 30 + +;; Append another run with a seed that leads to a different order +;; +;; RUN: cp %S/../../../scripts/fuzz_shell.js %t.js +;; RUN: echo "callExports(1337);" >> %t.js +;; RUN: node %t.js %t.wasm | filecheck %s --check-prefix=APPENDED +;; +;; The original order: a,b,c +;; APPENDED: [fuzz-exec] calling a +;; APPENDED: [fuzz-exec] note result: a => 10 +;; APPENDED: [fuzz-exec] calling b +;; APPENDED: [fuzz-exec] note result: b => 20 +;; APPENDED: [fuzz-exec] calling c +;; APPENDED: [fuzz-exec] note result: c => 30 + +;; A new order: b,c,a +;; APPENDED: [fuzz-exec] calling b +;; APPENDED: [fuzz-exec] note result: b => 20 +;; APPENDED: [fuzz-exec] calling c +;; APPENDED: [fuzz-exec] note result: c => 30 +;; APPENDED: [fuzz-exec] calling a +;; APPENDED: [fuzz-exec] note result: a => 10 + diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index cb5f7b81011..72197939d76 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -294,12 +294,19 @@ def test_file_contents(self): # one wasm in each testcase: each wasm has a chance. initial_content_regex = re.compile(r'[/][*] using initial content ([^ ]+) [*][/]') + # Some calls to callExports come with a random seed, so we have either + # + # callExports(); + # callExports(123456); + # + call_exports_regex = re.compile(r'callExports[(](\d*)[)]') + for i in range(1, N + 1): fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') with open(fuzz_file) as f: js = f.read() seen_builds.append(js.count('build(binary);')) - seen_calls.append(js.count('callExports();')) + seen_calls.append(re.findall(call_exports_regex, js)) seen_second_builds.append(js.count('build(secondBinary);')) # If JSPI is enabled, the async and await keywords should be @@ -331,12 +338,36 @@ def test_file_contents(self): print() - print('JS calls are distributed as ~ mean 4, stddev 5, median 2') - print(f'mean JS calls: {statistics.mean(seen_calls)}') - print(f'stdev JS calls: {statistics.stdev(seen_calls)}') - print(f'median JS calls: {statistics.median(seen_calls)}') - self.assertGreaterEqual(max(seen_calls), 2) - self.assertGreater(statistics.stdev(seen_calls), 0) + # Generate the counts of seen calls, for convenience. We convert + # [['11', '22'], [], ['99']] + # into + # [2, 0, 1] + num_seen_calls = [len(x) for x in seen_calls] + print('Num JS calls are distributed as ~ mean 4, stddev 5, median 2') + print(f'mean JS calls: {statistics.mean(num_seen_calls)}') + print(f'stdev JS calls: {statistics.stdev(num_seen_calls)}') + print(f'median JS calls: {statistics.median(num_seen_calls)}') + self.assertGreaterEqual(max(num_seen_calls), 2) + self.assertGreater(statistics.stdev(num_seen_calls), 0) + + # The initial callExports have no seed (that makes the first, default, + # callExports behave deterministically, so we can compare to + # wasm-opt --fuzz-exec etc.), and all subsequent ones must have a seed. + seeds = [] + for calls in seen_calls: + if calls: + self.assertEqual(calls[0], '') + for other in calls[1:]: + self.assertNotEqual(other, '') + seeds.append(int(other)) + + # The seeds are random numbers in 0..2^32-1, so overlap between them + # should be incredibly unlikely. Allow a few % of such overlap just to + # avoid extremely rare errors. + num_seeds = len(seeds) + num_unique_seeds = len(set(seeds)) + print(f'unique JS call seeds: {num_unique_seeds} (should be almost {num_seeds})') + self.assertGreaterEqual(num_unique_seeds / num_seeds, 0.95) print() From b6eacd7b3b05983bf6e3ae3c5b3ed2f51821d787 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jan 2025 11:39:07 -0800 Subject: [PATCH 229/622] Fuzzer: Fix wasm2js trap handling (#7206) wasm2js fuzzing must stop looking at code after a trap (since we trap differently than wasm semantics, e.g. no trap on load/store out of bounds). We must look at the fixed-up names of exports to properly find the place to stop. To do so, just move the trap-handling code to after we run fix_output_for_js(). --- scripts/fuzz_opt.py | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index adbb6ac83a2..037cfab63ae 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1048,24 +1048,6 @@ def handle_pair(self, input, before_wasm, after_wasm, opts): # with NaNs we can't compare the output, as a reinterpret through # memory might end up different in JS than wasm return - # we also cannot compare if the wasm hits a trap, as wasm2js does not - # trap on many things wasm would, and in those cases it can do weird - # undefined things. in such a case, at least compare up until before - # the trap, which lets us compare at least some results in some cases. - # (this is why wasm2js is not in CompareVMs, which does full - # comparisons - we need to limit the comparison in a special way here) - interpreter = run_bynterp(before_wasm_temp, ['--fuzz-exec-before']) - if TRAP_PREFIX in interpreter: - trap_index = interpreter.index(TRAP_PREFIX) - # we can't test this function, which the trap is in the middle of. - # erase everything from this function's output and onward, so we - # only compare the previous trap-free code - call_start = interpreter.rindex(FUZZ_EXEC_CALL_PREFIX, 0, trap_index) - call_end = interpreter.index('\n', call_start) - call_line = interpreter[call_start:call_end] - before = before[:before.index(call_line)] - after = after[:after.index(call_line)] - interpreter = interpreter[:interpreter.index(call_line)] def fix_output_for_js(x): # start with the normal output fixes that all VMs need @@ -1117,6 +1099,28 @@ def fix_number(x): before = fix_output_for_js(before) after = fix_output_for_js(after) + + # we must not compare if the wasm hits a trap, as wasm2js does not + # trap on many things wasm would, and in those cases it can do weird + # undefined things. in such a case, at least compare up until before + # the trap, which lets us compare at least some results in some cases. + # (this is why wasm2js is not in CompareVMs, which does full + # comparisons - we need to limit the comparison in a special way here) + interpreter = run_bynterp(before_wasm_temp, ['--fuzz-exec-before']) + if TRAP_PREFIX in interpreter: + trap_index = interpreter.index(TRAP_PREFIX) + # we can't test this function, which the trap is in the middle of. + # erase everything from this function's output and onward, so we + # only compare the previous trap-free code + call_start = interpreter.rindex(FUZZ_EXEC_CALL_PREFIX, 0, trap_index) + call_end = interpreter.index('\n', call_start) + call_line = interpreter[call_start:call_end] + # fix up the call line so it matches the JS + fixed_call_line = fix_output_for_js(call_line) + before = before[:before.index(fixed_call_line)] + after = after[:after.index(fixed_call_line)] + interpreter = interpreter[:interpreter.index(call_line)] + if compare_before_to_after: compare_between_vms(before, after, 'Wasm2JS (before/after)') if compare_to_interpreter: From caefb33e8abb313127677a3d8c419d242fe60161 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 10 Jan 2025 17:13:52 -0800 Subject: [PATCH 230/622] Atomic struct RMW instructions (#7194) Add `StructRMW` and `StructCmpxchg` expression classes with binary and text printing and parsing as well as validation. --- scripts/gen-s-parser.py | 7 + src/gen-s-parser.inc | 57 +++++ src/ir/ReFinalize.cpp | 2 + src/ir/child-typer.h | 23 ++ src/ir/cost.h | 8 + src/ir/effects.h | 26 ++ src/ir/possible-contents.cpp | 23 +- src/ir/subtype-exprs.h | 15 ++ src/parser/contexts.h | 35 ++- src/parser/parsers.h | 41 +++ src/passes/Print.cpp | 60 ++++- src/passes/TypeGeneralizing.cpp | 4 + src/wasm-binary.h | 11 +- src/wasm-builder.h | 28 ++ src/wasm-delegations-fields.def | 16 ++ src/wasm-delegations.def | 2 + src/wasm-interpreter.h | 4 + src/wasm-ir-builder.h | 3 + src/wasm.h | 30 +++ src/wasm/wasm-binary.cpp | 55 +++- src/wasm/wasm-ir-builder.cpp | 37 +++ src/wasm/wasm-stack.cpp | 43 ++++ src/wasm/wasm-validator.cpp | 97 ++++++- src/wasm/wasm.cpp | 27 ++ src/wasm2js.h | 8 + test/binaryen.js/kitchen-sink.js.txt | 30 +-- test/lit/basic/gc-atomics.wast | 297 +++++++++++++++++++++- test/lit/parse-bad-gc-cmpxchg-orders.wast | 14 + test/lit/parse-bad-gc-rmw-orders.wast | 13 + 29 files changed, 962 insertions(+), 54 deletions(-) create mode 100644 test/lit/parse-bad-gc-cmpxchg-orders.wast create mode 100644 test/lit/parse-bad-gc-rmw-orders.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index b5592d433be..6427d052804 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -622,6 +622,13 @@ ("struct.atomic.get_u", "makeAtomicStructGet(false)"), ("struct.set", "makeStructSet()"), ("struct.atomic.set", "makeAtomicStructSet()"), + ("struct.atomic.rmw.add", "makeStructRMW(RMWAdd)"), + ("struct.atomic.rmw.sub", "makeStructRMW(RMWSub)"), + ("struct.atomic.rmw.and", "makeStructRMW(RMWAnd)"), + ("struct.atomic.rmw.or", "makeStructRMW(RMWOr)"), + ("struct.atomic.rmw.xor", "makeStructRMW(RMWXor)"), + ("struct.atomic.rmw.xchg", "makeStructRMW(RMWXchg)"), + ("struct.atomic.rmw.cmpxchg", "makeStructCmpxchg()"), ("array.new", "makeArrayNew(false)"), ("array.new_default", "makeArrayNew(true)"), ("array.new_data", "makeArrayNewData()"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index a96ee265973..6b651db5fed 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -5043,6 +5043,63 @@ switch (buf[0]) { default: goto parse_error; } } + case 'r': { + switch (buf[18]) { + case 'a': { + switch (buf[19]) { + case 'd': + if (op == "struct.atomic.rmw.add"sv) { + CHECK_ERR(makeStructRMW(ctx, pos, annotations, RMWAdd)); + return Ok{}; + } + goto parse_error; + case 'n': + if (op == "struct.atomic.rmw.and"sv) { + CHECK_ERR(makeStructRMW(ctx, pos, annotations, RMWAnd)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + case 'c': + if (op == "struct.atomic.rmw.cmpxchg"sv) { + CHECK_ERR(makeStructCmpxchg(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case 'o': + if (op == "struct.atomic.rmw.or"sv) { + CHECK_ERR(makeStructRMW(ctx, pos, annotations, RMWOr)); + return Ok{}; + } + goto parse_error; + case 's': + if (op == "struct.atomic.rmw.sub"sv) { + CHECK_ERR(makeStructRMW(ctx, pos, annotations, RMWSub)); + return Ok{}; + } + goto parse_error; + case 'x': { + switch (buf[19]) { + case 'c': + if (op == "struct.atomic.rmw.xchg"sv) { + CHECK_ERR(makeStructRMW(ctx, pos, annotations, RMWXchg)); + return Ok{}; + } + goto parse_error; + case 'o': + if (op == "struct.atomic.rmw.xor"sv) { + CHECK_ERR(makeStructRMW(ctx, pos, annotations, RMWXor)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + default: goto parse_error; + } + } case 's': if (op == "struct.atomic.set"sv) { CHECK_ERR(makeAtomicStructSet(ctx, pos, annotations)); diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index c32d6efbf4c..5a62d225afe 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -158,6 +158,8 @@ void ReFinalize::visitBrOn(BrOn* curr) { void ReFinalize::visitStructNew(StructNew* curr) { curr->finalize(); } void ReFinalize::visitStructGet(StructGet* curr) { curr->finalize(); } void ReFinalize::visitStructSet(StructSet* curr) { curr->finalize(); } +void ReFinalize::visitStructRMW(StructRMW* curr) { curr->finalize(); } +void ReFinalize::visitStructCmpxchg(StructCmpxchg* curr) { curr->finalize(); } void ReFinalize::visitArrayNew(ArrayNew* curr) { curr->finalize(); } void ReFinalize::visitArrayNewData(ArrayNewData* curr) { curr->finalize(); } void ReFinalize::visitArrayNewElem(ArrayNewElem* curr) { curr->finalize(); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 3d45df32bf1..aac7744ab45 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -891,6 +891,29 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->value, fields[curr->index].type); } + void visitStructRMW(StructRMW* curr, + std::optional ht = std::nullopt) { + if (!ht) { + ht = curr->ref->type.getHeapType(); + } + const auto& fields = ht->getStruct().fields; + assert(curr->index < fields.size()); + note(&curr->ref, Type(*ht, Nullable)); + note(&curr->value, fields[curr->index].type); + } + + void visitStructCmpxchg(StructCmpxchg* curr, + std::optional ht = std::nullopt) { + if (!ht) { + ht = curr->ref->type.getHeapType(); + } + const auto& fields = ht->getStruct().fields; + assert(curr->index < fields.size()); + note(&curr->ref, Type(*ht, Nullable)); + note(&curr->expected, fields[curr->index].type); + note(&curr->replacement, fields[curr->index].type); + } + void visitArrayNew(ArrayNew* curr) { if (!curr->isWithDefault()) { note(&curr->init, curr->type.getHeapType().getArray().element.type); diff --git a/src/ir/cost.h b/src/ir/cost.h index fcee6c18edb..a172e900403 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -687,6 +687,14 @@ struct CostAnalyzer : public OverriddenVisitor { CostType visitStructSet(StructSet* curr) { return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->value); } + CostType visitStructRMW(StructRMW* curr) { + return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + + visit(curr->value); + } + CostType visitStructCmpxchg(StructCmpxchg* curr) { + return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + + visit(curr->expected) + visit(curr->replacement); + } CostType visitArrayNew(ArrayNew* curr) { return 4 + visit(curr->size) + maybeVisit(curr->init); } diff --git a/src/ir/effects.h b/src/ir/effects.h index ee596f67bfa..4b05ea34f12 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -899,6 +899,32 @@ class EffectAnalyzer { parent.isAtomic = true; } } + void visitStructRMW(StructRMW* curr) { + if (curr->ref->type.isNull()) { + parent.trap = true; + return; + } + parent.readsMutableStruct = true; + parent.writesStruct = true; + if (curr->ref->type.isNullable()) { + parent.implicitTrap = true; + } + assert(curr->order != MemoryOrder::Unordered); + parent.isAtomic = true; + } + void visitStructCmpxchg(StructCmpxchg* curr) { + if (curr->ref->type.isNull()) { + parent.trap = true; + return; + } + parent.readsMutableStruct = true; + parent.writesStruct = true; + if (curr->ref->type.isNullable()) { + parent.implicitTrap = true; + } + assert(curr->order != MemoryOrder::Unordered); + parent.isAtomic = true; + } void visitArrayNew(ArrayNew* curr) {} void visitArrayNewData(ArrayNewData* curr) { // Traps on out of bounds access to segments or access to dropped diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 00a2cb82500..49c8ee17615 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1016,6 +1016,22 @@ struct InfoCollector addChildParentLink(curr->ref, curr); addChildParentLink(curr->value, curr); } + void visitStructRMW(StructRMW* curr) { + if (curr->ref->type == Type::unreachable) { + return; + } + // TODO: Model the modification part of the RMW in addition to the read and + // the write. + addRoot(curr); + } + void visitStructCmpxchg(StructCmpxchg* curr) { + if (curr->ref->type == Type::unreachable) { + return; + } + // TODO: Model the modification part of the RMW in addition to the read and + // the write. + addRoot(curr); + } // Array operations access the array's location, parallel to how structs work. void visitArrayGet(ArrayGet* curr) { if (!isRelevant(curr->ref)) { @@ -1552,6 +1568,10 @@ void TNHOracle::scan(Function* func, void visitStructGet(StructGet* curr) { notePossibleTrap(curr->ref); } void visitStructSet(StructSet* curr) { notePossibleTrap(curr->ref); } + void visitStructRMW(StructRMW* curr) { notePossibleTrap(curr->ref); } + void visitStructCmpxchg(StructCmpxchg* curr) { + notePossibleTrap(curr->ref); + } void visitArrayGet(ArrayGet* curr) { notePossibleTrap(curr->ref); } void visitArraySet(ArraySet* curr) { notePossibleTrap(curr->ref); } void visitArrayLen(ArrayLen* curr) { notePossibleTrap(curr->ref); } @@ -2354,7 +2374,8 @@ bool Flower::updateContents(LocationIndex locationIndex, // we must only combine the filtered contents (e.g. if 0xff arrives which // as a signed read is truly 0xffffffff then we cannot first combine the // existing 0xffffffff with the new 0xff, as they are different, and the - // result will no longer be a constant). + // result will no longer be a constant). There is no need to filter atomic + // RMW operations here because they always do unsigned reads. filterPackedDataReads(newContents, *exprLoc); #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 std::cout << " pre-filtered packed read contents:\n"; diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index e6ee1816d3e..b1e40b7457f 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -324,6 +324,21 @@ struct SubtypingDiscoverer : public OverriddenVisitor { const auto& fields = curr->ref->type.getHeapType().getStruct().fields; self()->noteSubtype(curr->value, fields[curr->index].type); } + void visitStructRMW(StructRMW* curr) { + if (!curr->ref->type.isStruct()) { + return; + } + const auto& fields = curr->ref->type.getHeapType().getStruct().fields; + self()->noteSubtype(curr->value, fields[curr->index].type); + } + void visitStructCmpxchg(StructCmpxchg* curr) { + if (!curr->ref->type.isStruct()) { + return; + } + const auto& fields = curr->ref->type.getHeapType().getStruct().fields; + self()->noteSubtype(curr->expected, fields[curr->index].type); + self()->noteSubtype(curr->replacement, fields[curr->index].type); + } void visitArrayNew(ArrayNew* curr) { if (!curr->type.isArray() || curr->isWithDefault()) { return; diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 2f008c01940..1c25e881f7c 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -749,6 +749,20 @@ struct NullInstrParserCtx { return Ok{}; } template + Result<> makeStructRMW(Index, + const std::vector&, + AtomicRMWOp, + HeapTypeT, + FieldIdxT, + MemoryOrder) { + return Ok{}; + } + template + Result<> makeStructCmpxchg( + Index, const std::vector&, HeapTypeT, FieldIdxT, MemoryOrder) { + return Ok{}; + } + template Result<> makeArrayNew(Index, const std::vector&, HeapTypeT) { return Ok{}; } @@ -2453,7 +2467,7 @@ struct ParseDefsCtx : TypeParserCtx { HeapType type, Index field, bool signed_, - MemoryOrder order = MemoryOrder::Unordered) { + MemoryOrder order) { return withLoc(pos, irBuilder.makeStructGet(type, field, signed_, order)); } @@ -2461,10 +2475,27 @@ struct ParseDefsCtx : TypeParserCtx { const std::vector& annotations, HeapType type, Index field, - MemoryOrder order = MemoryOrder::Unordered) { + MemoryOrder order) { return withLoc(pos, irBuilder.makeStructSet(type, field, order)); } + Result<> makeStructRMW(Index pos, + const std::vector& annotations, + AtomicRMWOp op, + HeapType type, + Index field, + MemoryOrder order) { + return withLoc(pos, irBuilder.makeStructRMW(op, type, field, order)); + } + + Result<> makeStructCmpxchg(Index pos, + const std::vector& annotations, + HeapType type, + Index field, + MemoryOrder order) { + return withLoc(pos, irBuilder.makeStructCmpxchg(type, field, order)); + } + Result<> makeArrayNew(Index pos, const std::vector& annotations, HeapType type) { diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 59f34038d09..ca502355a9d 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -256,6 +256,10 @@ Result<> makeStructSet(Ctx&, Index, const std::vector&); template Result<> makeAtomicStructSet(Ctx&, Index, const std::vector&); template +Result<> makeStructRMW(AtomicRMWOp, Index, const std::vector&); +template +Result<> makeStructCmpxchg(Index, const std::vector&); +template Result<> makeArrayNew(Ctx&, Index, const std::vector&, bool default_); template @@ -2282,6 +2286,43 @@ Result<> makeAtomicStructSet(Ctx& ctx, return ctx.makeStructSet(pos, annotations, *type, *field, *order); } +template +Result<> makeStructRMW(Ctx& ctx, + Index pos, + const std::vector& annotations, + AtomicRMWOp op) { + auto order1 = memorder(ctx); + CHECK_ERR(order1); + auto order2 = memorder(ctx); + CHECK_ERR(order2); + if (*order1 != *order2) { + return ctx.in.err(pos, "struct.atomic.rmw memory orders must be identical"); + } + auto type = typeidx(ctx); + CHECK_ERR(type); + auto field = fieldidx(ctx, *type); + CHECK_ERR(field); + return ctx.makeStructRMW(pos, annotations, op, *type, *field, *order1); +} + +template +Result<> makeStructCmpxchg(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto order1 = memorder(ctx); + CHECK_ERR(order1); + auto order2 = memorder(ctx); + CHECK_ERR(order2); + if (*order1 != *order2) { + return ctx.in.err(pos, "struct.atomic.rmw memory orders must be identical"); + } + auto type = typeidx(ctx); + CHECK_ERR(type); + auto field = fieldidx(ctx, *type); + CHECK_ERR(field); + return ctx.makeStructCmpxchg(pos, annotations, *type, *field, *order1); +} + template Result<> makeArrayNew(Ctx& ctx, Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index d70034c859b..abacb86052c 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -327,12 +327,22 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } + void visitStructGet(StructGet* curr) { + if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { + visitExpression(curr); + } + } void visitStructSet(StructSet* curr) { if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { visitExpression(curr); } } - void visitStructGet(StructGet* curr) { + void visitStructRMW(StructRMW* curr) { + if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { + visitExpression(curr); + } + } + void visitStructCmpxchg(StructCmpxchg* curr) { if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { visitExpression(curr); } @@ -631,29 +641,33 @@ struct PrintExpressionContents } o << '.'; } - void visitAtomicRMW(AtomicRMW* curr) { - prepareColor(o); - printRMWSize(o, curr->type, curr->bytes); - switch (curr->op) { + void printAtomicRMWOp(AtomicRMWOp op) { + switch (op) { case RMWAdd: o << "add"; - break; + return; case RMWSub: o << "sub"; - break; + return; case RMWAnd: o << "and"; - break; + return; case RMWOr: o << "or"; - break; + return; case RMWXor: o << "xor"; - break; + return; case RMWXchg: o << "xchg"; - break; + return; } + WASM_UNREACHABLE("unexpected rmw op"); + } + void visitAtomicRMW(AtomicRMW* curr) { + prepareColor(o); + printRMWSize(o, curr->type, curr->bytes); + printAtomicRMWOp(curr->op); if (curr->type != Type::unreachable && curr->bytes != curr->type.getByteSize()) { o << "_u"; @@ -2322,6 +2336,30 @@ struct PrintExpressionContents o << ' '; printFieldName(heapType, curr->index); } + void visitStructRMW(StructRMW* curr) { + prepareColor(o); + o << "struct.atomic.rmw."; + printAtomicRMWOp(curr->op); + restoreNormalColor(o); + o << ' '; + printMemoryOrder(curr->order); + printMemoryOrder(curr->order); + auto heapType = curr->ref->type.getHeapType(); + printHeapType(heapType); + o << ' '; + printFieldName(heapType, curr->index); + } + void visitStructCmpxchg(StructCmpxchg* curr) { + prepareColor(o); + o << "struct.atomic.rmw.cmpxchg "; + restoreNormalColor(o); + printMemoryOrder(curr->order); + printMemoryOrder(curr->order); + auto heapType = curr->ref->type.getHeapType(); + printHeapType(heapType); + o << ' '; + printFieldName(heapType, curr->index); + } void visitArrayNew(ArrayNew* curr) { printMedium(o, "array.new"); if (curr->isWithDefault()) { diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index c0359b897c3..074e7c2505a 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -685,6 +685,10 @@ struct TransferFn : OverriddenVisitor { push(generalized.getStruct().fields[curr->index].type); } + void visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); } + + void visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } + void visitArrayNew(ArrayNew* curr) { // We cannot yet generalize allocations. Push a requirement for the // reference type needed to initialize the array, if any. diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 7d98302baae..0669d978e22 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1133,6 +1133,13 @@ enum ASTNodes { StructAtomicGetS = 0x5d, StructAtomicGetU = 0x5e, StructAtomicSet = 0x5f, + StructAtomicRMWAdd = 0x60, + StructAtomicRMWSub = 0x61, + StructAtomicRMWAnd = 0x62, + StructAtomicRMWOr = 0x63, + StructAtomicRMWXor = 0x64, + StructAtomicRMWXchg = 0x65, + StructAtomicRMWCmpxchg = 0x66, // stringref opcodes @@ -1361,7 +1368,7 @@ class WasmBinaryWriter { void writeField(const Field& field); - void writeMemoryOrder(MemoryOrder order); + void writeMemoryOrder(MemoryOrder order, bool isRMW = false); private: Module* wasm; @@ -1598,7 +1605,7 @@ class WasmBinaryReader { Index readMemoryAccess(Address& alignment, Address& offset); std::tuple getMemarg(); - MemoryOrder getMemoryOrder(); + MemoryOrder getMemoryOrder(bool isRMW = false); [[noreturn]] void throwError(std::string text) { throw ParseException(text, 0, pos); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 03d0e2da037..5201470e17e 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -962,6 +962,34 @@ class Builder { ret->finalize(); return ret; } + StructRMW* makeStructRMW(AtomicRMWOp op, + Index index, + Expression* ref, + Expression* value, + MemoryOrder order) { + auto* ret = wasm.allocator.alloc(); + ret->op = op; + ret->index = index; + ret->ref = ref; + ret->value = value; + ret->order = order; + ret->finalize(); + return ret; + } + StructCmpxchg* makeStructCmpxchg(Index index, + Expression* ref, + Expression* expected, + Expression* replacement, + MemoryOrder order) { + auto* ret = wasm.allocator.alloc(); + ret->index = index; + ret->ref = ref; + ret->expected = expected; + ret->replacement = replacement; + ret->order = order; + ret->finalize(); + return ret; + } ArrayNew* makeArrayNew(HeapType type, Expression* size, Expression* init = nullptr) { auto* ret = wasm.allocator.alloc(); diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index e883763a44b..623ccbd397e 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -649,6 +649,22 @@ DELEGATE_FIELD_CHILD(StructSet, ref) DELEGATE_FIELD_INT(StructSet, order) DELEGATE_FIELD_CASE_END(StructSet) +DELEGATE_FIELD_CASE_START(StructRMW) +DELEGATE_FIELD_INT(StructRMW, op) +DELEGATE_FIELD_INT(StructRMW, index) +DELEGATE_FIELD_CHILD(StructRMW, value) +DELEGATE_FIELD_CHILD(StructRMW, ref) +DELEGATE_FIELD_INT(StructRMW, order) +DELEGATE_FIELD_CASE_END(StructRMW) + +DELEGATE_FIELD_CASE_START(StructCmpxchg) +DELEGATE_FIELD_INT(StructCmpxchg, index) +DELEGATE_FIELD_CHILD(StructCmpxchg, replacement) +DELEGATE_FIELD_CHILD(StructCmpxchg, expected) +DELEGATE_FIELD_CHILD(StructCmpxchg, ref) +DELEGATE_FIELD_INT(StructCmpxchg, order) +DELEGATE_FIELD_CASE_END(StructCmpxchg) + DELEGATE_FIELD_CASE_START(ArrayNew) DELEGATE_FIELD_CHILD(ArrayNew, size) DELEGATE_FIELD_OPTIONAL_CHILD(ArrayNew, init) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index f4552a98b20..eb24feb7b83 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -81,6 +81,8 @@ DELEGATE(BrOn); DELEGATE(StructNew); DELEGATE(StructGet); DELEGATE(StructSet); +DELEGATE(StructRMW); +DELEGATE(StructCmpxchg); DELEGATE(ArrayNew); DELEGATE(ArrayNewData); DELEGATE(ArrayNewElem); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 937248d811c..72de28585ed 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1701,6 +1701,10 @@ class ExpressionRunner : public OverriddenVisitor { return Flow(); } + Flow visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); } + + Flow visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } + // Arbitrary deterministic limit on size. If we need to allocate a Literals // vector that takes around 1-2GB of memory then we are likely to hit memory // limits on 32-bit machines, and in particular on wasm32 VMs that do not diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index c46f9f2ca76..9abb0c12601 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -207,6 +207,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeStructGet(HeapType type, Index field, bool signed_, MemoryOrder order); Result<> makeStructSet(HeapType type, Index field, MemoryOrder order); + Result<> + makeStructRMW(AtomicRMWOp op, HeapType type, Index field, MemoryOrder order); + Result<> makeStructCmpxchg(HeapType type, Index field, MemoryOrder order); Result<> makeArrayNew(HeapType type); Result<> makeArrayNewDefault(HeapType type); Result<> makeArrayNewData(HeapType type, Name data); diff --git a/src/wasm.h b/src/wasm.h index 3f60a67d231..1ce9d9c02b5 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -722,6 +722,8 @@ class Expression { StructNewId, StructGetId, StructSetId, + StructRMWId, + StructCmpxchgId, ArrayNewId, ArrayNewDataId, ArrayNewElemId, @@ -1676,6 +1678,34 @@ class StructSet : public SpecificExpression { void finalize(); }; +class StructRMW : public SpecificExpression { +public: + StructRMW() = default; + StructRMW(MixedArena& allocator) {} + + AtomicRMWOp op; + Index index; + Expression* ref; + Expression* value; + MemoryOrder order; + + void finalize(); +}; + +class StructCmpxchg : public SpecificExpression { +public: + StructCmpxchg() = default; + StructCmpxchg(MixedArena& allocator) {} + + Index index; + Expression* ref; + Expression* expected; + Expression* replacement; + MemoryOrder order; + + void finalize(); +}; + class ArrayNew : public SpecificExpression { public: ArrayNew() = default; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2bcf3d44614..ca4d935c8a1 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1737,18 +1737,24 @@ void WasmBinaryWriter::writeField(const Field& field) { o << U32LEB(field.mutable_); } -void WasmBinaryWriter::writeMemoryOrder(MemoryOrder order) { +void WasmBinaryWriter::writeMemoryOrder(MemoryOrder order, bool isRMW) { + uint8_t code = 0; switch (order) { case MemoryOrder::Unordered: - break; - case MemoryOrder::SeqCst: - o << uint8_t(BinaryConsts::OrderSeqCst); + // Non-atomic get or set does not need a memory order. return; + case MemoryOrder::SeqCst: + code = BinaryConsts::OrderSeqCst; + break; case MemoryOrder::AcqRel: - o << uint8_t(BinaryConsts::OrderAcqRel); - return; + code = BinaryConsts::OrderAcqRel; + break; + } + if (isRMW) { + o << uint8_t((code << 4) | code); + } else { + o << code; } - WASM_UNREACHABLE("unexpected memory order"); } // reader @@ -3435,6 +3441,28 @@ Result<> WasmBinaryReader::readInst() { auto field = getU32LEB(); return builder.makeStructSet(type, field, order); } + +#define STRUCT_RMW(op) \ + case BinaryConsts::StructAtomicRMW##op: { \ + auto order = getMemoryOrder(true); \ + auto type = getIndexedHeapType(); \ + auto field = getU32LEB(); \ + return builder.makeStructRMW(RMW##op, type, field, order); \ + } + + STRUCT_RMW(Add) + STRUCT_RMW(Sub) + STRUCT_RMW(And) + STRUCT_RMW(Or) + STRUCT_RMW(Xor) + STRUCT_RMW(Xchg) + + case BinaryConsts::StructAtomicRMWCmpxchg: { + auto order = getMemoryOrder(true); + auto type = getIndexedHeapType(); + auto field = getU32LEB(); + return builder.makeStructCmpxchg(type, field, order); + } } return Err{"unknown atomic operation"}; } @@ -4983,13 +5011,22 @@ std::tuple WasmBinaryReader::getMemarg() { return {getMemoryName(memIdx), alignment, offset}; } -MemoryOrder WasmBinaryReader::getMemoryOrder() { +MemoryOrder WasmBinaryReader::getMemoryOrder(bool isRMW) { auto code = getInt8(); switch (code) { case BinaryConsts::OrderSeqCst: + // Covers the RMW case as well because (0 << 4 ) | 0 == 0. return MemoryOrder::SeqCst; case BinaryConsts::OrderAcqRel: - return MemoryOrder::AcqRel; + if (!isRMW) { + return MemoryOrder::AcqRel; + } + throwError("RMW memory orders must match"); + case ((BinaryConsts::OrderAcqRel << 4) | BinaryConsts::OrderAcqRel): + if (isRMW) { + return MemoryOrder::AcqRel; + } + break; } throwError("Unrecognized memory order code " + std::to_string(code)); } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index f5ffac99a42..73fbfe6de3f 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -601,6 +601,20 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } + Result<> visitStructRMW(StructRMW* curr, + std::optional ht = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitStructRMW(curr, ht); + return popConstrainedChildren(children); + } + + Result<> visitStructCmpxchg(StructCmpxchg* curr, + std::optional ht = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitStructCmpxchg(curr, ht); + return popConstrainedChildren(children); + } + Result<> visitArrayGet(ArrayGet* curr, std::optional ht = std::nullopt) { std::vector children; @@ -1860,6 +1874,29 @@ IRBuilder::makeStructSet(HeapType type, Index field, MemoryOrder order) { return Ok{}; } +Result<> IRBuilder::makeStructRMW(AtomicRMWOp op, + HeapType type, + Index field, + MemoryOrder order) { + StructRMW curr; + curr.index = field; + CHECK_ERR(ChildPopper{*this}.visitStructRMW(&curr, type)); + CHECK_ERR(validateTypeAnnotation(type, curr.ref)); + push(builder.makeStructRMW(op, field, curr.ref, curr.value, order)); + return Ok{}; +} + +Result<> +IRBuilder::makeStructCmpxchg(HeapType type, Index field, MemoryOrder order) { + StructCmpxchg curr; + curr.index = field; + CHECK_ERR(ChildPopper{*this}.visitStructCmpxchg(&curr, type)); + CHECK_ERR(validateTypeAnnotation(type, curr.ref)); + push(builder.makeStructCmpxchg( + field, curr.ref, curr.expected, curr.replacement, order)); + return Ok{}; +} + Result<> IRBuilder::makeArrayNew(HeapType type) { ArrayNew curr; curr.type = Type(type, NonNullable); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 08043b27ff4..dc360b94420 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2361,6 +2361,49 @@ void BinaryInstWriter::visitStructSet(StructSet* curr) { o << U32LEB(curr->index); } +void BinaryInstWriter::visitStructRMW(StructRMW* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + o << int8_t(BinaryConsts::AtomicPrefix); + switch (curr->op) { + case RMWAdd: + o << U32LEB(BinaryConsts::StructAtomicRMWAdd); + break; + case RMWSub: + o << U32LEB(BinaryConsts::StructAtomicRMWSub); + break; + case RMWAnd: + o << U32LEB(BinaryConsts::StructAtomicRMWAnd); + break; + case RMWOr: + o << U32LEB(BinaryConsts::StructAtomicRMWOr); + break; + case RMWXor: + o << U32LEB(BinaryConsts::StructAtomicRMWXor); + break; + case RMWXchg: + o << U32LEB(BinaryConsts::StructAtomicRMWXchg); + break; + } + parent.writeMemoryOrder(curr->order, /*isRMW=*/true); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); + o << U32LEB(curr->index); +} + +void BinaryInstWriter::visitStructCmpxchg(StructCmpxchg* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + o << int8_t(BinaryConsts::AtomicPrefix) + << U32LEB(BinaryConsts::StructAtomicRMWCmpxchg); + parent.writeMemoryOrder(curr->order, /*isRMW=*/true); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); + o << U32LEB(curr->index); +} + void BinaryInstWriter::visitArrayNew(ArrayNew* curr) { o << int8_t(BinaryConsts::GCPrefix); if (curr->isWithDefault()) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index faae35472a2..092e7a8c6fd 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -487,6 +487,8 @@ struct FunctionValidator : public WalkerPass> { void visitStructNew(StructNew* curr); void visitStructGet(StructGet* curr); void visitStructSet(StructSet* curr); + void visitStructRMW(StructRMW* curr); + void visitStructCmpxchg(StructCmpxchg* curr); void visitArrayNew(ArrayNew* curr); template void visitArrayNew(ArrayNew* curr); void visitArrayNewData(ArrayNewData* curr); @@ -2265,7 +2267,7 @@ void FunctionValidator::visitRefNull(RefNull* curr) { auto feats = curr->type.getFeatures(); if (!shouldBeTrue(!getFunction() || feats <= getModule()->features, curr, - "ref.null requires additional features")) { + "ref.null requires additional features ")) { getStream() << getMissingFeaturesList(*getModule(), feats) << '\n'; } if (!shouldBeTrue( @@ -3051,16 +3053,101 @@ void FunctionValidator::visitStructSet(StructSet* curr) { return; } const auto& fields = type.getStruct().fields; - shouldBeTrue(curr->index < fields.size(), curr, "bad struct.get field"); + if (!shouldBeTrue( + curr->index < fields.size(), curr, "bad struct.get field")) { + return; + } auto& field = fields[curr->index]; shouldBeSubType(curr->value->type, field.type, curr, - "struct.set must have the proper type"); + "struct.set value must have the proper type"); shouldBeEqual( field.mutable_, Mutable, curr, "struct.set field must be mutable"); } +void FunctionValidator::visitStructRMW(StructRMW* curr) { + auto expected = + FeatureSet::GC | FeatureSet::Atomics | FeatureSet::SharedEverything; + if (!shouldBeTrue(expected <= getModule()->features, + curr, + "struct.atomic.rmw requires additional features ")) { + getStream() << getMissingFeaturesList(*getModule(), expected) << '\n'; + } + if (curr->ref->type == Type::unreachable) { + return; + } + if (!shouldBeTrue(curr->ref->type.isRef(), + curr->ref, + "struct.atomic.rmw ref must be a reference type")) { + return; + } + auto type = curr->ref->type.getHeapType(); + if (type.isMaybeShared(HeapType::none)) { + return; + } + if (!shouldBeTrue( + type.isStruct(), curr->ref, "struct.atomic.rmw ref must be a struct")) { + return; + } + const auto& fields = type.getStruct().fields; + if (!shouldBeTrue( + curr->index < fields.size(), curr, "bad struct.atomic.rmw field")) { + return; + } + auto& field = fields[curr->index]; + shouldBeSubType(curr->value->type, + field.type, + curr, + "struct.atomic.rmw value must have the proper type"); + shouldBeEqual( + field.mutable_, Mutable, curr, "struct.atomic.rmw field must be mutable"); +} + +void FunctionValidator::visitStructCmpxchg(StructCmpxchg* curr) { + auto expected = + FeatureSet::GC | FeatureSet::Atomics | FeatureSet::SharedEverything; + if (!shouldBeTrue(expected <= getModule()->features, + curr, + "struct.atomic.rmw requires additional features ")) { + getStream() << getMissingFeaturesList(*getModule(), expected) << '\n'; + } + if (curr->ref->type == Type::unreachable) { + return; + } + if (!shouldBeTrue(curr->ref->type.isRef(), + curr->ref, + "struct.atomic.rmw ref must be a reference type")) { + return; + } + auto type = curr->ref->type.getHeapType(); + if (type.isMaybeShared(HeapType::none)) { + return; + } + if (!shouldBeTrue( + type.isStruct(), curr->ref, "struct.atomic.rmw ref must be a struct")) { + return; + } + const auto& fields = type.getStruct().fields; + if (!shouldBeTrue( + curr->index < fields.size(), curr, "bad struct.atomic.rmw field")) { + return; + } + auto& field = fields[curr->index]; + shouldBeSubType( + curr->expected->type, + field.type, + curr, + "struct.atomic.rmw.cmpxchg expected value must have the proper type"); + shouldBeSubType( + curr->replacement->type, + field.type, + curr, + "struct.atomic.rmw.cmpxchg replacement value must have the proper type"); + shouldBeEqual( + field.mutable_, Mutable, curr, "struct.atomic.rmw field must be mutable"); +} + void FunctionValidator::visitArrayNew(ArrayNew* curr) { shouldBeTrue( getModule()->features.hasGC(), curr, "array.new requires gc [--enable-gc]"); @@ -3976,7 +4063,7 @@ static void validateTables(Module& module, ValidationInfo& info) { if (!info.shouldBeTrue(table->type == funcref || typeFeats <= module.features, "table", - "table type requires additional features")) { + "table type requires additional features ")) { info.getStream(nullptr) << getMissingFeaturesList(module, typeFeats) << '\n'; } @@ -3999,7 +4086,7 @@ static void validateTables(Module& module, ValidationInfo& info) { if (!info.shouldBeTrue( segment->type == funcref || typeFeats <= module.features, "elem", - "element segment type requires additional features")) { + "element segment type requires additional features ")) { info.getStream(nullptr) << getMissingFeaturesList(module, typeFeats) << '\n'; } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 3e04f29ecef..766e1be8942 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1164,6 +1164,33 @@ void StructSet::finalize() { } } +void StructRMW::finalize() { + if (ref->type == Type::unreachable || value->type == Type::unreachable) { + type = Type::unreachable; + } else if (ref->type.isNull()) { + // See comment on CallRef for explanation. + if (type.isRef()) { + type = Type(type.getHeapType().getBottom(), NonNullable); + } + } else { + type = ref->type.getHeapType().getStruct().fields[index].type; + } +} + +void StructCmpxchg::finalize() { + if (ref->type == Type::unreachable || expected->type == Type::unreachable || + replacement->type == Type::unreachable) { + type = Type::unreachable; + } else if (ref->type.isNull()) { + // See comment on CallRef for explanation. + if (type.isRef()) { + type = Type(type.getHeapType().getBottom(), NonNullable); + } + } else { + type = ref->type.getHeapType().getStruct().fields[index].type; + } +} + void ArrayNew::finalize() { if (size->type == Type::unreachable || (init && init->type == Type::unreachable)) { diff --git a/src/wasm2js.h b/src/wasm2js.h index 6aa718e3917..fc0b2f6cff8 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2320,6 +2320,14 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitStructRMW(StructRMW* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } + Ref visitStructCmpxchg(StructCmpxchg* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitArrayNew(ArrayNew* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 637b1ab9549..b1554bec0f2 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -96,21 +96,21 @@ BrOnId: 64 StructNewId: 65 StructGetId: 66 StructSetId: 67 -ArrayNewId: 68 -ArrayNewFixedId: 71 -ArrayGetId: 72 -ArraySetId: 73 -ArrayLenId: 74 -ArrayCopy: 75 -RefAs: 79 -StringNew: 80 -StringConst: 81 -StringMeasure: 82 -StringEncode: 83 -StringConcat: 84 -StringEq: 85 -StringWTF16Get: 86 -StringSliceWTF: 87 +ArrayNewId: 70 +ArrayNewFixedId: 73 +ArrayGetId: 74 +ArraySetId: 75 +ArrayLenId: 76 +ArrayCopy: 77 +RefAs: 81 +StringNew: 82 +StringConst: 83 +StringMeasure: 84 +StringEncode: 85 +StringConcat: 86 +StringEq: 87 +StringWTF16Get: 88 +StringSliceWTF: 89 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/lit/basic/gc-atomics.wast b/test/lit/basic/gc-atomics.wast index c454b4c99fa..7e2d1ef4eda 100644 --- a/test/lit/basic/gc-atomics.wast +++ b/test/lit/basic/gc-atomics.wast @@ -9,7 +9,7 @@ ;; CHECK: (type $packed (struct (field (mut i8)))) (type $packed (struct (field (mut i8)))) - ;; CHECK: (func $get (type $3) (param $0 (ref null $struct)) (result i32) + ;; CHECK: (func $get (type $1) (param $0 (ref null $struct)) (result i32) ;; CHECK-NEXT: (struct.atomic.get $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -20,7 +20,7 @@ ) ) - ;; CHECK: (func $get-seqcst (type $3) (param $0 (ref null $struct)) (result i32) + ;; CHECK: (func $get-seqcst (type $1) (param $0 (ref null $struct)) (result i32) ;; CHECK-NEXT: (struct.atomic.get $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -31,7 +31,7 @@ ) ) - ;; CHECK: (func $get-acqrel (type $3) (param $0 (ref null $struct)) (result i32) + ;; CHECK: (func $get-acqrel (type $1) (param $0 (ref null $struct)) (result i32) ;; CHECK-NEXT: (struct.atomic.get acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -42,7 +42,7 @@ ) ) - ;; CHECK: (func $get-s (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-s (type $3) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_s $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -53,7 +53,7 @@ ) ) - ;; CHECK: (func $get-s-seqcst (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-s-seqcst (type $3) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_s $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -64,7 +64,7 @@ ) ) - ;; CHECK: (func $get-s-acqrel (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-s-acqrel (type $3) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_s acqrel $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -75,7 +75,7 @@ ) ) - ;; CHECK: (func $get-u (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-u (type $3) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_u $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -86,7 +86,7 @@ ) ) - ;; CHECK: (func $get-u-seqcst (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-u-seqcst (type $3) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_u $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -97,7 +97,7 @@ ) ) - ;; CHECK: (func $get-u-acqrel (type $2) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-u-acqrel (type $3) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_u acqrel $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -146,4 +146,283 @@ (i32.const 0) ) ) + + ;; CHECK: (func $rmw-add (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.add $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add (param (ref null $struct)) (result i32) + (struct.atomic.rmw.add $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-add-seqcst (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.add $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add-seqcst (param (ref null $struct)) (result i32) + (struct.atomic.rmw.add seqcst seqcst $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-add-acqrel (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.add acqrel acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add-acqrel (param (ref null $struct)) (result i32) + (struct.atomic.rmw.add acqrel acqrel $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.sub $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-sub (param (ref null $struct)) (result i32) + (struct.atomic.rmw.sub $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub-seqcst (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.sub $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-sub-seqcst (param (ref null $struct)) (result i32) + (struct.atomic.rmw.sub seqcst seqcst $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub-acqrel (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.sub acqrel acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-sub-acqrel (param (ref null $struct)) (result i32) + (struct.atomic.rmw.sub acqrel acqrel $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-and (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.and $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-and (param (ref null $struct)) (result i32) + (struct.atomic.rmw.and $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-and-seqcst (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.and $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-and-seqcst (param (ref null $struct)) (result i32) + (struct.atomic.rmw.and seqcst seqcst $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-and-acqrel (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.and acqrel acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-and-acqrel (param (ref null $struct)) (result i32) + (struct.atomic.rmw.and acqrel acqrel $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-or (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.or $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-or (param (ref null $struct)) (result i32) + (struct.atomic.rmw.or $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-or-seqcst (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.or $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-or-seqcst (param (ref null $struct)) (result i32) + (struct.atomic.rmw.or seqcst seqcst $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-or-acqrel (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.or acqrel acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-or-acqrel (param (ref null $struct)) (result i32) + (struct.atomic.rmw.or acqrel acqrel $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xor (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xor $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xor (param (ref null $struct)) (result i32) + (struct.atomic.rmw.xor $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xor-seqcst (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xor $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xor-seqcst (param (ref null $struct)) (result i32) + (struct.atomic.rmw.xor seqcst seqcst $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xor-acqrel (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xor acqrel acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xor-acqrel (param (ref null $struct)) (result i32) + (struct.atomic.rmw.xor acqrel acqrel $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xchg (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg (param (ref null $struct)) (result i32) + (struct.atomic.rmw.xchg $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xchg-seqcst (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-seqcst (param (ref null $struct)) (result i32) + (struct.atomic.rmw.xchg seqcst seqcst $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xchg-acqrel (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-acqrel (param (ref null $struct)) (result i32) + (struct.atomic.rmw.xchg acqrel acqrel $struct 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg (param (ref null $struct)) (result i32) + (struct.atomic.rmw.cmpxchg $struct 0 + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-seqcst (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-seqcst (param (ref null $struct)) (result i32) + (struct.atomic.rmw.cmpxchg seqcst seqcst $struct 0 + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-acqrel (type $1) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-acqrel (param (ref null $struct)) (result i32) + (struct.atomic.rmw.cmpxchg acqrel acqrel $struct 0 + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) ) diff --git a/test/lit/parse-bad-gc-cmpxchg-orders.wast b/test/lit/parse-bad-gc-cmpxchg-orders.wast new file mode 100644 index 00000000000..b6ce7e7b796 --- /dev/null +++ b/test/lit/parse-bad-gc-cmpxchg-orders.wast @@ -0,0 +1,14 @@ +;; RUN: not wasm-opt %s -all 2>&1 | filecheck %s + +(module + (type $struct (struct (field (mut i32)))) + + ;; CHECK: 8:5: error: struct.atomic.rmw memory orders must be identical + (func $cmpxchg (param (ref null $struct)) + (struct.atomic.rmw.cmpxchg seqcst acqrel 0 + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) +) diff --git a/test/lit/parse-bad-gc-rmw-orders.wast b/test/lit/parse-bad-gc-rmw-orders.wast new file mode 100644 index 00000000000..1cdc33d08f1 --- /dev/null +++ b/test/lit/parse-bad-gc-rmw-orders.wast @@ -0,0 +1,13 @@ +;; RUN: not wasm-opt %s -all 2>&1 | filecheck %s + +(module + (type $struct (struct (field (mut i32)))) + + ;; CHECK: 8:5: error: struct.atomic.rmw memory orders must be identical + (func $rmw (param (ref null $struct)) + (struct.atomic.rmw.add seqcst acqrel 0 + (local.get 0) + (i32.const 1) + ) + ) +) From 63be8c0fe9ae9a63c91f5bf3b0f8d6c2446620b2 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 10 Jan 2025 20:51:17 -0800 Subject: [PATCH 231/622] Lower branches with unsupported extra values in IRBuilder (#7202) WebAssembly allows all branch instructions to carry an arbitrary number of values, but Binaryen IR only supports this for br, br_if, and br_table. Previously we failed to parse any other branches that sent additional arguments. To be able to parse more standard modules, support extra values sent by other branches by lowering them away using scratch locals and trampolines in IRBuilder. For now, only lower BrOn expressions with extra values. Lowering for other branches will be done in follow-on commits. Fixes #7182. --- src/wasm-ir-builder.h | 66 +- src/wasm/wasm-ir-builder.cpp | 271 +- test/lit/basic/extra-branch-values.wast | 3106 +++++++++++++++++++++++ 3 files changed, 3381 insertions(+), 62 deletions(-) create mode 100644 test/lit/basic/extra-branch-values.wast diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 9abb0c12601..51c04a446c6 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -342,6 +342,16 @@ class IRBuilder : public UnifiedExpressionVisitor> { Type inputType; Index inputLocal = -1; + // If there are br_on_*, try_table, or resume branches that target this + // scope and carry additional values, we need to use a scratch local to + // deliver those additional values because the IR does not support them. We + // may need scratch locals of different arities for the same branch target. + // For each arity we also need a trampoline label to branch to. TODO: + // Support additional values on any branch once we have better multivalue + // optimization support. + std::vector outputLocals; + std::vector outputLabels; + // The stack of instructions being built in this scope. std::vector exprStack; @@ -372,14 +382,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { static ScopeCtx makeIf(If* iff, Name originalLabel, Type inputType) { return ScopeCtx(IfScope{iff, originalLabel}, inputType); } - static ScopeCtx makeElse(If* iff, - Name originalLabel, - Name label, - bool labelUsed, - Type inputType, - Index inputLocal) { - return ScopeCtx( - ElseScope{iff, originalLabel}, label, labelUsed, inputType, inputLocal); + static ScopeCtx makeElse(ScopeCtx&& scope) { + scope.scope = ElseScope{scope.getIf(), scope.getOriginalLabel()}; + scope.resetForDelimiter(/*keepInput=*/true); + return scope; } static ScopeCtx makeLoop(Loop* loop, Type inputType) { return ScopeCtx(LoopScope{loop}, inputType); @@ -387,27 +393,32 @@ class IRBuilder : public UnifiedExpressionVisitor> { static ScopeCtx makeTry(Try* tryy, Name originalLabel, Type inputType) { return ScopeCtx(TryScope{tryy, originalLabel}, inputType); } - static ScopeCtx makeCatch(Try* tryy, - Name originalLabel, - Name label, - bool labelUsed, - Name branchLabel) { - return ScopeCtx( - CatchScope{tryy, originalLabel}, label, labelUsed, branchLabel); - } - static ScopeCtx makeCatchAll(Try* tryy, - Name originalLabel, - Name label, - bool labelUsed, - Name branchLabel) { - return ScopeCtx( - CatchAllScope{tryy, originalLabel}, label, labelUsed, branchLabel); + static ScopeCtx makeCatch(ScopeCtx&& scope, Try* tryy) { + scope.scope = CatchScope{tryy, scope.getOriginalLabel()}; + scope.resetForDelimiter(/*keepInput=*/false); + return scope; + } + static ScopeCtx makeCatchAll(ScopeCtx&& scope, Try* tryy) { + scope.scope = CatchAllScope{tryy, scope.getOriginalLabel()}; + scope.resetForDelimiter(/*keepInput=*/false); + return scope; } static ScopeCtx makeTryTable(TryTable* trytable, Name originalLabel, Type inputType) { return ScopeCtx(TryTableScope{trytable, originalLabel}, inputType); } - + // When transitioning to a new scope for a delimiter like `else` or catch, + // most of the scope context is preserved, but some parts need to be reset. + // `keepInput` means that control flow parameters are available at the + // begninning of the scope after the delimiter. + void resetForDelimiter(bool keepInput) { + exprStack.clear(); + unreachable = false; + if (!keepInput) { + inputType = Type::none; + inputLocal = -1; + } + } bool isNone() { return std::get_if(&scope); } Function* getFunction() { if (auto* funcScope = std::get_if(&scope)) { @@ -514,6 +525,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { } WASM_UNREACHABLE("unexpected scope kind"); } + Type getLabelType() { + // Loops receive their input type rather than their output type. + return getLoop() ? inputType : getResultType(); + } Name getOriginalLabel() { if (std::get_if(&scope) || getFunction()) { return Name{}; @@ -651,6 +666,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result getLabelType(Index label); Result getLabelType(Name labelName); + Result> getExtraOutputLocalAndLabel(Index label, + size_t extraArity); + Expression* fixExtraOutput(ScopeCtx& scope, Name label, Expression* expr); void fixLoopWithInput(Loop* loop, Type inputType, Index scratch); void dump(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 73fbfe6de3f..47f610539f2 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -729,8 +729,7 @@ Result<> IRBuilder::visitExpression(Expression* curr) { Result IRBuilder::getLabelType(Index label) { auto scope = getScope(label); CHECK_ERR(scope); - // Loops receive their input type rather than their output type. - return (*scope)->getLoop() ? (*scope)->inputType : (*scope)->getResultType(); + return (*scope)->getLabelType(); } Result IRBuilder::getLabelType(Name labelName) { @@ -886,16 +885,11 @@ Result IRBuilder::finishScope(Block* block) { } Result<> IRBuilder::visitElse() { - auto& scope = getScope(); + auto scope = getScope(); auto* iff = scope.getIf(); if (!iff) { return Err{"unexpected else"}; } - auto originalLabel = scope.getOriginalLabel(); - auto label = scope.label; - auto labelUsed = scope.labelUsed; - auto inputType = scope.inputType; - auto inputLocal = scope.inputLocal; auto expr = finishScope(); CHECK_ERR(expr); iff->ifTrue = *expr; @@ -905,12 +899,11 @@ Result<> IRBuilder::visitElse() { lastBinaryPos - codeSectionOffset; } - return pushScope(ScopeCtx::makeElse( - iff, originalLabel, label, labelUsed, inputType, inputLocal)); + return pushScope(ScopeCtx::makeElse(std::move(scope))); } Result<> IRBuilder::visitCatch(Name tag) { - auto& scope = getScope(); + auto scope = getScope(); bool wasTry = true; auto* tryy = scope.getTry(); if (!tryy) { @@ -920,10 +913,6 @@ Result<> IRBuilder::visitCatch(Name tag) { if (!tryy) { return Err{"unexpected catch"}; } - auto originalLabel = scope.getOriginalLabel(); - auto label = scope.label; - auto labelUsed = scope.labelUsed; - auto branchLabel = scope.branchLabel; auto expr = finishScope(); CHECK_ERR(expr); if (wasTry) { @@ -938,8 +927,7 @@ Result<> IRBuilder::visitCatch(Name tag) { delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; } - CHECK_ERR(pushScope( - ScopeCtx::makeCatch(tryy, originalLabel, label, labelUsed, branchLabel))); + CHECK_ERR(pushScope(ScopeCtx::makeCatch(std::move(scope), tryy))); // Push a pop for the exception payload if necessary. auto params = wasm.getTag(tag)->sig.params; if (params != Type::none) { @@ -953,7 +941,7 @@ Result<> IRBuilder::visitCatch(Name tag) { } Result<> IRBuilder::visitCatchAll() { - auto& scope = getScope(); + auto scope = getScope(); bool wasTry = true; auto* tryy = scope.getTry(); if (!tryy) { @@ -963,10 +951,6 @@ Result<> IRBuilder::visitCatchAll() { if (!tryy) { return Err{"unexpected catch"}; } - auto originalLabel = scope.getOriginalLabel(); - auto label = scope.label; - auto labelUsed = scope.labelUsed; - auto branchLabel = scope.branchLabel; auto expr = finishScope(); CHECK_ERR(expr); if (wasTry) { @@ -980,8 +964,7 @@ Result<> IRBuilder::visitCatchAll() { delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; } - return pushScope( - ScopeCtx::makeCatchAll(tryy, originalLabel, label, labelUsed, branchLabel)); + return pushScope(ScopeCtx::makeCatchAll(std::move(scope), tryy)); } Result<> IRBuilder::visitDelegate(Index label) { @@ -1028,20 +1011,17 @@ Result<> IRBuilder::visitEnd() { auto expr = finishScope(scope.getBlock()); CHECK_ERR(expr); + bool isTry = scope.getTry() || scope.getCatch() || scope.getCatchAll(); + auto& label = isTry ? scope.branchLabel : scope.label; + auto blockType = scope.getResultType(); + // If the scope expression cannot be directly labeled, we may need to wrap it - // in a block. It's possible that the scope expression becomes typed - // unreachable when it is finalized, but if the wrapper block is targeted by - // any branches, the target block needs to have the original non-unreachable - // type of the scope expression. - auto originalScopeType = scope.getResultType(); + // in a block. auto maybeWrapForLabel = [&](Expression* curr) -> Expression* { - bool isTry = scope.getTry() || scope.getCatch() || scope.getCatchAll(); - auto& label = isTry ? scope.branchLabel : scope.label; if (!label) { return curr; } - auto blockType = - scope.labelUsed ? originalScopeType : scope.getResultType(); + curr = fixExtraOutput(scope, label, curr); // We can re-use unnamed blocks instead of wrapping them. if (auto* block = curr->dynCast(); block && !block->name) { block->name = label; @@ -1074,12 +1054,13 @@ Result<> IRBuilder::visitEnd() { labelHint = 0; } else if (auto* block = scope.getBlock()) { assert(*expr == block); - block->name = scope.label; + block = fixExtraOutput(scope, label, block)->cast(); + block->name = label; block->finalize(block->type, scope.labelUsed ? Block::HasBreak : Block::NoBreak); push(block); } else if (auto* loop = scope.getLoop()) { - loop->body = *expr; + loop->body = fixExtraOutput(scope, label, *expr); loop->name = scope.label; if (scope.inputType != Type::none && scope.labelUsed) { // Branches to this loop carry values, but Binaryen IR does not support @@ -1126,6 +1107,120 @@ Result<> IRBuilder::visitEnd() { return Ok{}; } +Result> +IRBuilder::getExtraOutputLocalAndLabel(Index label, size_t extraArity) { + auto scope = getScope(label); + CHECK_ERR(scope); + auto& s = **scope; + auto i = extraArity - 1; + if (s.outputLabels.size() < extraArity) { + s.outputLocals.resize(extraArity); + s.outputLabels.resize(extraArity); + } + if (!s.outputLabels[i]) { + auto labelType = s.getLabelType(); + auto it = labelType.begin(); + Type extraType = Tuple(it, it + extraArity); + auto local = addScratchLocal(extraType); + CHECK_ERR(local); + auto name = getLabelName(label); + s.outputLocals[i] = *local; + s.outputLabels[i] = makeFresh(*name); + } + + return std::make_pair(s.outputLocals[i], s.outputLabels[i]); +} + +// If there are branches to this scope that carried extra values in scratch +// locals, we need to set up the trampolines those branches will go to. The +// trampolines get the values out of the scratch locals and onto the stack in +// the correct order. +Expression* +IRBuilder::fixExtraOutput(ScopeCtx& scope, Name label, Expression* curr) { + // Add a trampoline branch target. Reuse unnamed blocks. + auto addTrampoline = + [&](Type receivedType, Name trampolineLabel, Name skipLabel) { + if (auto* block = curr->dynCast(); block && !block->name) { + block->name = trampolineLabel; + if (block->list.back()->type == Type::none) { + block->list.push_back(builder.makeBreak(skipLabel)); + } else { + block->list.back() = builder.makeBreak(skipLabel, block->list.back()); + } + block->type = receivedType; + } else { + assert(curr->type != Type::none); + curr = builder.makeBlock( + trampolineLabel, {builder.makeBreak(skipLabel, curr)}, receivedType); + } + }; + + auto labelType = scope.getLabelType(); + Name fallthroughLabel; + for (Index i = 0; i < scope.outputLabels.size(); ++i) { + auto extraLabel = scope.outputLabels[i]; + if (!extraLabel) { + continue; + } + + auto extraLocal = scope.outputLocals[i]; + auto extraType = func->getLocalType(extraLocal); + Type receivedType = + Tuple(labelType.begin() + extraType.size(), labelType.end()); + + // For normal blocks, the original fallthrough values can be sent to the + // scope label and they will end up in the right place, but for loops, the + // fallthrough values should _not_ go to the scope label. We will add a new + // branch target at the end to send the fallthrough values to. + if (scope.getLoop() && !fallthroughLabel) { + fallthroughLabel = makeFresh(label); + addTrampoline(receivedType, extraLabel, fallthroughLabel); + } else { + addTrampoline(receivedType, extraLabel, label); + } + + // If all the received values are in the scratch local, just fetch them out. + if (receivedType == Type::none) { + assert(extraType == labelType); + curr = builder.makeSequence( + curr, builder.makeLocalGet(extraLocal, extraType), extraType); + continue; + } + + // Otherwise, we have to reorder the received values past the extra values + // from the scratch local using an additional scratch local. + auto receivedLocal = *addScratchLocal(receivedType); + curr = builder.makeLocalSet(receivedLocal, curr); + + // Concatenate the extra values and received values into a tuple. + std::vector elems; + if (extraType.isSingle()) { + elems.push_back(builder.makeLocalGet(extraLocal, extraType)); + } else { + for (Index j = 0; j < extraType.size(); ++j) { + elems.push_back(builder.makeTupleExtract( + builder.makeLocalGet(extraLocal, extraType), j)); + } + } + if (receivedType.isSingle()) { + elems.push_back(builder.makeLocalGet(receivedLocal, receivedType)); + } else { + for (Index j = 0; j < receivedType.size(); ++j) { + elems.push_back(builder.makeTupleExtract( + builder.makeLocalGet(receivedLocal, receivedType), j)); + } + } + + curr = builder.makeSequence(curr, builder.makeTupleMake(elems), labelType); + } + + if (fallthroughLabel) { + // The loop fallthrough values need to propagate to one final trampoline. + addTrampoline(scope.getResultType(), fallthroughLabel, label); + } + return curr; +} + // Branches to this loop need to be trampolined through code that sets the value // carried by the branch to the appropriate scratch local before branching to // the loop. Transform this: @@ -1830,9 +1925,109 @@ Result<> IRBuilder::makeBrOn(Index label, BrOnOp op, Type in, Type out) { return Err{"expected input to match input type annotation"}; } } - auto name = getLabelName(label); - CHECK_ERR(name); - push(builder.makeBrOn(op, *name, curr.ref, out)); + + // Extra values need to be sent in a scratch local. + auto labelType = getLabelType(label); + CHECK_ERR(labelType); + auto extraArity = labelType->size(); + switch (op) { + case BrOnNull: + // Modeled as sending no values. + break; + case BrOnNonNull: + case BrOnCast: + case BrOnCastFail: + // Modeled as sending one value. + extraArity -= 1; + break; + } + + // Before we can put the extra values into the output scratch local, we need + // to stash the value under test in another scratch local. Find the type of + // that local. + Type testType; + switch (op) { + case BrOnNull: + case BrOnNonNull: + testType = curr.ref->type; + break; + case BrOnCast: + case BrOnCastFail: + testType = in; + break; + } + + // If the value under test is unreachable, then we can proceed without putting + // anything in locals since the branch will never be taken. The extra values + // will just be dropped. We can't leave this optimization to DCE because we + // wouldn't know what type to use for the scratch local if we tried to + // continue. + if (!extraArity || testType == Type::unreachable) { + auto name = getLabelName(label); + CHECK_ERR(name); + + push(builder.makeBrOn(op, *name, curr.ref, out)); + return Ok{}; + } + + auto testLocal = addScratchLocal(testType); + CHECK_ERR(testLocal); + + // Put the value under test back on the stack and stash it. + getScope().exprStack.push_back(curr.ref); + CHECK_ERR(makeLocalSet(*testLocal)); + + // Now we can stash the extra values. + auto info = getExtraOutputLocalAndLabel(label, extraArity); + CHECK_ERR(info); + auto [extraLocal, extraLabel] = *info; + CHECK_ERR(makeLocalSet(extraLocal)); + + // Restore the test value. + CHECK_ERR(makeLocalGet(*testLocal)); + + // Perform the branch. + CHECK_ERR(visitBrOn(&curr)); + push(builder.makeBrOn(op, extraLabel, curr.ref, out)); + + // If the branch wasn't taken, we need to leave the extra values on the + // stack. For all instructions except br_on_non_null the extra values need + // to be under the result value. br_on_non_null does not have a result + // value, so it is simpler. + if (op == BrOnNonNull) { + CHECK_ERR(makeLocalGet(extraLocal)); + return Ok{}; + } + + Type resultType; + switch (op) { + case BrOnNull: + resultType = Type(testType.getHeapType(), NonNullable); + break; + case BrOnNonNull: + WASM_UNREACHABLE("unexpected op"); + case BrOnCast: + if (out.isNullable()) { + resultType = Type(in.getHeapType(), NonNullable); + } else { + resultType = in; + } + break; + case BrOnCastFail: + if (in.isNonNullable()) { + resultType = Type(out.getHeapType(), NonNullable); + } else { + resultType = out; + } + break; + } + + auto resultLocal = addScratchLocal(resultType); + CHECK_ERR(resultLocal); + + CHECK_ERR(makeLocalSet(*resultLocal)); + CHECK_ERR(makeLocalGet(extraLocal)); + CHECK_ERR(makeLocalGet(*resultLocal)); return Ok{}; } diff --git a/test/lit/basic/extra-branch-values.wast b/test/lit/basic/extra-branch-values.wast new file mode 100644 index 00000000000..57d55afa685 --- /dev/null +++ b/test/lit/basic/extra-branch-values.wast @@ -0,0 +1,3106 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all -S -o - | filecheck %s +;; RUN: wasm-opt %s -all -O -S -o - -O | filecheck %s --check-prefix=OPT_O + +(module + ;; CHECK: (import "env" "i32" (global $i32 (mut i32))) + ;; OPT_O: (import "env" "i32" (global $i32 (mut i32))) + (import "env" "i32" (global $i32 (mut i32))) + ;; CHECK: (import "env" "i64" (global $i64 (mut i64))) + ;; OPT_O: (import "env" "i64" (global $i64 (mut i64))) + (import "env" "i64" (global $i64 (mut i64))) + ;; CHECK: (import "env" "any" (global $any (mut (ref any)))) + ;; OPT_O: (import "env" "any" (global $any (mut (ref any)))) + (import "env" "any" (global $any (mut (ref any)))) + ;; CHECK: (import "env" "anyref" (global $anyref (mut anyref))) + ;; OPT_O: (import "env" "anyref" (global $anyref (mut anyref))) + (import "env" "anyref" (global $anyref (mut anyref))) + ;; CHECK: (import "env" "eq" (global $eq (mut (ref eq)))) + ;; OPT_O: (import "env" "eq" (global $eq (mut (ref eq)))) + (import "env" "eq" (global $eq (mut (ref eq)))) + ;; CHECK: (import "env" "eqref" (global $eqref (mut eqref))) + ;; OPT_O: (import "env" "eqref" (global $eqref (mut eqref))) + (import "env" "eqref" (global $eqref (mut eqref))) + + ;; CHECK: (import "env" "use-i32-any" (func $use-i32-any (type $14) (param i32 (ref any)))) + ;; OPT_O: (import "env" "use-i32-any" (func $use-i32-any (type $14) (param i32 (ref any)))) + (import "env" "use-i32-any" (func $use-i32-any (param i32 (ref any)))) + + ;; CHECK: (tag $e (param i32)) + ;; OPT_O: (tag $e (param i32)) + (tag $e (param i32)) + ;; CHECK: (tag $e2 (param i32)) + ;; OPT_O: (tag $e2 (param i32)) + (tag $e2 (param i32)) + + ;; CHECK: (func $br_on_null-one (type $15) (param $0 i32) (param $1 anyref) (result i32) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (block $block (result i32) + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_null $block0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_null-one (type $15) (param $0 i32) (param $1 anyref) (result i32) + ;; OPT_O-NEXT: (block $block (result i32) + ;; OPT_O-NEXT: (block $block0 + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_null $block0 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (i32.const 1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_null-one (export "br_on_null-one") (param i32 anyref) (result i32) + block (result i32) + local.get 0 + local.get 1 + br_on_null 0 + global.set $any + global.set $i32 + i32.const 1 + end + ) + + ;; CHECK: (func $br_on_null-two (type $16) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_4 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_5 i64) + ;; CHECK-NEXT: (local $scratch_6 (ref any)) + ;; CHECK-NEXT: (local $scratch_7 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_8 i32) + ;; CHECK-NEXT: (block $block (type $12) (result i32 i64) + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_null $block0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i64 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_null-two (type $16) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64) + ;; OPT_O-NEXT: (local $3 (tuple i32 i64)) + ;; OPT_O-NEXT: (block $block (type $10) (result i32 i64) + ;; OPT_O-NEXT: (local.set $3 + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (block $block0 + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_null $block0 + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i64 + ;; OPT_O-NEXT: (tuple.extract 2 1 + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (tuple.extract 2 0 + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (i64.const 1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_null-two (export "br_on_null-two") (param i32 i64 anyref) (result i32 i64) + block (result i32 i64) + local.get 0 + local.get 1 + local.get 2 + br_on_null 0 + global.set $any + global.set $i64 + global.set $i32 + i32.const 0 + i64.const 1 + end + ) + + ;; CHECK: (func $br_on_non_null-one (type $8) (param $0 i32) (param $1 anyref) (result i32 (ref any)) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (block $block (type $3) (result i32 (ref any)) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (block $block0 (result (ref any)) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_on_non_null $block0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (global.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_non_null-one (type $6) (param $0 i32) (param $1 anyref) (result i32 (ref any)) + ;; OPT_O-NEXT: (block $block (type $1) (result i32 (ref any)) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result (ref any)) + ;; OPT_O-NEXT: (br_on_non_null $block0 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 1) + ;; OPT_O-NEXT: (global.get $any) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_non_null-one (export "br_on_non_null-one") (param i32 anyref) (result i32 (ref any)) + block (result i32 (ref any)) + local.get 0 + local.get 1 + br_on_non_null 0 + global.set $i32 + i32.const 1 + global.get $any + end + ) + + ;; CHECK: (func $br_on_non_null-two (type $9) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_4 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_5 i64) + ;; CHECK-NEXT: (local $scratch_6 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_7 i32) + ;; CHECK-NEXT: (local $scratch_8 (ref any)) + ;; CHECK-NEXT: (block $block (type $4) (result i32 i64 (ref any)) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block $block0 (result (ref any)) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_on_non_null $block0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i64 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: (global.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_non_null-two (type $7) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) + ;; OPT_O-NEXT: (block $block (type $4) (result i32 i64 (ref any)) + ;; OPT_O-NEXT: (tuple.make 3 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: (block $block0 (result (ref any)) + ;; OPT_O-NEXT: (br_on_non_null $block0 + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i64 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 3 + ;; OPT_O-NEXT: (i32.const 1) + ;; OPT_O-NEXT: (i64.const 2) + ;; OPT_O-NEXT: (global.get $any) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_non_null-two (export "br_on_non_null-two") (param i32 i64 anyref) (result i32 i64 (ref any)) + block (result i32 i64 (ref any)) + local.get 0 + local.get 1 + local.get 2 + br_on_non_null 0 + global.set $i64 + global.set $i32 + i32.const 1 + i64.const 2 + global.get $any + end + ) + + ;; CHECK: (func $br_on_cast-one (type $2) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 eqref) + ;; CHECK-NEXT: (block $block (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $block0 (result eqref) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_cast-one (type $3) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $block (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result eqref) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_cast-one (export "br_on_cast-one") (param i32 anyref) (result i32 eqref) + block (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + i32.const 0 + global.get $eq + end + ) + + ;; CHECK: (func $br_on_cast-two (type $17) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_4 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_5 i64) + ;; CHECK-NEXT: (local $scratch_6 (ref any)) + ;; CHECK-NEXT: (local $scratch_7 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_8 i32) + ;; CHECK-NEXT: (local $scratch_9 eqref) + ;; CHECK-NEXT: (block $block (type $13) (result i32 i64 eqref) + ;; CHECK-NEXT: (local.set $scratch_9 + ;; CHECK-NEXT: (block $block0 (result eqref) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_cast $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i64 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_cast-two (type $17) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 eqref) + ;; OPT_O-NEXT: (block $block (type $11) (result i32 i64 eqref) + ;; OPT_O-NEXT: (tuple.make 3 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: (block $block0 (result eqref) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i64 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 3 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (i64.const 1) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_cast-two (export "br_on_cast-two") (param i32 i64 anyref) (result i32 i64 eqref) + block (result i32 i64 eqref) + local.get 0 + local.get 1 + local.get 2 + br_on_cast 0 anyref eqref + global.set $any + global.set $i64 + global.set $i32 + i32.const 0 + i64.const 1 + global.get $eq + end + ) + + ;; CHECK: (func $br_on_cast-nn (type $18) (param $0 i32) (param $1 (ref any)) (result i32 (ref eq)) + ;; CHECK-NEXT: (local $scratch (ref any)) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 (ref eq)) + ;; CHECK-NEXT: (block $block (type $5) (result i32 (ref eq)) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $block0 (result (ref eq)) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast $block0 (ref any) (ref eq) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_cast-nn (type $18) (param $0 i32) (param $1 (ref any)) (result i32 (ref eq)) + ;; OPT_O-NEXT: (block $block (type $2) (result i32 (ref eq)) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result (ref eq)) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block0 (ref any) (ref eq) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_cast-nn (export "br_on_cast-nn") (param i32 (ref any)) (result i32 (ref eq)) + block (result i32 (ref eq)) + local.get 0 + local.get 1 + br_on_cast 0 (ref any) (ref eq) + global.set $any + global.set $i32 + i32.const 0 + global.get $eq + end + ) + + ;; CHECK: (func $br_on_cast-to-nn (type $19) (param $0 i32) (param $1 anyref) (result i32 (ref eq)) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 anyref) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 (ref eq)) + ;; CHECK-NEXT: (block $block (type $5) (result i32 (ref eq)) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $block0 (result (ref eq)) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast $block0 anyref (ref eq) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $anyref + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_cast-to-nn (type $19) (param $0 i32) (param $1 anyref) (result i32 (ref eq)) + ;; OPT_O-NEXT: (block $block (type $2) (result i32 (ref eq)) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result (ref eq)) + ;; OPT_O-NEXT: (global.set $anyref + ;; OPT_O-NEXT: (br_on_cast $block0 anyref (ref eq) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_cast-to-nn (export "br_on_cast-to-nn") (param i32 anyref) (result i32 (ref eq)) + block (result i32 (ref eq)) + local.get 0 + local.get 1 + br_on_cast 0 anyref (ref eq) + global.set $anyref + global.set $i32 + i32.const 0 + global.get $eq + end + ) + + ;; CHECK: (func $br_on_cast_fail-one (type $8) (param $0 i32) (param $1 anyref) (result i32 (ref any)) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 eqref) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 (ref any)) + ;; CHECK-NEXT: (block $block (type $3) (result i32 (ref any)) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $block0 (result (ref any)) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast_fail $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $eqref + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_cast_fail-one (type $6) (param $0 i32) (param $1 anyref) (result i32 (ref any)) + ;; OPT_O-NEXT: (block $block (type $1) (result i32 (ref any)) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result (ref any)) + ;; OPT_O-NEXT: (global.set $eqref + ;; OPT_O-NEXT: (br_on_cast_fail $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $any) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_cast_fail-one (export "br_on_cast_fail-one") (param i32 anyref) (result i32 (ref any)) + block (result i32 (ref any)) + local.get 0 + local.get 1 + br_on_cast_fail 0 anyref eqref + global.set $eqref + global.set $i32 + i32.const 0 + global.get $any + end + ) + + ;; CHECK: (func $br_on_cast_fail-two (type $9) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_4 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_5 i64) + ;; CHECK-NEXT: (local $scratch_6 eqref) + ;; CHECK-NEXT: (local $scratch_7 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_8 i32) + ;; CHECK-NEXT: (local $scratch_9 (ref any)) + ;; CHECK-NEXT: (block $block (type $4) (result i32 i64 (ref any)) + ;; CHECK-NEXT: (local.set $scratch_9 + ;; CHECK-NEXT: (block $block0 (result (ref any)) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_cast_fail $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $eqref + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i64 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: (global.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_cast_fail-two (type $7) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) + ;; OPT_O-NEXT: (block $block (type $4) (result i32 i64 (ref any)) + ;; OPT_O-NEXT: (tuple.make 3 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: (block $block0 (result (ref any)) + ;; OPT_O-NEXT: (global.set $eqref + ;; OPT_O-NEXT: (br_on_cast_fail $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i64 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 3 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (i64.const 1) + ;; OPT_O-NEXT: (global.get $any) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_cast_fail-two (export "br_on_cast_fail-two") (param i32 i64 anyref) (result i32 i64 (ref any)) + block (result i32 i64 (ref any)) + local.get 0 + local.get 1 + local.get 2 + br_on_cast_fail 0 anyref eqref + global.set $eqref + global.set $i64 + global.set $i32 + i32.const 0 + i64.const 1 + global.get $any + end + ) + + ;; CHECK: (func $br_on_cast_fail-nn (type $20) (param $0 i32) (param $1 (ref any)) (result i32 (ref any)) + ;; CHECK-NEXT: (local $scratch (ref any)) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref eq)) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 (ref any)) + ;; CHECK-NEXT: (block $block (type $3) (result i32 (ref any)) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $block0 (result (ref any)) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast_fail $block0 (ref any) (ref eq) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $eq + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_cast_fail-nn (type $20) (param $0 i32) (param $1 (ref any)) (result i32 (ref any)) + ;; OPT_O-NEXT: (block $block (type $1) (result i32 (ref any)) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result (ref any)) + ;; OPT_O-NEXT: (global.set $eq + ;; OPT_O-NEXT: (br_on_cast_fail $block0 (ref any) (ref eq) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $any) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_cast_fail-nn (export "br_on_cast_fail-nn") (param i32 (ref any)) (result i32 (ref any)) + block (result i32 (ref any)) + local.get 0 + local.get 1 + br_on_cast_fail 0 (ref any) (ref eq) + global.set $eq + global.set $i32 + i32.const 0 + global.get $any + end + ) + + ;; CHECK: (func $br_on_cast_fail-to-nn (type $10) (param $0 i32) (param $1 anyref) (result i32 anyref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref eq)) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 anyref) + ;; CHECK-NEXT: (block $block (type $1) (result i32 anyref) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $block0 (result anyref) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast_fail $block0 anyref (ref eq) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $eq + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $br_on_cast_fail-to-nn (type $8) (param $0 i32) (param $1 anyref) (result i32 anyref) + ;; OPT_O-NEXT: (block $block (type $12) (result i32 anyref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result anyref) + ;; OPT_O-NEXT: (global.set $eq + ;; OPT_O-NEXT: (br_on_cast_fail $block0 anyref (ref eq) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $any) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $br_on_cast_fail-to-nn (export "br_on_cast_fail-to-nn") (param i32 anyref) (result i32 anyref) + block (result i32 anyref) + local.get 0 + local.get 1 + br_on_cast_fail 0 anyref (ref eq) + global.set $eq + global.set $i32 + i32.const 0 + global.get $any + end + ) + + ;; CHECK: (func $matching-branches (type $21) (param $0 i32) (param $1 anyref) (param $2 i32) (param $3 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 (ref any)) + ;; CHECK-NEXT: (local $scratch_8 i32) + ;; CHECK-NEXT: (local $scratch_9 anyref) + ;; CHECK-NEXT: (local $scratch_10 i32) + ;; CHECK-NEXT: (local $scratch_11 (ref any)) + ;; CHECK-NEXT: (local $scratch_12 i32) + ;; CHECK-NEXT: (local $scratch_13 eqref) + ;; CHECK-NEXT: (block $block (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_13 + ;; CHECK-NEXT: (block $block0 (result eqref) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (br_on_cast $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_10 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_9 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_11 + ;; CHECK-NEXT: (br_on_cast $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_12 + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: (local.get $scratch_13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $matching-branches (type $21) (param $0 i32) (param $1 anyref) (param $2 i32) (param $3 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (local $4 eqref) + ;; OPT_O-NEXT: (block $block (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (local.set $4 + ;; OPT_O-NEXT: (block $block0 (result eqref) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (local.set $0 + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (local.get $4) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $matching-branches (export "matching-branches") (param i32 anyref i32 anyref) (result i32 eqref) + block (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + local.get 2 + local.get 3 + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + i32.const 0 + global.get $eqref + end + ) + + ;; CHECK: (func $different-branches (type $22) (param $0 i32) (param $1 anyref) (param $2 i32) (param $3 eqref) (param $4 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 i32) + ;; CHECK-NEXT: (local $scratch_8 (ref any)) + ;; CHECK-NEXT: (local $scratch_9 i32) + ;; CHECK-NEXT: (local $scratch_10 anyref) + ;; CHECK-NEXT: (local $scratch_11 (tuple i32 eqref)) + ;; CHECK-NEXT: (local $scratch_12 eqref) + ;; CHECK-NEXT: (local $scratch_13 (ref any)) + ;; CHECK-NEXT: (local $scratch_14 (tuple i32 eqref)) + ;; CHECK-NEXT: (local $scratch_15 i32) + ;; CHECK-NEXT: (local $scratch_16 eqref) + ;; CHECK-NEXT: (block $block (type $0) (result i32 eqref) + ;; CHECK-NEXT: (block $block1 + ;; CHECK-NEXT: (local.set $scratch_16 + ;; CHECK-NEXT: (block $block0 (result eqref) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (br_on_cast $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_9 + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_11 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (block (result eqref) + ;; CHECK-NEXT: (local.set $scratch_12 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_10 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_13 + ;; CHECK-NEXT: (br_on_null $block1 + ;; CHECK-NEXT: (local.get $scratch_10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_15 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_14 + ;; CHECK-NEXT: (local.get $scratch_11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch_14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $eqref + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: (local.get $scratch_16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $different-branches (type $22) (param $0 i32) (param $1 anyref) (param $2 i32) (param $3 eqref) (param $4 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (local $5 (tuple i32 eqref)) + ;; OPT_O-NEXT: (block $block (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (block $block1 + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result eqref) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (local.set $5 + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_null $block1 + ;; OPT_O-NEXT: (local.get $4) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $eqref + ;; OPT_O-NEXT: (tuple.extract 2 1 + ;; OPT_O-NEXT: (local.get $5) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (tuple.extract 2 0 + ;; OPT_O-NEXT: (local.get $5) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (local.get $5) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $different-branches (export "different-branches") (param i32 anyref i32 eqref anyref) (result i32 eqref) + block (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + local.get 2 + local.get 3 + local.get 4 + br_on_null 0 + global.set $any + global.set $eqref + global.set $i32 + i32.const 0 + global.get $eqref + end + ) + + ;; CHECK: (func $different-branches-2 (type $23) (param $0 i32) (param $1 eqref) (param $2 anyref) (param $3 i32) (param $4 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_6 (tuple i32 eqref)) + ;; CHECK-NEXT: (local $scratch_7 eqref) + ;; CHECK-NEXT: (local $scratch_8 (ref any)) + ;; CHECK-NEXT: (local $scratch_9 (tuple i32 eqref)) + ;; CHECK-NEXT: (local $scratch_10 i32) + ;; CHECK-NEXT: (local $scratch_11 anyref) + ;; CHECK-NEXT: (local $scratch_12 i32) + ;; CHECK-NEXT: (local $scratch_13 i32) + ;; CHECK-NEXT: (local $scratch_14 (ref any)) + ;; CHECK-NEXT: (local $scratch_15 i32) + ;; CHECK-NEXT: (local $scratch_16 eqref) + ;; CHECK-NEXT: (block $block (type $0) (result i32 eqref) + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (local.set $scratch_16 + ;; CHECK-NEXT: (block $block1 (result eqref) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result eqref) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (br_on_null $block0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_10 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_9 + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $eqref + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_12 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_13 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_11 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_14 + ;; CHECK-NEXT: (br_on_cast $block1 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_15 + ;; CHECK-NEXT: (local.get $scratch_12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_12) + ;; CHECK-NEXT: (local.get $scratch_16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $different-branches-2 (type $23) (param $0 i32) (param $1 eqref) (param $2 anyref) (param $3 i32) (param $4 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (local $5 (tuple i32 eqref)) + ;; OPT_O-NEXT: (block $block (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (block $block0 + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: (block $block1 (result eqref) + ;; OPT_O-NEXT: (local.set $5 + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_null $block0 + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $eqref + ;; OPT_O-NEXT: (tuple.extract 2 1 + ;; OPT_O-NEXT: (local.get $5) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (tuple.extract 2 0 + ;; OPT_O-NEXT: (local.get $5) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block1 anyref eqref + ;; OPT_O-NEXT: (local.get $4) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (local.get $5) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $different-branches-2 (export "different-branches-2") (param i32 eqref anyref i32 anyref) (result i32 eqref) + block (result i32 eqref) + local.get 0 + local.get 1 + local.get 2 + br_on_null 0 + global.set $any + global.set $eqref + global.set $i32 + local.get 3 + local.get 4 + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + i32.const 0 + global.get $eqref + end + ) + + ;; CHECK: (func $nested-branches (type $24) (param $0 i32) (param $1 anyref) (param $2 anyref) (result i32) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (local $scratch_6 (ref any)) + ;; CHECK-NEXT: (local $scratch_7 anyref) + ;; CHECK-NEXT: (local $scratch_8 i32) + ;; CHECK-NEXT: (local $scratch_9 i32) + ;; CHECK-NEXT: (local $scratch_10 (ref any)) + ;; CHECK-NEXT: (local $scratch_11 i32) + ;; CHECK-NEXT: (local $scratch_12 eqref) + ;; CHECK-NEXT: (local $scratch_13 (tuple i32 eqref)) + ;; CHECK-NEXT: (local $scratch_14 i32) + ;; CHECK-NEXT: (block $block1 (result i32) + ;; CHECK-NEXT: (block $block10 + ;; CHECK-NEXT: (local.set $scratch_14 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_13 + ;; CHECK-NEXT: (block $block (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_12 + ;; CHECK-NEXT: (block $block0 (result eqref) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_cast $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_9 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_10 + ;; CHECK-NEXT: (br_on_null $block10 + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_11 + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch_10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block1 + ;; CHECK-NEXT: (local.get $scratch_14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $nested-branches (type $24) (param $0 i32) (param $1 anyref) (param $2 anyref) (result i32) + ;; OPT_O-NEXT: (block $block1 (result i32) + ;; OPT_O-NEXT: (block $block10 + ;; OPT_O-NEXT: (br $block1 + ;; OPT_O-NEXT: (tuple.extract 2 0 + ;; OPT_O-NEXT: (block $block (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result eqref) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (drop + ;; OPT_O-NEXT: (br_on_null $block10 + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $nested-branches (export "nested-branches") (param i32 anyref anyref) (result i32) + block (result i32) + block (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + global.set $any + local.get 2 + br_on_null 1 + drop + drop + i32.const 0 + global.get $eqref + end + drop + end + ) + + ;; CHECK: (func $with-block-param (type $25) (param $0 i64) (param $1 anyref) (result i64 eqref) + ;; CHECK-NEXT: (local $scratch i64) + ;; CHECK-NEXT: (local $scratch_3 anyref) + ;; CHECK-NEXT: (local $scratch_4 i64) + ;; CHECK-NEXT: (local $scratch_5 i64) + ;; CHECK-NEXT: (local $scratch_6 (ref any)) + ;; CHECK-NEXT: (local $scratch_7 i64) + ;; CHECK-NEXT: (local $scratch_8 eqref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $block (type $6) (result i64 eqref) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block $block0 (result eqref) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_cast $block0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i64 + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $with-block-param (type $25) (param $0 i64) (param $1 anyref) (result i64 eqref) + ;; OPT_O-NEXT: (block $block (type $13) (result i64 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $block0 (result eqref) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $block0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i64 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $block + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i64.const 1) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $with-block-param (export "with-block-param") (param i64 anyref) (result i64 eqref) + local.get 0 + block (param i64) (result i64 eqref) + local.get 1 + br_on_cast 0 anyref eqref + global.set $any + global.set $i64 + i64.const 1 + global.get $eqref + end + ) + + ;; CHECK: (func $loop (type $26) (param $0 i32) (param $1 anyref) + ;; CHECK-NEXT: (local $scratch (tuple i32 anyref)) + ;; CHECK-NEXT: (local $scratch_3 (tuple i32 anyref)) + ;; CHECK-NEXT: (local $scratch_4 anyref) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 (ref any)) + ;; CHECK-NEXT: (local $scratch_8 i32) + ;; CHECK-NEXT: (local $scratch_9 anyref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label2 + ;; CHECK-NEXT: (block $label3 + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (type $1) (result i32 anyref) + ;; CHECK-NEXT: (block $label1 + ;; CHECK-NEXT: (local.set $scratch_9 + ;; CHECK-NEXT: (block $label0 (result anyref) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_3 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $loop (type $26) (param $0 i32) (param $1 anyref) + ;; OPT_O-NEXT: (loop $label2 + ;; OPT_O-NEXT: (block $label3 + ;; OPT_O-NEXT: (local.set $1 + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $label3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $label2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $loop (export "loop") (param i32 anyref) + local.get 0 + local.get 1 + loop (param i32 anyref) + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + end + ) + + ;; CHECK: (func $loop-results (type $10) (param $0 i32) (param $1 anyref) (result i32 anyref) + ;; CHECK-NEXT: (local $scratch (tuple i32 anyref)) + ;; CHECK-NEXT: (local $scratch_3 (tuple i32 anyref)) + ;; CHECK-NEXT: (local $scratch_4 anyref) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 (ref any)) + ;; CHECK-NEXT: (local $scratch_8 anyref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label2 (type $1) (result i32 anyref) + ;; CHECK-NEXT: (block $label3 (type $1) (result i32 anyref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (type $1) (result i32 anyref) + ;; CHECK-NEXT: (br $label3 + ;; CHECK-NEXT: (block $label1 (type $1) (result i32 anyref) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block $label0 (result anyref) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_3 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label1 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $loop-results (type $8) (param $0 i32) (param $1 anyref) (result i32 anyref) + ;; OPT_O-NEXT: (local $2 (tuple i32 anyref)) + ;; OPT_O-NEXT: (local $3 eqref) + ;; OPT_O-NEXT: (local.set $2 + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (loop $label2 (type $1) (result i32 (ref any)) + ;; OPT_O-NEXT: (block $label3 (type $1) (result i32 (ref any)) + ;; OPT_O-NEXT: (local.set $2 + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (br $label3 + ;; OPT_O-NEXT: (block $label1 (type $1) (result i32 (ref any)) + ;; OPT_O-NEXT: (local.set $3 + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label1 + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.tee $0 + ;; OPT_O-NEXT: (tuple.extract 2 0 + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (tuple.extract 2 1 + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $label2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $loop-results (export "loop-results") (param i32 anyref) (result i32 anyref) + local.get 0 + local.get 1 + loop (param i32 anyref) (result i32 anyref) + br_on_cast 0 anyref eqref + end + ) + + ;; CHECK: (func $if (type $11) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (local $scratch_6 (ref any)) + ;; CHECK-NEXT: (local $scratch_7 i32) + ;; CHECK-NEXT: (local $scratch_8 eqref) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (if (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $if (type $9) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (if (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (then + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (else + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 1) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $if (export "if") (param i32 i32 anyref) (result i32 eqref) + local.get 0 + if (result i32 eqref) + local.get 1 + local.get 2 + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + i32.const 0 + global.get $eqref + else + i32.const 1 + global.get $eq + end + ) + + ;; CHECK: (func $else (type $11) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (local $scratch_6 (ref any)) + ;; CHECK-NEXT: (local $scratch_7 i32) + ;; CHECK-NEXT: (local $scratch_8 eqref) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (if (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $else (type $9) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (if (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (then + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (else + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 1) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $else (export "else") (param i32 i32 anyref) (result i32 eqref) + local.get 0 + if (result i32 eqref) + i32.const 0 + global.get $eqref + else + local.get 1 + local.get 2 + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + i32.const 1 + global.get $eq + end + ) + + ;; CHECK: (func $if-else-params (type $27) (param $0 i32) (param $1 i32) (param $2 anyref) (param $3 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_5 anyref) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 i32) + ;; CHECK-NEXT: (local $scratch_8 (ref any)) + ;; CHECK-NEXT: (local $scratch_9 i32) + ;; CHECK-NEXT: (local $scratch_10 anyref) + ;; CHECK-NEXT: (local $scratch_11 i32) + ;; CHECK-NEXT: (local $scratch_12 (ref any)) + ;; CHECK-NEXT: (local $scratch_13 i32) + ;; CHECK-NEXT: (local $scratch_14 eqref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_14 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (if (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_8 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_9 + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_11 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_10 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_12 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_13 + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: (local.get $scratch_14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $if-else-params (type $27) (param $0 i32) (param $1 i32) (param $2 anyref) (param $3 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (if (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: (then + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (else + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $3) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $if-else-params (export "if-else-params") (param i32 i32 anyref anyref) (result i32 eqref) + local.get 0 + local.get 1 + if (param i32) (result i32 eqref) + local.get 2 + br_on_cast 0 anyref eqref + global.set $any + global.get $eq + else + local.get 3 + br_on_cast 0 anyref eqref + global.set $any + global.get $eqref + end + ) + + ;; CHECK: (func $try-catch (type $2) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 eqref) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (try (type $0) (result i32 eqref) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $use-i32-any + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $try-catch (type $3) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (try (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (do + ;; OPT_O-NEXT: (call $use-i32-any + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (catch $e + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (pop i32) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $try-catch (export "try-catch") (param i32 anyref) (result i32 eqref) + try (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + call $use-i32-any + i32.const 0 + global.get $eqref + catch $e + global.get $eq + end + ) + + ;; CHECK: (func $try-catch_all (type $2) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 eqref) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (try (type $0) (result i32 eqref) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $use-i32-any + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $try-catch_all (type $3) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (try (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (do + ;; OPT_O-NEXT: (call $use-i32-any + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (catch_all + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 1) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $try-catch_all (export "try-catch_all") (param i32 anyref) (result i32 eqref) + try (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + call $use-i32-any + i32.const 0 + global.get $eqref + catch_all + i32.const 1 + global.get $eq + end + ) + + ;; CHECK: (func $try-delegate (type $2) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 eqref) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (try (type $0) (result i32 eqref) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $use-i32-any + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $try-delegate (type $3) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (try (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (do + ;; OPT_O-NEXT: (call $use-i32-any + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (delegate 2) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $try-delegate (export "try-delegate") (param i32 anyref) (result i32 eqref) + try (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + call $use-i32-any + i32.const 0 + global.get $eqref + delegate 0 + ) + + ;; CHECK: (func $try-everything-params (type $2) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_3 anyref) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (local $scratch_6 (ref any)) + ;; CHECK-NEXT: (local $scratch_7 eqref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (try (type $0) (result i32 eqref) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $use-i32-any + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e2 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $try-everything-params (type $3) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (try (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (do + ;; OPT_O-NEXT: (call $use-i32-any + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (catch $e + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (pop i32) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (catch $e2 + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (pop i32) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (catch_all + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 1) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $try-everything-params (export "try-everything-params") (param i32 anyref) (result i32 eqref) + local.get 0 + try (param i32) (result i32 eqref) + local.get 1 + br_on_cast 0 anyref eqref + call $use-i32-any + i32.const 0 + global.get $eqref + catch $e + global.get $eqref + catch $e2 + global.get $eqref + catch_all + i32.const 1 + global.get $eqref + end + ) + + ;; CHECK: (func $try_table (type $2) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 eqref) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (try_table (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $use-i32-any + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $try_table (type $3) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (try_table (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (call $use-i32-any + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $try_table (export "try_table") (param i32 anyref) (result i32 eqref) + try_table (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + call $use-i32-any + i32.const 0 + global.get $eqref + end + ) + + ;; CHECK: (func $try_table-params (type $2) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_3 anyref) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 i32) + ;; CHECK-NEXT: (local $scratch_6 (ref any)) + ;; CHECK-NEXT: (local $scratch_7 eqref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (try_table (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $use-i32-any + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $try_table-params (type $3) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (try_table (type $2) (result i32 (ref eq)) + ;; OPT_O-NEXT: (call $use-i32-any + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eq) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $try_table-params (export "try_table-params") (param i32 anyref) (result i32 eqref) + local.get 0 + try_table (param i32) (result i32 eqref) + local.get 1 + br_on_cast 0 anyref eqref + call $use-i32-any + i32.const 0 + global.get $eq + end + ) + + ;; CHECK: (func $branch-to-func (type $2) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 i32) + ;; CHECK-NEXT: (local $scratch_7 eqref) + ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (block $label0 (result eqref) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $i32 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_6 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $eqref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $branch-to-func (type $3) (param $0 i32) (param $1 anyref) (result i32 eqref) + ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: (block $label0 (result eqref) + ;; OPT_O-NEXT: (global.set $any + ;; OPT_O-NEXT: (br_on_cast $label0 anyref eqref + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (global.set $i32 + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (br $label + ;; OPT_O-NEXT: (tuple.make 2 + ;; OPT_O-NEXT: (i32.const 0) + ;; OPT_O-NEXT: (global.get $eqref) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + (func $branch-to-func (export "branch-to-func") (param i32 anyref) (result i32 eqref) + local.get 0 + local.get 1 + br_on_cast 0 anyref eqref + global.set $any + global.set $i32 + i32.const 0 + global.get $eqref + ) +) From 3a3cad267a47149669d0f9b4cdfd2eb22c70622a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 13 Jan 2025 13:22:02 -0800 Subject: [PATCH 232/622] Do not fuzz gc-atomics.wast (#7211) The interpreter does not yet support the struct RMW ops, so this test file cannot be fuzzed. --- scripts/test/fuzzing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index be99af74631..f5fee637582 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -75,6 +75,8 @@ 'typed_continuations_contnew.wast', 'typed_continuations_contbind.wast', 'typed_continuations_suspend.wast', + # the fuzzer does not support struct RMW ops + 'gc-atomics.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 From 3f6831c0bd147ae1ae0ab1d9187d37bce7cca38b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 13 Jan 2025 16:34:21 -0800 Subject: [PATCH 233/622] Fuzzer: Fix up rethrows after changes (#7207) We may move a rethrow into an invalid position when we mutate() etc. This finds such invalid rethrows and replaces them with something trivial so that we validate. To do so, use an expression stack rather than a control flow stack in the fixer, as we need to see which child of a try the rethrow ends up being (so we need the full path from it to the try). --- src/tools/fuzzing/fuzzing.cpp | 48 ++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b3c9497f996..28f7a29dbc6 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1568,7 +1568,7 @@ void TranslateToFuzzReader::mutate(Function* func) { void TranslateToFuzzReader::fixAfterChanges(Function* func) { struct Fixer - : public ControlFlowWalker> { + : public ExpressionStackWalker> { Module& wasm; TranslateToFuzzReader& parent; @@ -1606,12 +1606,15 @@ void TranslateToFuzzReader::fixAfterChanges(Function* func) { void replace() { replaceCurrent(parent.makeTrivial(getCurrent()->type)); } bool hasBreakTarget(Name name) { - if (controlFlowStack.empty()) { + // The break must be on top. + assert(!expressionStack.empty()); + if (expressionStack.size() < 2) { + // There must be a scope for this break to be valid. return false; } - Index i = controlFlowStack.size() - 1; + Index i = expressionStack.size() - 2; while (1) { - auto* curr = controlFlowStack[i]; + auto* curr = expressionStack[i]; bool has = false; BranchUtils::operateOnScopeNameDefs(curr, [&](Name& def) { if (def == name) { @@ -1627,6 +1630,43 @@ void TranslateToFuzzReader::fixAfterChanges(Function* func) { i--; } } + + void visitRethrow(Rethrow* curr) { + if (!isValidRethrow(curr->target)) { + replace(); + } + } + + bool isValidRethrow(Name target) { + // The rethrow must be on top. + assert(!expressionStack.empty()); + assert(expressionStack.back()->is()); + if (expressionStack.size() < 2) { + // There must be a try for this rethrow to be valid. + return false; + } + Index i = expressionStack.size() - 2; + while (1) { + auto* curr = expressionStack[i]; + if (auto* tryy = curr->dynCast()) { + // The rethrow must target a try, and must be nested in a catch of + // that try (not the body). Look at the child above us to check, when + // we find the proper try. + if (tryy->name == target) { + if (i + 1 >= expressionStack.size()) { + return false; + } + auto* child = expressionStack[i + 1]; + return child != tryy->body; + } + } + if (i == 0) { + // We never found our try. + return false; + } + i--; + } + } }; Fixer fixer(wasm, *this); fixer.walk(func->body); From e5aaa6c220be03b5d5b5d1a0ea68d789913bd225 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 14 Jan 2025 10:39:22 -0800 Subject: [PATCH 234/622] Enable br_on_null and br_on_non_null spec tests (#7212) Now that we support sending additional values on these branches, we can run their spec tests, so enable them. Also fix a bug with block naming that the spec tests exposed. We still cannot run the br_on_cast and br_on_cast fail spec tests, but update the relevant comments to describe the new reason they fail. --- scripts/test/shared.py | 8 +- src/wasm/wasm-ir-builder.cpp | 1 + test/lit/basic/extra-branch-values.wast | 124 +++++++++++++++++++----- 3 files changed, 103 insertions(+), 30 deletions(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index ac9a408b613..489cfefda09 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -438,8 +438,8 @@ def get_tests(test_dir, extensions=[], recursive=False): 'try_catch.wast', # Requires wast `register` support 'tag.wast', # Non-empty tag results allowed by stack switching 'try_table.wast', # Requires try_table interpretation - 'br_on_non_null.wast', # Requires sending values on br_on_non_null - 'br_on_null.wast', # Requires sending values on br_on_null + # 'br_on_non_null.wast', # Requires sending values on br_on_non_null + # 'br_on_null.wast', # Requires sending values on br_on_null 'local_init.wast', # Requires local validation to respect unnamed blocks 'ref_func.wast', # Requires rejecting undeclared functions references 'ref_is_null.wast', # Requires ref.null wast constants @@ -452,8 +452,8 @@ def get_tests(test_dir, extensions=[], recursive=False): 'array.wast', # Requires support for table default elements 'array_init_elem.wast', # Requires support for elem.drop 'br_if.wast', # Requires more precise branch validation - 'br_on_cast.wast', # Requires sending values on br_on_cast - 'br_on_cast_fail.wast', # Requires sending values on br_on_cast_fail + 'br_on_cast.wast', # Requires host references to not be externalized i31refs + 'br_on_cast_fail.wast', # Requires host references to not be externalized i31refs 'extern.wast', # Requires ref.host wast constants 'i31.wast', # Requires support for table default elements 'ref_cast.wast', # Requires host references to not be externalized i31refs diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 47f610539f2..d85ee040e0d 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1054,6 +1054,7 @@ Result<> IRBuilder::visitEnd() { labelHint = 0; } else if (auto* block = scope.getBlock()) { assert(*expr == block); + block->name = Name(); block = fixExtraOutput(scope, label, block)->cast(); block->name = label; block->finalize(block->type, diff --git a/test/lit/basic/extra-branch-values.wast b/test/lit/basic/extra-branch-values.wast index 57d55afa685..abd609304ec 100644 --- a/test/lit/basic/extra-branch-values.wast +++ b/test/lit/basic/extra-branch-values.wast @@ -23,8 +23,8 @@ ;; OPT_O: (import "env" "eqref" (global $eqref (mut eqref))) (import "env" "eqref" (global $eqref (mut eqref))) - ;; CHECK: (import "env" "use-i32-any" (func $use-i32-any (type $14) (param i32 (ref any)))) - ;; OPT_O: (import "env" "use-i32-any" (func $use-i32-any (type $14) (param i32 (ref any)))) + ;; CHECK: (import "env" "use-i32-any" (func $use-i32-any (type $15) (param i32 (ref any)))) + ;; OPT_O: (import "env" "use-i32-any" (func $use-i32-any (type $15) (param i32 (ref any)))) (import "env" "use-i32-any" (func $use-i32-any (param i32 (ref any)))) ;; CHECK: (tag $e (param i32)) @@ -34,7 +34,7 @@ ;; OPT_O: (tag $e2 (param i32)) (tag $e2 (param i32)) - ;; CHECK: (func $br_on_null-one (type $15) (param $0 i32) (param $1 anyref) (result i32) + ;; CHECK: (func $br_on_null-one (type $8) (param $0 i32) (param $1 anyref) (result i32) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_3 i32) ;; CHECK-NEXT: (local $scratch_4 i32) @@ -76,7 +76,7 @@ ;; CHECK-NEXT: (local.get $scratch_3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $br_on_null-one (type $15) (param $0 i32) (param $1 anyref) (result i32) + ;; OPT_O: (func $br_on_null-one (type $6) (param $0 i32) (param $1 anyref) (result i32) ;; OPT_O-NEXT: (block $block (result i32) ;; OPT_O-NEXT: (block $block0 ;; OPT_O-NEXT: (global.set $any @@ -112,7 +112,7 @@ ;; CHECK-NEXT: (local $scratch_6 (ref any)) ;; CHECK-NEXT: (local $scratch_7 (tuple i32 i64)) ;; CHECK-NEXT: (local $scratch_8 i32) - ;; CHECK-NEXT: (block $block (type $12) (result i32 i64) + ;; CHECK-NEXT: (block $block (type $13) (result i32 i64) ;; CHECK-NEXT: (block $block0 ;; CHECK-NEXT: (local.set $scratch_4 ;; CHECK-NEXT: (tuple.make 2 @@ -168,7 +168,7 @@ ;; CHECK-NEXT: ) ;; OPT_O: (func $br_on_null-two (type $16) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64) ;; OPT_O-NEXT: (local $3 (tuple i32 i64)) - ;; OPT_O-NEXT: (block $block (type $10) (result i32 i64) + ;; OPT_O-NEXT: (block $block (type $11) (result i32 i64) ;; OPT_O-NEXT: (local.set $3 ;; OPT_O-NEXT: (tuple.make 2 ;; OPT_O-NEXT: (local.get $0) @@ -215,7 +215,7 @@ end ) - ;; CHECK: (func $br_on_non_null-one (type $8) (param $0 i32) (param $1 anyref) (result i32 (ref any)) + ;; CHECK: (func $br_on_non_null-one (type $9) (param $0 i32) (param $1 anyref) (result i32 (ref any)) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_3 i32) ;; CHECK-NEXT: (local $scratch_4 i32) @@ -254,7 +254,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $br_on_non_null-one (type $6) (param $0 i32) (param $1 anyref) (result i32 (ref any)) + ;; OPT_O: (func $br_on_non_null-one (type $7) (param $0 i32) (param $1 anyref) (result i32 (ref any)) ;; OPT_O-NEXT: (block $block (type $1) (result i32 (ref any)) ;; OPT_O-NEXT: (tuple.make 2 ;; OPT_O-NEXT: (local.get $0) @@ -286,7 +286,7 @@ end ) - ;; CHECK: (func $br_on_non_null-two (type $9) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) + ;; CHECK: (func $br_on_non_null-two (type $10) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_4 (tuple i32 i64)) ;; CHECK-NEXT: (local $scratch_5 i64) @@ -350,7 +350,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $br_on_non_null-two (type $7) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) + ;; OPT_O: (func $br_on_non_null-two (type $8) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) ;; OPT_O-NEXT: (block $block (type $4) (result i32 i64 (ref any)) ;; OPT_O-NEXT: (tuple.make 3 ;; OPT_O-NEXT: (local.get $0) @@ -484,7 +484,7 @@ ;; CHECK-NEXT: (local $scratch_7 (tuple i32 i64)) ;; CHECK-NEXT: (local $scratch_8 i32) ;; CHECK-NEXT: (local $scratch_9 eqref) - ;; CHECK-NEXT: (block $block (type $13) (result i32 i64 eqref) + ;; CHECK-NEXT: (block $block (type $14) (result i32 i64 eqref) ;; CHECK-NEXT: (local.set $scratch_9 ;; CHECK-NEXT: (block $block0 (result eqref) ;; CHECK-NEXT: (local.set $scratch_4 @@ -550,7 +550,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPT_O: (func $br_on_cast-two (type $17) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 eqref) - ;; OPT_O-NEXT: (block $block (type $11) (result i32 i64 eqref) + ;; OPT_O-NEXT: (block $block (type $12) (result i32 i64 eqref) ;; OPT_O-NEXT: (tuple.make 3 ;; OPT_O-NEXT: (local.get $0) ;; OPT_O-NEXT: (local.get $1) @@ -764,7 +764,7 @@ end ) - ;; CHECK: (func $br_on_cast_fail-one (type $8) (param $0 i32) (param $1 anyref) (result i32 (ref any)) + ;; CHECK: (func $br_on_cast_fail-one (type $9) (param $0 i32) (param $1 anyref) (result i32 (ref any)) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_3 i32) ;; CHECK-NEXT: (local $scratch_4 i32) @@ -815,7 +815,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $br_on_cast_fail-one (type $6) (param $0 i32) (param $1 anyref) (result i32 (ref any)) + ;; OPT_O: (func $br_on_cast_fail-one (type $7) (param $0 i32) (param $1 anyref) (result i32 (ref any)) ;; OPT_O-NEXT: (block $block (type $1) (result i32 (ref any)) ;; OPT_O-NEXT: (tuple.make 2 ;; OPT_O-NEXT: (local.get $0) @@ -850,7 +850,7 @@ end ) - ;; CHECK: (func $br_on_cast_fail-two (type $9) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) + ;; CHECK: (func $br_on_cast_fail-two (type $10) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_4 (tuple i32 i64)) ;; CHECK-NEXT: (local $scratch_5 i64) @@ -923,7 +923,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $br_on_cast_fail-two (type $7) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) + ;; OPT_O: (func $br_on_cast_fail-two (type $8) (param $0 i32) (param $1 i64) (param $2 anyref) (result i32 i64 (ref any)) ;; OPT_O-NEXT: (block $block (type $4) (result i32 i64 (ref any)) ;; OPT_O-NEXT: (tuple.make 3 ;; OPT_O-NEXT: (local.get $0) @@ -1052,7 +1052,7 @@ end ) - ;; CHECK: (func $br_on_cast_fail-to-nn (type $10) (param $0 i32) (param $1 anyref) (result i32 anyref) + ;; CHECK: (func $br_on_cast_fail-to-nn (type $11) (param $0 i32) (param $1 anyref) (result i32 anyref) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_3 i32) ;; CHECK-NEXT: (local $scratch_4 i32) @@ -1103,8 +1103,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $br_on_cast_fail-to-nn (type $8) (param $0 i32) (param $1 anyref) (result i32 anyref) - ;; OPT_O-NEXT: (block $block (type $12) (result i32 anyref) + ;; OPT_O: (func $br_on_cast_fail-to-nn (type $9) (param $0 i32) (param $1 anyref) (result i32 anyref) + ;; OPT_O-NEXT: (block $block (type $13) (result i32 anyref) ;; OPT_O-NEXT: (tuple.make 2 ;; OPT_O-NEXT: (local.get $0) ;; OPT_O-NEXT: (block $block0 (result anyref) @@ -1138,6 +1138,78 @@ end ) + ;; CHECK: (func $unreachable-fallthrough (type $8) (param $0 i32) (param $1 anyref) (result i32) + ;; CHECK-NEXT: (local $scratch anyref) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local $scratch_5 (ref any)) + ;; CHECK-NEXT: (local $scratch_6 (tuple i32 (ref any))) + ;; CHECK-NEXT: (local $scratch_7 i32) + ;; CHECK-NEXT: (local.set $scratch_7 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_6 + ;; CHECK-NEXT: (block $l (type $3) (result i32 (ref any)) + ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (block $l0 (result (ref any)) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_on_non_null $l0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $l + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_7) + ;; CHECK-NEXT: ) + ;; OPT_O: (func $unreachable-fallthrough (type $6) (param $0 i32) (param $1 anyref) (result i32) + ;; OPT_O-NEXT: (drop + ;; OPT_O-NEXT: (block $l0 (result (ref any)) + ;; OPT_O-NEXT: (br_on_non_null $l0 + ;; OPT_O-NEXT: (local.get $1) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (return + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: ) + ;; OPT_O-NEXT: (local.get $0) + ;; OPT_O-NEXT: ) + (func $unreachable-fallthrough (export "unreachable-fallthrough") (param i32 anyref) (result i32) + block $l (result i32 (ref any)) + local.get 0 + local.get 1 + br_on_non_null $l + return + end + drop + ) + ;; CHECK: (func $matching-branches (type $21) (param $0 i32) (param $1 anyref) (param $2 i32) (param $3 anyref) (result i32 eqref) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_5 i32) @@ -1824,7 +1896,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPT_O: (func $with-block-param (type $25) (param $0 i64) (param $1 anyref) (result i64 eqref) - ;; OPT_O-NEXT: (block $block (type $13) (result i64 eqref) + ;; OPT_O-NEXT: (block $block (type $14) (result i64 eqref) ;; OPT_O-NEXT: (tuple.make 2 ;; OPT_O-NEXT: (local.get $0) ;; OPT_O-NEXT: (block $block0 (result eqref) @@ -1960,7 +2032,7 @@ end ) - ;; CHECK: (func $loop-results (type $10) (param $0 i32) (param $1 anyref) (result i32 anyref) + ;; CHECK: (func $loop-results (type $11) (param $0 i32) (param $1 anyref) (result i32 anyref) ;; CHECK-NEXT: (local $scratch (tuple i32 anyref)) ;; CHECK-NEXT: (local $scratch_3 (tuple i32 anyref)) ;; CHECK-NEXT: (local $scratch_4 anyref) @@ -2026,7 +2098,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $loop-results (type $8) (param $0 i32) (param $1 anyref) (result i32 anyref) + ;; OPT_O: (func $loop-results (type $9) (param $0 i32) (param $1 anyref) (result i32 anyref) ;; OPT_O-NEXT: (local $2 (tuple i32 anyref)) ;; OPT_O-NEXT: (local $3 eqref) ;; OPT_O-NEXT: (local.set $2 @@ -2081,7 +2153,7 @@ end ) - ;; CHECK: (func $if (type $11) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) + ;; CHECK: (func $if (type $12) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_4 i32) ;; CHECK-NEXT: (local $scratch_5 i32) @@ -2143,7 +2215,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $if (type $9) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) + ;; OPT_O: (func $if (type $10) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) ;; OPT_O-NEXT: (tuple.make 2 ;; OPT_O-NEXT: (local.get $1) @@ -2193,7 +2265,7 @@ end ) - ;; CHECK: (func $else (type $11) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) + ;; CHECK: (func $else (type $12) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) ;; CHECK-NEXT: (local $scratch anyref) ;; CHECK-NEXT: (local $scratch_4 i32) ;; CHECK-NEXT: (local $scratch_5 i32) @@ -2255,7 +2327,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPT_O: (func $else (type $9) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) + ;; OPT_O: (func $else (type $10) (param $0 i32) (param $1 i32) (param $2 anyref) (result i32 eqref) ;; OPT_O-NEXT: (block $label (type $0) (result i32 eqref) ;; OPT_O-NEXT: (tuple.make 2 ;; OPT_O-NEXT: (local.get $1) From 7a258b344370aca6a87efc6d21aa41eb1553a239 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 14 Jan 2025 13:00:15 -0800 Subject: [PATCH 235/622] Fuzzer: Ignore tests with the linking section (#7213) We warn on that, saying we don't support it, which is both an error in the fuzzer but also means it isn't useful to fuzz. --- scripts/test/fuzzing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index f5fee637582..a733e0d8606 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -55,6 +55,9 @@ 'strip-dwarf.wasm', 'ignore_missing_func_dwarf.wasm', 'print.wasm', + # ignore linking section as it causes us to warn about it not being fully + # supported + 'strip-target-features.wasm', # TODO fuzzer support for multimemory 'multi-memories-atomics64.wast', 'multi-memories-basics.wast', From 98129d1f80527c1336e172563c45f2814ba5ca72 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 14 Jan 2025 17:13:47 -0800 Subject: [PATCH 236/622] Fix typing of struct RMW ops with null references (#7214) When the struct reference is null, there is no struct type from which to look up the proper field type when finalizing a struct RMW or cmpxchg expression. We previously reused code for this case from StructGet, but unlike StructGet, the RMW operations have additional operands that can constrain the type of the retrieved struct field. Take these operands into account when computing a type for the RMW expressions. --- scripts/test/fuzzing.py | 1 + src/wasm/wasm.cpp | 15 ++- test/lit/basic/gc-atomics-null-refs.wast | 117 +++++++++++++++++++++++ 3 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 test/lit/basic/gc-atomics-null-refs.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index a733e0d8606..ec8a6560490 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -80,6 +80,7 @@ 'typed_continuations_suspend.wast', # the fuzzer does not support struct RMW ops 'gc-atomics.wast', + 'gc-atomics-null-refs.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 766e1be8942..eb125d6c254 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1168,10 +1168,10 @@ void StructRMW::finalize() { if (ref->type == Type::unreachable || value->type == Type::unreachable) { type = Type::unreachable; } else if (ref->type.isNull()) { - // See comment on CallRef for explanation. - if (type.isRef()) { - type = Type(type.getHeapType().getBottom(), NonNullable); - } + // We have no struct type to read the field off of, but the most precise + // possible option is the type of the value we are using to make the + // modification. + type = value->type; } else { type = ref->type.getHeapType().getStruct().fields[index].type; } @@ -1182,10 +1182,9 @@ void StructCmpxchg::finalize() { replacement->type == Type::unreachable) { type = Type::unreachable; } else if (ref->type.isNull()) { - // See comment on CallRef for explanation. - if (type.isRef()) { - type = Type(type.getHeapType().getBottom(), NonNullable); - } + // Like StructRMW, but the most precise possible field type is the LUB of + // the expected and replacement values. + type = Type::getLeastUpperBound(expected->type, replacement->type); } else { type = ref->type.getHeapType().getStruct().fields[index].type; } diff --git a/test/lit/basic/gc-atomics-null-refs.wast b/test/lit/basic/gc-atomics-null-refs.wast new file mode 100644 index 00000000000..07a52f32fe7 --- /dev/null +++ b/test/lit/basic/gc-atomics-null-refs.wast @@ -0,0 +1,117 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s +;; RUN: wasm-opt -all %s --roundtrip -S -o - | filecheck %s --check-prefix=RTRIP + +(module + (type $struct-i32 (struct (field (mut i32)))) + (type $struct-eq (struct (field (mut eqref)))) + + ;; CHECK: (func $rmw-null (type $1) (result i32) + ;; CHECK-NEXT: (block ;; (replaces unreachable StructRMW we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $rmw-null (type $1) (result i32) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $rmw-null (result i32) + (struct.atomic.rmw.add $struct-i32 0 + (ref.null none) + (i32.const 1) + ) + ) + + ;; CHECK: (func $cmpxchg-null (type $0) (result i31ref) + ;; CHECK-NEXT: (block ;; (replaces unreachable StructCmpxchg we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $cmpxchg-null (type $0) (result i31ref) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.i31 + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $cmpxchg-null (result i31ref) + ;; This is technically invalid because the result should be eqref, but we + ;; lose that type information after parsing. Test that we compute a + ;; sufficiently general result type from the expected and replacement + ;; values. + (struct.atomic.rmw.cmpxchg $struct-eq 0 + (ref.null none) + (ref.null none) + (ref.i31 (i32.const 0)) + ) + ) + + ;; CHECK: (func $cmpxchg-null-2 (type $0) (result i31ref) + ;; CHECK-NEXT: (block ;; (replaces unreachable StructCmpxchg we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $cmpxchg-null-2 (type $0) (result i31ref) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.i31 + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $cmpxchg-null-2 (result i31ref) + ;; Same as above, but with the expected and replacement types reversed. + (struct.atomic.rmw.cmpxchg $struct-eq 0 + (ref.null none) + (ref.i31 (i32.const 0)) + (ref.null none) + ) + ) +) From 7453b1b6ce325a33dd024e7f229f31df369a377a Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 14 Jan 2025 22:59:22 -0600 Subject: [PATCH 237/622] Fix status badge (#7217) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03f7cd13de7..4f8ad344472 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![CI](https://github.com/WebAssembly/binaryen/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/WebAssembly/binaryen/actions?query=workflow%3ACI) +[![CI](https://github.com/WebAssembly/binaryen/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/WebAssembly/binaryen/actions/workflows/ci.yml?branch=main&event=push) # Binaryen From c8bfe2813dbed092b5ba51c146b4e8c5621eb250 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 15 Jan 2025 11:16:49 -0800 Subject: [PATCH 238/622] Improve struct RMW validation and interpretation (#7216) Add missing validation rules checking that accessed fields have allowed types. Copy the proposed upstream spec tests for this validation from https://github.com/WebAssembly/shared-everything-threads/pull/85 with minor changes to account for differences in supported text format and to comment out unsupported tests. Also implement interpretation for the RMW instructions and add spec tests testing their execution. It is simpler to implement both validation and interpretation at once because the proposed upstream spec tests require both to pass. --- scripts/test/fuzzing.py | 1 + src/wasm-interpreter.h | 66 ++- src/wasm/wasm-validator.cpp | 35 +- test/spec/shared-struct.wast | 100 ---- test/spec/shared-structs.wast | 890 ++++++++++++++++++++++++++++++++++ 5 files changed, 986 insertions(+), 106 deletions(-) delete mode 100644 test/spec/shared-struct.wast create mode 100644 test/spec/shared-structs.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index ec8a6560490..7bafe3be87d 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -81,6 +81,7 @@ # the fuzzer does not support struct RMW ops 'gc-atomics.wast', 'gc-atomics-null-refs.wast', + 'shared-structs.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 72de28585ed..d459ffdf5fe 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1701,9 +1701,71 @@ class ExpressionRunner : public OverriddenVisitor { return Flow(); } - Flow visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructRMW(StructRMW* curr) { + NOTE_ENTER("StructRMW"); + Flow ref = self()->visit(curr->ref); + if (ref.breaking()) { + return ref; + } + Flow value = self()->visit(curr->value); + if (value.breaking()) { + return value; + } + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + auto& field = data->values[curr->index]; + auto oldVal = field; + auto newVal = value.getSingleValue(); + switch (curr->op) { + case RMWAdd: + field = field.add(newVal); + break; + case RMWSub: + field = field.sub(newVal); + break; + case RMWAnd: + field = field.and_(newVal); + break; + case RMWOr: + field = field.or_(newVal); + break; + case RMWXor: + field = field.xor_(newVal); + break; + case RMWXchg: + field = newVal; + break; + } + return oldVal; + } - Flow visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructCmpxchg(StructCmpxchg* curr) { + NOTE_ENTER("StructCmpxchg"); + Flow ref = self()->visit(curr->ref); + if (ref.breaking()) { + return ref; + } + Flow expected = self()->visit(curr->expected); + if (expected.breaking()) { + return expected; + } + Flow replacement = self()->visit(curr->replacement); + if (replacement.breaking()) { + return replacement; + } + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + auto& field = data->values[curr->index]; + auto oldVal = field; + if (field == expected.getSingleValue()) { + field = replacement.getSingleValue(); + } + return oldVal; + } // Arbitrary deterministic limit on size. If we need to allocate a Literals // vector that takes around 1-2GB of memory then we are likely to hit memory diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 092e7a8c6fd..bda3c3713a5 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3096,12 +3096,26 @@ void FunctionValidator::visitStructRMW(StructRMW* curr) { return; } auto& field = fields[curr->index]; + shouldBeEqual( + field.mutable_, Mutable, curr, "struct.atomic.rmw field must be mutable"); + shouldBeFalse( + field.isPacked(), curr, "struct.atomic.rmw field must not be packed"); + bool isAny = + field.type.isRef() && + Type::isSubType( + field.type, + Type(HeapTypes::any.getBasic(field.type.getHeapType().getShared()), + Nullable)); + if (!shouldBeTrue(field.type == Type::i32 || field.type == Type::i64 || + (isAny && curr->op == RMWXchg), + curr, + "struct.atomic.rmw field type invalid for operation")) { + return; + } shouldBeSubType(curr->value->type, field.type, curr, "struct.atomic.rmw value must have the proper type"); - shouldBeEqual( - field.mutable_, Mutable, curr, "struct.atomic.rmw field must be mutable"); } void FunctionValidator::visitStructCmpxchg(StructCmpxchg* curr) { @@ -3134,6 +3148,21 @@ void FunctionValidator::visitStructCmpxchg(StructCmpxchg* curr) { return; } auto& field = fields[curr->index]; + shouldBeEqual( + field.mutable_, Mutable, curr, "struct.atomic.rmw field must be mutable"); + shouldBeFalse( + field.isPacked(), curr, "struct.atomic.rmw field must not be packed"); + bool isEq = + field.type.isRef() && + Type::isSubType( + field.type, + Type(HeapTypes::eq.getBasic(field.type.getHeapType().getShared()), + Nullable)); + if (!shouldBeTrue(field.type == Type::i32 || field.type == Type::i64 || isEq, + curr, + "struct.atomic.rmw field type invalid for operation")) { + return; + } shouldBeSubType( curr->expected->type, field.type, @@ -3144,8 +3173,6 @@ void FunctionValidator::visitStructCmpxchg(StructCmpxchg* curr) { field.type, curr, "struct.atomic.rmw.cmpxchg replacement value must have the proper type"); - shouldBeEqual( - field.mutable_, Mutable, curr, "struct.atomic.rmw field must be mutable"); } void FunctionValidator::visitArrayNew(ArrayNew* curr) { diff --git a/test/spec/shared-struct.wast b/test/spec/shared-struct.wast deleted file mode 100644 index f321d880061..00000000000 --- a/test/spec/shared-struct.wast +++ /dev/null @@ -1,100 +0,0 @@ -;; Shared struct declaration syntax -(module - (type (shared (struct))) - (type (sub final (shared (struct)))) - (rec - (type (sub final (shared (struct)))) - ) - - (global (ref 0) (struct.new 1)) - (global (ref 1) (struct.new 2)) - (global (ref 2) (struct.new 0)) -) - -;; Shared structs are distinct from non-shared structs -(assert_invalid - (module - (type (shared (struct))) - (type (struct)) - - (global (ref 0) (struct.new 1)) - ) - "not a subtype" -) - -(assert_invalid - (module - (type (shared (struct))) - (type (struct)) - - (global (ref 1) (struct.new 0)) - ) - "not a subtype" -) - -;; Shared structs may not be subtypes of non-shared structs -(assert_invalid - (module - (type (sub (struct))) - (type (sub 0 (shared (struct)))) - ) - "invalid supertype" -) - -;; Non-shared structs may not be subtypes of shared structs -(assert_invalid - (module - (type (sub (shared (struct)))) - (type (sub 0 (struct))) - ) - "invalid supertype" -) - -;; Shared structs may not contain non-shared references -(assert_invalid - (module - (type (shared (struct anyref))) - ) - "invalid field" -) - -;; But they may contain shared references -(module - (type (shared (struct (ref null (shared any))))) -) - -;; Non-shared structs may contain shared references -(module - (type (struct (ref null (shared any)))) -) - -;; Struct instructions work on shared structs. -(module - (type $i8 (shared (struct (mut i8)))) - (type $i32 (shared (struct (mut i32)))) - (type $unshared (struct (mut i8))) - - (func (struct.new $i8 (i32.const 0)) (drop)) - - (func (struct.new_default $i8) (drop)) - - (func (param (ref null $i8)) - (struct.get_s $i8 0 (local.get 0)) (drop)) - - (func (param (ref null $i8)) - (struct.get_u $i8 0 (local.get 0)) (drop)) - - (func (param (ref null $i32)) - (struct.get $i32 0 (local.get 0)) (drop)) - - (func (param (ref null $i8)) - (struct.set $i8 0 (local.get 0) (i32.const 0))) -) - -;; Bottom types -(module - (type $i8 (shared (struct (mut i8)))) - (func (drop (struct.get_s $i8 0 (ref.null (shared none))))) - (func (drop (struct.get_u $i8 0 (ref.null (shared none))))) - (func (struct.set $i8 0 (ref.null (shared none)) (i32.const 0))) -) diff --git a/test/spec/shared-structs.wast b/test/spec/shared-structs.wast new file mode 100644 index 00000000000..d7c368b7b80 --- /dev/null +++ b/test/spec/shared-structs.wast @@ -0,0 +1,890 @@ +;; Shared struct declaration syntax. +(module + (type (shared (struct))) + (type (sub final (shared (struct)))) + (rec + (type (sub final (shared (struct)))) + ) + + (global (ref 0) (struct.new 1)) + (global (ref 1) (struct.new 2)) + (global (ref 2) (struct.new 0)) +) + +;; Shared structs are distinct from non-shared structs. +(assert_invalid + (module + (type (shared (struct))) + (type (struct)) + + (global (ref 0) (struct.new 1)) + ) + "type mismatch" +) + +(assert_invalid + (module + (type (shared (struct))) + (type (struct)) + + (global (ref 1) (struct.new 0)) + ) + "type mismatch" +) + +;; Shared structs may not be subtypes of non-shared structs. +(assert_invalid + (module + (type (sub (struct))) + (type (sub 0 (shared (struct)))) + ) + "must match super type" +) + +;; Non-shared structs may not be subtypes of shared structs. +(assert_invalid + (module + (type (sub (shared (struct)))) + (type (sub 0 (struct))) + ) + "must match super type" +) + +;; Shared structs may not contain non-shared references. +(assert_invalid + (module + (type (shared (struct (field anyref)))) + ) + "must contain shared type" +) + +;; But they may contain shared references. +(module + (type (shared (struct (field (ref null (shared any)))))) +) + +;; Non-shared structs may contain shared references. +(module + (type (struct (field (ref null (shared any))))) +) + +;; Struct instructions work on shared structs. +(module + (type $i8 (shared (struct (field (mut i8))))) + (type $i32 (shared (struct (field (mut i32))))) + (type $unshared (struct (field (mut i8)))) + + (func (struct.new $i8 (i32.const 0)) (drop)) + + (func (struct.new_default $i8) (drop)) + + (func (param (ref null $i8)) + (struct.get_s $i8 0 (local.get 0)) (drop)) + + (func (param (ref null $i8)) + (struct.get_u $i8 0 (local.get 0)) (drop)) + + (func (param (ref null $i32)) + (struct.get $i32 0 (local.get 0)) (drop)) + + (func (param (ref null $i8)) + (struct.set $i8 0 (local.get 0) (i32.const 0))) +) + +;; Bottom types can be used as shared structs. +(module + (type $i8 (shared (struct (field (mut i8))))) + (func (drop (struct.get_s $i8 0 (ref.null (shared none))))) + (func (drop (struct.get_u $i8 0 (ref.null (shared none))))) + (func (struct.set $i8 0 (ref.null (shared none)) (i32.const 0))) +) + +;; Check that field-modifying instructions only work on mutable fields. +(assert_invalid + (module + (type $s (shared (struct (field (ref (shared any)))))) + (func (param $s (ref $s)) (param $a (ref (shared any))) (result (ref (shared any))) + (struct.atomic.set seqcst $s 0 (local.get $s) (local.get $a)) + ) + ) + "field is immutable" +) +(assert_invalid + (module + (type $s (shared (struct (field i32)))) + (func (param $s (ref $s)) (result i32) + (struct.atomic.rmw.add seqcst $s 0 (local.get $s) (i32.const 1)) + ) + ) + "field is immutable" +) +(assert_invalid + (module + (type $s (shared (struct (field i64)))) + (func (param $s (ref $s)) (result i64) + (struct.atomic.rmw.sub seqcst $s 0 (local.get $s) (i64.const 1)) + ) + ) + "field is immutable" +) +(assert_invalid + (module + (type $s (shared (struct (field i32)))) + (func (param $s (ref $s)) (result i32) + (struct.atomic.rmw.and acqrel $s 0 (local.get $s) (i32.const 1)) + ) + ) + "field is immutable" +) +(assert_invalid + (module + (type $s (shared (struct (field i64)))) + (func (param $s (ref $s)) (result i64) + (struct.atomic.rmw.or acqrel $s 0 (local.get $s) (i64.const 1)) + ) + ) + "field is immutable" +) +(assert_invalid + (module + (type $s (shared (struct (field i32)))) + (func (param $s (ref $s)) (result i32) + (struct.atomic.rmw.xor seqcst $s 0 (local.get $s) (i32.const 1)) + ) + ) + "field is immutable" +) +(assert_invalid + (module + (type $s (shared (struct (field (ref (shared any)))))) + (func (param $s (ref $s)) (param $a (ref (shared any))) (result (ref (shared any))) + (struct.atomic.rmw.xchg seqcst $s 0 (local.get $s) (local.get $a)) + ) + ) + "field is immutable" +) +(assert_invalid + (module + (type $s (shared (struct (field (ref (shared eq)))))) + (func (param $s (ref $s)) (param $e1 (ref (shared eq))) (param $e2 (ref (shared eq))) (result (ref (shared eq))) + (struct.atomic.rmw.cmpxchg acqrel $s 0 (local.get $s) (local.get $e1) (local.get $e2)) + ) + ) + "field is immutable" +) + +;; Exhaustively check `struct.atomic.rmw.*` instructions. +(module + (type $s (shared (struct + (field $i8 (mut i8)) + (field $i16 (mut i16)) + (field $i32 (mut i32)) + (field $i64 (mut i64)) + (field $anyref (mut (ref null (shared any)))) + (field $eqref (mut (ref null (shared eq))))))) + (func (export "struct-atomic-get-i32-seqcst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get seqcst $s $i32) + (func (export "struct-atomic-get-i64-seqcst") (param $x (ref null $s)) (result i64) + local.get $x + struct.atomic.get seqcst $s $i64) + (func (export "struct-atomic-get-anyref-seqcst") (param $x (ref null $s)) (result (ref null (shared any))) + local.get $x + struct.atomic.get seqcst $s $anyref) + (func (export "struct-atomic-get-i32-acqrel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get acqrel $s $i32) + (func (export "struct-atomic-get-i64-acqrel") (param $x (ref null $s)) (result i64) + local.get $x + struct.atomic.get acqrel $s $i64) + (func (export "struct-atomic-get-anyref-acqrel") (param $x (ref null $s)) (result (ref null (shared any))) + local.get $x + struct.atomic.get acqrel $s $anyref) + (func (export "struct-atomic-get_s-i8-seqcst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s seqcst $s $i8) + (func (export "struct-atomic-get_s-i16-seqcst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s seqcst $s $i16) + (func (export "struct-atomic-get_s-i8-acqrel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s acqrel $s $i8) + (func (export "struct-atomic-get_s-i16-acqrel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s acqrel $s $i16) + (func (export "struct-atomic-get_u-i8-seqcst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u seqcst $s $i8) + (func (export "struct-atomic-get_u-i16-seqcst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u seqcst $s $i16) + (func (export "struct-atomic-get_u-i8-acqrel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u acqrel $s $i8) + (func (export "struct-atomic-get_u-i16-acqrel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u acqrel $s $i16) + (func (export "struct-atomic-set-i8-seqcst") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seqcst $s $i8) + (func (export "struct-atomic-set-i16-seqcst") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seqcst $s $i16) + (func (export "struct-atomic-set-i32-seqcst") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seqcst $s $i32) + (func (export "struct-atomic-set-i64-seqcst") (param $x (ref null $s)) (param $y i64) + local.get $x + local.get $y + struct.atomic.set seqcst $s $i64) + (func (export "struct-atomic-set-anyref-seqcst") (param $x (ref null $s)) (param $y (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.set seqcst $s $anyref) + (func (export "struct-atomic-set-i8-acqrel") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acqrel $s $i8) + (func (export "struct-atomic-set-i16-acqrel") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acqrel $s $i16) + (func (export "struct-atomic-set-i32-acqrel") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acqrel $s $i32) + (func (export "struct-atomic-set-i64-acqrel") (param $x (ref null $s)) (param $y i64) + local.get $x + local.get $y + struct.atomic.set acqrel $s $i64) + (func (export "struct-atomic-set-anyref-acqrel") (param $x (ref null $s)) (param $y (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.set acqrel $s $anyref) + (func (export "struct-atomic-rmw.add-i32-seqcst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.add seqcst $s $i32) + (func (export "struct-atomic-rmw.add-i64-seqcst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.add seqcst $s $i64) + (func (export "struct-atomic-rmw.add-i32-acqrel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.add acqrel acqrel $s $i32) + (func (export "struct-atomic-rmw.add-i64-acqrel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.add acqrel acqrel $s $i64) + (func (export "struct-atomic-rmw.sub-i32-seqcst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.sub seqcst $s $i32) + (func (export "struct-atomic-rmw.sub-i64-seqcst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.sub seqcst $s $i64) + (func (export "struct-atomic-rmw.sub-i32-acqrel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.sub acqrel acqrel $s $i32) + (func (export "struct-atomic-rmw.sub-i64-acqrel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.sub acqrel acqrel $s $i64) + (func (export "struct-atomic-rmw.and-i32-seqcst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.and seqcst $s $i32) + (func (export "struct-atomic-rmw.and-i64-seqcst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.and seqcst $s $i64) + (func (export "struct-atomic-rmw.and-i32-acqrel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.and acqrel acqrel $s $i32) + (func (export "struct-atomic-rmw.and-i64-acqrel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.and acqrel acqrel $s $i64) + (func (export "struct-atomic-rmw.or-i32-seqcst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.or seqcst $s $i32) + (func (export "struct-atomic-rmw.or-i64-seqcst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.or seqcst $s $i64) + (func (export "struct-atomic-rmw.or-i32-acqrel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.or acqrel acqrel $s $i32) + (func (export "struct-atomic-rmw.or-i64-acqrel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.or acqrel acqrel $s $i64) + (func (export "struct-atomic-rmw.xor-i32-seqcst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xor seqcst $s $i32) + (func (export "struct-atomic-rmw.xor-i64-seqcst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xor seqcst $s $i64) + (func (export "struct-atomic-rmw.xor-i32-acqrel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xor acqrel acqrel $s $i32) + (func (export "struct-atomic-rmw.xor-i64-acqrel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xor acqrel acqrel $s $i64) + (func (export "struct-atomic-rmw.xchg-i32-seqcst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xchg seqcst $s $i32) + (func (export "struct-atomic-rmw.xchg-i64-seqcst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xchg seqcst $s $i64) + (func (export "struct-atomic-rmw.xchg-anyref-seqcst") (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.xchg seqcst $s $anyref) + (func (export "struct-atomic-rmw.xchg-i32-acqrel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xchg acqrel acqrel $s $i32) + (func (export "struct-atomic-rmw.xchg-i64-acqrel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xchg acqrel acqrel $s $i64) + (func (export "struct-atomic-rmw.xchg-anyref-acqrel") (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.xchg acqrel acqrel $s $anyref) + (func (export "struct-atomic-rmw.cmpxchg-i32-seqcst") (param $x (ref null $s)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seqcst $s $i32) + (func (export "struct-atomic-rmw.cmpxchg-i64-seqcst") (param $x (ref null $s)) (param $y i64) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seqcst $s $i64) + (func (export "struct-atomic-rmw.cmpxchg-eqref-seqcst") (param $x (ref null $s)) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seqcst $s $eqref) + (func (export "struct-atomic-rmw.cmpxchg-i32-acqrel") (param $x (ref null $s)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acqrel acqrel $s $i32) + (func (export "struct-atomic-rmw.cmpxchg-i64-acqrel") (param $x (ref null $s)) (param $y i64) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acqrel acqrel $s $i64) + (func (export "struct-atomic-rmw.cmpxchg-eqref-acqrel") (param $x (ref null $s)) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acqrel acqrel $s $eqref) +) + +;; TODO: Validate use of struct.get_s/u with appropriate fields during parsing. + +;; (assert_invalid (; get, i8 ;) +;; (module +;; (type $s (shared (struct (field $i8 (mut i8))))) +;; (func (param $x (ref null $s)) (result i32) +;; local.get $x +;; struct.atomic.get seqcst $s $i8) +;; ) +;; "non-packed storage type" +;; ) +;; (assert_invalid (; get_s, i32 ;) +;; (module +;; (type $s (shared (struct (field $i32 (mut i32))))) +;; (func (param $x (ref null $s)) (result i32) +;; local.get $x +;; struct.atomic.get_s seqcst $s $i32) +;; ) +;; "non-packed storage types" +;; ) +;; (assert_invalid (; get_s, anyref ;) +;; (module +;; (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) +;; (func (param $x (ref null $s)) (result (ref null (shared any))) +;; local.get $x +;; struct.atomic.get_s seqcst $s $anyref) +;; ) +;; "non-packed storage types" +;; ) +;; (assert_invalid (; get_u, i32 ;) +;; (module +;; (type $s (shared (struct (field $i32 (mut i32))))) +;; (func (param $x (ref null $s)) (result i32) +;; local.get $x +;; struct.atomic.get_u seqcst $s $i32) +;; ) +;; "non-packed storage types" +;; ) +;; (assert_invalid (; get_u, anyref ;) +;; (module +;; (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) +;; (func (param $x (ref null $s)) (result (ref null (shared any))) +;; local.get $x +;; struct.atomic.get_u seqcst $s $anyref) +;; ) +;; "non-packed storage types" +;; ) +(assert_invalid (; rmw.add, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.add seqcst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.sub, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.sub seqcst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.and, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.and seqcst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.or, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.or seqcst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.xor, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.xor seqcst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.xchg, i8 ;) + (module + (type $s (shared (struct (field $i8 (mut i8))))) + (func (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xchg seqcst $s $i8) + ) + "invalid type" +) +(assert_invalid (; rmw.cmpxchg, i8 ;) + (module + (type $s (shared (struct (field $i8 (mut i8))))) + (func (param $x (ref null $s)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seqcst $s $i8) + ) + "invalid type" +) +(assert_invalid (; rmw.cmpxchg, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seqcst $s $anyref) + ) + "invalid type" +) + +;; (assert_invalid +;; (module +;; (type $s (shared (struct (field $f f32)))) +;; (func +;; unreachable +;; struct.atomic.get seqcst $s $f +;; drop +;; )) +;; "invalid type: `struct.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`" +;; ) + +;; (assert_invalid +;; (module +;; (type $s (shared (struct (field $f (ref (shared func)))))) +;; (func +;; unreachable +;; struct.atomic.get seqcst $s $f +;; drop +;; )) +;; "invalid type: `struct.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`" +;; ) + +;; (assert_invalid +;; (module +;; (type $s (shared (struct (field $f i8)))) +;; (func +;; unreachable +;; struct.atomic.get seqcst $s $f +;; drop +;; )) +;; "can only use struct `get` with non-packed storage types" +;; ) + +;; (assert_invalid +;; (module +;; (type $s (shared (struct (field $f (mut (ref (shared extern))))))) +;; (func +;; unreachable +;; struct.atomic.set seqcst $s $f +;; )) +;; "invalid type: `struct.atomic.set` only allows `i8`, `i16`, `i32`, `i64` and subtypes of `anyref`" +;; ) + +(module + (type $i32 (struct (field (mut i32)))) + (type $i64 (struct (field (mut i64)))) + (type $any (struct (field (mut anyref)))) + (type $eq (struct (field (mut eqref)))) + (type $shared-i32 (shared (struct (field (mut i32))))) + (type $shared-i64 (shared (struct (field (mut i64))))) + (type $shared-any (shared (struct (field (mut (ref null (shared any))))))) + (type $shared-eq (shared (struct (field (mut (ref null (shared eq))))))) + + (global $i32 (ref null $i32) (struct.new_default $i32)) + (global $i64 (ref null $i64) (struct.new_default $i64)) + (global $any (ref null $any) (struct.new_default $any)) + (global $eq (ref null $eq) (struct.new_default $eq)) + (global $shared-i32 (ref null $shared-i32) (struct.new_default $shared-i32)) + (global $shared-i64 (ref null $shared-i64) (struct.new_default $shared-i64)) + (global $shared-any (ref null $shared-any) (struct.new_default $shared-any)) + (global $shared-eq (ref null $shared-eq) (struct.new_default $shared-eq)) + + (func (export "set-i32") (param i32) (struct.set $i32 0 (global.get $i32) (local.get 0))) + (func (export "set-i64") (param i64) (struct.set $i64 0 (global.get $i64) (local.get 0))) + (func (export "set-any") (param i32) (struct.set $any 0 (global.get $any) (struct.new $i32 (local.get 0)))) + (func (export "set-eq") (param i32) (struct.set $eq 0 (global.get $eq) (ref.i31 (local.get 0)))) + (func (export "set-shared-i32") (param i32) (struct.set $shared-i32 0 (global.get $shared-i32) (local.get 0))) + (func (export "set-shared-i64") (param i64) (struct.set $shared-i64 0 (global.get $shared-i64) (local.get 0))) + (func (export "set-shared-any") (param i32) (struct.set $shared-any 0 (global.get $shared-any) (struct.new $shared-i32 (local.get 0)))) + (func (export "set-shared-eq") (param i32) (struct.set $shared-eq 0 (global.get $shared-eq) (ref.i31_shared (local.get 0)))) + + (func (export "get-i32") (result i32) (struct.get $i32 0 (global.get $i32))) + (func (export "get-i64") (result i64) (struct.get $i64 0 (global.get $i64))) + (func (export "get-any") (result i32) (struct.get $i32 0 (ref.cast (ref null $i32) (struct.get $any 0 (global.get $any))))) + (func (export "get-eq") (result i32) (i31.get_u (ref.cast i31ref (struct.get $eq 0 (global.get $eq))))) + (func (export "get-shared-i32") (result i32) (struct.get $shared-i32 0 (global.get $shared-i32))) + (func (export "get-shared-i64") (result i64) (struct.get $shared-i64 0 (global.get $shared-i64))) + (func (export "get-shared-any") (result i32) (struct.get $shared-i32 0 (ref.cast (ref null $shared-i32) (struct.get $shared-any 0 (global.get $shared-any))))) + (func (export "get-shared-eq") (result i32) (i31.get_u (ref.cast (ref null (shared i31)) (struct.get $shared-eq 0 (global.get $shared-eq))))) + + (func (export "rmw-add-i32") (param i32) (result i32) + (struct.atomic.rmw.add $i32 0 (global.get $i32) (local.get 0)) + ) + (func (export "rmw-sub-i32") (param i32) (result i32) + (struct.atomic.rmw.sub $i32 0 (global.get $i32) (local.get 0)) + ) + (func (export "rmw-and-i32") (param i32) (result i32) + (struct.atomic.rmw.and $i32 0 (global.get $i32) (local.get 0)) + ) + (func (export "rmw-or-i32") (param i32) (result i32) + (struct.atomic.rmw.or $i32 0 (global.get $i32) (local.get 0)) + ) + (func (export "rmw-xor-i32") (param i32) (result i32) + (struct.atomic.rmw.xor $i32 0 (global.get $i32) (local.get 0)) + ) + (func (export "rmw-xchg-i32") (param i32) (result i32) + (struct.atomic.rmw.xchg $i32 0 (global.get $i32) (local.get 0)) + ) + (func (export "rmw-cmpxchg-i32") (param i32 i32) (result i32) + (struct.atomic.rmw.cmpxchg $i32 0 (global.get $i32) (local.get 0) (local.get 1)) + ) + + (func (export "rmw-add-i64") (param i64) (result i64) + (struct.atomic.rmw.add $i64 0 (global.get $i64) (local.get 0)) + ) + (func (export "rmw-sub-i64") (param i64) (result i64) + (struct.atomic.rmw.sub $i64 0 (global.get $i64) (local.get 0)) + ) + (func (export "rmw-and-i64") (param i64) (result i64) + (struct.atomic.rmw.and $i64 0 (global.get $i64) (local.get 0)) + ) + (func (export "rmw-or-i64") (param i64) (result i64) + (struct.atomic.rmw.or $i64 0 (global.get $i64) (local.get 0)) + ) + (func (export "rmw-xor-i64") (param i64) (result i64) + (struct.atomic.rmw.xor $i64 0 (global.get $i64) (local.get 0)) + ) + (func (export "rmw-xchg-i64") (param i64) (result i64) + (struct.atomic.rmw.xchg $i64 0 (global.get $i64) (local.get 0)) + ) + (func (export "rmw-cmpxchg-i64") (param i64 i64) (result i64) + (struct.atomic.rmw.cmpxchg $i64 0 (global.get $i64) (local.get 0) (local.get 1)) + ) + + (func (export "rmw-xchg-any") (param i32) (result i32) + (struct.get $i32 0 (ref.cast (ref null $i32) + (struct.atomic.rmw.xchg $any 0 (global.get $any) (struct.new $i32 (local.get 0))) + )) + ) + + (func (export "rmw-cmpxchg-eq") (param i32 i32) (result i32) + (i31.get_u (ref.cast i31ref + (struct.atomic.rmw.cmpxchg $eq 0 (global.get $eq) (ref.i31 (local.get 0)) (ref.i31 (local.get 1))) + )) + ) + + (func (export "rmw-add-shared-i32") (param i32) (result i32) + (struct.atomic.rmw.add $shared-i32 0 (global.get $shared-i32) (local.get 0)) + ) + (func (export "rmw-sub-shared-i32") (param i32) (result i32) + (struct.atomic.rmw.sub $shared-i32 0 (global.get $shared-i32) (local.get 0)) + ) + (func (export "rmw-and-shared-i32") (param i32) (result i32) + (struct.atomic.rmw.and $shared-i32 0 (global.get $shared-i32) (local.get 0)) + ) + (func (export "rmw-or-shared-i32") (param i32) (result i32) + (struct.atomic.rmw.or $shared-i32 0 (global.get $shared-i32) (local.get 0)) + ) + (func (export "rmw-xor-shared-i32") (param i32) (result i32) + (struct.atomic.rmw.xor $shared-i32 0 (global.get $shared-i32) (local.get 0)) + ) + (func (export "rmw-xchg-shared-i32") (param i32) (result i32) + (struct.atomic.rmw.xchg $shared-i32 0 (global.get $shared-i32) (local.get 0)) + ) + (func (export "rmw-cmpxchg-shared-i32") (param i32 i32) (result i32) + (struct.atomic.rmw.cmpxchg $shared-i32 0 (global.get $shared-i32) (local.get 0) (local.get 1)) + ) + + (func (export "rmw-add-shared-i64") (param i64) (result i64) + (struct.atomic.rmw.add $shared-i64 0 (global.get $shared-i64) (local.get 0)) + ) + (func (export "rmw-sub-shared-i64") (param i64) (result i64) + (struct.atomic.rmw.sub $shared-i64 0 (global.get $shared-i64) (local.get 0)) + ) + (func (export "rmw-and-shared-i64") (param i64) (result i64) + (struct.atomic.rmw.and $shared-i64 0 (global.get $shared-i64) (local.get 0)) + ) + (func (export "rmw-or-shared-i64") (param i64) (result i64) + (struct.atomic.rmw.or $shared-i64 0 (global.get $shared-i64) (local.get 0)) + ) + (func (export "rmw-xor-shared-i64") (param i64) (result i64) + (struct.atomic.rmw.xor $shared-i64 0 (global.get $shared-i64) (local.get 0)) + ) + (func (export "rmw-xchg-shared-i64") (param i64) (result i64) + (struct.atomic.rmw.xchg $shared-i64 0 (global.get $shared-i64) (local.get 0)) + ) + (func (export "rmw-cmpxchg-shared-i64") (param i64 i64) (result i64) + (struct.atomic.rmw.cmpxchg $shared-i64 0 (global.get $shared-i64) (local.get 0) (local.get 1)) + ) + + (func (export "rmw-xchg-shared-any") (param i32) (result i32) + (struct.get $shared-i32 0 (ref.cast (ref null $shared-i32) + (struct.atomic.rmw.xchg $shared-any 0 (global.get $shared-any) (struct.new $shared-i32 (local.get 0))) + )) + ) + + (func (export "rmw-cmpxchg-shared-eq") (param i32 i32) (result i32) + (i31.get_u (ref.cast (ref null (shared i31)) + (struct.atomic.rmw.cmpxchg $shared-eq 0 (global.get $shared-eq) (ref.i31_shared (local.get 0)) (ref.i31_shared (local.get 1))) + )) + ) +) + +(assert_return (invoke "set-i32" (i32.const 0))) +(assert_return (invoke "rmw-add-i32" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rmw-add-i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "get-i32") (i32.const 2)) + +(assert_return (invoke "set-i32" (i32.const 0))) +(assert_return (invoke "rmw-sub-i32" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rmw-sub-i32" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "get-i32") (i32.const -2)) + +(assert_return (invoke "set-i32" (i32.const 3))) +(assert_return (invoke "rmw-and-i32" (i32.const 1)) (i32.const 3)) +(assert_return (invoke "rmw-and-i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "get-i32") (i32.const 1)) + +(assert_return (invoke "set-i32" (i32.const 0))) +(assert_return (invoke "rmw-or-i32" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rmw-or-i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "get-i32") (i32.const 1)) + +(assert_return (invoke "set-i32" (i32.const 3))) +(assert_return (invoke "rmw-xor-i32" (i32.const 1)) (i32.const 3)) +(assert_return (invoke "rmw-xor-i32" (i32.const 3)) (i32.const 2)) +(assert_return (invoke "get-i32") (i32.const 1)) + +(assert_return (invoke "set-i32" (i32.const 1))) +(assert_return (invoke "rmw-xchg-i32" (i32.const 99)) (i32.const 1)) +(assert_return (invoke "rmw-xchg-i32" (i32.const 42)) (i32.const 99)) +(assert_return (invoke "get-i32") (i32.const 42)) + +(assert_return (invoke "set-i32" (i32.const 1))) +(assert_return (invoke "rmw-cmpxchg-i32" (i32.const 0) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "get-i32") (i32.const 1)) +(assert_return (invoke "rmw-cmpxchg-i32" (i32.const 1) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "get-i32") (i32.const 2)) + +(assert_return (invoke "set-i64" (i64.const 0))) +(assert_return (invoke "rmw-add-i64" (i64.const 1)) (i64.const 0)) +(assert_return (invoke "rmw-add-i64" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "get-i64") (i64.const 2)) + +(assert_return (invoke "set-i64" (i64.const 0))) +(assert_return (invoke "rmw-sub-i64" (i64.const 1)) (i64.const 0)) +(assert_return (invoke "rmw-sub-i64" (i64.const 1)) (i64.const -1)) +(assert_return (invoke "get-i64") (i64.const -2)) + +(assert_return (invoke "set-i64" (i64.const 3))) +(assert_return (invoke "rmw-and-i64" (i64.const 1)) (i64.const 3)) +(assert_return (invoke "rmw-and-i64" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "get-i64") (i64.const 1)) + +(assert_return (invoke "set-i64" (i64.const 0))) +(assert_return (invoke "rmw-or-i64" (i64.const 1)) (i64.const 0)) +(assert_return (invoke "rmw-or-i64" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "get-i64") (i64.const 1)) + +(assert_return (invoke "set-i64" (i64.const 3))) +(assert_return (invoke "rmw-xor-i64" (i64.const 1)) (i64.const 3)) +(assert_return (invoke "rmw-xor-i64" (i64.const 3)) (i64.const 2)) +(assert_return (invoke "get-i64") (i64.const 1)) + +(assert_return (invoke "set-i64" (i64.const 1))) +(assert_return (invoke "rmw-xchg-i64" (i64.const 99)) (i64.const 1)) +(assert_return (invoke "rmw-xchg-i64" (i64.const 42)) (i64.const 99)) +(assert_return (invoke "get-i64") (i64.const 42)) + +(assert_return (invoke "set-i64" (i64.const 1))) +(assert_return (invoke "rmw-cmpxchg-i64" (i64.const 0) (i64.const 2)) (i64.const 1)) +(assert_return (invoke "get-i64") (i64.const 1)) +(assert_return (invoke "rmw-cmpxchg-i64" (i64.const 1) (i64.const 2)) (i64.const 1)) +(assert_return (invoke "get-i64") (i64.const 2)) + +(assert_return (invoke "set-any" (i32.const 1))) +(assert_return (invoke "rmw-xchg-any" (i32.const 99)) (i32.const 1)) +(assert_return (invoke "rmw-xchg-any" (i32.const 42)) (i32.const 99)) +(assert_return (invoke "get-any") (i32.const 42)) + +(assert_return (invoke "set-eq" (i32.const 1))) +(assert_return (invoke "rmw-cmpxchg-eq" (i32.const 0) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "get-eq") (i32.const 1)) +(assert_return (invoke "rmw-cmpxchg-eq" (i32.const 1) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "get-eq") (i32.const 2)) + +(assert_return (invoke "set-shared-i32" (i32.const 0))) +(assert_return (invoke "rmw-add-shared-i32" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rmw-add-shared-i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "get-shared-i32") (i32.const 2)) + +(assert_return (invoke "set-shared-i32" (i32.const 0))) +(assert_return (invoke "rmw-sub-shared-i32" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rmw-sub-shared-i32" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "get-shared-i32") (i32.const -2)) + +(assert_return (invoke "set-shared-i32" (i32.const 3))) +(assert_return (invoke "rmw-and-shared-i32" (i32.const 1)) (i32.const 3)) +(assert_return (invoke "rmw-and-shared-i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "get-shared-i32") (i32.const 1)) + +(assert_return (invoke "set-shared-i32" (i32.const 0))) +(assert_return (invoke "rmw-or-shared-i32" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rmw-or-shared-i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "get-shared-i32") (i32.const 1)) + +(assert_return (invoke "set-shared-i32" (i32.const 3))) +(assert_return (invoke "rmw-xor-shared-i32" (i32.const 1)) (i32.const 3)) +(assert_return (invoke "rmw-xor-shared-i32" (i32.const 3)) (i32.const 2)) +(assert_return (invoke "get-shared-i32") (i32.const 1)) + +(assert_return (invoke "set-shared-i32" (i32.const 1))) +(assert_return (invoke "rmw-xchg-shared-i32" (i32.const 99)) (i32.const 1)) +(assert_return (invoke "rmw-xchg-shared-i32" (i32.const 42)) (i32.const 99)) +(assert_return (invoke "get-shared-i32") (i32.const 42)) + +(assert_return (invoke "set-shared-i32" (i32.const 1))) +(assert_return (invoke "rmw-cmpxchg-shared-i32" (i32.const 0) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "get-shared-i32") (i32.const 1)) +(assert_return (invoke "rmw-cmpxchg-shared-i32" (i32.const 1) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "get-shared-i32") (i32.const 2)) + +(assert_return (invoke "set-shared-i64" (i64.const 0))) +(assert_return (invoke "rmw-add-shared-i64" (i64.const 1)) (i64.const 0)) +(assert_return (invoke "rmw-add-shared-i64" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "get-shared-i64") (i64.const 2)) + +(assert_return (invoke "set-shared-i64" (i64.const 0))) +(assert_return (invoke "rmw-sub-shared-i64" (i64.const 1)) (i64.const 0)) +(assert_return (invoke "rmw-sub-shared-i64" (i64.const 1)) (i64.const -1)) +(assert_return (invoke "get-shared-i64") (i64.const -2)) + +(assert_return (invoke "set-shared-i64" (i64.const 3))) +(assert_return (invoke "rmw-and-shared-i64" (i64.const 1)) (i64.const 3)) +(assert_return (invoke "rmw-and-shared-i64" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "get-shared-i64") (i64.const 1)) + +(assert_return (invoke "set-shared-i64" (i64.const 0))) +(assert_return (invoke "rmw-or-shared-i64" (i64.const 1)) (i64.const 0)) +(assert_return (invoke "rmw-or-shared-i64" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "get-shared-i64") (i64.const 1)) + +(assert_return (invoke "set-shared-i64" (i64.const 3))) +(assert_return (invoke "rmw-xor-shared-i64" (i64.const 1)) (i64.const 3)) +(assert_return (invoke "rmw-xor-shared-i64" (i64.const 3)) (i64.const 2)) +(assert_return (invoke "get-shared-i64") (i64.const 1)) + +(assert_return (invoke "set-shared-i64" (i64.const 1))) +(assert_return (invoke "rmw-xchg-shared-i64" (i64.const 99)) (i64.const 1)) +(assert_return (invoke "rmw-xchg-shared-i64" (i64.const 42)) (i64.const 99)) +(assert_return (invoke "get-shared-i64") (i64.const 42)) + +(assert_return (invoke "set-shared-i64" (i64.const 1))) +(assert_return (invoke "rmw-cmpxchg-shared-i64" (i64.const 0) (i64.const 2)) (i64.const 1)) +(assert_return (invoke "get-shared-i64") (i64.const 1)) +(assert_return (invoke "rmw-cmpxchg-shared-i64" (i64.const 1) (i64.const 2)) (i64.const 1)) +(assert_return (invoke "get-shared-i64") (i64.const 2)) + +(assert_return (invoke "set-shared-any" (i32.const 1))) +(assert_return (invoke "rmw-xchg-shared-any" (i32.const 99)) (i32.const 1)) +(assert_return (invoke "rmw-xchg-shared-any" (i32.const 42)) (i32.const 99)) +(assert_return (invoke "get-shared-any") (i32.const 42)) + +(assert_return (invoke "set-shared-eq" (i32.const 1))) +(assert_return (invoke "rmw-cmpxchg-shared-eq" (i32.const 0) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "get-shared-eq") (i32.const 1)) +(assert_return (invoke "rmw-cmpxchg-shared-eq" (i32.const 1) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "get-shared-eq") (i32.const 2)) From c582f8d582e7ff3b31d39b38fe26037930b6422f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 15 Jan 2025 15:44:49 -0800 Subject: [PATCH 239/622] [NFC] Fix note about manually generated test output (#7221) The note mentioned update_lit_checks.py in the first line, which made update_lit_checks.py think that it _should_ update the test output, even though the comment was explicitly about how it should not update the test output. Reword the comment to avoid the problem. Also make the check in the update script stricter for good measure. --- scripts/update_lit_checks.py | 14 +++++++------- test/lit/passes/memory-copy-fill-lowering.wast | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/update_lit_checks.py b/scripts/update_lit_checks.py index b6bb213a8f7..753b967ca41 100755 --- a/scripts/update_lit_checks.py +++ b/scripts/update_lit_checks.py @@ -30,8 +30,8 @@ script_dir = os.path.dirname(__file__) script_name = os.path.basename(__file__) -NOTICE = (';; NOTE: Assertions have been generated by {script} and should not' + - ' be edited.') +NOTICE_PREFIX = f';; NOTE: Assertions have been generated by {script_name}' +NOTICE = NOTICE_PREFIX + '{args} and should not be edited.' RUN_LINE_RE = re.compile(r'^\s*;;\s*RUN:\s*(.*)$') CHECK_PREFIX_RE = re.compile(r'.*--check-prefix[= ](\S+).*') @@ -80,7 +80,7 @@ def itertests(args): with open(test) as f: lines = [line.rstrip() for line in f] first_line = lines[0] if lines else '' - if script_name not in first_line and not args.force: + if NOTICE_PREFIX not in first_line and not args.force: warn(f'Skipping test {test} which was not generated by ' f'{script_name}. Use -f to override.') continue @@ -258,12 +258,12 @@ def update_test(args, test, lines, tmp): _, kind, name = indentKindName(match) named_items.append((kind, name)) - script = script_name + notice_args = '' if all_items: - script += ' --all-items' + notice_args += ' --all-items' if output_kind != 'wat': - script += f' --output={output_kind}' - output_lines = [NOTICE.format(script=script)] + notice_args += f' --output={output_kind}' + output_lines = [NOTICE.format(args=notice_args)] def emit_checks(indent, prefix, lines): def pad(line): diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast index ea5198f893a..990c2fa30de 100644 --- a/test/lit/passes/memory-copy-fill-lowering.wast +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -1,5 +1,5 @@ -;; NOTE: These assertions have been manually generated, and cannot be updated by update_lit_checks.py -;; because of the assertion at the end (update_lit_checks.py ignores the features section because it's +;; NOTE: These assertions have been manually generated because of the assertion +;; at the end (update_lit_checks.py ignores the features section because it's ;; not semantically part of the module.) ;; RUN: wasm-opt --enable-bulk-memory %s --llvm-memory-copy-fill-lowering --emit-target-features -S -o - | filecheck %s From f9dd59e971678c2d05712e7d358eccc72a0153b4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 15 Jan 2025 16:07:25 -0800 Subject: [PATCH 240/622] Store HeapTypes in Tags (#7220) Tags in WebAssembly syntactically refer to function heap types, but Binaryen IR was previously storing signatures instead and reconstructing heap types as necessary when emitting binaries. Before WasmGC this was not a problem because the reconstructed types would be the same as the original types, but with WasmGC round tripping function types through Signature could lose information such as the rec group, the declared supertype, and the finality from the original type. Store the original heap type in the IR instead to stop losing this information. Fixes #7219. --- src/binaryen-c.cpp | 8 ++-- src/ir/child-typer.h | 4 +- src/ir/eh-utils.cpp | 2 +- src/ir/module-splitting.cpp | 2 +- src/ir/module-utils.cpp | 4 +- src/ir/possible-contents.cpp | 4 +- src/ir/subtype-exprs.h | 2 +- src/ir/type-updating.cpp | 6 +-- src/parser/contexts.h | 2 +- src/passes/MergeBlocks.cpp | 2 +- src/passes/Print.cpp | 44 ++++++++++------- src/passes/TranslateEH.cpp | 2 +- src/tools/fuzzing/fuzzing.cpp | 6 +-- src/tools/wasm-merge.cpp | 6 +-- src/wasm-builder.h | 4 +- src/wasm.h | 5 +- src/wasm/wasm-binary.cpp | 4 +- src/wasm/wasm-ir-builder.cpp | 6 +-- src/wasm/wasm-validator.cpp | 22 ++++----- src/wasm/wasm.cpp | 11 ++--- test/binaryen.js/exception-handling.js.txt | 2 +- test/binaryen.js/kitchen-sink.js.txt | 10 ++-- test/binaryen.js/tag.js.txt | 6 +-- test/br_to_try.wasm.fromBinary | 2 +- test/break-within-catch.wasm.fromBinary | 2 +- test/example/c-api-kitchen-sink.txt | 2 +- test/example/module-splitting.txt | 12 ++--- test/lit/basic/exception-handling-legacy.wast | 30 ++++++------ test/lit/basic/exception-handling.wast | 30 ++++++------ test/lit/basic/extra-branch-values.wast | 8 ++-- test/lit/basic/pop-fixup.wast | 4 +- test/lit/basic/reference-types.wast | 6 +-- test/lit/basic/tags.wast | 48 +++++++++---------- .../lit/basic/typed_continuations_resume.wast | 6 +-- .../basic/typed_continuations_suspend.wast | 6 +-- test/lit/binary/stacky-eh-legacy.test | 2 +- test/lit/binary/stacky-nn-tuple.test | 2 +- test/lit/control-flow-input.wast | 2 +- test/lit/gc-eh-legacy.wast | 2 +- test/lit/merge/fusing.wat | 2 +- test/lit/merge/names.wat | 8 ++-- test/lit/merge/renamings.wat | 10 ++-- test/lit/merge/types.wat | 4 +- .../lit/passes/coalesce-locals-eh-legacy.wast | 4 +- test/lit/passes/coalesce-locals-eh.wast | 4 +- test/lit/passes/code-folding-eh-legacy.wast | 2 +- test/lit/passes/code-folding-eh.wast | 2 +- test/lit/passes/code-pushing-eh-legacy.wast | 2 +- test/lit/passes/code-pushing-eh.wast | 2 +- test/lit/passes/dce-eh-legacy.wast | 6 +-- test/lit/passes/dce-eh.wast | 4 +- test/lit/passes/flatten-eh-legacy.wast | 4 +- test/lit/passes/global-effects-eh-legacy.wast | 4 +- test/lit/passes/global-effects.wast | 4 +- test/lit/passes/gto-mutability.wast | 4 +- test/lit/passes/gufa-eh.wast | 2 +- test/lit/passes/gufa-refs.wast | 10 ++-- test/lit/passes/gufa-tags.wast | 4 +- test/lit/passes/heap-store-optimization.wast | 2 +- test/lit/passes/heap2local.wast | 2 +- test/lit/passes/inlining-eh-legacy.wast | 2 +- .../passes/instrument-locals-eh-legacy.wast | 2 +- test/lit/passes/local-subtyping.wast | 2 +- test/lit/passes/merge-blocks-eh.wast | 6 +-- test/lit/passes/once-reduction.wast | 2 +- .../optimize-instructions-eh-legacy.wast | 2 +- .../optimize-instructions-iit-eh-legacy.wast | 2 +- test/lit/passes/poppify.wast | 2 +- test/lit/passes/remove-unused-brs-eh.wast | 10 ++-- .../passes/remove-unused-names-eh-legacy.wast | 2 +- test/lit/passes/rse-eh-legacy.wast | 4 +- test/lit/passes/rse-eh.wast | 4 +- test/lit/passes/signature-pruning.wast | 28 +++++------ .../lit/passes/simplify-locals-eh-legacy.wast | 2 +- test/lit/passes/simplify-locals-eh.wast | 2 +- test/lit/passes/stack-ir-eh-legacy.wast | 2 +- test/lit/passes/stack-ir-eh.wast | 2 +- .../passes/stack-ir-roundtrip-eh-legacy.wast | 2 +- test/lit/passes/translate-to-exnref.wast | 12 ++--- test/lit/passes/type-refining.wast | 4 +- test/lit/passes/unsubtyping.wast | 20 ++++---- test/lit/passes/vacuum-eh-legacy.wast | 4 +- test/lit/passes/vacuum-eh.wast | 4 +- test/lit/passes/vacuum-tnh.wast | 4 +- test/lit/wat-kitchen-sink.wast | 16 +++---- test/metadce/rooted-export.wast.dced | 2 +- test/metadce/tag.wast.dced | 4 +- test/passes/dwarf_with_exceptions.bin.txt | 4 +- test/passes/metrics_all-features.txt | 4 +- ...inify-imports-and-exports_all-features.txt | 4 +- test/passes/minify-imports_all-features.txt | 4 +- ...unused-names_merge-blocks_all-features.txt | 2 +- ...nfunction-module-elements_all-features.txt | 2 +- test/try-delegate.wasm.fromBinary | 2 +- 94 files changed, 290 insertions(+), 294 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index fa834d2686c..8eb73b4c259 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4770,7 +4770,7 @@ BinaryenTagRef BinaryenAddTag(BinaryenModuleRef module, BinaryenType results) { auto* ret = new Tag(); ret->setExplicitName(name); - ret->sig = Signature(Type(params), Type(results)); + ret->type = Signature(Type(params), Type(results)); ((Module*)module)->addTag(ret); return ret; } @@ -4874,7 +4874,7 @@ void BinaryenAddTagImport(BinaryenModuleRef module, tag->name = internalName; tag->module = externalModuleName; tag->base = externalBaseName; - tag->sig = Signature(Type(params), Type(results)); + tag->type = Signature(Type(params), Type(results)); ((Module*)module)->addTag(std::move(tag)); } else { // already exists so just set module and base @@ -5838,11 +5838,11 @@ const char* BinaryenTagGetName(BinaryenTagRef tag) { return ((Tag*)tag)->name.str.data(); } BinaryenType BinaryenTagGetParams(BinaryenTagRef tag) { - return ((Tag*)tag)->sig.params.getID(); + return ((Tag*)tag)->params().getID(); } BinaryenType BinaryenTagGetResults(BinaryenTagRef tag) { - return ((Tag*)tag)->sig.results.getID(); + return ((Tag*)tag)->results().getID(); } // diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index aac7744ab45..685c171d016 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -789,7 +789,7 @@ template struct ChildTyper : OverriddenVisitor { void visitTryTable(TryTable* curr) { note(&curr->body, curr->type); } void visitThrow(Throw* curr) { - auto type = wasm.getTag(curr->tag)->sig.params; + auto type = wasm.getTag(curr->tag)->params(); assert(curr->operands.size() == type.size()); for (size_t i = 0; i < type.size(); ++i) { note(&curr->operands[i], type[i]); @@ -1113,7 +1113,7 @@ template struct ChildTyper : OverriddenVisitor { } void visitSuspend(Suspend* curr) { - auto params = wasm.getTag(curr->tag)->sig.params; + auto params = wasm.getTag(curr->tag)->params(); assert(params.size() == curr->operands.size()); for (size_t i = 0; i < params.size(); ++i) { note(&curr->operands[i], params[i]); diff --git a/src/ir/eh-utils.cpp b/src/ir/eh-utils.cpp index 6d65de4ad1c..43fd1627d31 100644 --- a/src/ir/eh-utils.cpp +++ b/src/ir/eh-utils.cpp @@ -111,7 +111,7 @@ void handleBlockNestedPop(Try* try_, Function* func, Module& wasm) { for (Index i = 0; i < try_->catchTags.size(); i++) { Name tagName = try_->catchTags[i]; auto* tag = wasm.getTag(tagName); - if (tag->sig.params == Type::none) { + if (tag->params() == Type::none) { continue; } diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index a7ee96fa28f..bcf0b344201 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -883,7 +883,7 @@ void ModuleSplitter::shareImportableItems() { for (auto& tag : primary.tags) { auto secondaryTag = std::make_unique(); - secondaryTag->sig = tag->sig; + secondaryTag->type = tag->type; makeImportExport(*tag, *secondaryTag, "tag", ExternalKind::Tag); secondary.addTag(std::move(secondaryTag)); } diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 431e33cc6e7..89c1503399f 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -125,7 +125,7 @@ Tag* copyTag(Tag* tag, Module& out) { auto* ret = new Tag(); ret->name = tag->name; ret->hasExplicitName = tag->hasExplicitName; - ret->sig = tag->sig; + ret->type = tag->type; ret->module = tag->module; ret->base = tag->base; out.addTag(ret); @@ -474,7 +474,7 @@ InsertOrderedMap collectHeapTypeInfo( info.note(curr->type); } for (auto& curr : wasm.tags) { - info.note(curr->sig); + info.note(curr->type); } for (auto& curr : wasm.tables) { info.note(curr->type); diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 49c8ee17615..0bc03160ed7 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1151,7 +1151,7 @@ struct InfoCollector auto tag = curr->catchTags[tagIndex]; auto* body = curr->catchBodies[tagIndex]; - auto params = getModule()->getTag(tag)->sig.params; + auto params = getModule()->getTag(tag)->params(); if (params.size() == 0) { continue; } @@ -1189,7 +1189,7 @@ struct InfoCollector Index exnrefIndex = 0; if (tag.is()) { - auto params = getModule()->getTag(tag)->sig.params; + auto params = getModule()->getTag(tag)->params(); for (Index i = 0; i < params.size(); i++) { if (isRelevant(params[i])) { diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index b1e40b7457f..ba5640eff7f 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -257,7 +257,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } void visitThrow(Throw* curr) { - Type params = self()->getModule()->getTag(curr->tag)->sig.params; + Type params = self()->getModule()->getTag(curr->tag)->params(); assert(params.size() == curr->operands.size()); for (size_t i = 0, size = curr->operands.size(); i < size; ++i) { self()->noteSubtype(curr->operands[i], params[i]); diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 8b74ebfcd5d..b90d8eb8790 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -225,10 +225,6 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { return type; } - Signature getNew(Signature sig) { - return Signature(getNew(sig.params), getNew(sig.results)); - } - void visitExpression(Expression* curr) { // local.get and local.tee are special in that their type is tied to the // type of the local in the function, which is tied to the signature. That @@ -304,7 +300,7 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { global->type = updater.getNew(global->type); } for (auto& tag : wasm.tags) { - tag->sig = updater.getNew(tag->sig); + tag->type = updater.getNew(tag->type); } } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 1c25e881f7c..c2e6eebf102 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1372,7 +1372,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, if (!use.type.isSignature()) { return in.err(pos, "tag type must be a signature"); } - t->sig = use.type.getSignature(); + t->type = use.type; return Ok{}; } }; diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index e0c5e575a37..3013f67ac9b 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -144,7 +144,7 @@ struct ProblemFinder // make more sense in TupleOptimization though as it would need // to track uses of parts of a tuple. if (!tryy->catchTags[i] || - getModule()->getTag(tryy->catchTags[i])->sig.params.size() == 0) { + getModule()->getTag(tryy->catchTags[i])->params().size() == 0) { // There must be a ref here, otherwise there is no value being sent // at all, and we should not be running ProblemFinder at all. assert(tryy->catchRefs[i]); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index abacb86052c..e9f8509fef6 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -413,6 +413,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { void visitTag(Tag* curr); void visitImportedTag(Tag* curr); void visitDefinedTag(Tag* curr); + void printTagType(HeapType type); void printTableHeader(Table* curr); void visitTable(Table* curr); void visitElementSegment(ElementSegment* curr); @@ -3185,16 +3186,9 @@ void PrintSExpression::visitImportedTag(Tag* curr) { emitImportHeader(curr); o << "(tag "; curr->name.print(o); - if (curr->sig.params != Type::none) { - o << maybeSpace; - printParamType(curr->sig.params); - } - if (curr->sig.results != Type::none) { - o << maybeSpace; - printResultType(curr->sig.results); - } - o << "))"; - o << maybeNewLine; + o << maybeSpace; + printTagType(curr->type); + o << "))" << maybeNewLine; } void PrintSExpression::visitDefinedTag(Tag* curr) { @@ -3202,15 +3196,31 @@ void PrintSExpression::visitDefinedTag(Tag* curr) { o << '('; printMedium(o, "tag "); curr->name.print(o); - if (curr->sig.params != Type::none) { - o << maybeSpace; - printParamType(curr->sig.params); + o << maybeSpace; + printTagType(curr->type); + o << ')' << maybeNewLine; +} + +void PrintSExpression::printTagType(HeapType type) { + o << "(type "; + printHeapType(type); + o << ')'; + if (auto params = type.getSignature().params; params != Type::none) { + o << maybeSpace << "(param"; + for (auto t : params) { + o << ' '; + printType(t); + } + o << ')'; } - if (curr->sig.results != Type::none) { - o << maybeSpace; - printResultType(curr->sig.results); + if (auto results = type.getSignature().results; results != Type::none) { + o << maybeSpace << "(result"; + for (auto t : results) { + o << ' '; + printType(t); + } + o << ')'; } - o << ")" << maybeNewLine; } void PrintSExpression::printTableHeader(Table* curr) { diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index a3411cda967..7e13ee53179 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -596,7 +596,7 @@ struct TranslateToExnref : public WalkerPass> { auto* catchBody = curr->catchBodies[i]; Type tagType = Type::none; if (tryTable->catchTags[i]) { - tagType = wasm->getTag(tryTable->catchTags[i])->sig.params; + tagType = wasm->getTag(tryTable->catchTags[i])->params(); } // This is to be the body of the next(outer) level block diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 28f7a29dbc6..50191045fed 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -2177,7 +2177,7 @@ Expression* TranslateToFuzzReader::makeTry(Type type) { // Catch bodies (aside from a catch-all) begin with a pop. Expression* prefix = nullptr; if (i < numTags) { - auto tagType = wasm.getTag(catchTags[i])->sig.params; + auto tagType = wasm.getTag(catchTags[i])->params(); if (tagType != Type::none) { auto* pop = builder.makePop(tagType); // Capture the pop in a local, so that it can be used later. @@ -2223,7 +2223,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) { // Look for a specific tag. auto& tag = pick(wasm.tags); tagName = tag->name; - tagType = tag->sig.params; + tagType = tag->params(); } else { // Add a catch_all at the end, some of the time (but all of the time if we // have nothing else). @@ -4793,7 +4793,7 @@ Expression* TranslateToFuzzReader::makeThrow(Type type) { addTag(); } auto* tag = pick(wasm.tags).get(); - auto tagType = tag->sig.params; + auto tagType = tag->params(); std::vector operands; for (auto t : tagType) { operands.push_back(make(t)); diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index 3de2283502c..166c875aecd 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -525,10 +525,10 @@ void fuseImportsAndExports() { kindModuleExportMaps[ExternalKind::Tag][import->module][import->base]; if (internalName.is()) { auto* export_ = merged.getTag(internalName); - if (HeapType(export_->sig) != HeapType(import->sig)) { + if (export_->type != import->type) { reportTypeMismatch(valid, "tag", import); - std::cerr << "export type " << export_->sig - << " is different from import type " << import->sig << ".\n"; + std::cerr << "export type " << export_->type + << " is different from import type " << import->type << ".\n"; } } }); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 5201470e17e..0666a489bfe 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -162,10 +162,10 @@ class Builder { return glob; } - static std::unique_ptr makeTag(Name name, Signature sig) { + static std::unique_ptr makeTag(Name name, HeapType type) { auto tag = std::make_unique(); tag->name = name; - tag->sig = sig; + tag->type = type; return tag; } diff --git a/src/wasm.h b/src/wasm.h index 1ce9d9c02b5..eee81fe106b 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2297,7 +2297,10 @@ class Global : public Importable { class Tag : public Importable { public: - Signature sig; + HeapType type; + + Type params() { return type.getSignature().params; } + Type results() { return type.getSignature().results; } }; // "Opaque" data, not part of the core wasm spec, that is held in binaries. diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index ca4d935c8a1..0841d4b8093 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -349,7 +349,7 @@ void WasmBinaryWriter::writeImports() { writeImportHeader(tag); o << U32LEB(int32_t(ExternalKind::Tag)); o << uint8_t(0); // Reserved 'attribute' field. Always 0. - o << U32LEB(getTypeIndex(tag->sig)); + o << U32LEB(getTypeIndex(tag->type)); }); ModuleUtils::iterImportedMemories(*wasm, [&](Memory* memory) { writeImportHeader(memory); @@ -854,7 +854,7 @@ void WasmBinaryWriter::writeTags() { o << U32LEB(num); ModuleUtils::iterDefinedTags(*wasm, [&](Tag* tag) { o << uint8_t(0); // Reserved 'attribute' field. Always 0. - o << U32LEB(getTypeIndex(tag->sig)); + o << U32LEB(getTypeIndex(tag->type)); }); finishSection(start); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index d85ee040e0d..9371046c304 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -929,7 +929,7 @@ Result<> IRBuilder::visitCatch(Name tag) { CHECK_ERR(pushScope(ScopeCtx::makeCatch(std::move(scope), tryy))); // Push a pop for the exception payload if necessary. - auto params = wasm.getTag(tag)->sig.params; + auto params = wasm.getTag(tag)->params(); if (params != Type::none) { // Note that we have a pop to help determine later whether we need to run // the fixup for pops within blocks. @@ -1815,7 +1815,7 @@ Result<> IRBuilder::makeTryTable(Name label, Result<> IRBuilder::makeThrow(Name tag) { Throw curr(wasm.allocator); curr.tag = tag; - curr.operands.resize(wasm.getTag(tag)->sig.params.size()); + curr.operands.resize(wasm.getTag(tag)->params().size()); CHECK_ERR(visitThrow(&curr)); push(builder.makeThrow(tag, curr.operands)); return Ok{}; @@ -2343,7 +2343,7 @@ Result<> IRBuilder::makeResume(HeapType ct, Result<> IRBuilder::makeSuspend(Name tag) { Suspend curr(wasm.allocator); curr.tag = tag; - curr.operands.resize(wasm.getTag(tag)->sig.params.size()); + curr.operands.resize(wasm.getTag(tag)->params().size()); CHECK_ERR(visitSuspend(&curr)); std::vector operands(curr.operands.begin(), curr.operands.end()); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index bda3c3713a5..68951e1759d 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2585,14 +2585,14 @@ void FunctionValidator::visitTry(Try* curr) { auto* tag = getModule()->getTagOrNull(tagName); if (!shouldBeTrue(tag != nullptr, curr, "")) { getStream() << "tag name is invalid: " << tagName << "\n"; - } else if (!shouldBeEqual(tag->sig.results, Type(Type::none), curr, "")) { + } else if (!shouldBeEqual(tag->results(), Type(Type::none), curr, "")) { getStream() << "catch's tag (" << tagName << ") has result values, which is not allowed for exception handling"; } else { auto* catchBody = curr->catchBodies[i]; auto pops = EHUtils::findPops(catchBody); - if (tag->sig.params == Type::none) { + if (tag->params() == Type::none) { if (!shouldBeTrue(pops.empty(), curr, "")) { getStream() << "catch's tag (" << tagName << ") doesn't have any params, but there are pops"; @@ -2600,7 +2600,7 @@ void FunctionValidator::visitTry(Try* curr) { } else { if (shouldBeTrue(pops.size() == 1, curr, "")) { auto* pop = *pops.begin(); - if (!shouldBeSubType(tag->sig.params, pop->type, curr, "")) { + if (!shouldBeSubType(tag->params(), pop->type, curr, "")) { getStream() << "catch's tag (" << tagName << ")'s pop doesn't have the same type as the tag's params"; @@ -2672,7 +2672,7 @@ void FunctionValidator::visitTryTable(TryTable* curr) { auto* tag = getModule()->getTagOrNull(tagName); if (!shouldBeTrue(tag != nullptr, curr, "")) { getStream() << "catch's tag name is invalid: " << tagName << "\n"; - } else if (!shouldBeEqual(tag->sig.results, Type(Type::none), curr, "")) { + } else if (!shouldBeEqual(tag->results(), Type(Type::none), curr, "")) { getStream() << "catch's tag (" << tagName << ") has result values, which is not allowed for exception handling"; @@ -2680,7 +2680,7 @@ void FunctionValidator::visitTryTable(TryTable* curr) { // tagType and sentType should be the same (except for the possible exnref // at the end of sentType) - auto tagType = tag->sig.params; + auto tagType = tag->params(); tagTypeSize = tagType.size(); for (Index j = 0; j < tagType.size(); j++) { shouldBeEqual(tagType[j], sentType[j], curr, invalidSentTypeMsg); @@ -2722,18 +2722,18 @@ void FunctionValidator::visitThrow(Throw* curr) { return; } shouldBeEqual( - tag->sig.results, + tag->results(), Type(Type::none), curr, "tags with result types must not be used for exception handling"); if (!shouldBeEqual(curr->operands.size(), - tag->sig.params.size(), + tag->params().size(), curr, "tag's param numbers must match")) { return; } size_t i = 0; - for (const auto& param : tag->sig.params) { + for (const auto& param : tag->params()) { if (!shouldBeSubType(curr->operands[i]->type, param, curr->operands[i], @@ -4164,20 +4164,20 @@ static void validateTags(Module& module, ValidationInfo& info) { "Tags require exception-handling [--enable-exception-handling]"); } for (auto& curr : module.tags) { - if (curr->sig.results != Type(Type::none)) { + if (curr->results() != Type::none) { info.shouldBeTrue(module.features.hasTypedContinuations(), curr->name, "Tags with result types require typed continuations " "feature [--enable-typed-continuations]"); } - if (curr->sig.params.isTuple()) { + if (curr->params().isTuple()) { info.shouldBeTrue( module.features.hasMultivalue(), curr->name, "Multivalue tag type requires multivalue [--enable-multivalue]"); } FeatureSet features; - for (const auto& param : curr->sig.params) { + for (const auto& param : curr->params()) { features |= param.getFeatures(); info.shouldBeTrue(param.isConcrete(), curr->name, diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index eb125d6c254..94454456288 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -926,7 +926,7 @@ static void populateTryTableSentTypes(TryTable* curr, Module* wasm) { auto tagName = curr->catchTags[i]; std::vector sentType; if (tagName) { - for (auto t : wasm->getTag(tagName)->sig.params) { + for (auto t : wasm->getTag(tagName)->params()) { sentType.push_back(t); } } @@ -1412,11 +1412,10 @@ static void populateResumeSentTypes(Resume* curr, Module* wasm) { curr->sentTypes.clear(); curr->sentTypes.resize(curr->handlerTags.size()); for (Index i = 0; i < curr->handlerTags.size(); i++) { - auto& tag = curr->handlerTags[i]; - auto& tagSig = wasm->getTag(tag)->sig; + auto tag = wasm->getTag(curr->handlerTags[i]); - auto& tgps = tagSig.params; - auto& tgrs = tagSig.results; + auto tgps = tag->params(); + auto tgrs = tag->results(); HeapType ftPrime{Signature(tgrs, ctrs)}; HeapType ctPrime{Continuation(ftPrime)}; @@ -1450,7 +1449,7 @@ void Resume::finalize(Module* wasm) { void Suspend::finalize(Module* wasm) { if (!handleUnreachableOperands(this) && wasm) { auto tag = wasm->getTag(this->tag); - type = tag->sig.results; + type = tag->results(); } } diff --git a/test/binaryen.js/exception-handling.js.txt b/test/binaryen.js/exception-handling.js.txt index f8b71302538..6be0eb40a58 100644 --- a/test/binaryen.js/exception-handling.js.txt +++ b/test/binaryen.js/exception-handling.js.txt @@ -1,7 +1,7 @@ (module (type $0 (func (param i32))) (type $1 (func)) - (tag $e (param i32)) + (tag $e (type $0) (param i32)) (func $test (try $l0 (do diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index b1554bec0f2..16e19a6a0c9 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -132,14 +132,14 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} (import "module" "base" (global $a-global-imp i32)) (import "module" "base" (global $a-mut-global-imp (mut i32))) (import "module" "base" (func $an-imported (type $2) (param i32 f64) (result f32))) - (import "module" "base" (tag $a-tag-imp (param i32))) + (import "module" "base" (tag $a-tag-imp (type $1) (param i32))) (global $a-global i32 (i32.const 1)) (memory $0 1 256 shared) (data $x0 (i32.const 10) "hello, world") (data $y1 "I am passive") (table $t0 1 funcref) (elem $e0 (i32.const 0) $"kitchen()sinker") - (tag $a-tag (param i32)) + (tag $a-tag (type $1) (param i32)) (export "mem" (memory $0)) (export "kitchen_sinker" (func $"kitchen()sinker")) (export "a-global-exp" (global $a-global)) @@ -2724,7 +2724,7 @@ module loaded from binary form: (type $0 (func (param i32 i32))) (type $1 (func (param i32 i32) (result i32))) (global $a-global i32 (i32.const 3)) - (tag $a-tag (param i32 i32)) + (tag $a-tag (type $0) (param i32 i32)) (func $adder (type $1) (param $0 i32) (param $1 i32) (result i32) (i32.add (local.get $0) @@ -2766,7 +2766,7 @@ test_parsing text: (type $0 (func (param i32))) (type $1 (func (param i32 i32) (result i32))) (global $a-global i32 (i32.const 3)) - (tag $a-tag (param i32)) + (tag $a-tag (type $0) (param i32)) (func $adder (type $1) (param $0 i32) (param $1 i32) (result i32) (i32.add (local.get $0) @@ -2780,7 +2780,7 @@ module loaded from text form: (type $0 (func (param i32))) (type $1 (func (param i32 i32) (result i32))) (global $a-global i32 (i32.const 3)) - (tag $a-tag (param i32)) + (tag $a-tag (type $0) (param i32)) (func $ADD_ER (type $1) (param $0 i32) (param $1 i32) (result i32) (i32.add (local.get $0) diff --git a/test/binaryen.js/tag.js.txt b/test/binaryen.js/tag.js.txt index 06c0c9d68fa..0bdec723f51 100644 --- a/test/binaryen.js/tag.js.txt +++ b/test/binaryen.js/tag.js.txt @@ -3,13 +3,13 @@ getTagInfo={"name":"a-tag","module":"","base":"","params":2,"results":0} (module (type $0 (func (param i32))) (type $1 (func (param i32 f32))) - (import "module" "base" (tag $a-tag-imp (param i32 f32))) - (tag $a-tag (param i32)) + (import "module" "base" (tag $a-tag-imp (type $1) (param i32 f32))) + (tag $a-tag (type $0) (param i32)) (export "a-tag-exp" (tag $a-tag)) ) (module (type $0 (func (param i32 f32))) - (import "module" "base" (tag $a-tag-imp (param i32 f32))) + (import "module" "base" (tag $a-tag-imp (type $0) (param i32 f32))) ) diff --git a/test/br_to_try.wasm.fromBinary b/test/br_to_try.wasm.fromBinary index a3bed8ee0fa..af187786658 100644 --- a/test/br_to_try.wasm.fromBinary +++ b/test/br_to_try.wasm.fromBinary @@ -1,7 +1,7 @@ (module (type $0 (func (param i32))) (type $1 (func)) - (tag $tag$0 (param i32)) + (tag $tag$0 (type $0) (param i32)) (func $0 (block $label (try diff --git a/test/break-within-catch.wasm.fromBinary b/test/break-within-catch.wasm.fromBinary index 7d151f48c48..1c21f33649b 100644 --- a/test/break-within-catch.wasm.fromBinary +++ b/test/break-within-catch.wasm.fromBinary @@ -1,7 +1,7 @@ (module (type $0 (func (param i32))) (type $1 (func)) - (tag $tag$0 (param i32)) + (tag $tag$0 (type $0) (param i32)) (func $0 (block $label (try diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 032d15db434..ba4a520268e 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -91,7 +91,7 @@ BinaryenFeatureAll: 2097151 (table $0 1 1 funcref) (elem $0 (table $0) (i32.const 0) func $"kitchen()sinker") (elem $passive func $"kitchen()sinker") - (tag $a-tag (param i32)) + (tag $a-tag (type $4) (param i32)) (export "mem" (memory $0)) (export "kitchen_sinker" (func $"kitchen()sinker")) (start $starter) diff --git a/test/example/module-splitting.txt b/test/example/module-splitting.txt index 709ebd4d755..04d43631fb8 100644 --- a/test/example/module-splitting.txt +++ b/test/example/module-splitting.txt @@ -16,7 +16,7 @@ Before: (global $glob (mut i32) (i32.const 7)) (memory $mem 3 42 shared) (table $tab 3 42 funcref) - (tag $e (param i32)) + (tag $e (type $0) (param i32)) ) Keeping: After: @@ -25,7 +25,7 @@ After: (global $glob (mut i32) (i32.const 7)) (memory $mem 3 42 shared) (table $tab 3 42 funcref) - (tag $e (param i32)) + (tag $e (type $0) (param i32)) (export "%memory" (memory $mem)) (export "%table" (table $tab)) (export "%global" (global $glob)) @@ -42,7 +42,7 @@ Before: (import "env" "mem" (memory $mem 3 42 shared)) (import "env" "tab" (table $tab 3 42 funcref)) (import "env" "glob" (global $glob (mut i32))) - (import "env" "e" (tag $e (param i32))) + (import "env" "e" (tag $e (type $0) (param i32))) ) Keeping: After: @@ -51,7 +51,7 @@ After: (import "env" "mem" (memory $mem 3 42 shared)) (import "env" "tab" (table $tab 3 42 funcref)) (import "env" "glob" (global $glob (mut i32))) - (import "env" "e" (tag $e (param i32))) + (import "env" "e" (tag $e (type $0) (param i32))) (export "%memory" (memory $mem)) (export "%table" (table $tab)) (export "%global" (global $glob)) @@ -68,7 +68,7 @@ Before: (global $glob (mut i32) (i32.const 7)) (memory $mem 3 42 shared) (table $tab 3 42 funcref) - (tag $e (param i32)) + (tag $e (type $0) (param i32)) (export "mem" (memory $mem)) (export "tab" (table $tab)) (export "glob" (global $glob)) @@ -81,7 +81,7 @@ After: (global $glob (mut i32) (i32.const 7)) (memory $mem 3 42 shared) (table $tab 3 42 funcref) - (tag $e (param i32)) + (tag $e (type $0) (param i32)) (export "mem" (memory $mem)) (export "tab" (table $tab)) (export "glob" (global $glob)) diff --git a/test/lit/basic/exception-handling-legacy.wast b/test/lit/basic/exception-handling-legacy.wast index 791745b69ef..3f019d5323e 100644 --- a/test/lit/basic/exception-handling-legacy.wast +++ b/test/lit/basic/exception-handling-legacy.wast @@ -20,7 +20,7 @@ ;; CHECK-TEXT: (type $4 (func (param eqref))) - ;; CHECK-TEXT: (tag $e-i32 (param i32)) + ;; CHECK-TEXT: (tag $e-i32 (type $1) (param i32)) ;; CHECK-BIN: (type $0 (func)) ;; CHECK-BIN: (type $1 (func (param i32))) @@ -31,19 +31,19 @@ ;; CHECK-BIN: (type $4 (func (param eqref))) - ;; CHECK-BIN: (tag $e-i32 (param i32)) + ;; CHECK-BIN: (tag $e-i32 (type $1) (param i32)) (tag $e-i32 (param i32)) - ;; CHECK-TEXT: (tag $e-i64 (param i64)) - ;; CHECK-BIN: (tag $e-i64 (param i64)) + ;; CHECK-TEXT: (tag $e-i64 (type $2) (param i64)) + ;; CHECK-BIN: (tag $e-i64 (type $2) (param i64)) (tag $e-i64 (param i64)) - ;; CHECK-TEXT: (tag $e-i32-i64 (param i32 i64)) - ;; CHECK-BIN: (tag $e-i32-i64 (param i32 i64)) + ;; CHECK-TEXT: (tag $e-i32-i64 (type $3) (param i32 i64)) + ;; CHECK-BIN: (tag $e-i32-i64 (type $3) (param i32 i64)) (tag $e-i32-i64 (param i32 i64)) - ;; CHECK-TEXT: (tag $e-eqref (param eqref)) - ;; CHECK-BIN: (tag $e-eqref (param eqref)) + ;; CHECK-TEXT: (tag $e-eqref (type $4) (param eqref)) + ;; CHECK-BIN: (tag $e-eqref (type $4) (param eqref)) (tag $e-eqref (param (ref null eq))) - ;; CHECK-TEXT: (tag $e-empty) - ;; CHECK-BIN: (tag $e-empty) + ;; CHECK-TEXT: (tag $e-empty (type $0)) + ;; CHECK-BIN: (tag $e-empty (type $0)) (tag $e-empty) ;; CHECK-TEXT: (func $foo (type $0) @@ -1312,15 +1312,15 @@ ;; CHECK-BIN-NODEBUG: (type $4 (func (param eqref))) -;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32)) +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $1) (param i32)) -;; CHECK-BIN-NODEBUG: (tag $tag$1 (param i64)) +;; CHECK-BIN-NODEBUG: (tag $tag$1 (type $2) (param i64)) -;; CHECK-BIN-NODEBUG: (tag $tag$2 (param i32 i64)) +;; CHECK-BIN-NODEBUG: (tag $tag$2 (type $3) (param i32 i64)) -;; CHECK-BIN-NODEBUG: (tag $tag$3 (param eqref)) +;; CHECK-BIN-NODEBUG: (tag $tag$3 (type $4) (param eqref)) -;; CHECK-BIN-NODEBUG: (tag $tag$4) +;; CHECK-BIN-NODEBUG: (tag $tag$4 (type $0)) ;; CHECK-BIN-NODEBUG: (func $0 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/exception-handling.wast b/test/lit/basic/exception-handling.wast index 41c10f6311a..6b1518b65b4 100644 --- a/test/lit/basic/exception-handling.wast +++ b/test/lit/basic/exception-handling.wast @@ -30,7 +30,7 @@ ;; CHECK-TEXT: (type $9 (func (param eqref))) - ;; CHECK-TEXT: (tag $e-i32 (param i32)) + ;; CHECK-TEXT: (tag $e-i32 (type $6) (param i32)) ;; CHECK-BIN: (type $0 (func)) ;; CHECK-BIN: (type $1 (func (result i32 i64))) @@ -51,19 +51,19 @@ ;; CHECK-BIN: (type $9 (func (param eqref))) - ;; CHECK-BIN: (tag $e-i32 (param i32)) + ;; CHECK-BIN: (tag $e-i32 (type $6) (param i32)) (tag $e-i32 (param i32)) - ;; CHECK-TEXT: (tag $e-i64 (param i64)) - ;; CHECK-BIN: (tag $e-i64 (param i64)) + ;; CHECK-TEXT: (tag $e-i64 (type $7) (param i64)) + ;; CHECK-BIN: (tag $e-i64 (type $7) (param i64)) (tag $e-i64 (param i64)) - ;; CHECK-TEXT: (tag $e-i32-i64 (param i32 i64)) - ;; CHECK-BIN: (tag $e-i32-i64 (param i32 i64)) + ;; CHECK-TEXT: (tag $e-i32-i64 (type $8) (param i32 i64)) + ;; CHECK-BIN: (tag $e-i32-i64 (type $8) (param i32 i64)) (tag $e-i32-i64 (param i32 i64)) - ;; CHECK-TEXT: (tag $e-eqref (param eqref)) - ;; CHECK-BIN: (tag $e-eqref (param eqref)) + ;; CHECK-TEXT: (tag $e-eqref (type $9) (param eqref)) + ;; CHECK-BIN: (tag $e-eqref (type $9) (param eqref)) (tag $e-eqref (param (ref null eq))) - ;; CHECK-TEXT: (tag $e-empty) - ;; CHECK-BIN: (tag $e-empty) + ;; CHECK-TEXT: (tag $e-empty (type $0)) + ;; CHECK-BIN: (tag $e-empty (type $0)) (tag $e-empty) ;; CHECK-TEXT: (func $foo (type $0) @@ -740,15 +740,15 @@ ;; CHECK-BIN-NODEBUG: (type $9 (func (param eqref))) -;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32)) +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $6) (param i32)) -;; CHECK-BIN-NODEBUG: (tag $tag$1 (param i64)) +;; CHECK-BIN-NODEBUG: (tag $tag$1 (type $7) (param i64)) -;; CHECK-BIN-NODEBUG: (tag $tag$2 (param i32 i64)) +;; CHECK-BIN-NODEBUG: (tag $tag$2 (type $8) (param i32 i64)) -;; CHECK-BIN-NODEBUG: (tag $tag$3 (param eqref)) +;; CHECK-BIN-NODEBUG: (tag $tag$3 (type $9) (param eqref)) -;; CHECK-BIN-NODEBUG: (tag $tag$4) +;; CHECK-BIN-NODEBUG: (tag $tag$4 (type $0)) ;; CHECK-BIN-NODEBUG: (func $0 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/extra-branch-values.wast b/test/lit/basic/extra-branch-values.wast index abd609304ec..6989bf62ca9 100644 --- a/test/lit/basic/extra-branch-values.wast +++ b/test/lit/basic/extra-branch-values.wast @@ -27,11 +27,11 @@ ;; OPT_O: (import "env" "use-i32-any" (func $use-i32-any (type $15) (param i32 (ref any)))) (import "env" "use-i32-any" (func $use-i32-any (param i32 (ref any)))) - ;; CHECK: (tag $e (param i32)) - ;; OPT_O: (tag $e (param i32)) + ;; CHECK: (tag $e (type $7) (param i32)) + ;; OPT_O: (tag $e (type $5) (param i32)) (tag $e (param i32)) - ;; CHECK: (tag $e2 (param i32)) - ;; OPT_O: (tag $e2 (param i32)) + ;; CHECK: (tag $e2 (type $7) (param i32)) + ;; OPT_O: (tag $e2 (type $5) (param i32)) (tag $e2 (param i32)) ;; CHECK: (func $br_on_null-one (type $8) (param $0 i32) (param $1 anyref) (result i32) diff --git a/test/lit/basic/pop-fixup.wast b/test/lit/basic/pop-fixup.wast index 9ef51dad82d..f3578b178a9 100644 --- a/test/lit/basic/pop-fixup.wast +++ b/test/lit/basic/pop-fixup.wast @@ -2,9 +2,9 @@ ;; RUN: wasm-opt %s -all -S -o - | filecheck %s (module - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $1) (param i32)) (tag $e (param i32)) - ;; CHECK: (tag $e2 (param i32 i32)) + ;; CHECK: (tag $e2 (type $2) (param i32 i32)) (tag $e2 (param i32 i32)) ;; CHECK: (func $stacky (type $0) diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 61497e6124f..82b1f8da4e3 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -73,7 +73,7 @@ ;; CHECK-TEXT: (elem declare func $foo $ref-taken-but-not-in-table) - ;; CHECK-TEXT: (tag $e-i32 (param i32)) + ;; CHECK-TEXT: (tag $e-i32 (type $7) (param i32)) ;; CHECK-TEXT: (export "export_func" (func $import_func)) @@ -97,7 +97,7 @@ ;; CHECK-BIN: (elem declare func $foo $ref-taken-but-not-in-table) - ;; CHECK-BIN: (tag $e-i32 (param i32)) + ;; CHECK-BIN: (tag $e-i32 (type $7) (param i32)) ;; CHECK-BIN: (export "export_func" (func $import_func)) @@ -2089,7 +2089,7 @@ ;; CHECK-BIN-NEXT: ) (func $ref-taken-but-not-in-table) ) -;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32)) +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $7) (param i32)) ;; CHECK-BIN-NODEBUG: (export "export_func" (func $fimport$0)) diff --git a/test/lit/basic/tags.wast b/test/lit/basic/tags.wast index 7d99816c1d8..4d5a9395651 100644 --- a/test/lit/basic/tags.wast +++ b/test/lit/basic/tags.wast @@ -23,40 +23,40 @@ ;; CHECK-TEXT: (type $2 (func)) - ;; CHECK-TEXT: (import "env" "im0" (tag $e-import (param i32))) + ;; CHECK-TEXT: (import "env" "im0" (tag $e-import (type $1) (param i32))) - ;; CHECK-TEXT: (import "env" "im1" (tag $eimport$0 (param i32 f32))) + ;; CHECK-TEXT: (import "env" "im1" (tag $eimport$0 (type $0) (param i32 f32))) - ;; CHECK-TEXT: (tag $tag$1 (param i32)) + ;; CHECK-TEXT: (tag $tag$1 (type $1) (param i32)) - ;; CHECK-TEXT: (tag $e (param i32 f32)) + ;; CHECK-TEXT: (tag $e (type $0) (param i32 f32)) ;; CHECK-BIN: (type $0 (func (param i32 f32))) ;; CHECK-BIN: (type $1 (func (param i32))) ;; CHECK-BIN: (type $2 (func)) - ;; CHECK-BIN: (import "env" "im0" (tag $e-import (param i32))) + ;; CHECK-BIN: (import "env" "im0" (tag $e-import (type $1) (param i32))) - ;; CHECK-BIN: (import "env" "im1" (tag $eimport$1 (param i32 f32))) + ;; CHECK-BIN: (import "env" "im1" (tag $eimport$1 (type $0) (param i32 f32))) - ;; CHECK-BIN: (tag $tag$0 (param i32)) + ;; CHECK-BIN: (tag $tag$0 (type $1) (param i32)) - ;; CHECK-BIN: (tag $e (param i32 f32)) + ;; CHECK-BIN: (tag $e (type $0) (param i32 f32)) (tag $e (param i32 f32)) - ;; CHECK-TEXT: (tag $empty) - ;; CHECK-BIN: (tag $empty) + ;; CHECK-TEXT: (tag $empty (type $2)) + ;; CHECK-BIN: (tag $empty (type $2)) (tag $empty) - ;; CHECK-TEXT: (tag $e-params0 (param i32 f32)) - ;; CHECK-BIN: (tag $e-params0 (param i32 f32)) + ;; CHECK-TEXT: (tag $e-params0 (type $0) (param i32 f32)) + ;; CHECK-BIN: (tag $e-params0 (type $0) (param i32 f32)) (tag $e-params0 (param i32 f32)) - ;; CHECK-TEXT: (tag $e-params1 (param i32 f32)) - ;; CHECK-BIN: (tag $e-params1 (param i32 f32)) + ;; CHECK-TEXT: (tag $e-params1 (type $0) (param i32 f32)) + ;; CHECK-BIN: (tag $e-params1 (type $0) (param i32 f32)) (tag $e-params1 (param i32) (param f32)) - ;; CHECK-TEXT: (tag $e-export (param i32)) - ;; CHECK-BIN: (tag $e-export (param i32)) + ;; CHECK-TEXT: (tag $e-export (type $1) (param i32)) + ;; CHECK-BIN: (tag $e-export (type $1) (param i32)) (tag $e-export (export "ex0") (param i32)) ;; CHECK-TEXT: (export "ex0" (tag $e-export)) @@ -73,21 +73,21 @@ ;; CHECK-BIN-NODEBUG: (type $2 (func)) -;; CHECK-BIN-NODEBUG: (import "env" "im0" (tag $eimport$0 (param i32))) +;; CHECK-BIN-NODEBUG: (import "env" "im0" (tag $eimport$0 (type $1) (param i32))) -;; CHECK-BIN-NODEBUG: (import "env" "im1" (tag $eimport$1 (param i32 f32))) +;; CHECK-BIN-NODEBUG: (import "env" "im1" (tag $eimport$1 (type $0) (param i32 f32))) -;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32)) +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $1) (param i32)) -;; CHECK-BIN-NODEBUG: (tag $tag$1 (param i32 f32)) +;; CHECK-BIN-NODEBUG: (tag $tag$1 (type $0) (param i32 f32)) -;; CHECK-BIN-NODEBUG: (tag $tag$2) +;; CHECK-BIN-NODEBUG: (tag $tag$2 (type $2)) -;; CHECK-BIN-NODEBUG: (tag $tag$3 (param i32 f32)) +;; CHECK-BIN-NODEBUG: (tag $tag$3 (type $0) (param i32 f32)) -;; CHECK-BIN-NODEBUG: (tag $tag$4 (param i32 f32)) +;; CHECK-BIN-NODEBUG: (tag $tag$4 (type $0) (param i32 f32)) -;; CHECK-BIN-NODEBUG: (tag $tag$5 (param i32)) +;; CHECK-BIN-NODEBUG: (tag $tag$5 (type $1) (param i32)) ;; CHECK-BIN-NODEBUG: (export "ex0" (tag $tag$5)) diff --git a/test/lit/basic/typed_continuations_resume.wast b/test/lit/basic/typed_continuations_resume.wast index 07afb9388f3..b80e1e2fa93 100644 --- a/test/lit/basic/typed_continuations_resume.wast +++ b/test/lit/basic/typed_continuations_resume.wast @@ -27,12 +27,12 @@ ;; CHECK-TEXT: (type $3 (func (param (ref $ct)) (result i32))) - ;; CHECK-TEXT: (tag $t (param i32) (result i32)) + ;; CHECK-TEXT: (tag $t (type $ft) (param i32) (result i32)) ;; CHECK-BIN: (type $2 (func (result i32 (ref $ct)))) ;; CHECK-BIN: (type $3 (func (param (ref $ct)) (result i32))) - ;; CHECK-BIN: (tag $t (param i32) (result i32)) + ;; CHECK-BIN: (tag $t (type $ft) (param i32) (result i32)) (tag $t (param i32) (result i32)) ;; CHECK-BINARY: (func $go (type $3) (param $x (ref $ct)) (result i32) @@ -129,7 +129,7 @@ ;; CHECK-BIN-NODEBUG: (type $3 (func (param (ref $1)) (result i32))) -;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32) (result i32)) +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $0) (param i32) (result i32)) ;; CHECK-BIN-NODEBUG: (func $0 (type $3) (param $0 (ref $1)) (result i32) ;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 (ref $1))) diff --git a/test/lit/basic/typed_continuations_suspend.wast b/test/lit/basic/typed_continuations_suspend.wast index 62a09c2133c..e7cfc139d11 100644 --- a/test/lit/basic/typed_continuations_suspend.wast +++ b/test/lit/basic/typed_continuations_suspend.wast @@ -14,12 +14,12 @@ ;; CHECK-TEXT: (type $1 (func (result i64))) - ;; CHECK-TEXT: (tag $t (param i32) (result i64)) + ;; CHECK-TEXT: (tag $t (type $0) (param i32) (result i64)) ;; CHECK-BIN: (type $0 (func (param i32) (result i64))) ;; CHECK-BIN: (type $1 (func (result i64))) - ;; CHECK-BIN: (tag $t (param i32) (result i64)) + ;; CHECK-BIN: (tag $t (type $0) (param i32) (result i64)) (tag $t (param i32) (result i64)) ;; CHECK-TEXT: (func $f (type $1) (result i64) @@ -40,7 +40,7 @@ ;; CHECK-BIN-NODEBUG: (type $1 (func (result i64))) -;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32) (result i64)) +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $0) (param i32) (result i64)) ;; CHECK-BIN-NODEBUG: (func $0 (type $1) (result i64) ;; CHECK-BIN-NODEBUG-NEXT: (suspend $tag$0 diff --git a/test/lit/binary/stacky-eh-legacy.test b/test/lit/binary/stacky-eh-legacy.test index 792c436d527..9c933574c67 100644 --- a/test/lit/binary/stacky-eh-legacy.test +++ b/test/lit/binary/stacky-eh-legacy.test @@ -41,7 +41,7 @@ ;; CHECK: (type $1 (func)) -;; CHECK: (tag $tag$0 (param i32)) +;; CHECK: (tag $tag$0 (type $0) (param i32)) ;; CHECK: (func $0 ;; CHECK-NEXT: (local $0 i32) diff --git a/test/lit/binary/stacky-nn-tuple.test b/test/lit/binary/stacky-nn-tuple.test index 1f6b5bb59eb..c7ab57e9090 100644 --- a/test/lit/binary/stacky-nn-tuple.test +++ b/test/lit/binary/stacky-nn-tuple.test @@ -47,7 +47,7 @@ ;; CHECK: (type $3 (func)) -;; CHECK: (tag $tag$0 (param (ref null $0) (ref null $1))) +;; CHECK: (tag $tag$0 (type $2) (param (ref null $0) (ref null $1))) ;; CHECK: (func $0 (type $3) ;; CHECK-NEXT: (local $0 (ref null $0)) diff --git a/test/lit/control-flow-input.wast b/test/lit/control-flow-input.wast index 7baae35f0ef..39cb28d9fa1 100644 --- a/test/lit/control-flow-input.wast +++ b/test/lit/control-flow-input.wast @@ -11,7 +11,7 @@ (module (type $id (func (param i32) (result i32))) - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $3) (param i32)) (tag $e (param i32)) ;; CHECK: (func $block (type $0) (result i32) diff --git a/test/lit/gc-eh-legacy.wast b/test/lit/gc-eh-legacy.wast index 2c12cec49d5..85b0c287f42 100644 --- a/test/lit/gc-eh-legacy.wast +++ b/test/lit/gc-eh-legacy.wast @@ -10,7 +10,7 @@ (field (mut i32)) )) - ;; CHECK: (tag $tagA (param (ref $A))) + ;; CHECK: (tag $tagA (type $1) (param (ref $A))) (tag $tagA (param (ref $A))) ;; CHECK: (func $foo (type $2) (result (ref null $A)) diff --git a/test/lit/merge/fusing.wat b/test/lit/merge/fusing.wat index e074d30ba0c..9f80c80e96f 100644 --- a/test/lit/merge/fusing.wat +++ b/test/lit/merge/fusing.wat @@ -32,7 +32,7 @@ ;; CHECK: (memory $second.mem 2) - ;; CHECK: (tag $exn) + ;; CHECK: (tag $exn (type $0)) ;; CHECK: (export "foo" (func $first.foo)) diff --git a/test/lit/merge/names.wat b/test/lit/merge/names.wat index 6f51b54cac5..0c5030d2400 100644 --- a/test/lit/merge/names.wat +++ b/test/lit/merge/names.wat @@ -36,13 +36,13 @@ ;; CHECK: (table $3 1 funcref) - ;; CHECK: (tag $tag0) + ;; CHECK: (tag $tag0 (type $0)) - ;; CHECK: (tag $tag$1) + ;; CHECK: (tag $tag$1 (type $0)) - ;; CHECK: (tag $tag2) + ;; CHECK: (tag $tag2 (type $0)) - ;; CHECK: (tag $tag$3) + ;; CHECK: (tag $tag$3 (type $0)) ;; CHECK: (export "f0" (func $func0)) diff --git a/test/lit/merge/renamings.wat b/test/lit/merge/renamings.wat index e680b276b3e..acaff3e295a 100644 --- a/test/lit/merge/renamings.wat +++ b/test/lit/merge/renamings.wat @@ -21,7 +21,7 @@ ;; CHECK: (type $6 (func (param f32))) - ;; CHECK: (import "elsewhere" "some.tag" (tag $imported (param f64))) + ;; CHECK: (import "elsewhere" "some.tag" (tag $imported (type $2) (param f64))) ;; CHECK: (global $foo i32 (i32.const 1)) (global $foo i32 (i32.const 1)) @@ -83,17 +83,17 @@ ;; CHECK: (elem $bar_2 func $other $foo_3) - ;; CHECK: (tag $foo (param i32)) + ;; CHECK: (tag $foo (type $4) (param i32)) (tag $foo (param i32)) - ;; CHECK: (tag $bar (param i64)) + ;; CHECK: (tag $bar (type $5) (param i64)) (tag $bar (param i64)) ;; This export has a conflict in second.wat, and so second.wat's $foo ;; will be renamed. - ;; CHECK: (tag $foo_2 (param f32)) + ;; CHECK: (tag $foo_2 (type $6) (param f32)) - ;; CHECK: (tag $other (param f64)) + ;; CHECK: (tag $other (type $2) (param f64)) ;; CHECK: (export "foo" (func $foo)) (export "foo" (func $foo)) diff --git a/test/lit/merge/types.wat b/test/lit/merge/types.wat index e9ee76b3478..956d885e034 100644 --- a/test/lit/merge/types.wat +++ b/test/lit/merge/types.wat @@ -17,8 +17,8 @@ ;; CHECK-NEXT: Type mismatch when importing global g2 from module env ($bad3): export type eqref is different from import type i31ref. ;; CHECK-NEXT: Type mismatch when importing global g2 from module env ($bad4): export type eqref is different from import type anyref. ;; CHECK-NEXT: Type mismatch when importing global g1 from module env ($bad5): type eqref is not a subtype of i31ref. -;; CHECK-NEXT: Type mismatch when importing tag t from module env ($bad1): export type (func (param eqref)) is different from import type (func (param anyref)). -;; CHECK-NEXT: Type mismatch when importing tag t from module env ($bad2): export type (func (param eqref)) is different from import type (func (param i31ref)). +;; CHECK-NEXT: Type mismatch when importing tag t from module env ($bad1): export type (type $func.0 (func (param eqref))) is different from import type (type $func.0 (func (param anyref))). +;; CHECK-NEXT: Type mismatch when importing tag t from module env ($bad2): export type (type $func.0 (func (param eqref))) is different from import type (type $func.0 (func (param i31ref))). ;; CHECK-NEXT: Fatal: import/export mismatches (module diff --git a/test/lit/passes/coalesce-locals-eh-legacy.wast b/test/lit/passes/coalesce-locals-eh-legacy.wast index b5b41b66154..75807f0a858 100644 --- a/test/lit/passes/coalesce-locals-eh-legacy.wast +++ b/test/lit/passes/coalesce-locals-eh-legacy.wast @@ -2,10 +2,10 @@ ;; RUN: wasm-opt %s --coalesce-locals -all -S -o - | filecheck %s (module - ;; CHECK: (tag $e) + ;; CHECK: (tag $e (type $0)) (tag $e) - ;; CHECK: (tag $any (param (ref any))) + ;; CHECK: (tag $any (type $1) (param (ref any))) (tag $any (param (ref any))) ;; CHECK: (func $bar (type $2) (result i32) diff --git a/test/lit/passes/coalesce-locals-eh.wast b/test/lit/passes/coalesce-locals-eh.wast index 90458f32fef..6f3deaa76b5 100644 --- a/test/lit/passes/coalesce-locals-eh.wast +++ b/test/lit/passes/coalesce-locals-eh.wast @@ -2,10 +2,10 @@ ;; RUN: wasm-opt %s --coalesce-locals -all -S -o - | filecheck %s (module - ;; CHECK: (tag $e) + ;; CHECK: (tag $e (type $0)) (tag $e) - ;; CHECK: (tag $any (param (ref any))) + ;; CHECK: (tag $any (type $1) (param (ref any))) (tag $any (param (ref any))) ;; CHECK: (func $bar (type $2) (result i32) diff --git a/test/lit/passes/code-folding-eh-legacy.wast b/test/lit/passes/code-folding-eh-legacy.wast index cde0fba2836..a2fc562c926 100644 --- a/test/lit/passes/code-folding-eh-legacy.wast +++ b/test/lit/passes/code-folding-eh-legacy.wast @@ -3,7 +3,7 @@ ;; RUN: | filecheck %s (module - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $2) (param i32)) (tag $e-i32 (param i32)) ;; CHECK: (func $pop-test (type $1) diff --git a/test/lit/passes/code-folding-eh.wast b/test/lit/passes/code-folding-eh.wast index 94eb0b38d32..e56518121dd 100644 --- a/test/lit/passes/code-folding-eh.wast +++ b/test/lit/passes/code-folding-eh.wast @@ -3,7 +3,7 @@ ;; RUN: | filecheck %s (module - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $2) (param i32)) (tag $e-i32 (param i32)) ;; CHECK: (func $try_table-call-optimize-terminating-tails-success (type $0) (result i32) diff --git a/test/lit/passes/code-pushing-eh-legacy.wast b/test/lit/passes/code-pushing-eh-legacy.wast index 06b2e3e7140..74cedc5f6ca 100644 --- a/test/lit/passes/code-pushing-eh-legacy.wast +++ b/test/lit/passes/code-pushing-eh-legacy.wast @@ -4,7 +4,7 @@ ;; The tests in this file test EffectAnalyzer, which is used by CodePushing. (module - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $1) (param i32)) (tag $e (param i32)) ;; CHECK: (func $can-push-past-try (type $0) diff --git a/test/lit/passes/code-pushing-eh.wast b/test/lit/passes/code-pushing-eh.wast index 3c6d005b123..f19a746beaa 100644 --- a/test/lit/passes/code-pushing-eh.wast +++ b/test/lit/passes/code-pushing-eh.wast @@ -4,7 +4,7 @@ ;; The tests in this file test EffectAnalyzer, which is used by CodePushing. (module - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $1) (param i32)) (tag $e (param i32)) ;; CHECK: (func $cannot-push-past-call (type $0) diff --git a/test/lit/passes/dce-eh-legacy.wast b/test/lit/passes/dce-eh-legacy.wast index 31ba4fac2de..2bc87f0819b 100644 --- a/test/lit/passes/dce-eh-legacy.wast +++ b/test/lit/passes/dce-eh-legacy.wast @@ -17,11 +17,11 @@ ;; CHECK: (type $struct (struct (field (mut eqref)))) (type $struct (struct (field (mut (ref null eq))))) - ;; CHECK: (tag $e) + ;; CHECK: (tag $e (type $0)) (tag $e) - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $1) (param i32)) (tag $e-i32 (param i32)) - ;; CHECK: (tag $e-eqref (param eqref)) + ;; CHECK: (tag $e-eqref (type $2) (param eqref)) (tag $e-eqref (param (ref null eq))) ;; CHECK: (func $foo (type $0) diff --git a/test/lit/passes/dce-eh.wast b/test/lit/passes/dce-eh.wast index b2742913b54..b9c7b596009 100644 --- a/test/lit/passes/dce-eh.wast +++ b/test/lit/passes/dce-eh.wast @@ -4,10 +4,10 @@ ;; If either try_table body or any of catch handler is reachable, the whole ;; try_table construct is reachable. (module - ;; CHECK: (tag $e) + ;; CHECK: (tag $e (type $0)) (tag $e) - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $1) (param i32)) (tag $e-i32 (param i32)) ;; CHECK: (func $foo (type $0) diff --git a/test/lit/passes/flatten-eh-legacy.wast b/test/lit/passes/flatten-eh-legacy.wast index 34b5cf08b82..3d22b41a4e5 100644 --- a/test/lit/passes/flatten-eh-legacy.wast +++ b/test/lit/passes/flatten-eh-legacy.wast @@ -2,9 +2,9 @@ ;; RUN: wasm-opt %s -all --flatten -S -o - | filecheck %s (module - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $2) (param i32)) (tag $e-i32 (param i32)) - ;; CHECK: (tag $e-f32 (param f32)) + ;; CHECK: (tag $e-f32 (type $3) (param f32)) (tag $e-f32 (param f32)) ;; CHECK: (func $try_catch (type $1) diff --git a/test/lit/passes/global-effects-eh-legacy.wast b/test/lit/passes/global-effects-eh-legacy.wast index 7390f996d8d..dae61274e10 100644 --- a/test/lit/passes/global-effects-eh-legacy.wast +++ b/test/lit/passes/global-effects-eh-legacy.wast @@ -31,10 +31,10 @@ ;; WITHOUT: (elem declare func $throw) - ;; WITHOUT: (tag $tag) + ;; WITHOUT: (tag $tag (type $void)) ;; INCLUDE: (elem declare func $throw) - ;; INCLUDE: (tag $tag) + ;; INCLUDE: (tag $tag (type $void)) (tag $tag) ;; WITHOUT: (func $main (type $void) diff --git a/test/lit/passes/global-effects.wast b/test/lit/passes/global-effects.wast index c3c6d207370..1125f738e68 100644 --- a/test/lit/passes/global-effects.wast +++ b/test/lit/passes/global-effects.wast @@ -31,10 +31,10 @@ ;; WITHOUT: (elem declare func $throw) - ;; WITHOUT: (tag $tag) + ;; WITHOUT: (tag $tag (type $void)) ;; INCLUDE: (elem declare func $throw) - ;; INCLUDE: (tag $tag) + ;; INCLUDE: (tag $tag (type $void)) (tag $tag) ;; WITHOUT: (func $main (type $void) diff --git a/test/lit/passes/gto-mutability.wast b/test/lit/passes/gto-mutability.wast index 3cf0e77016d..0e64c4aa777 100644 --- a/test/lit/passes/gto-mutability.wast +++ b/test/lit/passes/gto-mutability.wast @@ -24,13 +24,11 @@ ;; CHECK: (type $4 (func (param (ref $struct)))) - ;; CHECK: (type $5 (func (param (ref $struct)))) - ;; CHECK: (table $0 0 funcref) ;; CHECK: (elem declare func $func-two-params) - ;; CHECK: (tag $tag (param (ref $struct))) + ;; CHECK: (tag $tag (type $4) (param (ref $struct))) (tag $tag (param (ref $struct))) ;; CHECK: (func $func (type $4) (param $x (ref $struct)) diff --git a/test/lit/passes/gufa-eh.wast b/test/lit/passes/gufa-eh.wast index 79265a31825..d4e30473d81 100644 --- a/test/lit/passes/gufa-eh.wast +++ b/test/lit/passes/gufa-eh.wast @@ -9,7 +9,7 @@ ;; CHECK: (type $2 (func)) - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $0) (param i32)) (tag $e (param i32)) ;; CHECK: (func $try_table-target-block-is-not-unreachable (type $1) (result i32) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index ec67cf40cfd..a4878df7b31 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -1904,13 +1904,13 @@ ;; CHECK: (type $struct (struct)) (type $struct (struct)) - ;; CHECK: (tag $nothing (param anyref)) + ;; CHECK: (tag $nothing (type $1) (param anyref)) (tag $nothing (param (ref null any))) - ;; CHECK: (tag $something (param anyref)) + ;; CHECK: (tag $something (type $1) (param anyref)) (tag $something (param (ref null any))) - ;; CHECK: (tag $empty) + ;; CHECK: (tag $empty (type $0)) (tag $empty (param)) ;; CHECK: (func $func (type $0) @@ -2136,7 +2136,7 @@ ;; CHECK: (type $struct (struct)) (type $struct (struct)) - ;; CHECK: (tag $tag (param anyref anyref)) + ;; CHECK: (tag $tag (type $0) (param anyref anyref)) (tag $tag (param (ref null any)) (param (ref null any))) ;; CHECK: (func $func (type $1) @@ -3859,7 +3859,7 @@ ;; CHECK: (table $t 0 externref) - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $2) (param i32)) (tag $e-i32 (param i32)) (memory $0 10) diff --git a/test/lit/passes/gufa-tags.wast b/test/lit/passes/gufa-tags.wast index a035673073b..4bac560900b 100644 --- a/test/lit/passes/gufa-tags.wast +++ b/test/lit/passes/gufa-tags.wast @@ -11,9 +11,9 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (tag $tag$i32 (param i32)) + ;; CHECK: (tag $tag$i32 (type $0) (param i32)) (tag $tag$i32 (param i32)) - ;; CHECK: (tag $tag$f32 (param f32)) + ;; CHECK: (tag $tag$f32 (type $1) (param f32)) (tag $tag$f32 (param f32)) ;; CHECK: (func $test (type $2) diff --git a/test/lit/passes/heap-store-optimization.wast b/test/lit/passes/heap-store-optimization.wast index d317888be37..2a773a3882f 100644 --- a/test/lit/passes/heap-store-optimization.wast +++ b/test/lit/passes/heap-store-optimization.wast @@ -12,7 +12,7 @@ ;; CHECK: (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) - ;; CHECK: (tag $tag) + ;; CHECK: (tag $tag (type $1)) (tag $tag) (type $struct (struct (field (mut i32)))) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 0af7e7bb4ea..53f1fe289f4 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -4497,7 +4497,7 @@ ;; CHECK: (type $2 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) - ;; CHECK: (tag $tag (param i32)) + ;; CHECK: (tag $tag (type $1) (param i32)) (tag $tag (param i32)) ;; CHECK: (func $struct-with-pop (type $0) diff --git a/test/lit/passes/inlining-eh-legacy.wast b/test/lit/passes/inlining-eh-legacy.wast index 4d9d3a7aa94..ca005fc7c58 100644 --- a/test/lit/passes/inlining-eh-legacy.wast +++ b/test/lit/passes/inlining-eh-legacy.wast @@ -5,7 +5,7 @@ ;; --------------------------------------------------------------------------- ;; CHECK: (import "a" "b" (func $foo (type $2) (result i32))) (import "a" "b" (func $foo (result i32))) - ;; CHECK: (tag $tag$0 (param i32)) + ;; CHECK: (tag $tag$0 (type $1) (param i32)) (tag $tag$0 (param i32)) (func $callee-with-label (try $label diff --git a/test/lit/passes/instrument-locals-eh-legacy.wast b/test/lit/passes/instrument-locals-eh-legacy.wast index 23d944a0cff..10188038848 100644 --- a/test/lit/passes/instrument-locals-eh-legacy.wast +++ b/test/lit/passes/instrument-locals-eh-legacy.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --instrument-locals -all -S -o - | filecheck %s (module - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $7) (param i32)) (tag $e (param i32)) ;; CHECK: (func $test (type $8) diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 04890521dea..795a0a01cd1 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -24,7 +24,7 @@ ;; CHECK: (import "out" "i64" (func $i64 (type $4) (result i64))) (import "out" "i64" (func $i64 (result i64))) - ;; CHECK: (tag $e-anyref (param anyref)) + ;; CHECK: (tag $e-anyref (type $7) (param anyref)) (tag $e-anyref (param anyref)) ;; Refinalization can find a more specific type, where the declared type was diff --git a/test/lit/passes/merge-blocks-eh.wast b/test/lit/passes/merge-blocks-eh.wast index e56f536f4b6..75fecd532db 100644 --- a/test/lit/passes/merge-blocks-eh.wast +++ b/test/lit/passes/merge-blocks-eh.wast @@ -5,13 +5,13 @@ ;; CHECK: (import "a" "b" (func $import (type $0))) (import "a" "b" (func $import)) - ;; CHECK: (tag $empty) + ;; CHECK: (tag $empty (type $0)) (tag $empty) - ;; CHECK: (tag $i32 (param i32)) + ;; CHECK: (tag $i32 (type $3) (param i32)) (tag $i32 (param i32)) - ;; CHECK: (tag $exnref (param exnref)) + ;; CHECK: (tag $exnref (type $4) (param exnref)) (tag $exnref (param exnref)) ;; CHECK: (func $drop-block-try_catch_all_ref (type $0) diff --git a/test/lit/passes/once-reduction.wast b/test/lit/passes/once-reduction.wast index 640d14461f1..f8a6a1cee67 100644 --- a/test/lit/passes/once-reduction.wast +++ b/test/lit/passes/once-reduction.wast @@ -1194,7 +1194,7 @@ ;; CHECK: (global $once (mut i32) (i32.const 0)) - ;; CHECK: (tag $tag (param i32)) + ;; CHECK: (tag $tag (type $1) (param i32)) (tag $tag (param i32)) (global $once (mut i32) (i32.const 0)) diff --git a/test/lit/passes/optimize-instructions-eh-legacy.wast b/test/lit/passes/optimize-instructions-eh-legacy.wast index dd51d6c1753..423d17d219b 100644 --- a/test/lit/passes/optimize-instructions-eh-legacy.wast +++ b/test/lit/passes/optimize-instructions-eh-legacy.wast @@ -3,7 +3,7 @@ ;; RUN: | filecheck %s (module - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $1) (param i32)) ;; CHECK: (func $dummy (type $0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-iit-eh-legacy.wast b/test/lit/passes/optimize-instructions-iit-eh-legacy.wast index 79cb6f6f1c8..c9ed6ca2dfa 100644 --- a/test/lit/passes/optimize-instructions-iit-eh-legacy.wast +++ b/test/lit/passes/optimize-instructions-iit-eh-legacy.wast @@ -5,7 +5,7 @@ (module ;; CHECK: (type $struct.A (struct (field i32))) (type $struct.A (struct i32)) - ;; CHECK: (tag $e (param (ref null $struct.A))) + ;; CHECK: (tag $e (type $1) (param (ref null $struct.A))) (tag $e (param (ref null $struct.A))) ;; CHECK: (func $ref-cast-statically-removed (type $2) diff --git a/test/lit/passes/poppify.wast b/test/lit/passes/poppify.wast index 82825753202..d22bb874c7b 100644 --- a/test/lit/passes/poppify.wast +++ b/test/lit/passes/poppify.wast @@ -3,7 +3,7 @@ ;; RUN: wasm-opt %s --poppify --no-validation -all -S -o - | filecheck %s (module - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $3) (param i32)) (tag $e (param i32)) ;; CHECK: (func $id (type $4) (param $x i32) (result i32) diff --git a/test/lit/passes/remove-unused-brs-eh.wast b/test/lit/passes/remove-unused-brs-eh.wast index 336b3b5a807..ca492d4d0ff 100644 --- a/test/lit/passes/remove-unused-brs-eh.wast +++ b/test/lit/passes/remove-unused-brs-eh.wast @@ -2,11 +2,11 @@ ;; RUN: foreach %s %t wasm-opt -all --remove-unused-brs -S -o - | filecheck %s (module - ;; CHECK: (tag $e) + ;; CHECK: (tag $e (type $0)) (tag $e) - ;; CHECK: (tag $f) + ;; CHECK: (tag $f (type $0)) (tag $f) - ;; CHECK: (tag $g) + ;; CHECK: (tag $g (type $0)) (tag $g) ;; CHECK: (func $throw-caught-all (type $0) @@ -368,10 +368,10 @@ ;; CHECK: (import "a" "b" (func $effect (type $2) (result i32))) (import "a" "b" (func $effect (result i32))) - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $1) (param i32)) (tag $e (param i32)) - ;; CHECK: (tag $multi (param i32 f64)) + ;; CHECK: (tag $multi (type $3) (param i32 f64)) (tag $multi (param i32 f64)) ;; CHECK: (func $throw-caught-all (type $1) (param $x i32) diff --git a/test/lit/passes/remove-unused-names-eh-legacy.wast b/test/lit/passes/remove-unused-names-eh-legacy.wast index 797dabb55cd..ca753206992 100644 --- a/test/lit/passes/remove-unused-names-eh-legacy.wast +++ b/test/lit/passes/remove-unused-names-eh-legacy.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --remove-unused-names -all -S -o - | filecheck %s (module - ;; CHECK: (tag $tag$0 (param i32)) + ;; CHECK: (tag $tag$0 (type $1) (param i32)) (tag $tag$0 (param i32)) ;; CHECK: (func $func0 (type $0) diff --git a/test/lit/passes/rse-eh-legacy.wast b/test/lit/passes/rse-eh-legacy.wast index 95a1459c3ba..6a491242186 100644 --- a/test/lit/passes/rse-eh-legacy.wast +++ b/test/lit/passes/rse-eh-legacy.wast @@ -2,9 +2,9 @@ ;; RUN: wasm-opt %s --rse -all -S -o - | filecheck %s (module - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $1) (param i32)) (tag $e (param i32)) - ;; CHECK: (tag $e2) + ;; CHECK: (tag $e2 (type $0)) (tag $e2) ;; CHECK: (func $try1 (type $0) diff --git a/test/lit/passes/rse-eh.wast b/test/lit/passes/rse-eh.wast index 8c26946b1ac..5ef0cb72c82 100644 --- a/test/lit/passes/rse-eh.wast +++ b/test/lit/passes/rse-eh.wast @@ -8,9 +8,9 @@ ;; CHECK: (type $2 (func (param i32))) - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $2) (param i32)) (tag $e-i32 (param i32)) - ;; CHECK: (tag $e-empty) + ;; CHECK: (tag $e-empty (type $0)) (tag $e-empty) ;; CHECK: (func $foo (type $0) diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast index 6a20d281551..f7ce07a621f 100644 --- a/test/lit/passes/signature-pruning.wast +++ b/test/lit/passes/signature-pruning.wast @@ -970,19 +970,17 @@ ) (module - ;; CHECK: (type $0 (func (param i32))) - ;; CHECK: (rec - ;; CHECK-NEXT: (type $1 (func (param i32))) + ;; CHECK-NEXT: (type $0 (func (param i32))) - ;; CHECK: (type $2 (func (result i32))) + ;; CHECK: (type $1 (func (result i32))) - ;; CHECK: (type $3 (func (param i32))) + ;; CHECK: (type $2 (func (param i32))) - ;; CHECK: (tag $tag (param i32)) + ;; CHECK: (tag $tag (type $2) (param i32)) (tag $tag (param i32)) - ;; CHECK: (func $catch-pop (type $2) (result i32) + ;; CHECK: (func $catch-pop (type $1) (result i32) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -1043,7 +1041,7 @@ ) ) - ;; CHECK: (func $target (type $1) (param $0 i32) + ;; CHECK: (func $target (type $0) (param $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) @@ -1059,19 +1057,17 @@ ;; As above, but remove the other parameter (the pop). (module - ;; CHECK: (type $0 (func (param i32))) - ;; CHECK: (rec - ;; CHECK-NEXT: (type $1 (func (param i32))) + ;; CHECK-NEXT: (type $0 (func (param i32))) - ;; CHECK: (type $2 (func (result i32))) + ;; CHECK: (type $1 (func (result i32))) - ;; CHECK: (type $3 (func (param i32))) + ;; CHECK: (type $2 (func (param i32))) - ;; CHECK: (tag $tag (param i32)) + ;; CHECK: (tag $tag (type $2) (param i32)) (tag $tag (param i32)) - ;; CHECK: (func $catch-pop (type $2) (result i32) + ;; CHECK: (func $catch-pop (type $1) (result i32) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -1127,7 +1123,7 @@ ) ) - ;; CHECK: (func $target (type $1) (param $0 i32) + ;; CHECK: (func $target (type $0) (param $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) diff --git a/test/lit/passes/simplify-locals-eh-legacy.wast b/test/lit/passes/simplify-locals-eh-legacy.wast index f9ab2f8c197..e113fcde922 100644 --- a/test/lit/passes/simplify-locals-eh-legacy.wast +++ b/test/lit/passes/simplify-locals-eh-legacy.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s (module - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $2) (param i32)) (tag $e-i32 (param i32)) ;; CHECK: (func $foo (type $3) (param $0 i32) (param $1 i32) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/simplify-locals-eh.wast b/test/lit/passes/simplify-locals-eh.wast index f455bd4e5a0..b65b5ba4613 100644 --- a/test/lit/passes/simplify-locals-eh.wast +++ b/test/lit/passes/simplify-locals-eh.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s (module - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $0) (param i32)) (tag $e-i32 (param i32)) ;; CHECK: (func $bar (type $1) (result i32) diff --git a/test/lit/passes/stack-ir-eh-legacy.wast b/test/lit/passes/stack-ir-eh-legacy.wast index a5f0b924952..14e53a33a98 100644 --- a/test/lit/passes/stack-ir-eh-legacy.wast +++ b/test/lit/passes/stack-ir-eh-legacy.wast @@ -3,7 +3,7 @@ ;; RUN: -all --print-stack-ir | filecheck %s (module - ;; CHECK: (tag $e0 (param i32)) + ;; CHECK: (tag $e0 (type $0) (param i32)) (tag $e0 (param i32)) ;; CHECK: (func $eh (type $1) diff --git a/test/lit/passes/stack-ir-eh.wast b/test/lit/passes/stack-ir-eh.wast index e06e5e5172d..0165bdc1341 100644 --- a/test/lit/passes/stack-ir-eh.wast +++ b/test/lit/passes/stack-ir-eh.wast @@ -3,7 +3,7 @@ ;; RUN: -all --print-stack-ir | filecheck %s (module - ;; CHECK: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $2) (param i32)) (tag $e-i32 (param i32)) ;; CHECK: (func $foo (type $0) diff --git a/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast b/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast index dbaa2c6015c..67c48c235bb 100644 --- a/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast +++ b/test/lit/passes/stack-ir-roundtrip-eh-legacy.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --generate-stack-ir --roundtrip -all -S -o - | filecheck %s (module - ;; CHECK: (tag $tag (param i32)) + ;; CHECK: (tag $tag (type $0) (param i32)) (tag $tag (param i32)) ;; CHECK: (func $delegate-child (type $1) ;; CHECK-NEXT: (try diff --git a/test/lit/passes/translate-to-exnref.wast b/test/lit/passes/translate-to-exnref.wast index 388bcf8bda8..b4168505256 100644 --- a/test/lit/passes/translate-to-exnref.wast +++ b/test/lit/passes/translate-to-exnref.wast @@ -18,7 +18,7 @@ ;; CHECK: (type $6 (func (param i32 i64))) - ;; CHECK: (tag $e-empty) + ;; CHECK: (tag $e-empty (type $1)) ;; STACKIR-OPT: (type $0 (func (result i32 i64))) ;; STACKIR-OPT: (type $1 (func)) @@ -33,13 +33,13 @@ ;; STACKIR-OPT: (type $6 (func (param i32 i64))) - ;; STACKIR-OPT: (tag $e-empty) + ;; STACKIR-OPT: (tag $e-empty (type $1)) (tag $e-empty) - ;; CHECK: (tag $e-i32 (param i32)) - ;; STACKIR-OPT: (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32 (type $5) (param i32)) + ;; STACKIR-OPT: (tag $e-i32 (type $5) (param i32)) (tag $e-i32 (param i32)) - ;; CHECK: (tag $e-i32-i64 (param i32 i64)) - ;; STACKIR-OPT: (tag $e-i32-i64 (param i32 i64)) + ;; CHECK: (tag $e-i32-i64 (type $6) (param i32 i64)) + ;; STACKIR-OPT: (tag $e-i32-i64 (type $6) (param i32 i64)) (tag $e-i32-i64 (param i32 i64)) ;; CHECK: (func $foo (type $1) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index fad016e1d57..622e7422011 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1247,9 +1247,7 @@ ;; CHECK: (type $5 (func (param (ref noextern)))) - ;; CHECK: (type $6 (func)) - - ;; CHECK: (tag $tag) + ;; CHECK: (tag $tag (type $1)) (tag $tag) ;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref) diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index 14964716551..8696e565af5 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -922,16 +922,14 @@ ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) - ;; CHECK: (type $2 (func)) - - ;; CHECK: (type $3 (func (param (ref $super)))) + ;; CHECK: (type $2 (func (param (ref $super)))) - ;; CHECK: (type $4 (func (param (ref $super)))) + ;; CHECK: (type $3 (func)) - ;; CHECK: (tag $t (param (ref $super))) + ;; CHECK: (tag $t (type $2) (param (ref $super))) (tag $t (param (ref $super))) - ;; CHECK: (func $throw (type $2) + ;; CHECK: (func $throw (type $3) ;; CHECK-NEXT: (throw $t ;; CHECK-NEXT: (struct.new_default $sub) ;; CHECK-NEXT: ) @@ -1790,16 +1788,14 @@ (type $sub (sub $super (func))) ) - ;; CHECK: (type $2 (func (result (ref $super)))) - - ;; CHECK: (type $3 (func (param (ref $sub)))) + ;; CHECK: (type $2 (func (param (ref $sub)))) - ;; CHECK: (type $4 (func (param (ref $sub)))) + ;; CHECK: (type $3 (func (result (ref $super)))) - ;; CHECK: (tag $tag (param (ref $sub))) + ;; CHECK: (tag $tag (type $2) (param (ref $sub))) (tag $tag (param (ref $sub))) - ;; CHECK: (func $test (type $2) (result (ref $super)) + ;; CHECK: (func $test (type $3) (result (ref $super)) ;; CHECK-NEXT: (block $label (result (ref $sub)) ;; CHECK-NEXT: (try_table (catch $tag $label) ;; CHECK-NEXT: (unreachable) diff --git a/test/lit/passes/vacuum-eh-legacy.wast b/test/lit/passes/vacuum-eh-legacy.wast index 0eb53bb56e3..cb780206a55 100644 --- a/test/lit/passes/vacuum-eh-legacy.wast +++ b/test/lit/passes/vacuum-eh-legacy.wast @@ -8,9 +8,9 @@ ;; CHECK: (table $t 0 funcref) (table $t 0 funcref) - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $1) (param i32)) (tag $e (param i32)) - ;; CHECK: (tag $e2 (param i32)) + ;; CHECK: (tag $e2 (type $1) (param i32)) (tag $e2 (param i32)) ;; CHECK: (func $try-test (type $void) diff --git a/test/lit/passes/vacuum-eh.wast b/test/lit/passes/vacuum-eh.wast index 4df41f854f1..fb0cc0732ce 100644 --- a/test/lit/passes/vacuum-eh.wast +++ b/test/lit/passes/vacuum-eh.wast @@ -8,9 +8,9 @@ ;; CHECK: (table $t 0 funcref) (table $t 0 funcref) - ;; CHECK: (tag $e (param i32)) + ;; CHECK: (tag $e (type $1) (param i32)) (tag $e (param i32)) - ;; CHECK: (tag $e2 (param i32)) + ;; CHECK: (tag $e2 (type $1) (param i32)) (tag $e2 (param i32)) ;; CHECK: (func $try_table-test (type $void) diff --git a/test/lit/passes/vacuum-tnh.wast b/test/lit/passes/vacuum-tnh.wast index 1bb4ba13976..8bfcfd95170 100644 --- a/test/lit/passes/vacuum-tnh.wast +++ b/test/lit/passes/vacuum-tnh.wast @@ -8,10 +8,10 @@ (module ;; YESTNH: (type $struct (struct (field (mut i32)))) - ;; YESTNH: (tag $tag (param i32)) + ;; YESTNH: (tag $tag (type $1) (param i32)) ;; NO_TNH: (type $struct (struct (field (mut i32)))) - ;; NO_TNH: (tag $tag (param i32)) + ;; NO_TNH: (tag $tag (type $2) (param i32)) (tag $tag (param i32)) (memory 1 1) diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index 9cf7f27c5d0..9f85b526fc2 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -300,11 +300,11 @@ ;; CHECK: (import "mod" "imported-f" (func $fimport$1 (type $5) (result i32 i64))) - ;; CHECK: (import "mod" "t0" (tag $imported (param i32 i64))) + ;; CHECK: (import "mod" "t0" (tag $imported (type $7) (param i32 i64))) - ;; CHECK: (import "mod" "t1" (tag $eimport$0)) + ;; CHECK: (import "mod" "t1" (tag $eimport$0 (type $0))) - ;; CHECK: (import "mod" "imported-tag" (tag $eimport$1)) + ;; CHECK: (import "mod" "imported-tag" (tag $eimport$1 (type $0))) ;; CHECK: (global $global$2 (mut i32) (i32.const 0)) @@ -413,18 +413,18 @@ ;; tags (tag) - ;; CHECK: (tag $tag$2) + ;; CHECK: (tag $tag$2 (type $0)) - ;; CHECK: (tag $empty) + ;; CHECK: (tag $empty (type $0)) (tag $empty) - ;; CHECK: (tag $tag-i32 (param i32)) + ;; CHECK: (tag $tag-i32 (type $18) (param i32)) (tag $tag-i32 (param $x i32)) - ;; CHECK: (tag $tag-pair (param i32 i64)) + ;; CHECK: (tag $tag-pair (type $7) (param i32 i64)) (tag $tag-pair (param i32 i64)) - ;; CHECK: (tag $tag-pair-to-pair (param i32 i64) (result i32 i64)) + ;; CHECK: (tag $tag-pair-to-pair (type $21) (param i32 i64) (result i32 i64)) (tag $tag-pair-to-pair (param i32 i64) (result i32 i64)) ;; explicit exports diff --git a/test/metadce/rooted-export.wast.dced b/test/metadce/rooted-export.wast.dced index 186746e5602..ceaf18277c7 100644 --- a/test/metadce/rooted-export.wast.dced +++ b/test/metadce/rooted-export.wast.dced @@ -1,7 +1,7 @@ (module (type $0 (func (param i32))) (type $1 (func)) - (tag $b_wasm_tag (param i32)) + (tag $b_wasm_tag (type $0) (param i32)) (export "wasm_func_b" (func $b_wasm_func)) (export "wasm_tag_b" (tag $b_wasm_tag)) (func $b_wasm_func (type $1) diff --git a/test/metadce/tag.wast.dced b/test/metadce/tag.wast.dced index f2d5cf6250b..498906c59bf 100644 --- a/test/metadce/tag.wast.dced +++ b/test/metadce/tag.wast.dced @@ -1,7 +1,7 @@ (module (type $0 (func)) - (import "env" "imported_tag" (tag $t0)) - (tag $t1) + (import "env" "imported_tag" (tag $t0 (type $0))) + (tag $t1 (type $0)) (export "test" (func $test)) (func $test (type $0) (try diff --git a/test/passes/dwarf_with_exceptions.bin.txt b/test/passes/dwarf_with_exceptions.bin.txt index a4e3b5e614d..f6a40099430 100644 --- a/test/passes/dwarf_with_exceptions.bin.txt +++ b/test/passes/dwarf_with_exceptions.bin.txt @@ -8,7 +8,7 @@ (import "env" "_ZSt9terminatev" (func $std::terminate\28\29)) (global $__stack_pointer (mut i32) (i32.const 66560)) (memory $0 2) - (tag $tag$0 (param i32)) + (tag $tag$0 (type $1) (param i32)) (export "memory" (memory $0)) (func $__wasm_call_ctors ) @@ -414,7 +414,7 @@ file_names[ 1]: (import "env" "_ZSt9terminatev" (func $std::terminate\28\29)) (global $__stack_pointer (mut i32) (i32.const 66560)) (memory $0 2) - (tag $tag$0 (param i32)) + (tag $tag$0 (type $1) (param i32)) (export "memory" (memory $0)) (func $__wasm_call_ctors ) diff --git a/test/passes/metrics_all-features.txt b/test/passes/metrics_all-features.txt index a5e80db455c..14e0f9bce69 100644 --- a/test/passes/metrics_all-features.txt +++ b/test/passes/metrics_all-features.txt @@ -25,8 +25,8 @@ total (data $0 (i32.const 0) "\ff\ef\0f\1f 0@P\99") (table $0 256 256 funcref) (elem $0 (i32.const 0) $ifs $ifs $ifs) - (tag $e0 (param i32)) - (tag $e1 (param i32 i32)) + (tag $e0 (type $0) (param i32)) + (tag $e1 (type $1) (param i32 i32)) (func $ifs (type $0) (param $x i32) (local $y f32) (block $block0 diff --git a/test/passes/minify-imports-and-exports_all-features.txt b/test/passes/minify-imports-and-exports_all-features.txt index 87aa3c2f289..17f954cd80f 100644 --- a/test/passes/minify-imports-and-exports_all-features.txt +++ b/test/passes/minify-imports-and-exports_all-features.txt @@ -10014,8 +10014,8 @@ longname4880 => zza (import "other" "anything" (func $internalInfinity (type $0))) (import "wasi_unstable" "f" (func $internal3_wasi (type $0))) (import "wasi_unstable" "LBa" (func $internal3_wasi_only (type $0))) - (import "env" "MBa" (tag $tagname1 (param i32))) - (tag $tag1 (param i32 i32)) + (import "env" "MBa" (tag $tagname1 (type $1) (param i32))) + (tag $tag1 (type $2) (param i32 i32)) (export "NBa" (func $foo1)) (export "OBa" (func $foo2)) (export "PBa" (tag $tag1)) diff --git a/test/passes/minify-imports_all-features.txt b/test/passes/minify-imports_all-features.txt index ec25564396a..bff76bb395c 100644 --- a/test/passes/minify-imports_all-features.txt +++ b/test/passes/minify-imports_all-features.txt @@ -10008,8 +10008,8 @@ longname4880 => zza (import "env" "JBa" (func $internal4998 (type $0))) (import "env" "KBa" (func $internal4999 (type $0))) (import "other" "anything" (func $internalInfinity (type $0))) - (import "env" "LBa" (tag $tagname1 (param i32))) - (tag $tag1 (param i32 i32)) + (import "env" "LBa" (tag $tagname1 (type $1) (param i32))) + (tag $tag1 (type $2) (param i32 i32)) (export "foo1" (func $foo1)) (export "foo2" (func $foo2)) (export "tag1" (tag $tag1)) diff --git a/test/passes/remove-unused-names_merge-blocks_all-features.txt b/test/passes/remove-unused-names_merge-blocks_all-features.txt index 2169d636b71..974b76b4fe4 100644 --- a/test/passes/remove-unused-names_merge-blocks_all-features.txt +++ b/test/passes/remove-unused-names_merge-blocks_all-features.txt @@ -1655,7 +1655,7 @@ (module (type $0 (func)) (type $1 (func (param i32))) - (tag $e (param i32)) + (tag $e (type $1) (param i32)) (func $foo (type $0) ) (func $throw (type $0) diff --git a/test/passes/remove-unused-nonfunction-module-elements_all-features.txt b/test/passes/remove-unused-nonfunction-module-elements_all-features.txt index c56b99253d1..83a7c472b48 100644 --- a/test/passes/remove-unused-nonfunction-module-elements_all-features.txt +++ b/test/passes/remove-unused-nonfunction-module-elements_all-features.txt @@ -342,7 +342,7 @@ (module (type $1 (func (param i64))) (type $0 (func (param i32))) - (tag $e1 (param i64)) + (tag $e1 (type $1) (param i64)) (export "e1" (tag $e1)) (func $f (type $0) (param $0 i32) ) diff --git a/test/try-delegate.wasm.fromBinary b/test/try-delegate.wasm.fromBinary index a3e799627d6..ba3b098033b 100644 --- a/test/try-delegate.wasm.fromBinary +++ b/test/try-delegate.wasm.fromBinary @@ -1,6 +1,6 @@ (module (type $0 (func)) - (tag $tag$0) + (tag $tag$0 (type $0)) (func $0 (try $label (do From e4bfcd2a06db0640bfbf1654f575239ecab72443 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 15 Jan 2025 20:33:50 -0800 Subject: [PATCH 241/622] Update Heap2Local for struct RMW and cmpxchg (#7222) Update the escape analysis to note that RMW modification values and cmpxchg replacement values can escape, but other operands do not escape. When the ref operands are being replaced with locals, lower the RMW and cmpxchg ops to the corresponding local and binary operations, being careful to use scratch locals necessary to prevent reordering problems. Add tests in a new file that will be ignored by the fuzzer until we have better fuzzing support for RMW and cmpxchg ops. --- scripts/test/fuzzing.py | 1 + src/passes/Heap2Local.cpp | 139 ++++++ test/lit/passes/heap2local-rmw.wast | 714 ++++++++++++++++++++++++++++ 3 files changed, 854 insertions(+) create mode 100644 test/lit/passes/heap2local-rmw.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 7bafe3be87d..51384c41b44 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -82,6 +82,7 @@ 'gc-atomics.wast', 'gc-atomics-null-refs.wast', 'shared-structs.wast', + 'heap2local-rmw.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index b050c4a762b..d56d3d1ce21 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -150,6 +150,7 @@ // This optimization focuses on such cases. // +#include "ir/abstract.h" #include "ir/bits.h" #include "ir/branch-utils.h" #include "ir/eh-utils.h" @@ -414,6 +415,18 @@ struct EscapeAnalyzer { escapes = false; fullyConsumes = true; } + void visitStructRMW(StructRMW* curr) { + if (curr->ref == child) { + escapes = false; + fullyConsumes = true; + } + } + void visitStructCmpxchg(StructCmpxchg* curr) { + if (curr->ref == child || curr->expected == child) { + escapes = false; + fullyConsumes = true; + } + } void visitArraySet(ArraySet* curr) { if (!curr->index->is()) { // Array operations on nonconstant indexes do not escape in the normal @@ -891,6 +904,132 @@ struct Struct2Local : PostWalker { } replaceCurrent(builder.blockify(replacement, value)); } + + void visitStructRMW(StructRMW* curr) { + if (analyzer.getInteraction(curr) == ParentChildInteraction::None) { + return; + } + + auto& field = fields[curr->index]; + auto type = curr->type; + assert(type == field.type); + assert(!field.isPacked()); + + // We need a scratch local to hold the old, unmodified field value while we + // update the original local with the modified value. We also need another + // scratch local to hold the evaluated modification value while we set the + // first scratch local in case the evaluation of the modification value ends + // up changing the field value. This is similar to the scratch locals used + // for struct.new. + auto oldScratch = builder.addVar(func, type); + auto valScratch = builder.addVar(func, type); + auto local = localIndexes[curr->index]; + + auto* block = + builder.makeSequence(builder.makeDrop(curr->ref), + builder.makeLocalSet(valScratch, curr->value)); + + // Stash the old value to return. + block->list.push_back( + builder.makeLocalSet(oldScratch, builder.makeLocalGet(local, type))); + + // Store the updated value. + Expression* newVal = nullptr; + if (curr->op == RMWXchg) { + newVal = builder.makeLocalGet(valScratch, type); + } else { + Abstract::Op binop = Abstract::Add; + switch (curr->op) { + case RMWAdd: + binop = Abstract::Add; + break; + case RMWSub: + binop = Abstract::Sub; + break; + case RMWAnd: + binop = Abstract::And; + break; + case RMWOr: + binop = Abstract::Or; + break; + case RMWXor: + binop = Abstract::Xor; + break; + case RMWXchg: + WASM_UNREACHABLE("unexpected op"); + } + newVal = builder.makeBinary(Abstract::getBinary(type, binop), + builder.makeLocalGet(local, type), + builder.makeLocalGet(valScratch, type)); + } + block->list.push_back(builder.makeLocalSet(local, newVal)); + + // See the notes on seqcst struct.get and struct.set. + if (curr->order == MemoryOrder::SeqCst) { + block->list.push_back(builder.makeAtomicFence()); + } + + // Unstash the old value. + block->list.push_back(builder.makeLocalGet(oldScratch, type)); + block->type = type; + replaceCurrent(block); + } + + void visitStructCmpxchg(StructCmpxchg* curr) { + if (analyzer.getInteraction(curr->ref) != ParentChildInteraction::Flows) { + // The allocation can't flow into `replacement` if we've made it this far, + // but it might flow into `expected`, in which case we don't need to do + // anything because we would still be performing the cmpxchg on a real + // struct. We only need to replace the cmpxchg if the ref is being + // replaced with locals. + return; + } + + auto& field = fields[curr->index]; + auto type = curr->type; + assert(type == field.type); + assert(!field.isPacked()); + + // Hold everything in scratch locals, just like for other RMW ops and + // struct.new. + auto oldScratch = builder.addVar(func, type); + auto expectedScratch = builder.addVar(func, type); + auto replacementScratch = builder.addVar(func, type); + auto local = localIndexes[curr->index]; + + auto* block = builder.makeBlock( + {builder.makeDrop(curr->ref), + builder.makeLocalSet(expectedScratch, curr->expected), + builder.makeLocalSet(replacementScratch, curr->replacement), + builder.makeLocalSet(oldScratch, builder.makeLocalGet(local, type))}); + + // Create the check for whether we should do the exchange. + auto* lhs = builder.makeLocalGet(local, type); + auto* rhs = builder.makeLocalGet(expectedScratch, type); + Expression* pred; + if (type.isRef()) { + pred = builder.makeRefEq(lhs, rhs); + } else { + pred = + builder.makeBinary(Abstract::getBinary(type, Abstract::Eq), lhs, rhs); + } + + // The conditional exchange. + block->list.push_back( + builder.makeIf(pred, + builder.makeLocalSet( + local, builder.makeLocalGet(replacementScratch, type)))); + + // See the notes on seqcst struct.get and struct.set. + if (curr->order == MemoryOrder::SeqCst) { + block->list.push_back(builder.makeAtomicFence()); + } + + // Unstash the old value. + block->list.push_back(builder.makeLocalGet(oldScratch, type)); + block->type = type; + replaceCurrent(block); + } }; // An optimizer that handles the rewriting to turn a nonescaping array diff --git a/test/lit/passes/heap2local-rmw.wast b/test/lit/passes/heap2local-rmw.wast new file mode 100644 index 00000000000..1301d61eb2e --- /dev/null +++ b/test/lit/passes/heap2local-rmw.wast @@ -0,0 +1,714 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all --heap2local -S -o - | filecheck %s + +(module + (type $i32 (struct (field (mut i32)))) + (type $i64 (struct (field (mut i64)))) + ;; CHECK: (type $struct (struct (field (mut (ref null $struct))))) + (type $struct (struct (field (mut (ref null $struct))))) + + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (type $2 (func (result i64))) + + ;; CHECK: (type $3 (func (param (ref null $struct)) (result (ref null $struct)))) + + ;; CHECK: (type $4 (func (param (ref null $struct) (ref null $struct)) (result (ref null $struct)))) + + ;; CHECK: (func $escape-rmw (type $3) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $escape-rmw (param (ref null $struct)) (result (ref null $struct)) + ;; Allocations that flow into RMW modification values can be written and escape. + (struct.atomic.rmw.xchg $struct 0 + (local.get 0) + (struct.new_default $struct) + ) + ) + + ;; CHECK: (func $escape-cmpxchg (type $3) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $escape-cmpxchg (param (ref null $struct)) (result (ref null $struct)) + ;; Similarly, allocations that flow into cmpxchg replacement values can escape. + (struct.atomic.rmw.cmpxchg $struct 0 + (local.get 0) + (local.get 0) + (struct.new_default $struct) + ) + ) + + ;; CHECK: (func $no-escape-cmpxchg-expected (type $3) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (local $1 (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $no-escape-cmpxchg-expected (param (ref null $struct)) (result (ref null $struct)) + ;; Allocations that flow into the cmpxchg `expected` operand do not escape + ;; and can be optimized, but do not require any fixups of the cmpxchg. + (struct.atomic.rmw.cmpxchg $struct 0 + (local.get 0) + (struct.new_default $struct) + (local.get 0) + ) + ) + + ;; CHECK: (func $rmw-add-i32 (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-add-i32 (result i32) + (struct.atomic.rmw.add $i32 0 + (struct.new_default $i32) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub-i32 (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.sub + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-sub-i32 (result i32) + (struct.atomic.rmw.sub $i32 0 + (struct.new_default $i32) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-and-i32 (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-and-i32 (result i32) + (struct.atomic.rmw.and $i32 0 + (struct.new_default $i32) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-or-i32 (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-or-i32 (result i32) + (struct.atomic.rmw.or $i32 0 + (struct.new_default $i32) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xor-i32 (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.xor + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-xor-i32 (result i32) + (struct.atomic.rmw.xor $i32 0 + (struct.new_default $i32) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xchg-i32 (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-xchg-i32 (result i32) + (struct.atomic.rmw.xchg $i32 0 + (struct.new_default $i32) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-i32 (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-i32 (result i32) + (struct.atomic.rmw.cmpxchg $i32 0 + (struct.new_default $i32) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $rmw-add-i64 (type $2) (result i64) + ;; CHECK-NEXT: (local $0 i64) + ;; CHECK-NEXT: (local $1 i64) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.add + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-add-i64 (result i64) + (struct.atomic.rmw.add $i64 0 + (struct.new_default $i64) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub-i64 (type $2) (result i64) + ;; CHECK-NEXT: (local $0 i64) + ;; CHECK-NEXT: (local $1 i64) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.sub + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-sub-i64 (result i64) + (struct.atomic.rmw.sub $i64 0 + (struct.new_default $i64) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-and-i64 (type $2) (result i64) + ;; CHECK-NEXT: (local $0 i64) + ;; CHECK-NEXT: (local $1 i64) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-and-i64 (result i64) + (struct.atomic.rmw.and $i64 0 + (struct.new_default $i64) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-or-i64 (type $2) (result i64) + ;; CHECK-NEXT: (local $0 i64) + ;; CHECK-NEXT: (local $1 i64) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.or + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-or-i64 (result i64) + (struct.atomic.rmw.or $i64 0 + (struct.new_default $i64) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-xor-i64 (type $2) (result i64) + ;; CHECK-NEXT: (local $0 i64) + ;; CHECK-NEXT: (local $1 i64) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.xor + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-xor-i64 (result i64) + (struct.atomic.rmw.xor $i64 0 + (struct.new_default $i64) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-xchg-i64 (type $2) (result i64) + ;; CHECK-NEXT: (local $0 i64) + ;; CHECK-NEXT: (local $1 i64) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-xchg-i64 (result i64) + (struct.atomic.rmw.xchg $i64 0 + (struct.new_default $i64) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-i64 (type $2) (result i64) + ;; CHECK-NEXT: (local $0 i64) + ;; CHECK-NEXT: (local $1 i64) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (local $3 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-i64 (result i64) + (struct.atomic.rmw.cmpxchg $i64 0 + (struct.new_default $i64) + (i64.const 1) + (i64.const 2) + ) + ) + + ;; CHECK: (func $rmw-xchg-ref (type $3) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (local $1 (ref null $struct)) + ;; CHECK-NEXT: (local $2 (ref null $struct)) + ;; CHECK-NEXT: (local $3 (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $rmw-xchg-ref (param (ref null $struct)) (result (ref null $struct)) + (struct.atomic.rmw.xchg $struct 0 + (struct.new_default $struct) + (local.get 0) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-ref (type $4) (param $0 (ref null $struct)) (param $1 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (local $2 (ref null $struct)) + ;; CHECK-NEXT: (local $3 (ref null $struct)) + ;; CHECK-NEXT: (local $4 (ref null $struct)) + ;; CHECK-NEXT: (local $5 (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-ref (param (ref null $struct) (ref null $struct)) (result (ref null $struct)) + (struct.atomic.rmw.cmpxchg $struct 0 + (struct.new_default $struct) + (local.get 0) + (local.get 1) + ) + ) + + ;; CHECK: (func $rmw-acqrel (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $rmw-acqrel (result i32) + ;; The replacement for an acqrel rmw does not need a fence. + (struct.atomic.rmw.add acqrel acqrel $i32 0 + (struct.new_default $i32) + (i32.const 1) + ) + ) + + ;; CHECK: (func $cmpxchg-acqrel (type $1) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $cmpxchg-acqrel (result i32) + ;; The replacement for an acqrel rmw does not need a fence. + (struct.atomic.rmw.cmpxchg acqrel acqrel $i32 0 + (struct.new_default $i32) + (i32.const 1) + (i32.const 2) + ) + ) +) From de004ab8fc95375d6876ce883288a58900fa5f4a Mon Sep 17 00:00:00 2001 From: walkingeyerobot Date: Thu, 16 Jan 2025 16:05:23 -0500 Subject: [PATCH 242/622] [NFC] Mark unused variables (#7224) --- src/passes/Heap2Local.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index d56d3d1ce21..56d105c05db 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -910,7 +910,7 @@ struct Struct2Local : PostWalker { return; } - auto& field = fields[curr->index]; + [[maybe_unused]] auto& field = fields[curr->index]; auto type = curr->type; assert(type == field.type); assert(!field.isPacked()); @@ -985,7 +985,7 @@ struct Struct2Local : PostWalker { return; } - auto& field = fields[curr->index]; + [[maybe_unused]] auto& field = fields[curr->index]; auto type = curr->type; assert(type == field.type); assert(!field.isPacked()); From 42faa40680d2fc9bd4d42be40e410deb3afc1e5d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 17 Jan 2025 09:17:35 -0800 Subject: [PATCH 243/622] Fuzzer: Fix escaping of wasm-ctor-eval names (#7223) Apparently we have some exports that need escaping in their names, and we should not escape them twice. --- scripts/fuzz_opt.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 037cfab63ae..bf7e1ef480e 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1343,6 +1343,11 @@ def handle(self, wasm): if not ctors: return + # Fix escaping of the names, as we will be passing them as commandline + # parameters below (e.g. we want --ctors=foo\28bar and not + # --ctors=foo\\28bar; that extra escaping \ would cause an error). + ctors = ctors.replace('\\\\', '\\') + # eval the wasm. # we can use --ignore-external-input because the fuzzer passes in 0 to # all params, which is the same as ctor-eval assumes in this mode. From 8623f733f93829c29dbcb972b1be452a4a46e295 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 17 Jan 2025 11:50:33 -0800 Subject: [PATCH 244/622] Optimize struct RMW ops in OptimizeInstructions (#7225) When the RMW operation can be proven not to change the accessed value, optimize it to a simple atomic get instead. This is valid because a write that does not change an in-memory value does not synchronize with any subsequent reads of that value, since those reads can be considered to be reading from the previous write. Also optimize RMW operations on unshared structs to their non-atomic equivalent operations. This can increase code size, but can also enable follow-on optimizations of the simpler operations and can be less expensive at runtime. --- scripts/test/fuzzing.py | 1 + src/passes/OptimizeInstructions.cpp | 188 +++ .../optimize-instructions-struct-rmw.wast | 1270 +++++++++++++++++ 3 files changed, 1459 insertions(+) create mode 100644 test/lit/passes/optimize-instructions-struct-rmw.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 51384c41b44..62b833a2653 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -83,6 +83,7 @@ 'gc-atomics-null-refs.wast', 'shared-structs.wast', 'heap2local-rmw.wast', + 'optimize-instructions-struct-rmw.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 6a528d74fa9..c58630a8e7a 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1862,6 +1862,194 @@ struct OptimizeInstructions } } + void visitStructRMW(StructRMW* curr) { + skipNonNullCast(curr->ref, curr); + if (trapOnNull(curr, curr->ref)) { + return; + } + + if (!curr->ref->type.isStruct()) { + return; + } + + Builder builder(*getModule()); + + // Even when the RMW access is to shared memory, we can optimize out the + // modify and write parts if we know that the modified value is the same as + // the original value. This is valid because reads from writes that don't + // change the in-memory value can be considered to be reads from the + // previous write to the same location instead. That means there is no read + // that necessarily synchronizes with the write. + auto* value = + Properties::getFallthrough(curr->value, getPassOptions(), *getModule()); + if (Properties::isSingleConstantExpression(value)) { + auto val = Properties::getLiteral(value); + bool canOptimize = false; + switch (curr->op) { + case RMWAdd: + case RMWSub: + case RMWOr: + case RMWXor: + canOptimize = val.getInteger() == 0; + break; + case RMWAnd: + canOptimize = val == Literal::makeNegOne(val.type); + break; + case RMWXchg: + canOptimize = false; + break; + } + if (canOptimize) { + replaceCurrent(builder.makeStructGet( + curr->index, + getResultOfFirst(curr->ref, builder.makeDrop(curr->value)), + curr->order, + curr->type)); + return; + } + } + + if (curr->ref->type.getHeapType().isShared()) { + return; + } + + // Lower the RMW to its more basic operations. Breaking the atomic + // operation into several non-atomic operations is safe because no other + // thread can observe an intermediate state in the unshared memory. This + // initially increases code size, but the more basic operations may be + // more optimizable than the original RMW. + // TODO: Experiment to determine whether this is worthwhile on real code. + // Maybe we should do this optimization only when optimizing for speed over + // size. + auto ref = builder.addVar(getFunction(), curr->ref->type); + auto val = builder.addVar(getFunction(), curr->type); + auto result = builder.addVar(getFunction(), curr->type); + auto* block = builder.makeBlock( + {builder.makeLocalSet(ref, curr->ref), + builder.makeLocalSet(val, curr->value), + builder.makeLocalSet( + result, + builder.makeStructGet(curr->index, + builder.makeLocalGet(ref, curr->ref->type), + MemoryOrder::Unordered, + curr->type))}); + Expression* newVal = nullptr; + if (curr->op == RMWXchg) { + newVal = builder.makeLocalGet(val, curr->type); + } else { + Abstract::Op binop = Abstract::Add; + switch (curr->op) { + case RMWAdd: + binop = Abstract::Add; + break; + case RMWSub: + binop = Abstract::Sub; + break; + case RMWAnd: + binop = Abstract::And; + break; + case RMWOr: + binop = Abstract::Or; + break; + case RMWXor: + binop = Abstract::Xor; + break; + case RMWXchg: + WASM_UNREACHABLE("unexpected op"); + } + newVal = builder.makeBinary(Abstract::getBinary(curr->type, binop), + builder.makeLocalGet(result, curr->type), + builder.makeLocalGet(val, curr->type)); + } + block->list.push_back( + builder.makeStructSet(curr->index, + builder.makeLocalGet(ref, curr->ref->type), + newVal, + MemoryOrder::Unordered)); + + // We must maintain this operation's effect on the global order of seqcst + // operations. + if (curr->order == MemoryOrder::SeqCst) { + block->list.push_back(builder.makeAtomicFence()); + } + + block->list.push_back(builder.makeLocalGet(result, curr->type)); + block->type = curr->type; + replaceCurrent(block); + } + + void visitStructCmpxchg(StructCmpxchg* curr) { + skipNonNullCast(curr->ref, curr); + if (trapOnNull(curr, curr->ref)) { + return; + } + + if (!curr->ref->type.isStruct()) { + return; + } + + Builder builder(*getModule()); + + // Just like other RMW operations, cmpxchg can be optimized to just a read + // if it is known not to change the in-memory value. This is the case when + // `expected` and `replacement` are known to be the same. + if (areConsecutiveInputsEqual(curr->expected, curr->replacement)) { + auto* ref = getResultOfFirst( + curr->ref, + builder.makeSequence(builder.makeDrop(curr->expected), + builder.makeDrop(curr->replacement))); + replaceCurrent( + builder.makeStructGet(curr->index, ref, curr->order, curr->type)); + return; + } + + if (curr->ref->type.getHeapType().isShared()) { + return; + } + + // Just like other RMW operations, lower to basic operations when operating + // on unshared memory. + auto ref = builder.addVar(getFunction(), curr->ref->type); + auto expected = builder.addVar(getFunction(), curr->type); + auto replacement = builder.addVar(getFunction(), curr->type); + auto result = builder.addVar(getFunction(), curr->type); + auto* block = + builder.makeBlock({builder.makeLocalSet(ref, curr->ref), + builder.makeLocalSet(expected, curr->expected), + builder.makeLocalSet(replacement, curr->replacement)}); + auto* lhs = builder.makeLocalTee( + result, + builder.makeStructGet(curr->index, + builder.makeLocalGet(ref, curr->ref->type), + MemoryOrder::Unordered, + curr->type), + curr->type); + auto* rhs = builder.makeLocalGet(expected, curr->type); + Expression* pred = nullptr; + if (curr->type.isRef()) { + pred = builder.makeRefEq(lhs, rhs); + } else { + pred = builder.makeBinary( + Abstract::getBinary(curr->type, Abstract::Eq), lhs, rhs); + } + block->list.push_back(builder.makeIf( + pred, + builder.makeStructSet(curr->index, + builder.makeLocalGet(ref, curr->ref->type), + builder.makeLocalGet(replacement, curr->type), + MemoryOrder::Unordered))); + + // We must maintain this operation's effect on the global order of seqcst + // operations. + if (curr->order == MemoryOrder::SeqCst) { + block->list.push_back(builder.makeAtomicFence()); + } + + block->list.push_back(builder.makeLocalGet(result, curr->type)); + block->type = curr->type; + replaceCurrent(block); + } + void visitArrayNew(ArrayNew* curr) { // If a value is provided, we can optimize in some cases. if (curr->type == Type::unreachable || curr->isWithDefault()) { diff --git a/test/lit/passes/optimize-instructions-struct-rmw.wast b/test/lit/passes/optimize-instructions-struct-rmw.wast new file mode 100644 index 00000000000..1a9bcec6c41 --- /dev/null +++ b/test/lit/passes/optimize-instructions-struct-rmw.wast @@ -0,0 +1,1270 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --optimize-instructions --preserve-type-order -S -o - | filecheck %s + +(module + ;; CHECK: (type $i32 (shared (struct (field (mut i32))))) + (type $i32 (shared (struct (field (mut i32))))) + ;; CHECK: (type $i64 (shared (struct (field (mut i64))))) + (type $i64 (shared (struct (field (mut i64))))) + ;; CHECK: (type $struct (shared (struct (field (mut (ref null $struct)))))) + (type $struct (shared (struct (field (mut (ref null $struct)))))) + ;; CHECK: (type $unshared-i32 (struct (field (mut i32)))) + (type $unshared-i32 (struct (field (mut i32)))) + ;; CHECK: (type $unshared-i64 (struct (field (mut i64)))) + (type $unshared-i64 (struct (field (mut i64)))) + ;; CHECK: (type $unshared-struct (struct (field (mut (ref null $unshared-struct))))) + (type $unshared-struct (struct (field (mut (ref null $unshared-struct))))) + + ;; CHECK: (func $rmw-skip-non-null-cast (type $6) (param $0 (ref null $i32)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.add $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-skip-non-null-cast (param (ref null $i32) i32) (result i32) + (struct.atomic.rmw.add $i32 0 + (ref.as_non_null + (local.get 0) + ) + (local.get 1) + ) + ) + + ;; CHECK: (func $cmpxchg-skip-non-null-cast (type $7) (param $0 (ref null $i32)) (param $1 i32) (param $2 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-skip-non-null-cast (param (ref null $i32) i32 i32) (result i32) + (struct.atomic.rmw.cmpxchg $i32 0 + (ref.as_non_null + (local.get 0) + ) + (local.get 1) + (local.get 2) + ) + ) + + ;; CHECK: (func $rmw-trap-on-null (type $8) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $rmw-trap-on-null (result i32) + (struct.atomic.rmw.add $i32 0 + (ref.null (shared none)) + (i32.const 1) + ) + ) + + ;; CHECK: (func $cmpxchg-trap-on-null (type $8) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cmpxchg-trap-on-null (result i32) + (struct.atomic.rmw.cmpxchg $i32 0 + (ref.null (shared none)) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $rmw-add-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (block (result (ref null $i32)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add-i32-ident (param (ref null $i32)) (result i32) + ;; This can be optimized to just an atomic load. + (struct.atomic.rmw.add $i32 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-add-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.add $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add-i32-noident (param (ref null $i32)) (result i32) + ;; But this cannot be optimized at all. + (struct.atomic.rmw.add $i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (block (result (ref null $i32)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-sub-i32-ident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.sub $i32 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-sub-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.sub $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-sub-i32-noident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.sub $i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-and-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (block (result (ref null $i32)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-and-i32-ident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.and $i32 0 + (local.get 0) + (i32.const -1) + ) + ) + + ;; CHECK: (func $rmw-and-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.and $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-and-i32-noident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.and $i32 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-or-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (block (result (ref null $i32)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-or-i32-ident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.or $i32 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-or-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.or $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-or-i32-noident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.or $i32 0 + (local.get 0) + (i32.const -1) + ) + ) + + ;; CHECK: (func $rmw-xor-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (block (result (ref null $i32)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xor-i32-ident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.xor $i32 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-xor-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xor $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xor-i32-noident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.xor $i32 0 + (local.get 0) + (i32.const -1) + ) + ) + + ;; CHECK: (func $rmw-xchg-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.get $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-i32-ident (param (ref null $i32)) (result i32) + ;; TODO: Optimize this case. + (struct.atomic.rmw.xchg $i32 0 + (local.get 0) + (struct.get $i32 0 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $rmw-xchg-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-i32-noident (param (ref null $i32)) (result i32) + (struct.atomic.rmw.xchg $i32 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $cmpxchg-i32-ident (type $6) (param $0 (ref null $i32)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (block (result (ref null $i32)) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-i32-ident (param (ref null $i32) i32) (result i32) + (struct.atomic.rmw.cmpxchg $i32 0 + (local.get 0) + (local.get 1) + (local.get 1) + ) + ) + + ;; CHECK: (func $cmpxchg-i32-noident (type $6) (param $0 (ref null $i32)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-i32-noident (param (ref null $i32) i32) (result i32) + (struct.atomic.rmw.cmpxchg $i32 0 + (local.get 0) + (i32.const 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-add-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (block (result (ref null $i64)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add-i64-ident (param (ref null $i64)) (result i64) + ;; This can be optimized to just an atomic load. + (struct.atomic.rmw.add $i64 0 + (local.get 0) + (i64.const 0) + ) + ) + + ;; CHECK: (func $rmw-add-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.rmw.add $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add-i64-noident (param (ref null $i64)) (result i64) + ;; But this cannot be optimized at all. + (struct.atomic.rmw.add $i64 0 + (local.get 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (block (result (ref null $i64)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-sub-i64-ident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.sub $i64 0 + (local.get 0) + (i64.const 0) + ) + ) + + ;; CHECK: (func $rmw-sub-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.rmw.sub $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-sub-i64-noident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.sub $i64 0 + (local.get 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-and-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (block (result (ref null $i64)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-and-i64-ident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.and $i64 0 + (local.get 0) + (i64.const -1) + ) + ) + + ;; CHECK: (func $rmw-and-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.rmw.and $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-and-i64-noident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.and $i64 0 + (local.get 0) + (i64.const 0) + ) + ) + + ;; CHECK: (func $rmw-or-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (block (result (ref null $i64)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-or-i64-ident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.or $i64 0 + (local.get 0) + (i64.const 0) + ) + ) + + ;; CHECK: (func $rmw-or-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.rmw.or $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i64.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-or-i64-noident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.or $i64 0 + (local.get 0) + (i64.const -1) + ) + ) + + ;; CHECK: (func $rmw-xor-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (block (result (ref null $i64)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xor-i64-ident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.xor $i64 0 + (local.get 0) + (i64.const 0) + ) + ) + + ;; CHECK: (func $rmw-xor-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.rmw.xor $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i64.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xor-i64-noident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.xor $i64 0 + (local.get 0) + (i64.const -1) + ) + ) + + ;; CHECK: (func $rmw-xchg-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.get $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-i64-ident (param (ref null $i64)) (result i64) + ;; TODO: Optimize this case. + (struct.atomic.rmw.xchg $i64 0 + (local.get 0) + (struct.get $i64 0 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $rmw-xchg-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-i64-noident (param (ref null $i64)) (result i64) + (struct.atomic.rmw.xchg $i64 0 + (local.get 0) + (i64.const 0) + ) + ) + + ;; CHECK: (func $cmpxchg-i64-ident (type $11) (param $0 (ref null $i64)) (param $1 i64) (result i64) + ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (block (result (ref null $i64)) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-i64-ident (param (ref null $i64) i64) (result i64) + (struct.atomic.rmw.cmpxchg $i64 0 + (local.get 0) + (local.get 1) + (local.get 1) + ) + ) + + ;; CHECK: (func $cmpxchg-i64-noident (type $11) (param $0 (ref null $i64)) (param $1 i64) (result i64) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $i64 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-i64-noident (param (ref null $i64) i64) (result i64) + (struct.atomic.rmw.cmpxchg $i64 0 + (local.get 0) + (i64.const 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-xchg-ref-ident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-ref-ident (param (ref null $struct)) (result (ref null $struct)) + ;; TODO: Optimize this case. + (struct.atomic.rmw.xchg $struct 0 + (local.get 0) + (struct.get $struct 0 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $rmw-xchg-ref-noident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-ref-noident (param (ref null $struct)) (result (ref null $struct)) + (struct.atomic.rmw.xchg $struct 0 + (local.get 0) + (local.get 0) + ) + ) + + ;; CHECK: (func $cmpxchg-ref-ident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.get $struct 0 + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-ref-ident (param (ref null $struct)) (result (ref null $struct)) + (struct.atomic.rmw.cmpxchg $struct 0 + (local.get 0) + (local.get 0) + (local.get 0) + ) + ) + + ;; CHECK: (func $cmpxchg-ref-ident-null (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.get $struct 0 + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-ref-ident-null (param (ref null $struct)) (result (ref null $struct)) + (struct.atomic.rmw.cmpxchg $struct 0 + (local.get 0) + (ref.null (shared none)) + (ref.null (shared none)) + ) + ) + + ;; CHECK: (func $cmpxchg-ref-noident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-ref-noident (param (ref null $struct)) (result (ref null $struct)) + (struct.atomic.rmw.cmpxchg $struct 0 + (local.get 0) + (ref.null (shared none)) + (local.get 0) + ) + ) + + ;; CHECK: (func $rmw-add-i32-acqrel-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 + ;; CHECK-NEXT: (block (result (ref null $i32)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add-i32-acqrel-ident (param (ref null $i32)) (result i32) + ;; Check that acqrel rmws are optimized to acquire gets. + (struct.atomic.rmw.add acqrel acqrel $i32 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-add-i32-unshared-ident (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $unshared-i32 0 + ;; CHECK-NEXT: (block (result (ref null $unshared-i32)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-add-i32-unshared-ident (param (ref null $unshared-i32)) (result i32) + ;; Check just one unshared case to make sure we do the same identity + ;; optimizations tested above. + (struct.atomic.rmw.add $unshared-i32 0 + (local.get 0) + (i32.const 0) + ) + ) + + + ;; CHECK: (func $cmpxchg-i32-unshared-ident (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.get $unshared-i32 0 + ;; CHECK-NEXT: (block (result (ref null $unshared-i32)) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-i32-unshared-ident (param (ref null $unshared-i32)) (result i32) + ;; Check the same for cmpxchg. + (struct.atomic.rmw.cmpxchg $unshared-i32 0 + (local.get 0) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-add-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-add-i32-lower (param (ref null $unshared-i32)) (result i32) + (struct.atomic.rmw.add $unshared-i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.sub + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-sub-i32-lower (param (ref null $unshared-i32)) (result i32) + (struct.atomic.rmw.sub $unshared-i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-and-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-and-i32-lower (param (ref null $unshared-i32)) (result i32) + (struct.atomic.rmw.and $unshared-i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-or-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-or-i32-lower (param (ref null $unshared-i32)) (result i32) + (struct.atomic.rmw.or $unshared-i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xor-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.xor + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-xor-i32-lower (param (ref null $unshared-i32)) (result i32) + (struct.atomic.rmw.xor $unshared-i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $rmw-xchg-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-xchg-i32-lower (param (ref null $unshared-i32)) (result i32) + (struct.atomic.rmw.xchg $unshared-i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $cmpxchg-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + (func $cmpxchg-i32-lower (param (ref null $unshared-i32)) (result i32) + (struct.atomic.rmw.cmpxchg $unshared-i32 0 + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $rmw-add-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (local $3 i64) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i64.add + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-add-i64-lower (param (ref null $unshared-i64)) (result i64) + (struct.atomic.rmw.add $unshared-i64 0 + (local.get 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-sub-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (local $3 i64) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i64.sub + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-sub-i64-lower (param (ref null $unshared-i64)) (result i64) + (struct.atomic.rmw.sub $unshared-i64 0 + (local.get 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-and-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (local $3 i64) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-and-i64-lower (param (ref null $unshared-i64)) (result i64) + (struct.atomic.rmw.and $unshared-i64 0 + (local.get 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-or-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (local $3 i64) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i64.or + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-or-i64-lower (param (ref null $unshared-i64)) (result i64) + (struct.atomic.rmw.or $unshared-i64 0 + (local.get 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-xor-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (local $3 i64) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i64.xor + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-xor-i64-lower (param (ref null $unshared-i64)) (result i64) + (struct.atomic.rmw.xor $unshared-i64 0 + (local.get 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $rmw-xchg-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (local $3 i64) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-xchg-i64-lower (param (ref null $unshared-i64)) (result i64) + (struct.atomic.rmw.xchg $unshared-i64 0 + (local.get 0) + (i64.const 1) + ) + ) + + ;; CHECK: (func $cmpxchg-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) + ;; CHECK-NEXT: (local $2 i64) + ;; CHECK-NEXT: (local $3 i64) + ;; CHECK-NEXT: (local $4 i64) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (struct.set $unshared-i64 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + (func $cmpxchg-i64-lower (param (ref null $unshared-i64)) (result i64) + (struct.atomic.rmw.cmpxchg $unshared-i64 0 + (local.get 0) + (i64.const 1) + (i64.const 2) + ) + ) + + ;; CHECK: (func $rmw-xchg-ref-lower (type $15) (param $0 (ref null $unshared-struct)) (result (ref null $unshared-struct)) + ;; CHECK-NEXT: (local $1 (ref null $unshared-struct)) + ;; CHECK-NEXT: (local $2 (ref null $unshared-struct)) + ;; CHECK-NEXT: (local $3 (ref null $unshared-struct)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-struct 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-struct 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-xchg-ref-lower (param (ref null $unshared-struct)) (result (ref null $unshared-struct)) + (struct.atomic.rmw.xchg $unshared-struct 0 + (local.get 0) + (ref.null none) + ) + ) + + ;; CHECK: (func $cmpxchg-ref-lower (type $15) (param $0 (ref null $unshared-struct)) (result (ref null $unshared-struct)) + ;; CHECK-NEXT: (local $1 (ref null $unshared-struct)) + ;; CHECK-NEXT: (local $2 (ref null $unshared-struct)) + ;; CHECK-NEXT: (local $3 (ref null $unshared-struct)) + ;; CHECK-NEXT: (local $4 (ref null $unshared-struct)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $unshared-struct 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (struct.set $unshared-struct 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + (func $cmpxchg-ref-lower (param (ref null $unshared-struct)) (result (ref null $unshared-struct)) + (struct.atomic.rmw.cmpxchg $unshared-struct 0 + (local.get 0) + (ref.null none) + (local.get 0) + ) + ) + + ;; CHECK: (func $rmw-add-i32-acqrel (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + (func $rmw-add-i32-acqrel (param (ref null $unshared-i32)) (result i32) + ;; Check that the lowering of an acqrel RMW does not have a fence. + (struct.atomic.rmw.add acqrel acqrel $unshared-i32 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $cmpxchg-i32-acqrel (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (struct.set $unshared-i32 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + (func $cmpxchg-i32-acqrel (param (ref null $unshared-i32)) (result i32) + ;; Same for an cmpxchg. + (struct.atomic.rmw.cmpxchg acqrel acqrel $unshared-i32 0 + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) +) From e984c89654e69dcce45adaeeef1a04d7a4c971b8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 17 Jan 2025 15:36:17 -0800 Subject: [PATCH 245/622] Add V8 testing on CI (#7208) This adds v8 from jsvu to CI, and a single lit test that uses it, to get started. The tests are skipped on Alpine Linux, which jsvu is not compatible with. To do so, we just delete the tests before running lit (for lack of a better skipping mechanism, as `lit --xfail` is not supported in our lit on CI). --- .github/workflows/ci.yml | 33 ++++++++++++++++++++++++++ .github/workflows/create_release.yml | 5 +++- test/lit/d8/fuzz_shell.wast | 19 +++++++++++++++ test/lit/lit.cfg.py | 35 ++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 test/lit/d8/fuzz_shell.wast diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b826c840bac..5bb8aef00c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,11 @@ jobs: run: choco install ninja if: matrix.os == 'windows-latest' + - name: install v8 + run: | + npm install jsvu -g + jsvu --os=default --engines=v8 + - name: mkdir run: mkdir -p out @@ -125,6 +130,10 @@ jobs: submodules: true - name: install ninja run: sudo apt-get install ninja-build + - name: install v8 + run: | + npm install jsvu -g + jsvu --os=default --engines=v8 - name: install Python dev dependencies run: pip3 install -r requirements-dev.txt - name: cmake @@ -161,6 +170,10 @@ jobs: sudo ./llvm.sh 18 - name: install ninja run: sudo apt-get install ninja-build + - name: install v8 + run: | + npm install jsvu -g + jsvu --os=default --engines=v8 - name: install Python dev dependencies run: pip3 install -r requirements-dev.txt - name: cmake @@ -196,6 +209,10 @@ jobs: ./alpine.sh apk update ./alpine.sh apk add build-base cmake git python3 py3-pip clang ninja + - name: avoid d8 tests (jsvu is not compatible with alpine) + run: | + ./alpine.sh rm -Rf test/lit/d8 + - name: install python dev dependencies run: ./alpine.sh pip3 install --break-system-packages -r requirements-dev.txt @@ -227,6 +244,10 @@ jobs: submodules: true - name: install ninja run: sudo apt-get install ninja-build + - name: install v8 + run: | + npm install jsvu -g + jsvu --os=default --engines=v8 - name: install Python dev dependencies run: pip3 install -r requirements-dev.txt - name: cmake @@ -261,6 +282,10 @@ jobs: sudo ./llvm.sh 18 - name: install ninja run: sudo apt-get install ninja-build + - name: install v8 + run: | + npm install jsvu -g + jsvu --os=default --engines=v8 - name: install Python dev dependencies run: pip3 install -r requirements-dev.txt - name: cmake @@ -344,6 +369,10 @@ jobs: submodules: true - name: install ninja run: sudo apt-get install ninja-build + - name: install v8 + run: | + npm install jsvu -g + jsvu --os=default --engines=v8 - name: install Python dev dependencies run: pip3 install -r requirements-dev.txt - name: cmake @@ -378,6 +407,10 @@ jobs: submodules: true - name: install ninja run: sudo apt-get install ninja-build + - name: install v8 + run: | + npm install jsvu -g + jsvu --os=default --engines=v8 - name: install Python dev dependencies run: pip3 install -r requirements-dev.txt - name: cmake diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index a5d1f459e56..8cff451c779 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -126,12 +126,15 @@ jobs: echo 'docker exec alpine "$@";' > ./alpine.sh chmod +x ./alpine.sh - - name: install packages run: | ./alpine.sh apk update ./alpine.sh apk add build-base cmake git python3 clang ninja py3-pip + - name: avoid d8 tests (jsvu is not compatible with alpine) + run: | + ./alpine.sh rm -Rf test/lit/d8 + - name: install python dev dependencies run: ./alpine.sh pip3 install --break-system-packages -r requirements-dev.txt diff --git a/test/lit/d8/fuzz_shell.wast b/test/lit/d8/fuzz_shell.wast new file mode 100644 index 00000000000..99d8ea989c6 --- /dev/null +++ b/test/lit/d8/fuzz_shell.wast @@ -0,0 +1,19 @@ +;; Test running a wasm file in fuzz_shell.js. + +(module + (func $test (export "test") (result i32) + (i32.const 42) + ) +) + +;; Build to a binary wasm. +;; +;; RUN: wasm-opt %s -o %t.wasm -q + +;; Run in d8. +;; +;; RUN: v8 %S/../../../scripts/fuzz_shell.js -- %t.wasm | filecheck %s +;; +;; CHECK: [fuzz-exec] calling test +;; CHECK: [fuzz-exec] note result: test => 42 + diff --git a/test/lit/lit.cfg.py b/test/lit/lit.cfg.py index b337bddcd9f..7ae6c943f17 100644 --- a/test/lit/lit.cfg.py +++ b/test/lit/lit.cfg.py @@ -25,3 +25,38 @@ tool_file = config.binaryen_src_root + '/scripts/' + tool + '.py' python = sys.executable.replace('\\', '/') config.substitutions.append((tool, python + ' ' + tool_file)) + +# Finds the given executable 'program' in PATH. +# Operates like the Unix tool 'which'. +# This is similar to script/test/shared.py, but does not use binaryen_root, and +# instead is tuned to jsvu's install dir. +def which(program): + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + # Prefer the path, or jsvu's install dir. + paths = os.environ['PATH'].split(os.pathsep) + [ + os.path.expanduser('~/.jsvu/bin'), + ] + for path in paths: + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + if '.' not in fname: + if is_exe(exe_file + '.exe'): + return exe_file + '.exe' + if is_exe(exe_file + '.cmd'): + return exe_file + '.cmd' + if is_exe(exe_file + '.bat'): + return exe_file + '.bat' + +# v8 may be provided by jsvu, or it may be "d8". It may also not exist at all, +# in which case the relevant lit tests should be skipped. +V8 = os.environ.get('V8') or which('v8') or which('d8') +if V8: + config.substitutions.append(('v8', V8)) From 8e7fa99ce38dfc4bf8d63c54e3e24cbfebe838ca Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sat, 18 Jan 2025 12:39:04 -0800 Subject: [PATCH 246/622] Do not optimize tag types in SignatureRefining (#7230) Now that we preserve tag heap types in the IR, it was possible for SignatureRefining to refine heap types used by tags in such a way that the tag uses would no longer be valid. An ideal fix would be to have SignatureRefining analyze and optimize tag usage as well as calls, but for now just skip optimizing any heap type used in a tag. --- src/passes/SignatureRefining.cpp | 7 ++++ test/lit/passes/signature-refining.wast | 43 +++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp index db71c167a72..67f45aed08d 100644 --- a/src/passes/SignatureRefining.cpp +++ b/src/passes/SignatureRefining.cpp @@ -154,6 +154,13 @@ struct SignatureRefining : public Pass { } } + // Also skip modifying types used in tags, even private tags, since we don't + // analyze exception handling or stack switching instructions. TODO: Analyze + // and optimize exception handling and stack switching instructions. + for (auto& tag : module->tags) { + allInfo[tag->type].canModify = false; + } + // For now, do not optimize types that have subtypes. When we modify such a // type we need to modify subtypes as well, similar to the analysis in // TypeRefining, and perhaps we can unify this pass with that. TODO diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast index b401237b8a7..471d95326f9 100644 --- a/test/lit/passes/signature-refining.wast +++ b/test/lit/passes/signature-refining.wast @@ -1126,8 +1126,7 @@ ;; CHECK: (func $func (type $sig) (param $x anyref) ;; CHECK-NEXT: ) - (func $func (type $sig) (param $x anyref) - ) + (func $func (type $sig) (param $x anyref)) ;; CHECK: (func $caller (type $2) ;; CHECK-NEXT: (call $func @@ -1141,3 +1140,43 @@ ) ) +;; Tags: The type we'd like to refine, $sig, is used by a tag, so do not +;; optimize. +(module + ;; CHECK: (type $sig (func (param anyref))) + (type $sig (func (param anyref))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (tag $e (type $sig) (param anyref)) + (tag $e (type $sig)) + + ;; CHECK: (func $optimizable (type $sig) (param $0 anyref) + ;; CHECK-NEXT: (call $optimizable + ;; CHECK-NEXT: (ref.cast eqref + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $optimizable (type $sig) (param anyref) + (call $optimizable + (ref.cast eqref + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $throw (type $1) + ;; CHECK-NEXT: (local $0 anyref) + ;; CHECK-NEXT: (throw $e + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw + (local anyref) + ;; This would be invalid if we optimized $sig. + (throw $e + (local.get 0) + ) + ) +) From 9795fc13fc18d5ff9e869a81a5599c1baedc7e06 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sat, 18 Jan 2025 12:48:59 -0800 Subject: [PATCH 247/622] Do not optimize tag types in SignaturePruning (#7231) Now that we preserve tag heap types in the IR, it was possible for SignaturePruning to optimize heap types used by tags in such a way that the tag uses would no longer be valid. An ideal fix would be to have SignaturePruning analyze and optimize tag usage as well as calls, but for now just skip optimizing any heap type used in a tag. --- src/passes/SignaturePruning.cpp | 6 +++++ test/lit/passes/signature-pruning.wast | 31 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp index d5a62e29816..274b6edcf1e 100644 --- a/src/passes/SignaturePruning.cpp +++ b/src/passes/SignaturePruning.cpp @@ -169,6 +169,12 @@ struct SignaturePruning : public Pass { } } + // Similarly, we cannot yet modify types used in exception handling or stack + // switching tags. TODO. + for (auto& tag : module->tags) { + allInfo[tag->type].optimizable = false; + } + // A type must have the same number of parameters and results as its // supertypes and subtypes, so we only attempt to modify types without // supertypes or subtypes. diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast index f7ce07a621f..a5791729f9a 100644 --- a/test/lit/passes/signature-pruning.wast +++ b/test/lit/passes/signature-pruning.wast @@ -1239,3 +1239,34 @@ ) ) +;; Test that we do not prune parameters from types used in tags. +(module + ;; CHECK: (type $sig (func (param anyref))) + (type $sig (func (param anyref))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (tag $e (type $sig) (param anyref)) + (tag $e (type $sig)) + + ;; CHECK: (func $unused-param (type $sig) (param $0 anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $unused-param (type $sig) (param anyref) + (nop) + ) + + ;; CHECK: (func $throw (type $1) + ;; CHECK-NEXT: (local $0 anyref) + ;; CHECK-NEXT: (throw $e + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw + (local anyref) + ;; This would be invalid if we optimized $sig. + (throw $e + (local.get 0) + ) + ) +) From c7fdc01c7b0a255ff04f59a957ef3022210645dc Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 21 Jan 2025 10:38:35 -0800 Subject: [PATCH 248/622] Optimize callers when DAE removes uninhabitable results (#7233) In principle the optimizer should be using the fact that calls (or any other expressions) that produce uninhabitable types will never return. Previously, when DAE removed unused, uninhabitable results, the caller would lose this useful information and have no way of determining that the call would never return. Fix this by inserting an `unreachable` after the call in the caller. Also run follow-up optimizations on the caller because the new `unreachable` is very likely to lead to improvements. --- src/passes/DeadArgumentElimination.cpp | 28 ++++++++++-- test/lit/passes/dae-optimizing.wast | 59 ++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index 63f2d7fdc38..8d801e87e9b 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -388,7 +388,12 @@ struct DAE : public Pass { if (!allDropped) { continue; } - removeReturnValue(func.get(), calls, module); + if (removeReturnValue(func.get(), calls, module)) { + // We should optimize the callers. + for (auto* call : calls) { + worthOptimizing.insert(module->getFunction(expressionFuncs[call])); + } + } // TODO Removing a drop may also open optimization opportunities in the // callers. worthOptimizing.insert(func.get()); @@ -413,8 +418,17 @@ struct DAE : public Pass { private: std::unordered_map allDroppedCalls; - void + // Returns `true` if the caller should be optimized. + bool removeReturnValue(Function* func, std::vector& calls, Module* module) { + // If the result type is uninhabitable, then the caller knows the call will + // never return. That useful information would be lost if we did nothing + // else when removing the return value, but we will insert an `unreachable` + // after the call in the caller to preserve the optimization effect. TODO: + // Do this for more complicated uninhabitable types such as non-nullable + // references to structs with non-nullable reference cycles. + bool wasReturnUninhabitable = + func->getResults().isNull() && func->getResults().isNonNullable(); func->setResults(Type::none); // Remove the drops on the calls. Note that we must do this before updating // returns in ReturnUpdater, as there may be recursive calls of this @@ -425,7 +439,12 @@ struct DAE : public Pass { auto iter = allDroppedCalls.find(call); assert(iter != allDroppedCalls.end()); Expression** location = iter->second; - *location = call; + if (wasReturnUninhabitable) { + Builder builder(*module); + *location = builder.makeSequence(call, builder.makeUnreachable()); + } else { + *location = call; + } // Update the call's type. if (call->type != Type::unreachable) { call->type = Type::none; @@ -433,6 +452,9 @@ struct DAE : public Pass { } // Remove any return values. ReturnUtils::removeReturns(func, *module); + // It's definitely worth optimizing the caller after inserting the + // unreachable. + return wasReturnUninhabitable; } // Given a function and all the calls to it, see if we can refine the type of diff --git a/test/lit/passes/dae-optimizing.wast b/test/lit/passes/dae-optimizing.wast index b9b7e7e2e2b..316c377586f 100644 --- a/test/lit/passes/dae-optimizing.wast +++ b/test/lit/passes/dae-optimizing.wast @@ -1,7 +1,7 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. -;; RUN: foreach %s %t wasm-opt --dae-optimizing -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --dae-optimizing -S -o - | filecheck %s (module (type $0 (func (param f32) (result f32))) @@ -14,7 +14,7 @@ (type $2 (func (param f64 f32 f32 f64 f32 i32 i32 f64) (result i32))) ;; CHECK: (global $global$0 (mut i32) (i32.const 10)) (global $global$0 (mut i32) (i32.const 10)) - ;; CHECK: (func $0 (result i32) + ;; CHECK: (func $0 (type $3) (result i32) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop @@ -96,13 +96,13 @@ ) (i32.const -11) ) - ;; CHECK: (func $1 (result f32) + ;; CHECK: (func $1 (type $4) (result f32) ;; CHECK-NEXT: (f32.const 0) ;; CHECK-NEXT: ) (func $1 (; 1 ;) (type $0) (param $0 f32) (result f32) (f32.const 0) ) - ;; CHECK: (func $2 (param $0 f64) (param $1 f32) (param $2 f32) (param $3 f64) (param $4 f32) (param $5 i32) (param $6 i32) (param $7 f64) (result i32) + ;; CHECK: (func $2 (type $2) (param $0 f64) (param $1 f32) (param $2 f32) (param $3 f64) (param $4 f32) (param $5 i32) (param $6 i32) (param $7 f64) (result i32) ;; CHECK-NEXT: (call $0) ;; CHECK-NEXT: ) (func $2 (; 2 ;) (type $2) (param $0 f64) (param $1 f32) (param $2 f32) (param $3 f64) (param $4 f32) (param $5 i32) (param $6 i32) (param $7 f64) (result i32) @@ -118,3 +118,54 @@ ) ) +;; Test that we optimize callers when there are unused uninhabitable results. +(module + (func $import (import "" "") (result i32)) + (func $impossible (import "" "") (result (ref none))) + ;; CHECK: (type $0 (func (result i32))) + + ;; CHECK: (type $1 (func (result (ref none)))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (import "" "" (func $import (type $0) (result i32))) + + ;; CHECK: (import "" "" (func $impossible (type $1) (result (ref none)))) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $0) (result i32) + ;; CHECK-NEXT: (call $internal) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $export (export "export") (result i32) + (drop + ;; This should be optimized to an unreachable sequence. + (call $internal + (i32.const 0) + ) + ) + ;; Everything else should be removed by DCE. + (drop + (call $internal + (i32.const 1) + ) + ) + (i32.const 0) + ) + ;; CHECK: (func $internal (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $impossible) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $internal (param i32) (result (ref none)) + ;; Prevent this from being removed entirely. + (drop + (call $import) + ) + (call $impossible) + ) +) From a1e8b7c510560f53813b5dc59f0297ddebe9ac35 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 21 Jan 2025 10:48:44 -0800 Subject: [PATCH 249/622] Classify public tag types as public (#7232) Tags previously preserved only their signatures and not their heap types in the IR, so there was no need or opportunity to classify their types as public, even for tags that were imported or exported. When we updated tags to preserve their heap types, we did not update the visibility classification code to handle them. Update that code now. --- src/ir/module-utils.cpp | 5 +- .../remove-unused-types-public-tags.wast | 84 +++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 test/lit/passes/remove-unused-types-public-tags.wast diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 89c1503399f..44652a5531e 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -605,8 +605,7 @@ void classifyTypeVisibility(Module& wasm, } }; - // TODO: Consider Tags as well, but they should store HeapTypes instead of - // Signatures first. + ModuleUtils::iterImportedTags(wasm, [&](Tag* tag) { notePublic(tag->type); }); ModuleUtils::iterImportedTables(wasm, [&](Table* table) { assert(table->type.isRef()); notePublic(table->type.getHeapType()); @@ -647,7 +646,7 @@ void classifyTypeVisibility(Module& wasm, continue; } case ExternalKind::Tag: - // TODO + notePublic(wasm.getTag(ex->value)->type); continue; case ExternalKind::Invalid: break; diff --git a/test/lit/passes/remove-unused-types-public-tags.wast b/test/lit/passes/remove-unused-types-public-tags.wast new file mode 100644 index 00000000000..4b65ef210b1 --- /dev/null +++ b/test/lit/passes/remove-unused-types-public-tags.wast @@ -0,0 +1,84 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Check that public tag types are correctly considered public and do not become +;; part of the single, large rec group. + +;; RUN: foreach %s %t wasm-opt --remove-unused-types --closed-world -all -S -o - | filecheck %s + +;; Neither imported nor exported tag. The tag type is private. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $other-2 (struct (field i8))) + + ;; CHECK: (type $other-1 (struct)) + + ;; CHECK: (type $tag-type (func)) + (type $tag-type (func)) + (type $other-1 (struct)) + (type $other-2 (struct (field i8))) + + ;; CHECK: (global $tag-type (ref null $tag-type) (ref.null nofunc)) + + ;; CHECK: (global $other-1 (ref null $other-1) (ref.null none)) + + ;; CHECK: (global $other-2 (ref null $other-2) (ref.null none)) + + ;; CHECK: (tag $exported (type $tag-type)) + (tag $exported (type $tag-type)) + + ;; Use all the types. + (global $tag-type (ref null $tag-type) (ref.null nofunc)) + (global $other-1 (ref null $other-1) (ref.null none)) + (global $other-2 (ref null $other-2) (ref.null none)) +) + +;; Imported tag. +(module + ;; CHECK: (type $tag-type (func)) + (type $tag-type (func)) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $other-2 (struct (field i8))) + + ;; CHECK: (type $other-1 (struct)) + (type $other-1 (struct)) + (type $other-2 (struct (field i8))) + + (tag $imported (import "" "") (type $tag-type)) + + ;; Use all the types. + ;; CHECK: (import "" "" (tag $imported (type $tag-type))) + + ;; CHECK: (global $tag-type (ref null $tag-type) (ref.null nofunc)) + (global $tag-type (ref null $tag-type) (ref.null nofunc)) + ;; CHECK: (global $other-1 (ref null $other-1) (ref.null none)) + (global $other-1 (ref null $other-1) (ref.null none)) + ;; CHECK: (global $other-2 (ref null $other-2) (ref.null none)) + (global $other-2 (ref null $other-2) (ref.null none)) +) + +;; Exported tag. +(module + ;; CHECK: (type $tag-type (func)) + (type $tag-type (func)) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $other-2 (struct (field i8))) + + ;; CHECK: (type $other-1 (struct)) + (type $other-1 (struct)) + (type $other-2 (struct (field i8))) + + ;; CHECK: (global $tag-type (ref null $tag-type) (ref.null nofunc)) + + ;; CHECK: (global $other-1 (ref null $other-1) (ref.null none)) + + ;; CHECK: (global $other-2 (ref null $other-2) (ref.null none)) + + ;; CHECK: (tag $exported (type $tag-type)) + (tag $exported (export "") (type $tag-type)) + + ;; Use all the types. + (global $tag-type (ref null $tag-type) (ref.null nofunc)) + (global $other-1 (ref null $other-1) (ref.null none)) + (global $other-2 (ref null $other-2) (ref.null none)) +) +;; CHECK: (export "" (tag $exported)) From eb1f90aa3bcfbe8e1d4b9118351e073c74eaeaf4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Jan 2025 11:23:25 -0800 Subject: [PATCH 250/622] GUFA: Assume nothing about the contents of public tables (#7234) Imported and exported tables may get new items from the outside, so we cannot infer values there. This uncovered a bug with doing addRoot on tuples, which is also fixed here. Also a tiny fix to debug logging. --- src/ir/possible-contents.cpp | 58 +++++++++++++--- src/ir/possible-contents.h | 19 ++++++ test/gtest/possible-contents.cpp | 12 ++++ test/lit/passes/gufa-tables.wast | 109 +++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 test/lit/passes/gufa-tables.wast diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 0bc03160ed7..a9a26f3d719 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -468,6 +468,12 @@ namespace wasm { namespace { +// Information that is shared with InfoCollector. +struct SharedInfo { + // The names of tables that are imported or exported. + std::unordered_set publicTables; +}; + // The data we gather from each function, as we process them in parallel. Later // this will be merged into a single big graph. struct CollectedFuncInfo { @@ -533,11 +539,14 @@ struct BreakTargetWalker : public PostWalker { // main flow will begin. struct InfoCollector : public BreakTargetWalker> { + SharedInfo& shared; CollectedFuncInfo& info; const PassOptions& options; - InfoCollector(CollectedFuncInfo& info, const PassOptions& options) - : info(info), options(options) {} + InfoCollector(SharedInfo& shared, + CollectedFuncInfo& info, + const PassOptions& options) + : shared(shared), info(info), options(options) {} // Check if a type is relevant for us. If not, we can ignore it entirely. bool isRelevant(Type type) { @@ -888,9 +897,16 @@ struct InfoCollector curr->operands.push_back(target); } void visitCallIndirect(CallIndirect* curr) { - // TODO: the table identity could also be used here // TODO: optimize the call target like CallRef handleIndirectCall(curr, curr->heapType); + + // If this goes to a public table, then we must root the output, as the + // table could contain anything at all, and calling functions there could + // return anything at all. + if (shared.publicTables.count(curr->table)) { + addRoot(curr); + } + // TODO: the table identity could also be used here in more ways } void visitCallRef(CallRef* curr) { handleIndirectCall(curr, curr->target->type); @@ -1375,7 +1391,15 @@ struct InfoCollector if (contents.isMany()) { contents = PossibleContents::fromType(curr->type); } - addRoot(ExpressionLocation{curr, 0}, contents); + + if (!curr->type.isTuple()) { + addRoot(ExpressionLocation{curr, 0}, contents); + } else { + // For a tuple, we create a root for each index. + for (Index i = 0; i < curr->type.size(); i++) { + addRoot(ExpressionLocation{curr, i}, contents.getTupleItem(i)); + } + } } } @@ -2121,10 +2145,26 @@ Flower::Flower(Module& wasm, const PassOptions& options) std::cout << "parallel phase\n"; #endif - // First, collect information from each function. + // Compute shared info that we need for the main pass over each function, such + // as the imported/exported tables. + SharedInfo shared; + + for (auto& table : wasm.tables) { + if (table->imported()) { + shared.publicTables.insert(table->name); + } + } + + for (auto& ex : wasm.exports) { + if (ex->kind == ExternalKind::Table) { + shared.publicTables.insert(ex->value); + } + } + + // Collect information from each function. ModuleUtils::ParallelFunctionAnalysis analysis( wasm, [&](Function* func, CollectedFuncInfo& info) { - InfoCollector finder(info, options); + InfoCollector finder(shared, info, options); if (func->imported()) { // Imports return unknown values. @@ -2146,7 +2186,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) // Also walk the global module code (for simplicity, also add it to the // function map, using a "function" key of nullptr). auto& globalInfo = analysis.map[nullptr]; - InfoCollector finder(globalInfo, options); + InfoCollector finder(shared, globalInfo, options); finder.walkModuleCode(&wasm); #ifdef POSSIBLE_CONTENTS_DEBUG @@ -2944,8 +2984,8 @@ void Flower::dump(Location location) { std::cout << " globalloc " << loc->name << '\n'; } else if (std::get_if(&location)) { std::cout << " sigparamloc " << '\n'; - } else if (std::get_if(&location)) { - std::cout << " sigresultloc " << '\n'; + } else if (auto* loc = std::get_if(&location)) { + std::cout << " sigresultloc " << loc->type << " : " << loc->index << '\n'; } else if (auto* loc = std::get_if(&location)) { std::cout << " Nullloc " << loc->type << '\n'; } else { diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 21f5ecb4535..2f28dcf3c0a 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -301,6 +301,25 @@ class PossibleContents { } } + // For possible contents of a tuple type, get one of the items. + PossibleContents getTupleItem(Index i) { + auto type = getType(); + assert(type.isTuple()); + if (std::get_if(&value)) { + WASM_UNREACHABLE("TODO: use Literals"); + } else if (std::get_if(&value)) { + WASM_UNREACHABLE("TODO"); + } else if (auto* cone = std::get_if(&value)) { + // Return a full cone of the appropriate type, as we lack depth info for + // the separate items in the tuple (tuples themselves have no subtyping, + // so the tuple's depth must be 0, i.e., an exact type). + assert(cone->depth == 0); + return fullConeType(type[i]); + } else { + WASM_UNREACHABLE("not a tuple"); + } + } + size_t hash() const { // First hash the index of the variant, then add the internals for each. size_t ret = std::hash()(value.index()); diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index c495a418771..f267742a71b 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -948,3 +948,15 @@ TEST_F(PossibleContentsTest, TestOracleNoFullCones) { ASSERT_TRUE(bodyContents.isConeType()); EXPECT_EQ(bodyContents.getCone().depth, Index(2)); } + +TEST_F(PossibleContentsTest, TestTupleItems) { + // All tuples must be exact (there is no subtyping for tuples). + Type funcref = Type(HeapType::func, Nullable); + auto tupleType = Type({Type::i32, funcref}); + PossibleContents tuple = PossibleContents::exactType(tupleType); + + // We can get the tuple items. The funcref is a full cone, as we did not have + // depth info for it. + EXPECT_EQ(tuple.getTupleItem(0), PossibleContents::fullConeType(Type::i32)); + EXPECT_EQ(tuple.getTupleItem(1), PossibleContents::fullConeType(funcref)); +} diff --git a/test/lit/passes/gufa-tables.wast b/test/lit/passes/gufa-tables.wast new file mode 100644 index 00000000000..99d328f11b1 --- /dev/null +++ b/test/lit/passes/gufa-tables.wast @@ -0,0 +1,109 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s + +;; Non-private tables can contain anything, so we cannot assume anything about +;; the results of a call to them. +(module + ;; CHECK: (type $type (func (result f64))) + (type $type (func (result f64))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (table $imported 30 40 funcref)) + (import "a" "b" (table $imported 30 40 funcref)) + + ;; CHECK: (table $exported 10 20 funcref) + (table $exported 10 20 funcref) + + ;; CHECK: (table $private 50 60 funcref) + (table $private 50 60 funcref) + + ;; CHECK: (export "func" (func $func)) + + ;; CHECK: (export "table" (table $exported)) + (export "table" (table $exported)) + + ;; CHECK: (func $func (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call_indirect $exported (type $type) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call_indirect $imported (type $type) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call_indirect $private (type $type) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func (export "func") + ;; We cannot optimize anything with the exported or imported table. + (drop + (call_indirect $exported (type $type) + (i32.const 0) + ) + ) + (drop + (call_indirect $imported (type $type) + (i32.const 0) + ) + ) + ;; We can optimize the private table: this will trap. + (drop + (call_indirect $private (type $type) + (i32.const 0) + ) + ) + ) +) + +;; Test a tuple-returning call_indirect. There is nothing to optimize here, but +;; we should not error while marking the call_indirect as returning a tuple of +;; unknown values (unknown, since the table is exported). +(module + ;; CHECK: (type $5 (func (result f64 i32))) + (type $5 (func (result f64 i32))) + + ;; CHECK: (table $table 44 funcref) + (table $table 44 funcref) + + ;; CHECK: (elem $elem (i32.const 0) $make) + (elem $elem (i32.const 0) $make) + + ;; CHECK: (export "table" (table $table)) + (export "table" (table $table)) + + ;; CHECK: (func $make (type $5) (result f64 i32) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $make (type $5) (result f64 i32) + (tuple.make 2 + (f64.const 3.14159) + (i32.const 42) + ) + ) + + ;; CHECK: (func $call (type $5) (result f64 i32) + ;; CHECK-NEXT: (call_indirect $table (type $5) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call (result f64 i32) + (call_indirect $table (type $5) + (i32.const 0) + ) + ) +) + From 09300e47969923487faafed06861bf1a6b93684c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 22 Jan 2025 13:21:17 -0800 Subject: [PATCH 251/622] [interpreter] Begin implementation of a new interpreter (#7227) The current interpreter used in wasm-shell, the fuzzer, and optimizations like precompute works by recursively walking the expression tree and computing expression results as it goes. This kind of recursive interpretation is not going to work for stack switching, since stack switching requires stashing context away and restoring it later. The recursive interpreter stores intermediate values on the native stack and returns early to implement control flow, so there is no way to suspend a computation and resume it later. To support stack switching and support other use future interpreter use cases such as running the full spec test suite and fuzzing multithreaded programs, introduce a new interpreter that is not recursive and does not store intermediate state that needs to persist beyond the execution of a single instruction on the native stack. The new interpreter works by iterating through instructions and visiting them one at a time in a loop. The visitor pushes and pops values from a stack and signals control flow via its return values. Control flow transfers are handled by the main interpreter loop, so expressions are only visited when they are actually executed. This design will not only support stack switching and other features better than the old interpreter, but it will also significantly decrease the amount of code in the interpreter. In addition to the core interpreter loop, also lay out a skeleton of the execution context for the new interpreter, including a call stack and store. The code contains several TODOs describing how these runtime structures will need to be extended to support interpreting the full spec test suite, including the ability to interpret over multiple linked instances at once. Most of the actual interpretation of expressions is left as future work, but the interpretation of `Const` expressions and i32.add is implemented and tested in a new gtest file to demonstrate it working end-to-end. One of the first milestones for the new interpreter will be getting real spec tests running with it, at which point the gtest file can be removed. --- CMakeLists.txt | 8 +- src/interpreter/CMakeLists.txt | 7 + src/interpreter/expression-iterator.cpp | 39 +++++ src/interpreter/expression-iterator.h | 73 ++++++++++ src/interpreter/interpreter.cpp | 183 ++++++++++++++++++++++++ src/interpreter/interpreter.h | 37 +++++ src/interpreter/store.h | 71 +++++++++ test/gtest/CMakeLists.txt | 1 + test/gtest/interpreter.cpp | 43 ++++++ 9 files changed, 459 insertions(+), 3 deletions(-) create mode 100644 src/interpreter/CMakeLists.txt create mode 100644 src/interpreter/expression-iterator.cpp create mode 100644 src/interpreter/expression-iterator.h create mode 100644 src/interpreter/interpreter.cpp create mode 100644 src/interpreter/interpreter.h create mode 100644 src/interpreter/store.h create mode 100644 test/gtest/interpreter.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d02096fa06e..c7e3699f7f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -408,6 +408,7 @@ add_subdirectory(src/ir) add_subdirectory(src/asmjs) add_subdirectory(src/cfg) add_subdirectory(src/emscripten-optimizer) +add_subdirectory(src/interpreter) add_subdirectory(src/passes) add_subdirectory(src/parser) add_subdirectory(src/support) @@ -439,7 +440,8 @@ set(binaryen_objs $ $ $ - $) + $ + $) if(BUILD_LLVM_DWARF) SET(binaryen_objs ${binaryen_objs} $) @@ -481,7 +483,7 @@ if(EMSCRIPTEN) # binaryen.js WebAssembly variant add_executable(binaryen_wasm ${binaryen_SOURCES}) - target_link_libraries(binaryen_wasm wasm asmjs emscripten-optimizer passes ir cfg support analysis parser wasm) + target_link_libraries(binaryen_wasm wasm asmjs emscripten-optimizer passes ir cfg support analysis parser interpreter wasm) target_link_libraries(binaryen_wasm "-sFILESYSTEM") target_link_libraries(binaryen_wasm "-sEXPORT_NAME=Binaryen") target_link_libraries(binaryen_wasm "-sNODERAWFS=0") @@ -511,7 +513,7 @@ if(EMSCRIPTEN) # binaryen.js JavaScript variant add_executable(binaryen_js ${binaryen_SOURCES}) - target_link_libraries(binaryen_js wasm asmjs emscripten-optimizer passes ir cfg support analysis parser wasm) + target_link_libraries(binaryen_js wasm asmjs emscripten-optimizer passes ir cfg support analysis parser interpreter wasm) target_link_libraries(binaryen_js "-sWASM=0") target_link_libraries(binaryen_js "-sWASM_ASYNC_COMPILATION=0") if(${CMAKE_CXX_COMPILER_VERSION} STREQUAL "6.0.1") diff --git a/src/interpreter/CMakeLists.txt b/src/interpreter/CMakeLists.txt new file mode 100644 index 00000000000..170376476d5 --- /dev/null +++ b/src/interpreter/CMakeLists.txt @@ -0,0 +1,7 @@ +FILE(GLOB interpreter_HEADERS *.h) +set(interpreter_SOURCES + expression-iterator.cpp + interpreter.cpp + ${interpreter_HEADERS} +) +add_library(interpreter OBJECT ${interpreter_SOURCES}) diff --git a/src/interpreter/expression-iterator.cpp b/src/interpreter/expression-iterator.cpp new file mode 100644 index 00000000000..b084d81b6db --- /dev/null +++ b/src/interpreter/expression-iterator.cpp @@ -0,0 +1,39 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "expression-iterator.h" +#include "wasm-traversal.h" + +namespace wasm::interpreter { + +ExpressionIterator::ExpressionIterator(Expression* root) { + // TODO: Visit loops at their beginnings instead of their ends. + struct Collector + : PostWalker> { + std::vector& exprs; + Collector(std::vector& exprs) : exprs(exprs) {} + void visitExpression(Expression* curr) { exprs.push_back(curr); } + } collector(exprs); + collector.walk(root); + + // Reverse the expressions so we can pop from the back to advance the + // iterator. + std::reverse(exprs.begin(), exprs.end()); +} + +} // namespace wasm::interpreter diff --git a/src/interpreter/expression-iterator.h b/src/interpreter/expression-iterator.h new file mode 100644 index 00000000000..9544430a463 --- /dev/null +++ b/src/interpreter/expression-iterator.h @@ -0,0 +1,73 @@ +/* + * Copyright 2025 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 interpreter_expression_iterator_h +#define interpreter_expression_iterator_h + +#include +#include +#include + +#include "wasm.h" + +namespace wasm::interpreter { + +// TODO: This is a quick and dirty hack. We should implement a proper iterator +// in ir/iteration.h that keeps only a vector of (Expression*, index) pairs or +// alternatively a stack of ChildIterators to find the current location in the +// expression tree. Better yet, improve ChildIterator and then use it here. +struct ExpressionIterator { + using difference_type = std::ptrdiff_t; + using value_type = Expression*; + using pointer = Expression**; + using reference = Expression*&; + using iterator_category = std::input_iterator_tag; + + // The list of remaining instructions in reverse order so we can pop from the + // back to advance the iterator. + std::vector exprs; + + ExpressionIterator(Expression* root); + + ExpressionIterator() = default; + ExpressionIterator(const ExpressionIterator&) = default; + ExpressionIterator(ExpressionIterator&&) = default; + ExpressionIterator& operator=(const ExpressionIterator&) = default; + ExpressionIterator& operator=(ExpressionIterator&&) = default; + + operator bool() { return exprs.size(); } + + Expression* operator*() { + assert(exprs.size()); + return exprs.back(); + } + + ExpressionIterator& operator++() { + assert(exprs.size()); + exprs.pop_back(); + return *this; + } + + bool operator==(const ExpressionIterator& other) { + return exprs.size() == other.exprs.size(); + } + + bool operator!=(const ExpressionIterator& other) { return !(*this == other); } +}; + +} // namespace wasm::interpreter + +#endif // interpreter_expression_iterator_h diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp new file mode 100644 index 00000000000..1a3a828f7f9 --- /dev/null +++ b/src/interpreter/interpreter.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "interpreter/interpreter.h" +#include "interpreter/expression-iterator.h" +#include "interpreter/store.h" +#include "wasm-traversal.h" + +namespace wasm { + +using namespace interpreter; + +// Provides access to the interpreter's private store. +class InterpreterImpl { +public: + static WasmStore& getStore(Interpreter& interpreter) { + return interpreter.store; + } +}; + +namespace { + +struct Branch { + Name label; +}; + +// TODO: Handle other forms of control flow. +struct Flow : std::variant { + operator bool() { return !std::get_if(this); } +}; + +struct ExpressionInterpreter : OverriddenVisitor { + Interpreter& parent; + ExpressionInterpreter(Interpreter& parent) : parent(parent) {} + + WasmStore& store() { return InterpreterImpl::getStore(parent); } + void push(Literal val) { store().push(val); } + Literal pop() { return store().pop(); } + + Flow visitNop(Nop* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitBlock(Block* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitIf(If* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitLoop(Loop* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitBreak(Break* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSwitch(Switch* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitCall(Call* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitCallIndirect(CallIndirect* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitLocalGet(LocalGet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitLocalSet(LocalSet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitGlobalGet(GlobalGet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitGlobalSet(GlobalSet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitLoad(Load* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStore(Store* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitAtomicRMW(AtomicRMW* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitAtomicWait(AtomicWait* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitAtomicNotify(AtomicNotify* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitAtomicFence(AtomicFence* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSIMDExtract(SIMDExtract* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSIMDReplace(SIMDReplace* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSIMDShuffle(SIMDShuffle* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSIMDTernary(SIMDTernary* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSIMDShift(SIMDShift* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSIMDLoad(SIMDLoad* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { + WASM_UNREACHABLE("TODO"); + } + Flow visitMemoryInit(MemoryInit* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitDataDrop(DataDrop* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitMemoryCopy(MemoryCopy* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitMemoryFill(MemoryFill* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitConst(Const* curr) { + push(curr->value); + return {}; + } + Flow visitUnary(Unary* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitBinary(Binary* curr) { + auto rhs = pop(); + auto lhs = pop(); + // TODO: switch-case over all operations. + if (curr->op == AddInt32) { + push(lhs.add(rhs)); + return {}; + } + WASM_UNREACHABLE("TODO"); + } + Flow visitSelect(Select* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitDrop(Drop* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitReturn(Return* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitMemorySize(MemorySize* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitMemoryGrow(MemoryGrow* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitUnreachable(Unreachable* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitPop(Pop* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefNull(RefNull* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefIsNull(RefIsNull* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefFunc(RefFunc* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefEq(RefEq* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTableGet(TableGet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTableSet(TableSet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTableSize(TableSize* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTableGrow(TableGrow* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTableFill(TableFill* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTableCopy(TableCopy* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTableInit(TableInit* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTry(Try* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitThrow(Throw* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitThrowRef(ThrowRef* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTupleMake(TupleMake* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitTupleExtract(TupleExtract* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefI31(RefI31* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitI31Get(I31Get* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitCallRef(CallRef* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefTest(RefTest* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefCast(RefCast* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitBrOn(BrOn* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructNew(StructNew* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructGet(StructGet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructSet(StructSet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayNew(ArrayNew* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayNewData(ArrayNewData* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayNewElem(ArrayNewElem* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayNewFixed(ArrayNewFixed* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayGet(ArrayGet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArraySet(ArraySet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayLen(ArrayLen* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayCopy(ArrayCopy* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayFill(ArrayFill* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayInitData(ArrayInitData* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayInitElem(ArrayInitElem* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefAs(RefAs* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringNew(StringNew* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringConst(StringConst* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringMeasure(StringMeasure* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringEncode(StringEncode* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringConcat(StringConcat* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringEq(StringEq* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitContNew(ContNew* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSuspend(Suspend* curr) { WASM_UNREACHABLE("TODO"); } +}; + +} // anonymous namespace + +std::vector Interpreter::run(Expression* root) { + // Create a fresh store and execution frame, then run the expression to + // completion. + store = WasmStore(); + store.callStack.emplace_back(); + store.callStack.back().exprs = ExpressionIterator(root); + + ExpressionInterpreter interpreter(*this); + while (auto& it = store.callStack.back().exprs) { + if (auto flow = interpreter.visit(*it)) { + // TODO: Handle control flow transfers. + } else { + ++it; + } + } + + return store.callStack.back().valueStack; +} + +} // namespace wasm diff --git a/src/interpreter/interpreter.h b/src/interpreter/interpreter.h new file mode 100644 index 00000000000..688a989d3f5 --- /dev/null +++ b/src/interpreter/interpreter.h @@ -0,0 +1,37 @@ +/* + * Copyright 2025 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 interpreter_interpreter_h +#define interpreter_interpreter_h + +#include "store.h" + +namespace wasm { + +class Interpreter { +public: + // TODO: Methods to instantiate modules. + // TODO: Methods to run exported functions. + std::vector run(Expression* root); + +private: + interpreter::WasmStore store; + friend class InterpreterImpl; +}; + +} // namespace wasm + +#endif // interpreter_interpreter_h diff --git a/src/interpreter/store.h b/src/interpreter/store.h new file mode 100644 index 00000000000..03b3ff3ef07 --- /dev/null +++ b/src/interpreter/store.h @@ -0,0 +1,71 @@ +/* + * Copyright 2025 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 interpreter_store_h +#define interpreter_store_h + +#include +#include + +#include "expression-iterator.h" +#include "literal.h" + +namespace wasm::interpreter { + +// A frame of execution for a function call. +struct Frame { + // TODO: Reference to current instance to find referenced functions, globals, + // tables, memories, etc. + + std::vector locals; + std::vector valueStack; + ExpressionIterator exprs; + + // TODO: Map loops to ExpressionIterators so we can branch backwards. + + // Helpers to push and pop the current value stack. + Literal pop() { + assert(valueStack.size()); + Literal val = valueStack.back(); + valueStack.pop_back(); + return val; + } + + void push(Literal val) { valueStack.push_back(val); } +}; + +// The WebAssembly store, which maintains the state for a universe of linked +// WebAssembly instances. +// TODO: generalize this so different users can override memory loads and +// stores, etc. +struct WasmStore { + // TODO: Storage for memories, tables, globals, heap objects, etc. + // TODO: Map instances and import names to other instances to find imports. + std::vector callStack; + + Frame& getFrame() { + assert(!callStack.empty()); + return callStack.back(); + } + + // Helpers to push and pop the current value stack. + Literal pop() { return getFrame().pop(); } + void push(Literal val) { getFrame().push(val); } +}; + +} // namespace wasm::interpreter + +#endif // interpreter_store_h diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 102d3ca2a48..41c2d4d0097 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -7,6 +7,7 @@ set(unittest_SOURCES cfg.cpp dfa_minimization.cpp disjoint_sets.cpp + interpreter.cpp json.cpp lattices.cpp local-graph.cpp diff --git a/test/gtest/interpreter.cpp b/test/gtest/interpreter.cpp new file mode 100644 index 00000000000..1e6f6cdd139 --- /dev/null +++ b/test/gtest/interpreter.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2025 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. + */ + +// TODO: Replace this test file with spec tests as soon as possible. + +#include "interpreter/interpreter.h" +#include "literal.h" +#include "wasm-ir-builder.h" +#include "wasm.h" + +#include "gtest/gtest.h" + +using namespace wasm; + +TEST(InterpreterTest, Add) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(AddInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(3))}; + + EXPECT_EQ(results, expected); +} From e006ccce9723492372f071518982617b8ce3f5a7 Mon Sep 17 00:00:00 2001 From: gkgoat1 <65402989+gkgoat1@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:31:27 -0500 Subject: [PATCH 252/622] Asyncify: Allow specifying the external memory (#7125) This is important for multi-memory. --- src/passes/Asyncify.cpp | 25 +++- ...ncify_pass-arg=asyncify-memory@memory.wast | 124 ++++++++++++++++++ 2 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/asyncify_pass-arg=asyncify-memory@memory.wast diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index 7ac9cf48921..994142e482f 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -244,6 +244,10 @@ // Logs out instrumentation decisions to the console. This can help figure // out why a certain function was instrumented. // +// --pass-arg=asyncify-memory@memory +// Picks which exported memory of the module to store and load data from +// and to (useful if the module contains multiple memories). +// // For manual fine-tuning of the list of instrumented functions, there are lists // that you can set. These must be used carefully, as misuse can break your // application - for example, if a function is called that should be @@ -1648,14 +1652,31 @@ struct Asyncify : public Pass { auto propagateAddList = hasArgument("asyncify-propagate-addlist"); // Ensure there is a memory, as we need it. + if (secondaryMemory) { auto secondaryMemorySizeString = getArgumentOrDefault("asyncify-secondary-memory-size", "1"); Address secondaryMemorySize = std::stoi(secondaryMemorySizeString); asyncifyMemory = createSecondaryMemory(module, secondaryMemorySize); } else { - MemoryUtils::ensureExists(module); - asyncifyMemory = module->memories[0]->name; + if (module->memories.size() <= 1) { + MemoryUtils::ensureExists(module); + asyncifyMemory = module->memories[0]->name; + } else { + auto asyncifyMemoryValue = + getArgumentOrDefault("asyncify-memory", "memory"); + for (auto& theExport : module->exports) { + if (theExport->kind == ExternalKind::Memory && + theExport->name == asyncifyMemoryValue) { + asyncifyMemory = theExport->value; + break; + } + } + if (!asyncifyMemory) { + Fatal() << "Please specify which of the multiple memories to use, " + "with --pass-arg=asyncify-memory@memory"; + } + } } pointerType = module->getMemory(asyncifyMemory)->is64() ? Type::i64 : Type::i32; diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-memory@memory.wast b/test/lit/passes/asyncify_pass-arg=asyncify-memory@memory.wast new file mode 100644 index 00000000000..86bc1d10aba --- /dev/null +++ b/test/lit/passes/asyncify_pass-arg=asyncify-memory@memory.wast @@ -0,0 +1,124 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --enable-multimemory --enable-mutable-globals --asyncify --pass-arg=asyncify-memory@memory -S -o - | filecheck %s + +;; This test checks that the asyncify-memory argument can pick which memory to use in asyncify. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (global $__asyncify_state (mut i32) (i32.const 0)) + + ;; CHECK: (global $__asyncify_data (mut i32) (i32.const 0)) + + ;; CHECK: (memory $unused0 1 1) + (memory $unused0 (export "unused0") 1 1) + ;; CHECK: (memory $mem 1 1) + (memory $mem (export "memory") 1 1) + ;; CHECK: (memory $ignore 1 1) + (memory $ignore (export "unused") 1 1) +) +;; CHECK: (export "unused0" (memory $unused0)) + +;; CHECK: (export "memory" (memory $mem)) + +;; CHECK: (export "unused" (memory $ignore)) + +;; CHECK: (export "asyncify_start_unwind" (func $asyncify_start_unwind)) + +;; CHECK: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) + +;; CHECK: (export "asyncify_start_rewind" (func $asyncify_start_rewind)) + +;; CHECK: (export "asyncify_stop_rewind" (func $asyncify_stop_rewind)) + +;; CHECK: (export "asyncify_get_state" (func $asyncify_get_state)) + +;; CHECK: (func $asyncify_start_unwind (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load $mem +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load $mem offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_unwind +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load $mem +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load $mem offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_start_rewind (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load $mem +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load $mem offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_rewind +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load $mem +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load $mem offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_get_state (result i32) +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: ) From 81035b040ca1e431f698e198f4a367bf2c037eee Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 22 Jan 2025 17:40:47 -0800 Subject: [PATCH 253/622] Fix indentations in README.md (#7236) The next level `*` should have two spaces indentation from the previous level. Currently the sentences under `Reference Types` have only one, making them appear in the same level in the markdown format. --- README.md | 88 +++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 4f8ad344472..e634491229b 100644 --- a/README.md +++ b/README.md @@ -124,50 +124,50 @@ There are a few differences between Binaryen IR and the WebAssembly language: used in the containing node. Such a block is sometimes called an "implicit block". * Reference Types - * The wasm text and binary formats require that a function whose address is - taken by `ref.func` must be either in the table, or declared via an - `(elem declare func $..)`. Binaryen will emit that data when necessary, but - it does not represent it in IR. That is, IR can be worked on without needing - to think about declaring function references. - * Binaryen IR allows non-nullable locals in the form that the wasm spec does, - (which was historically nicknamed "1a"), in which a `local.get` must be - structurally dominated by a `local.set` in order to validate (that ensures - we do not read the default value of null). Despite being aligned with the - wasm spec, there are some minor details that you may notice: - * A nameless `Block` in Binaryen IR does not interfere with validation. - Nameless blocks are never emitted into the binary format (we just emit - their contents), so we ignore them for purposes of non-nullable locals. As - a result, if you read wasm text emitted by Binaryen then you may see what - seems to be code that should not validate per the spec (and may not - validate in wasm text parsers), but that difference will not exist in the - binary format (binaries emitted by Binaryen will always work everywhere, - aside for bugs of course). - * The Binaryen pass runner will automatically fix up validation after each - pass (finding things that do not validate and fixing them up, usually by - demoting a local to be nullable). As a result you do not need to worry - much about this when writing Binaryen passes. For more details see the - `requiresNonNullableLocalFixups()` hook in `pass.h` and the - `LocalStructuralDominance` class. - * Binaryen IR uses the most refined types possible for references, - specifically: - * The IR type of a `ref.func` is always a specific function type, and not - plain `funcref`. It is also non-nullable. - * Non-nullable types are also used for the type that `try_table` sends - on branches (if we branch, a null is never sent), that is, it sends - (ref exn) and not (ref null exn). - In both cases if GC is not enabled then we emit the less-refined type in the - binary. When reading a binary, the more refined types will be applied as we - build the IR. - * `br_if` output types are more refined in Binaryen IR: they have the type of - the value, when a value flows in. In the wasm spec the type is that of the - branch target, which may be less refined. Using the more refined type here - ensures that we optimize in the best way possible, using all the type - information, but it does mean that some roundtripping operations may look a - little different. In particular, when we emit a `br_if` whose type is more - refined in Binaryen IR then we emit a cast right after it, so that the - output has the right type in the wasm spec. That may cause a few bytes of - extra size in rare cases (we avoid this overhead in the common case where - the `br_if` value is unused). + * The wasm text and binary formats require that a function whose address is + taken by `ref.func` must be either in the table, or declared via an + `(elem declare func $..)`. Binaryen will emit that data when necessary, but + it does not represent it in IR. That is, IR can be worked on without needing + to think about declaring function references. + * Binaryen IR allows non-nullable locals in the form that the wasm spec does, + (which was historically nicknamed "1a"), in which a `local.get` must be + structurally dominated by a `local.set` in order to validate (that ensures + we do not read the default value of null). Despite being aligned with the + wasm spec, there are some minor details that you may notice: + * A nameless `Block` in Binaryen IR does not interfere with validation. + Nameless blocks are never emitted into the binary format (we just emit + their contents), so we ignore them for purposes of non-nullable locals. As + a result, if you read wasm text emitted by Binaryen then you may see what + seems to be code that should not validate per the spec (and may not + validate in wasm text parsers), but that difference will not exist in the + binary format (binaries emitted by Binaryen will always work everywhere, + aside for bugs of course). + * The Binaryen pass runner will automatically fix up validation after each + pass (finding things that do not validate and fixing them up, usually by + demoting a local to be nullable). As a result you do not need to worry + much about this when writing Binaryen passes. For more details see the + `requiresNonNullableLocalFixups()` hook in `pass.h` and the + `LocalStructuralDominance` class. + * Binaryen IR uses the most refined types possible for references, + specifically: + * The IR type of a `ref.func` is always a specific function type, and not + plain `funcref`. It is also non-nullable. + * Non-nullable types are also used for the type that `try_table` sends + on branches (if we branch, a null is never sent), that is, it sends + (ref exn) and not (ref null exn). + In both cases if GC is not enabled then we emit the less-refined type in the + binary. When reading a binary, the more refined types will be applied as we + build the IR. + * `br_if` output types are more refined in Binaryen IR: they have the type of + the value, when a value flows in. In the wasm spec the type is that of the + branch target, which may be less refined. Using the more refined type here + ensures that we optimize in the best way possible, using all the type + information, but it does mean that some roundtripping operations may look a + little different. In particular, when we emit a `br_if` whose type is more + refined in Binaryen IR then we emit a cast right after it, so that the + output has the right type in the wasm spec. That may cause a few bytes of + extra size in rare cases (we avoid this overhead in the common case where + the `br_if` value is unused). * Strings * Binaryen allows string views (`stringview_wtf16` etc.) to be cast using `ref.cast`. This simplifies the IR, as it allows `ref.cast` to always be From 7074d8759242f573c58ebaeac0a18811bad144ea Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 23 Jan 2025 14:10:03 -0800 Subject: [PATCH 254/622] Simplify cmake by having a single binaryen library target (#7238) Instead of declaring a separate static library target for each subdirectory, declare a single binaryen library target up front and then add sources to it from each subdirectory. Requires updating the minimum cmake version to avoid policy errors. --- CMakeLists.txt | 54 +++++++++---------------- src/analysis/CMakeLists.txt | 2 +- src/asmjs/CMakeLists.txt | 2 +- src/cfg/CMakeLists.txt | 2 +- src/emscripten-optimizer/CMakeLists.txt | 2 +- src/interpreter/CMakeLists.txt | 2 +- src/ir/CMakeLists.txt | 2 +- src/parser/CMakeLists.txt | 2 +- src/passes/CMakeLists.txt | 2 +- src/support/CMakeLists.txt | 4 +- src/wasm/CMakeLists.txt | 6 +-- third_party/llvm-project/CMakeLists.txt | 5 ++- 12 files changed, 32 insertions(+), 53 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7e3699f7f1..3860d31bd33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ # Version set according the the cmake versions available in Ubuntu/Bionic: -# https://packages.ubuntu.com/bionic/cmake -cmake_minimum_required(VERSION 3.10.2) +# https://packages.ubuntu.com/focal/cmake +cmake_minimum_required(VERSION 3.16.3) # Needed for C++17 (std::variant) # TODO(https://github.com/WebAssembly/binaryen/issues/4299): We need @@ -172,12 +172,12 @@ if(NOT EMSCRIPTEN) endif() endif() -# Compiler setup. +# Compiler setup. Use SYSTEM to avoid warnings and errors from third-party headers. include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/FP16/include) +include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/FP16/include) if(BUILD_LLVM_DWARF) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/llvm-project/include) + include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/llvm-project/include) endif() # Add output directory to include path so config.h can be found @@ -400,10 +400,15 @@ if(UNIX AND CMAKE_GENERATOR STREQUAL "Ninja") endif() endif() -# Static libraries -# Current (partial) dependency structure is as follows: -# tools -> passes -> wasm -> asmjs -> support -# TODO: It's odd that wasm should depend on asmjs, maybe we should fix that. +if(BUILD_STATIC_LIB) + message(STATUS "Building libbinaryen as statically linked library.") + add_library(binaryen STATIC) + add_definitions(-DBUILD_STATIC_LIBRARY) +else() + message(STATUS "Building libbinaryen as shared library.") + add_library(binaryen SHARED) +endif() + add_subdirectory(src/ir) add_subdirectory(src/asmjs) add_subdirectory(src/cfg) @@ -430,23 +435,6 @@ if(BUILD_TESTS) add_subdirectory(test/gtest) endif() -# Object files -set(binaryen_objs - $ - $ - $ - $ - $ - $ - $ - $ - $ - $) - -if(BUILD_LLVM_DWARF) - SET(binaryen_objs ${binaryen_objs} $) -endif() - # Sources. file(GLOB binaryen_HEADERS src/*.h) @@ -454,15 +442,9 @@ set(binaryen_SOURCES src/binaryen-c.cpp ${binaryen_HEADERS} ) -if(BUILD_STATIC_LIB) - message(STATUS "Building libbinaryen as statically linked library.") - add_library(binaryen STATIC ${binaryen_SOURCES} ${binaryen_objs}) - add_definitions(-DBUILD_STATIC_LIBRARY) -else() - message(STATUS "Building libbinaryen as shared library.") - add_library(binaryen SHARED ${binaryen_SOURCES} ${binaryen_objs}) -endif() +target_sources(binaryen PRIVATE ${binaryen_SOURCES}) target_link_libraries(binaryen ${CMAKE_THREAD_LIBS_INIT}) + if(INSTALL_LIBS OR NOT BUILD_STATIC_LIB) install(TARGETS binaryen RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} @@ -483,7 +465,7 @@ if(EMSCRIPTEN) # binaryen.js WebAssembly variant add_executable(binaryen_wasm ${binaryen_SOURCES}) - target_link_libraries(binaryen_wasm wasm asmjs emscripten-optimizer passes ir cfg support analysis parser interpreter wasm) + target_link_libraries(binaryen_wasm binaryen) target_link_libraries(binaryen_wasm "-sFILESYSTEM") target_link_libraries(binaryen_wasm "-sEXPORT_NAME=Binaryen") target_link_libraries(binaryen_wasm "-sNODERAWFS=0") @@ -513,7 +495,7 @@ if(EMSCRIPTEN) # binaryen.js JavaScript variant add_executable(binaryen_js ${binaryen_SOURCES}) - target_link_libraries(binaryen_js wasm asmjs emscripten-optimizer passes ir cfg support analysis parser interpreter wasm) + target_link_libraries(binaryen_js binaryen) target_link_libraries(binaryen_js "-sWASM=0") target_link_libraries(binaryen_js "-sWASM_ASYNC_COMPILATION=0") if(${CMAKE_CXX_COMPILER_VERSION} STREQUAL "6.0.1") diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index 313eec0d5d3..8b843db5373 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -3,4 +3,4 @@ set(analysis_SOURCES cfg.cpp ${analysis_HEADERS} ) -add_library(analysis OBJECT ${analysis_SOURCES}) +target_sources(binaryen PRIVATE ${analysis_SOURCES}) diff --git a/src/asmjs/CMakeLists.txt b/src/asmjs/CMakeLists.txt index d677f1bfe81..9f80aa7d399 100644 --- a/src/asmjs/CMakeLists.txt +++ b/src/asmjs/CMakeLists.txt @@ -5,4 +5,4 @@ set(asmjs_SOURCES shared-constants.cpp ${asmjs_HEADERS} ) -add_library(asmjs OBJECT ${asmjs_SOURCES}) +target_sources(binaryen PRIVATE ${asmjs_SOURCES}) diff --git a/src/cfg/CMakeLists.txt b/src/cfg/CMakeLists.txt index 71d77f0b03e..175d5fbb7b9 100644 --- a/src/cfg/CMakeLists.txt +++ b/src/cfg/CMakeLists.txt @@ -3,4 +3,4 @@ set(cfg_SOURCES Relooper.cpp ${cfg_HEADERS} ) -add_library(cfg OBJECT ${cfg_SOURCES}) +target_sources(binaryen PRIVATE ${cfg_SOURCES}) diff --git a/src/emscripten-optimizer/CMakeLists.txt b/src/emscripten-optimizer/CMakeLists.txt index 391c1444f7a..127954248d7 100644 --- a/src/emscripten-optimizer/CMakeLists.txt +++ b/src/emscripten-optimizer/CMakeLists.txt @@ -5,4 +5,4 @@ set(emscripten-optimizer_SOURCES simple_ast.cpp ${emscripten-optimizer_HEADERS} ) -add_library(emscripten-optimizer OBJECT ${emscripten-optimizer_SOURCES}) +target_sources(binaryen PRIVATE ${emscripten-optimizer_SOURCES}) diff --git a/src/interpreter/CMakeLists.txt b/src/interpreter/CMakeLists.txt index 170376476d5..dd0b038f650 100644 --- a/src/interpreter/CMakeLists.txt +++ b/src/interpreter/CMakeLists.txt @@ -4,4 +4,4 @@ set(interpreter_SOURCES interpreter.cpp ${interpreter_HEADERS} ) -add_library(interpreter OBJECT ${interpreter_SOURCES}) +target_sources(binaryen PRIVATE ${interpreter_SOURCES}) diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index 45b08702de8..fa2ee1127d7 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -24,4 +24,4 @@ set(ir_SOURCES module-splitting.cpp ${ir_HEADERS} ) -add_library(ir OBJECT ${ir_SOURCES}) +target_sources(binaryen PRIVATE ${ir_SOURCES}) diff --git a/src/parser/CMakeLists.txt b/src/parser/CMakeLists.txt index 045948ba1bb..8b7846ca9e9 100644 --- a/src/parser/CMakeLists.txt +++ b/src/parser/CMakeLists.txt @@ -12,4 +12,4 @@ set(parser_SOURCES wat-parser.cpp ${parser_HEADERS} ) -add_library(parser OBJECT ${parser_SOURCES}) +target_sources(binaryen PRIVATE ${parser_SOURCES}) diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index ed816ba09d4..83a17d36608 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -144,4 +144,4 @@ if(EMSCRIPTEN) list(REMOVE_ITEM passes_SOURCES "hash-stringify-walker.cpp") list(REMOVE_ITEM passes_SOURCES "Outlining.cpp") endif() -add_library(passes OBJECT ${passes_SOURCES}) +target_sources(binaryen PRIVATE ${passes_SOURCES}) diff --git a/src/support/CMakeLists.txt b/src/support/CMakeLists.txt index 54ee7b206e4..5c793662e1c 100644 --- a/src/support/CMakeLists.txt +++ b/src/support/CMakeLists.txt @@ -22,12 +22,12 @@ set(support_SOURCES # suffix_tree_node source files no longer depend on LLVM code in the # third_party folder if(EMSCRIPTEN) - add_library(support OBJECT ${support_SOURCES}) + target_sources(binaryen PRIVATE ${support_SOURCES}) else() set(support_with_suffix_tree_SOURCES suffix_tree.cpp suffix_tree_node.cpp ${support_SOURCES} ) - add_library(support OBJECT ${support_with_suffix_tree_SOURCES}) + target_sources(binaryen PRIVATE ${support_with_suffix_tree_SOURCES}) endif() diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt index 64c88c99723..07e067c49b8 100644 --- a/src/wasm/CMakeLists.txt +++ b/src/wasm/CMakeLists.txt @@ -17,8 +17,4 @@ set(wasm_SOURCES wasm-validator.cpp ${wasm_HEADERS} ) -# wasm-debug.cpp includes LLVM header using std::iterator (deprecated in C++17) -if (NOT MSVC) - set_source_files_properties(wasm-debug.cpp PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations) -endif() -add_library(wasm OBJECT ${wasm_SOURCES}) +target_sources(binaryen PRIVATE ${wasm_SOURCES}) diff --git a/third_party/llvm-project/CMakeLists.txt b/third_party/llvm-project/CMakeLists.txt index d338dd6e0c5..8d5f2b1e347 100644 --- a/third_party/llvm-project/CMakeLists.txt +++ b/third_party/llvm-project/CMakeLists.txt @@ -63,7 +63,7 @@ SET(llvm_dwarf_SOURCES raw_ostream.cpp ScopedPrinter.cpp SmallVector.cpp - SourceMgr.cpp + SourceMgr.cpp StringMap.cpp StringRef.cpp SymbolicFile.cpp @@ -73,4 +73,5 @@ SET(llvm_dwarf_SOURCES YAMLParser.cpp # XXX needed? YAMLTraits.cpp ) -ADD_LIBRARY(llvm_dwarf OBJECT ${llvm_dwarf_SOURCES}) +add_library(llvm_dwarf OBJECT ${llvm_dwarf_SOURCES}) +target_link_libraries(binaryen llvm_dwarf) From ff423ae11293a7051dd3806998cf133944a330ce Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 23 Jan 2025 15:22:45 -0800 Subject: [PATCH 255/622] Fix bug in SIMDLoadStoreLane interpretation (#7237) The interpreter incorrectly trapped on OOB addresses before evaluating the vector operand. This bug became visible when the vector operand had side effects. Reorder the code to fix the problem. --- src/wasm-interpreter.h | 18 ++++++------ test/lit/exec/simd-load-lane-oob.wast | 42 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 test/lit/exec/simd-load-lane-oob.wast diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d459ffdf5fe..186245ab004 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3812,20 +3812,20 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { NOTE_ENTER("SIMDLoadStoreLane"); - Flow flow = self()->visit(curr->ptr); - if (flow.breaking()) { - return flow; + Flow ptrFlow = self()->visit(curr->ptr); + if (ptrFlow.breaking()) { + return ptrFlow; } NOTE_EVAL1(flow); + Flow vecFlow = self()->visit(curr->vec); + if (vecFlow.breaking()) { + return vecFlow; + } auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); Address addr = info.instance->getFinalAddress( - curr, flow.getSingleValue(), curr->getMemBytes(), memorySize); - flow = self()->visit(curr->vec); - if (flow.breaking()) { - return flow; - } - Literal vec = flow.getSingleValue(); + curr, ptrFlow.getSingleValue(), curr->getMemBytes(), memorySize); + Literal vec = vecFlow.getSingleValue(); switch (curr->op) { case Load8LaneVec128: case Store8LaneVec128: { diff --git a/test/lit/exec/simd-load-lane-oob.wast b/test/lit/exec/simd-load-lane-oob.wast new file mode 100644 index 00000000000..2d0ed4d3af3 --- /dev/null +++ b/test/lit/exec/simd-load-lane-oob.wast @@ -0,0 +1,42 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +;; Regression test for a bug where the vector operand to SIMDLoadStoreLane was +;; not evaluated if the preceding ptr operand was OOB. + +(module + (memory $mem 1 1) + (global $g (mut i32) (i32.const 0)) + + ;; CHECK: [fuzz-exec] calling oob + ;; CHECK-NEXT: [trap final > memory: 18446744073709551615 > 65536] + (func $oob (export "oob") + (drop + ;; This should trap, but not until after setting the global. + (v128.load64_lane 0 + (i32.const -1) + (block (result v128) + (global.set $g + (i32.const 1) + ) + (v128.const i32x4 0 0 0 0) + ) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling get + ;; CHECK-NEXT: [fuzz-exec] note result: get => 1 + (func $get (export "get") (result i32) + ;; This should be 1 + (global.get $g) + ) +) +;; CHECK: [fuzz-exec] calling oob +;; CHECK-NEXT: [trap final > memory: 18446744073709551615 > 65536] + +;; CHECK: [fuzz-exec] calling get +;; CHECK-NEXT: [fuzz-exec] note result: get => 1 +;; CHECK-NEXT: [fuzz-exec] comparing get +;; CHECK-NEXT: [fuzz-exec] comparing oob From ee0191a37cb3578dd45eee5c71c431d324a006bc Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 24 Jan 2025 09:35:06 -0800 Subject: [PATCH 256/622] Apply warning and error compile flags only to 1P code (#7241) Move all the configuration of error and warning flags to after we have added the third_party subdirectory and declared all of the 3P targets. This ensures that only the basic configuration that e.g. affects ABI is applied to the 3P code. This will make it easier to add 3P code that does not compile cleanly with all the warnings and errors we enable for our own code. --- CMakeLists.txt | 198 +++++++++++++----------- test/gtest/CMakeLists.txt | 3 +- third_party/llvm-project/CMakeLists.txt | 1 - 3 files changed, 109 insertions(+), 93 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3860d31bd33..0e0be46b856 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,12 +31,26 @@ endif() # more useful error reports from users. option(BYN_ENABLE_ASSERTIONS "Enable assertions" ON) +option(BYN_ENABLE_LTO "Build with LTO" Off) + # Turn this off to avoid the dependency on gtest. option(BUILD_TESTS "Build GTest-based tests" ON) # Turn this off to build only the library. option(BUILD_TOOLS "Build tools" ON) +option(BUILD_LLVM_DWARF "Enable full DWARF support" ON) +if(EMSCRIPTEN) + # For now, don't include full DWARF support in JS builds, for size. + set(BUILD_LLVM_DWARF OFF) +endif() + +option(BUILD_STATIC_LIB "Build as a static library" OFF) +if(MSVC) + # We don't have dllexport declarations set up for windows yet. + set(BUILD_STATIC_LIB ON) +endif() + # Turn this off to install only tools and not static/dynamic libs option(INSTALL_LIBS "Install libraries" ON) @@ -61,6 +75,8 @@ option(EMSCRIPTEN_ENABLE_PTHREADS "Enable pthreads in emscripten build" OFF) # This is useful for debugging, performance analysis, and other testing. option(EMSCRIPTEN_ENABLE_SINGLE_FILE "Enable SINGLE_FILE mode in emscripten build" ON) +option(ENABLE_WERROR "Enable -Werror" ON) + # For git users, attempt to generate a more useful version string if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git) find_package(Git QUIET REQUIRED) @@ -80,6 +96,11 @@ endif() configure_file(config.h.in config.h) +# Configure threads + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + # Support functionality. function(add_compile_flag value) @@ -89,11 +110,6 @@ function(add_compile_flag value) endforeach(variable) endfunction() -function(add_cxx_flag value) - message(STATUS "Building with ${value}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${value}" PARENT_SCOPE) -endfunction() - function(add_debug_compile_flag value) if("${CMAKE_BUILD_TYPE}" MATCHES "Debug") message(STATUS "Building with ${value}") @@ -145,33 +161,12 @@ endfunction() function(binaryen_add_executable name sources) add_executable(${name} ${sources}) - target_link_libraries(${name} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(${name} Threads::Threads) target_link_libraries(${name} binaryen) binaryen_setup_rpath(${name}) install(TARGETS ${name} DESTINATION ${CMAKE_INSTALL_BINDIR}) endfunction() -# Options - -option(BUILD_STATIC_LIB "Build as a static library" OFF) -if(MSVC) - # We don't have dllexport declarations set up for windows yet. - set(BUILD_STATIC_LIB ON) -endif() - -# For now, don't include full DWARF support in JS builds, for size. -if(NOT EMSCRIPTEN) - option(BUILD_LLVM_DWARF "Enable full DWARF support" ON) - - if(BUILD_LLVM_DWARF) - if(MSVC) - ADD_COMPILE_FLAG("/DBUILD_LLVM_DWARF") - else() - ADD_COMPILE_FLAG("-DBUILD_LLVM_DWARF") - endif() - endif() -endif() - # Compiler setup. Use SYSTEM to avoid warnings and errors from third-party headers. include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) @@ -183,6 +178,8 @@ endif() # Add output directory to include path so config.h can be found include_directories(${CMAKE_CURRENT_BINARY_DIR}) +# Configure output locations. + # Force output to bin/ and lib/. This is to suppress CMake multigenerator output paths and avoid bin/Debug, bin/Release/ and so on, which is CMake default. foreach(SUFFIX "_DEBUG" "_RELEASE" "_RELWITHDEBINFO" "_MINSIZEREL" "") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY${SUFFIX} "${PROJECT_BINARY_DIR}/bin") @@ -190,7 +187,18 @@ foreach(SUFFIX "_DEBUG" "_RELEASE" "_RELWITHDEBINFO" "_MINSIZEREL" "") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY${SUFFIX} "${PROJECT_BINARY_DIR}/lib") endforeach() -option(BYN_ENABLE_LTO "Build with LTO" Off) +# Compiler setup for both 1P and 3P code. + +if(NOT MSVC AND NOT EMSCRIPTEN) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + # wasm doesn't allow for x87 floating point math + add_compile_flag("-msse2") + add_compile_flag("-mfpmath=sse") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^armv[2-6]" AND NOT CMAKE_CXX_FLAGS MATCHES "-mfpu=") + add_compile_flag("-mfpu=vfpv3") + endif() +endif() + if(BYN_ENABLE_LTO) if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") message(FATAL_ERROR "ThinLTO is only supported by clang") @@ -210,19 +218,10 @@ if(MSVC) add_compile_flag("/arch:sse2") endif() endif() - add_compile_flag("/wd4146") # Ignore warning "warning C4146: unary minus operator applied to unsigned type, result still unsigned", this pattern is used somewhat commonly in the code. - # 4267 and 4244 are conversion/truncation warnings. We might want to fix these but they are currently pervasive. - add_compile_flag("/wd4267") - add_compile_flag("/wd4244") - # 4722 warns that destructors never return, even with [[noreturn]]. - add_compile_flag("/wd4722") - # "destructor was implicitly defined as deleted" caused by LLVM headers. - add_compile_flag("/wd4624") - add_compile_flag("/WX-") + add_debug_compile_flag("/Od") add_nondebug_compile_flag("/O2") - add_compile_flag("/D_CRT_SECURE_NO_WARNINGS") - add_compile_flag("/D_SCL_SECURE_NO_WARNINGS") + # workaround for https://github.com/WebAssembly/binaryen/issues/3661 add_compile_flag("/D_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING") # Visual Studio 2018 15.8 implemented conformant support for std::aligned_storage, but the conformant support is only enabled when the following flag is passed, to avoid @@ -257,51 +256,10 @@ if(MSVC) add_link_flag("/STACK:8388608") - if(RUN_STATIC_ANALYZER) - add_definitions(/analyze) - endif() -else() +else() # MSVC - option(ENABLE_WERROR "Enable -Werror" ON) - - set(THREADS_PREFER_PTHREAD_FLAG ON) - set(CMAKE_THREAD_PREFER_PTHREAD ON) - find_package(Threads REQUIRED) - if(NOT EMSCRIPTEN) - if(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") - # wasm doesn't allow for x87 floating point math - add_compile_flag("-msse2") - add_compile_flag("-mfpmath=sse") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^armv[2-6]" AND NOT CMAKE_CXX_FLAGS MATCHES "-mfpu=") - add_compile_flag("-mfpu=vfpv3") - endif() - endif() - add_compile_flag("-Wall") - if(ENABLE_WERROR) - add_compile_flag("-Werror") - endif() - add_compile_flag("-Wextra") - add_compile_flag("-Wno-unused-parameter") - add_compile_flag("-Wno-dangling-pointer") # false positive in gcc add_compile_flag("-fno-omit-frame-pointer") add_compile_flag("-fno-rtti") - # TODO(https://github.com/WebAssembly/binaryen/pull/2314): Remove these two - # flags once we resolve the issue. - add_compile_flag("-Wno-implicit-int-float-conversion") - add_compile_flag("-Wno-unknown-warning-option") - add_compile_flag("-Wswitch") # we explicitly expect this in the code - add_compile_flag("-Wimplicit-fallthrough") - add_compile_flag("-Wnon-virtual-dtor") - - if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - # Google style requires this, so make sure we compile cleanly with it. - add_compile_flag("-Wctad-maybe-unsupported") - # Disable a warning that started to happen on system headers (so we can't - # fix it in our codebase) on github CI: - # https://github.com/WebAssembly/binaryen/pull/6597 - add_compile_flag("-Wno-deprecated-declarations") - endif() - if(WIN32) add_compile_flag("-D_GNU_SOURCE") add_compile_flag("-D__STDC_FORMAT_MACROS") @@ -400,6 +358,69 @@ if(UNIX AND CMAKE_GENERATOR STREQUAL "Ninja") endif() endif() +# Add targets and sources for 3P code. + +add_subdirectory(third_party) + +# Additionally configure compiler for 1P code. + +if(BUILD_LLVM_DWARF) + if(MSVC) + add_compile_flag("/DBUILD_LLVM_DWARF") + else() + add_compile_flag("-DBUILD_LLVM_DWARF") + endif() +endif() + +# Configure warnings and errors + +if(MSVC) + add_compile_flag("/wd4146") # Ignore warning "warning C4146: unary minus operator applied to unsigned type, result still unsigned", this pattern is used somewhat commonly in the code. + # 4267 and 4244 are conversion/truncation warnings. We might want to fix these but they are currently pervasive. + add_compile_flag("/wd4267") + add_compile_flag("/wd4244") + # 4722 warns that destructors never return, even with [[noreturn]]. + add_compile_flag("/wd4722") + # "destructor was implicitly defined as deleted" caused by LLVM headers. + add_compile_flag("/wd4624") + add_compile_flag("/WX-") + add_compile_flag("/D_CRT_SECURE_NO_WARNINGS") + add_compile_flag("/D_SCL_SECURE_NO_WARNINGS") + + if(RUN_STATIC_ANALYZER) + add_definitions(/analyze) + endif() + +else() # MSVC + + add_compile_flag("-Wall") + if(ENABLE_WERROR) + add_compile_flag("-Werror") + endif() + add_compile_flag("-Wextra") + add_compile_flag("-Wno-unused-parameter") + add_compile_flag("-Wno-dangling-pointer") # false positive in gcc + # TODO(https://github.com/WebAssembly/binaryen/pull/2314): Remove these two + # flags once we resolve the issue. + add_compile_flag("-Wno-implicit-int-float-conversion") + add_compile_flag("-Wno-unknown-warning-option") + add_compile_flag("-Wswitch") # we explicitly expect this in the code + add_compile_flag("-Wimplicit-fallthrough") + add_compile_flag("-Wnon-virtual-dtor") + + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # Google style requires this, so make sure we compile cleanly with it. + add_compile_flag("-Wctad-maybe-unsupported") + # Disable a warning that started to happen on system headers (so we can't + # fix it in our codebase) on github CI: + # https://github.com/WebAssembly/binaryen/pull/6597 + add_compile_flag("-Wno-deprecated-declarations") + endif() + +endif() + +# Declare libbinaryen + if(BUILD_STATIC_LIB) message(STATUS "Building libbinaryen as statically linked library.") add_library(binaryen STATIC) @@ -408,6 +429,10 @@ else() message(STATUS "Building libbinaryen as shared library.") add_library(binaryen SHARED) endif() +target_link_libraries(binaryen Threads::Threads) +if(BUILD_LLVM_DWARF) + target_link_libraries(binaryen llvm_dwarf) +endif() add_subdirectory(src/ir) add_subdirectory(src/asmjs) @@ -425,8 +450,6 @@ if(BUILD_TOOLS) add_subdirectory(src/tools) endif() -add_subdirectory(third_party) - # Configure lit tests add_subdirectory(test/lit) @@ -443,7 +466,6 @@ set(binaryen_SOURCES ${binaryen_HEADERS} ) target_sources(binaryen PRIVATE ${binaryen_SOURCES}) -target_link_libraries(binaryen ${CMAKE_THREAD_LIBS_INIT}) if(INSTALL_LIBS OR NOT BUILD_STATIC_LIB) install(TARGETS binaryen @@ -459,12 +481,9 @@ endif() # # Note that we can't emit binaryen.js directly, as there is libbinaryen already # declared earlier, so we create binaryen_wasm/js.js, which must then be copied. -# Note that SHELL: is needed as otherwise cmake will coalesce -s link flags -# in an incorrect way for emscripten. if(EMSCRIPTEN) # binaryen.js WebAssembly variant - add_executable(binaryen_wasm - ${binaryen_SOURCES}) + add_executable(binaryen_wasm ${binaryen_SOURCES}) target_link_libraries(binaryen_wasm binaryen) target_link_libraries(binaryen_wasm "-sFILESYSTEM") target_link_libraries(binaryen_wasm "-sEXPORT_NAME=Binaryen") @@ -493,8 +512,7 @@ if(EMSCRIPTEN) install(TARGETS binaryen_wasm DESTINATION ${CMAKE_INSTALL_BINDIR}) # binaryen.js JavaScript variant - add_executable(binaryen_js - ${binaryen_SOURCES}) + add_executable(binaryen_js ${binaryen_SOURCES}) target_link_libraries(binaryen_js binaryen) target_link_libraries(binaryen_js "-sWASM=0") target_link_libraries(binaryen_js "-sWASM_ASYNC_COMPILATION=0") diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 41c2d4d0097..3a586e00e47 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -1,5 +1,4 @@ -include_directories(../../third_party/googletest/googletest/include) -include_directories(../../src/wasm) +include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third_party/googletest/googletest/include) set(unittest_SOURCES arena.cpp diff --git a/third_party/llvm-project/CMakeLists.txt b/third_party/llvm-project/CMakeLists.txt index 8d5f2b1e347..4636aac5942 100644 --- a/third_party/llvm-project/CMakeLists.txt +++ b/third_party/llvm-project/CMakeLists.txt @@ -74,4 +74,3 @@ SET(llvm_dwarf_SOURCES YAMLTraits.cpp ) add_library(llvm_dwarf OBJECT ${llvm_dwarf_SOURCES}) -target_link_libraries(binaryen llvm_dwarf) From bb876a580516d2fee5c78f40517b25fe470d44cf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Jan 2025 11:33:09 -0800 Subject: [PATCH 257/622] JSPI Fuzzing: Interleave executions (#7226) Rather than always do await export() now we might stash the Promise on the side, and execute it later, after other stacks are executed and perhaps also saved. To do this, rewrite the logic for calling the exports in a more flexible manner. (That required altering the random seed in fuzz_shell_orders.wast, to preserve the current order it was emitting.) We do not fuzz with top-level await, so the output here looks a bit out of order, but it does still end up with interleaved executions, which I think is useful for fuzzing. --- scripts/fuzz_shell.js | 101 ++++++++++++++++++++------- test/lit/d8/fuzz_shell_jspi.wast | 81 +++++++++++++++++++++ test/lit/node/fuzz_shell_orders.wast | 2 +- 3 files changed, 156 insertions(+), 28 deletions(-) create mode 100644 test/lit/d8/fuzz_shell_jspi.wast diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 2c7681cb501..1856c815b3e 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -387,27 +387,12 @@ function hashCombine(seed, value) { /* async */ function callExports(ordering) { // Call the exports we were told, or if we were not given an explicit list, // call them all. - var relevantExports = exportsToCall || exportList; - - if (ordering !== undefined) { - // Copy the list, and sort it in the simple Fisher-Yates manner. - // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm - relevantExports = relevantExports.slice(0); - for (var i = 0; i < relevantExports.length - 1; i++) { - // Pick the index of the item to place at index |i|. - ordering = hashCombine(ordering, i); - // The number of items to pick from begins at the full length, then - // decreases with i. - var j = i + (ordering % (relevantExports.length - i)); - // Swap the item over here. - var t = relevantExports[j]; - relevantExports[j] = relevantExports[i]; - relevantExports[i] = t; - } - } + let relevantExports = exportsToCall || exportList; - for (var e of relevantExports) { - var name, value; + // Build the list of call tasks to run, one for each relevant export. + let tasks = []; + for (let e of relevantExports) { + let name, value; if (typeof e === 'string') { // We are given a string name to call. Look it up in the global namespace. name = e; @@ -423,16 +408,78 @@ function hashCombine(seed, value) { continue; } + // A task is a name + a function to call. For an export, the function is + // simply a call of the export. + tasks.push({ name: name, func: /* async */ () => callFunc(value) }); + } + + // Reverse the array, so the first task is at the end, for efficient + // popping in the common case. + tasks.reverse(); + + // Execute tasks while they remain. + while (tasks.length) { + let task; + if (ordering === undefined) { + // Use the natural order. + task = tasks.pop(); + } else { + // Pick a random task. + ordering = hashCombine(ordering, tasks.length); + let i = ordering % tasks.length; + task = tasks.splice(i, 1)[0]; + } + + // Execute the task. + console.log('[fuzz-exec] calling ' + task.name); + let result; try { - console.log('[fuzz-exec] calling ' + name); - // TODO: Based on |ordering|, do not always await, leaving a promise - // for later, so we interleave stacks. - var result = /* await */ callFunc(value); - if (typeof result !== 'undefined') { - console.log('[fuzz-exec] note result: ' + name + ' => ' + printed(result)); - } + result = task.func(); } catch (e) { console.log('exception thrown: ' + e); + continue; + } + + if (JSPI) { + // When we are changing up the order, in JSPI we can also leave some + // promises unresolved until later, which lets us interleave them. Note we + // never defer a task more than once, and we only defer a promise (which + // we check for using .then). + // TODO: Deferring more than once may make sense, by chaining promises in + // JS (that would not add wasm execution in the middle, but might + // find JS issues in principle). We could also link promises by + // depending on each other, ensuring certain orders of execution. + if (ordering !== undefined && !task.deferred && result && + typeof result == 'object' && typeof result.then === 'function') { + // Hash with -1 here, just to get something different than the hashing a + // few lines above. + ordering = hashCombine(ordering, -1); + if (ordering & 1) { + // Defer it for later. Reuse the existing task for simplicity. + console.log(`(jspi: defer ${task.name})`); + task.func = /* async */ () => { + console.log(`(jspi: finish ${task.name})`); + return /* await */ result; + }; + task.deferred = true; + tasks.push(task); + continue; + } + // Otherwise, continue down. + } + + // Await it right now. + try { + result = /* await */ result; + } catch (e) { + console.log('exception thrown: ' + e); + continue; + } + } + + // Log the result. + if (typeof result !== 'undefined') { + console.log('[fuzz-exec] note result: ' + task.name + ' => ' + printed(result)); } } } diff --git a/test/lit/d8/fuzz_shell_jspi.wast b/test/lit/d8/fuzz_shell_jspi.wast new file mode 100644 index 00000000000..cc1e35f0274 --- /dev/null +++ b/test/lit/d8/fuzz_shell_jspi.wast @@ -0,0 +1,81 @@ +(module + (import "fuzzing-support" "log-i32" (func $log (param i32))) + + (func $a (export "a") (result i32) + (i32.const 10) + ) + + (func $b (export "b") (result i32) + (i32.const 20) + ) + + (func $c (export "c") (result i32) + (i32.const 30) + ) + + (func $d (export "d") (result i32) + (i32.const 40) + ) + + (func $e (export "e") (result i32) + (i32.const 50) + ) +) + +;; Apply JSPI: first, prepend JSPI = 1. + +;; RUN: echo "JSPI = 1;" > %t.js + +;; Second, remove comments around async and await: feed fuzz_shell.js into node +;; as stdin, so all node needs to do is read stdin, do the replacements, and +;; write to stdout. + +;; RUN: cat %S/../../../scripts/fuzz_shell.js | node -e "process.stdout.write(require('fs').readFileSync(0, 'utf-8').replace(/[/][*] async [*][/]/g, 'async').replace(/[/][*] await [*][/]/g, 'await'))" >> %t.js + +;; Append another run with a random seed, so we reorder and delay execution. +;; RUN: echo "callExports(42);" >> %t.js + +;; Run that JS shell with our wasm. +;; RUN: wasm-opt %s -o %t.wasm -q +;; RUN: v8 --wasm-staging %t.js -- %t.wasm | filecheck %s +;; +;; The output here looks a little out of order, in particular because we do not +;; |await| the toplevel callExports() calls. That |await| is only valid if we +;; pass --module, which we do not fuzz with. As a result, the first await +;; operation in the first callExports() leaves that function and continues to +;; the next, but we do get around to executing all the things we need. In +;; particular, the output here should contain two "node result" lines for each +;; of the 5 functions (one from each callExports()). The important thing is that +;; we get a random-like ordering, which includes some defers (each of which has +;; a later finish), showing that we interleave stacks. +;; +;; CHECK: [fuzz-exec] calling a +;; CHECK: [fuzz-exec] calling b +;; CHECK: [fuzz-exec] note result: a => 10 +;; CHECK: [fuzz-exec] calling b +;; CHECK: [fuzz-exec] note result: b => 20 +;; CHECK: [fuzz-exec] calling a +;; CHECK: (jspi: defer a) +;; CHECK: [fuzz-exec] calling d +;; CHECK: (jspi: defer d) +;; CHECK: [fuzz-exec] calling e +;; CHECK: [fuzz-exec] note result: b => 20 +;; CHECK: [fuzz-exec] calling c +;; CHECK: [fuzz-exec] note result: e => 50 +;; CHECK: [fuzz-exec] calling c +;; CHECK: (jspi: defer c) +;; CHECK: [fuzz-exec] calling c +;; CHECK: (jspi: finish c) +;; CHECK: [fuzz-exec] note result: c => 30 +;; CHECK: [fuzz-exec] calling d +;; CHECK: [fuzz-exec] note result: c => 30 +;; CHECK: [fuzz-exec] calling d +;; CHECK: (jspi: finish d) +;; CHECK: [fuzz-exec] note result: d => 40 +;; CHECK: [fuzz-exec] calling e +;; CHECK: [fuzz-exec] note result: d => 40 +;; CHECK: [fuzz-exec] calling a +;; CHECK: (jspi: finish a) +;; CHECK: [fuzz-exec] note result: a => 10 +;; CHECK: [fuzz-exec] note result: e => 50 + diff --git a/test/lit/node/fuzz_shell_orders.wast b/test/lit/node/fuzz_shell_orders.wast index 6d51c4e1b2c..76fd40f4548 100644 --- a/test/lit/node/fuzz_shell_orders.wast +++ b/test/lit/node/fuzz_shell_orders.wast @@ -32,7 +32,7 @@ ;; Append another run with a seed that leads to a different order ;; ;; RUN: cp %S/../../../scripts/fuzz_shell.js %t.js -;; RUN: echo "callExports(1337);" >> %t.js +;; RUN: echo "callExports(34);" >> %t.js ;; RUN: node %t.js %t.wasm | filecheck %s --check-prefix=APPENDED ;; ;; The original order: a,b,c From d555a063dd04707aa8faabd8f687f526320cf604 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Jan 2025 13:03:40 -0800 Subject: [PATCH 258/622] [EH] Fix nested pops when GTO removes a GC operation (#7242) --- src/passes/GlobalTypeOptimization.cpp | 12 +++++++ test/lit/passes/gto-removals.wast | 52 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index b2ba23b0feb..3d006468fc7 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -22,6 +22,7 @@ // * Fields that are never read from can be removed entirely. // +#include "ir/eh-utils.h" #include "ir/localize.h" #include "ir/module-utils.h" #include "ir/ordering.h" @@ -453,6 +454,8 @@ struct GlobalTypeOptimization : public Pass { return std::make_unique(parent); } + bool needEHFixups = false; + void visitStructNew(StructNew* curr) { if (curr->type == Type::unreachable) { return; @@ -476,6 +479,8 @@ struct GlobalTypeOptimization : public Pass { ChildLocalizer localizer( curr, getFunction(), *getModule(), getPassOptions()); replaceCurrent(localizer.getReplacement()); + // Adding a block here requires EH fixups. + needEHFixups = true; // Remove and reorder operands. Index removed = 0; @@ -519,6 +524,7 @@ struct GlobalTypeOptimization : public Pass { getFunction(), getModule(), getPassOptions()); + needEHFixups = true; Expression* replacement = builder.makeDrop(builder.makeRefAs(RefAsNonNull, flipped)); if (curr->order == MemoryOrder::SeqCst) { @@ -545,6 +551,12 @@ struct GlobalTypeOptimization : public Pass { curr->index = newIndex; } + void visitFunction(Function* curr) { + if (needEHFixups) { + EHUtils::handleBlockNestedPops(curr, *getModule()); + } + } + private: Index getNewIndex(HeapType type, Index index) { auto iter = parent.indexesAfterRemovals.find(type); diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index 6ab126611ea..e02430a3634 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -1627,3 +1627,55 @@ ) ) ) + +;; A struct with a pop, which requires EH fixups to avoid popping in a nested +;; block. +(module + (type $i32 (func (param i32))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (struct)) + (type $struct (struct (field (mut i32)))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $i32 (func (param i32))) + + ;; CHECK: (tag $tag (type $i32) (param i32)) + (tag $tag (type $i32) (param i32)) + + ;; CHECK: (func $func (type $1) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + (try + (do + ) + (catch $tag + (drop + (struct.new $struct + (pop i32) + ) + ) + ) + ) + ) +) From 716e3c78fef627bdbbbbd4e1c31690d7c8330aff Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Mon, 27 Jan 2025 16:05:35 -0800 Subject: [PATCH 259/622] Use CMAKE_MSVC_RUNTIME_LIBRARY to choose MSVC CRT (#7243) The current approach doesn't work when CMake uses the clang-style version of the flag (i.e. `-MD` instead of `/MD`) to choose the runtime. Now that we are depending on CMake newer than version 3.15 we can use its builtin support for choosing the MSVC runtime. --- CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e0be46b856..54103c7b6c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,12 +246,11 @@ if(MSVC) CMAKE_C_FLAGS_MINSIZEREL) string(REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " " "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") - - # Compile with `/MT` to link against `libcmt.lib`, removing a dependency - # on `msvcrt.dll`. May result in slightly larger binaries but they should - # be more portable across systems. - string(REPLACE "/MD" "/MT" ${flags_var_to_scrub} "${${flags_var_to_scrub}}") endforeach() + # Compile with `/MT` to link against `libcmt.lib`, removing a dependency + # on `msvcrt.dll`. May result in slightly larger binaries but they should + # be more portable across systems. + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded") endif() add_link_flag("/STACK:8388608") From 3e85182ab7a0c8e11f68bc3b97e6fa82ad8bd0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Tue, 28 Jan 2025 21:32:48 +0000 Subject: [PATCH 260/622] Stack switching proposal support (#7041) This patch implements text and binary encoding/decoding support for the stack switching proposal. It does so by adapting the previous typed-continunations implementation. Particular changes: * Support for new `resume` encoding. * Added support for `resume_throw` and `switch`. * Feature flag `typed-continuations` has been renamed to `stack-switching`. A small unfortunate implementation detail is that the internal name `Switch` was already taken by the `br_table` instruction, so I opted to give the `switch` instruction the internal name `StackSwitch`. A minor detail is that I have reordered the declarations/definitions of the stack switching instructions such that they appear in ascending order according to their opcode value (this is the same order that the stack-switching explainer document present them in). I can look into adding validation support in a subsequent patch. --- scripts/gen-s-parser.py | 6 +- scripts/test/fuzzing.py | 8 + src/gen-s-parser.inc | 27 +- src/interpreter/interpreter.cpp | 6 +- src/ir/ReFinalize.cpp | 4 +- src/ir/branch-utils.h | 11 +- src/ir/child-typer.h | 67 +- src/ir/cost.h | 26 +- src/ir/effects.h | 37 +- src/ir/module-utils.cpp | 15 +- src/ir/possible-contents.cpp | 12 +- src/ir/subtype-exprs.h | 10 +- src/parser/contexts.h | 112 ++- src/parser/parsers.h | 96 +- src/passes/Print.cpp | 111 +- src/passes/TypeGeneralizing.cpp | 6 +- src/tools/tool-options.h | 2 +- src/wasm-binary.h | 11 +- src/wasm-builder.h | 65 +- src/wasm-delegations-fields.def | 34 +- src/wasm-delegations.def | 6 +- src/wasm-features.h | 12 +- src/wasm-interpreter.h | 16 +- src/wasm-ir-builder.h | 11 +- src/wasm-type.h | 3 + src/wasm.h | 83 +- src/wasm/wasm-binary.cpp | 62 +- src/wasm/wasm-ir-builder.cpp | 228 ++++- src/wasm/wasm-stack.cpp | 63 +- src/wasm/wasm-type-shape.cpp | 2 + src/wasm/wasm-type.cpp | 4 +- src/wasm/wasm-validator.cpp | 116 ++- src/wasm/wasm.cpp | 96 +- src/wasm2js.h | 12 +- test/lit/basic/stack_switching.wast | 947 ++++++++++++++++++ ...ind.wast => stack_switching_contbind.wast} | 27 + ...tnew.wast => stack_switching_contnew.wast} | 55 +- ...esume.wast => stack_switching_resume.wast} | 0 .../basic/stack_switching_resume_throw.wast | 163 +++ ...pend.wast => stack_switching_suspend.wast} | 0 test/lit/basic/stack_switching_switch.wast | 219 ++++ test/lit/basic/typed_continuations.wast | 68 -- test/lit/help/wasm-as.test | 4 +- test/lit/help/wasm-ctor-eval.test | 4 +- test/lit/help/wasm-dis.test | 4 +- test/lit/help/wasm-emscripten-finalize.test | 4 +- test/lit/help/wasm-merge.test | 4 +- test/lit/help/wasm-metadce.test | 4 +- test/lit/help/wasm-opt.test | 4 +- test/lit/help/wasm-reduce.test | 4 +- test/lit/help/wasm-split.test | 4 +- test/lit/help/wasm2js.test | 4 +- ..._roundtrip_print-features_all-features.txt | 2 +- test/unit/test_features.py | 16 +- 54 files changed, 2454 insertions(+), 463 deletions(-) create mode 100644 test/lit/basic/stack_switching.wast rename test/lit/basic/{typed_continuations_contbind.wast => stack_switching_contbind.wast} (81%) rename test/lit/basic/{typed_continuations_contnew.wast => stack_switching_contnew.wast} (56%) rename test/lit/basic/{typed_continuations_resume.wast => stack_switching_resume.wast} (100%) create mode 100644 test/lit/basic/stack_switching_resume_throw.wast rename test/lit/basic/{typed_continuations_suspend.wast => stack_switching_suspend.wast} (100%) create mode 100644 test/lit/basic/stack_switching_switch.wast delete mode 100644 test/lit/basic/typed_continuations.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 6427d052804..ebb7213635a 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -596,11 +596,13 @@ # Typed function references instructions ("call_ref", "makeCallRef(/*isReturn=*/false)"), ("return_call_ref", "makeCallRef(/*isReturn=*/true)"), - # Typed continuations instructions + # Stack switching instructions ("cont.new", "makeContNew()"), ("cont.bind", "makeContBind()"), - ("resume", "makeResume()"), ("suspend", "makeSuspend()"), + ("resume", "makeResume()"), + ("resume_throw", "makeResumeThrow()"), + ("switch", "makeStackSwitch()"), # GC ("ref.i31", "makeRefI31(Unshared)"), ("ref.i31_shared", "makeRefI31(Shared)"), diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 62b833a2653..78b78a089d8 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -90,6 +90,14 @@ 'names.wast', # huge amount of locals that make it extremely slow 'too_much_for_liveness.wasm', + # TODO: fuzzer support for stack switching + 'stack_switching.wast', + 'stack_switching_contnew.wast', + 'stack_switching_contbind.wast', + 'stack_switching_suspend.wast', + 'stack_switching_resume.wast', + 'stack_switching_resume_throw.wast', + 'stack_switching_switch.wast' ] diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 6b651db5fed..d7d1c5197e3 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -4807,12 +4807,23 @@ switch (buf[0]) { default: goto parse_error; } } - case 's': - if (op == "resume"sv) { - CHECK_ERR(makeResume(ctx, pos, annotations)); - return Ok{}; + case 's': { + switch (buf[6]) { + case '\0': + if (op == "resume"sv) { + CHECK_ERR(makeResume(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case '_': + if (op == "resume_throw"sv) { + CHECK_ERR(makeResumeThrow(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } case 't': { switch (buf[3]) { case 'h': @@ -5172,6 +5183,12 @@ switch (buf[0]) { return Ok{}; } goto parse_error; + case 'w': + if (op == "switch"sv) { + CHECK_ERR(makeStackSwitch(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; default: goto parse_error; } } diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 1a3a828f7f9..d60030b69ba 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -153,10 +153,12 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitStringEq(StringEq* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); } - Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); } Flow visitContNew(ContNew* curr) { WASM_UNREACHABLE("TODO"); } - Flow visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); } Flow visitSuspend(Suspend* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } }; } // anonymous namespace diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 5a62d225afe..42b13919726 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -182,8 +182,10 @@ void ReFinalize::visitStringWTF16Get(StringWTF16Get* curr) { curr->finalize(); } void ReFinalize::visitStringSliceWTF(StringSliceWTF* curr) { curr->finalize(); } void ReFinalize::visitContNew(ContNew* curr) { curr->finalize(); } void ReFinalize::visitContBind(ContBind* curr) { curr->finalize(); } -void ReFinalize::visitResume(Resume* curr) { curr->finalize(); } void ReFinalize::visitSuspend(Suspend* curr) { curr->finalize(getModule()); } +void ReFinalize::visitResume(Resume* curr) { curr->finalize(); } +void ReFinalize::visitResumeThrow(ResumeThrow* curr) { curr->finalize(); } +void ReFinalize::visitStackSwitch(StackSwitch* curr) { curr->finalize(); } void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); } void ReFinalize::visitGlobal(Global* curr) { WASM_UNREACHABLE("unimp"); } diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index 369365e728c..1771f2e0e88 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -85,7 +85,14 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) { } else if (auto* r = expr->dynCast()) { for (Index i = 0; i < r->handlerTags.size(); i++) { auto dest = r->handlerTags[i]; - if (dest == name) { + if (!dest.isNull() && dest == name) { + func(name, r->sentTypes[i]); + } + } + } else if (auto* r = expr->dynCast()) { + for (Index i = 0; i < r->handlerTags.size(); i++) { + auto dest = r->handlerTags[i]; + if (!dest.isNull() && dest == name) { func(name, r->sentTypes[i]); } } @@ -118,6 +125,8 @@ void operateOnScopeNameUsesAndSentValues(Expression* expr, T func) { // The values are supplied by suspend instructions executed while running // the continuation, so we are unable to know what they will be here. func(name, nullptr); + } else if (expr->is()) { + func(name, nullptr); } else { assert(expr->is() || expr->is()); // delegate or rethrow } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 685c171d016..5d2bed102e4 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1085,39 +1085,76 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->end, Type::i32); } - void visitContBind(ContBind* curr) { - auto paramsBefore = - curr->contTypeBefore.getContinuation().type.getSignature().params; - auto paramsAfter = - curr->contTypeAfter.getContinuation().type.getSignature().params; - assert(paramsBefore.size() >= paramsAfter.size()); - auto n = paramsBefore.size() - paramsAfter.size(); + void visitContNew(ContNew* curr) { note(&curr->func, curr->type); } + + void visitContBind(ContBind* curr, + std::optional src = std::nullopt, + std::optional dest = std::nullopt) { + if (!src.has_value()) { + src = curr->cont->type.getHeapType(); + } + if (!dest.has_value()) { + dest = curr->type.getHeapType(); + } + auto sourceParams = src->getContinuation().type.getSignature().params; + auto targetParams = dest->getContinuation().type.getSignature().params; + assert(sourceParams.size() >= targetParams.size()); + auto n = sourceParams.size() - targetParams.size(); assert(curr->operands.size() == n); for (size_t i = 0; i < n; ++i) { - note(&curr->operands[i], paramsBefore[i]); + note(&curr->operands[i], sourceParams[i]); } - note(&curr->cont, Type(curr->contTypeBefore, Nullable)); + note(&curr->cont, Type(*src, Nullable)); } - void visitContNew(ContNew* curr) { - note(&curr->func, Type(curr->contType.getContinuation().type, Nullable)); + void visitSuspend(Suspend* curr) { + auto params = wasm.getTag(curr->tag)->params(); + assert(params.size() == curr->operands.size()); + for (size_t i = 0; i < params.size(); ++i) { + note(&curr->operands[i], params[i]); + } } - void visitResume(Resume* curr) { - auto params = curr->contType.getContinuation().type.getSignature().params; + void visitResume(Resume* curr, std::optional ct = std::nullopt) { + if (!ct.has_value()) { + ct = curr->cont->type.getHeapType(); + } + assert(ct->isContinuation()); + auto params = ct->getContinuation().type.getSignature().params; assert(params.size() == curr->operands.size()); for (size_t i = 0; i < params.size(); ++i) { note(&curr->operands[i], params[i]); } - note(&curr->cont, Type(curr->contType, Nullable)); + note(&curr->cont, Type(*ct, Nullable)); } - void visitSuspend(Suspend* curr) { + void visitResumeThrow(ResumeThrow* curr, + std::optional ct = std::nullopt) { + if (!ct.has_value()) { + ct = curr->cont->type.getHeapType(); + } + assert(ct->isContinuation()); auto params = wasm.getTag(curr->tag)->params(); assert(params.size() == curr->operands.size()); for (size_t i = 0; i < params.size(); ++i) { note(&curr->operands[i], params[i]); } + note(&curr->cont, Type(*ct, Nullable)); + } + + void visitStackSwitch(StackSwitch* curr, + std::optional ct = std::nullopt) { + if (!ct.has_value()) { + ct = curr->cont->type.getHeapType(); + } + assert(ct->isContinuation()); + auto params = ct->getContinuation().type.getSignature().params; + assert(params.size() >= 1 && + ((params.size() - 1) == curr->operands.size())); + for (size_t i = 0; i < params.size() - 1; ++i) { + note(&curr->operands[i], params[i]); + } + note(&curr->cont, Type(*ct, Nullable)); } }; diff --git a/src/ir/cost.h b/src/ir/cost.h index a172e900403..5be31895287 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -764,6 +764,10 @@ struct CostAnalyzer : public OverriddenVisitor { return 8 + visit(curr->ref) + visit(curr->start) + visit(curr->end); } + CostType visitContNew(ContNew* curr) { + // Some arbitrary "high" value, reflecting that this may allocate a stack + return 14 + visit(curr->func); + } CostType visitContBind(ContBind* curr) { // Inspired by struct.new: The only cost of cont.bind is that it may need to // allocate a buffer to hold the arguments. @@ -774,9 +778,12 @@ struct CostAnalyzer : public OverriddenVisitor { } return ret; } - CostType visitContNew(ContNew* curr) { - // Some arbitrary "high" value, reflecting that this may allocate a stack - return 14 + visit(curr->func); + CostType visitSuspend(Suspend* curr) { + CostType ret = 12; + for (auto* arg : curr->operands) { + ret += visit(arg); + } + return ret; } CostType visitResume(Resume* curr) { // Inspired by indirect calls, but twice the cost. @@ -786,8 +793,17 @@ struct CostAnalyzer : public OverriddenVisitor { } return ret; } - CostType visitSuspend(Suspend* curr) { - CostType ret = 12; + CostType visitResumeThrow(ResumeThrow* curr) { + // Inspired by indirect calls, but twice the cost. + CostType ret = 12 + visit(curr->cont); + for (auto* arg : curr->operands) { + ret += visit(arg); + } + return ret; + } + CostType visitStackSwitch(StackSwitch* curr) { + // Inspired by indirect calls, but twice the cost. + CostType ret = 12 + visit(curr->cont); for (auto* arg : curr->operands) { ret += visit(arg); } diff --git a/src/ir/effects.h b/src/ir/effects.h index 4b05ea34f12..fa216799729 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1049,12 +1049,23 @@ class EffectAnalyzer { // traps when ref is null. parent.implicitTrap = true; } + void visitContNew(ContNew* curr) { + // traps when curr->func is null ref. + parent.implicitTrap = true; + } void visitContBind(ContBind* curr) { // traps when curr->cont is null ref. parent.implicitTrap = true; } - void visitContNew(ContNew* curr) { - // traps when curr->func is null ref. + void visitSuspend(Suspend* curr) { + // Similar to resume/call: Suspending means that we execute arbitrary + // other code before we may resume here. + parent.calls = true; + if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { + parent.throws_ = true; + } + + // A suspend may go unhandled and therefore trap. parent.implicitTrap = true; } void visitResume(Resume* curr) { @@ -1069,10 +1080,26 @@ class EffectAnalyzer { parent.throws_ = true; } } - void visitSuspend(Suspend* curr) { - // Similar to resume/call: Suspending means that we execute arbitrary - // other code before we may resume here. + void visitResumeThrow(ResumeThrow* curr) { + // This acts as a kitchen sink effect. parent.calls = true; + + // resume_throw instructions accept nullable continuation + // references and trap on null. + parent.implicitTrap = true; + + if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { + parent.throws_ = true; + } + } + void visitStackSwitch(StackSwitch* curr) { + // This acts as a kitchen sink effect. + parent.calls = true; + + // switch instructions accept nullable continuation references + // and trap on null. + parent.implicitTrap = true; + if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { parent.throws_ = true; } diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 44652a5531e..328aee64d22 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -448,12 +448,19 @@ struct CodeScanner } else if (auto* set = curr->dynCast()) { info.note(set->ref->type); } else if (auto* contBind = curr->dynCast()) { - info.note(contBind->contTypeBefore); - info.note(contBind->contTypeAfter); + info.note(contBind->cont->type); + info.note(contBind->type); } else if (auto* contNew = curr->dynCast()) { - info.note(contNew->contType); + info.note(contNew->type); } else if (auto* resume = curr->dynCast()) { - info.note(resume->contType); + info.note(resume->cont->type); + info.note(resume->type); + } else if (auto* resumeThrow = curr->dynCast()) { + info.note(resumeThrow->cont->type); + info.note(resumeThrow->type); + } else if (auto* switch_ = curr->dynCast()) { + info.note(switch_->cont->type); + info.note(switch_->type); } else if (Properties::isControlFlowStructure(curr)) { info.noteControlFlow(Signature(Type::none, curr->type)); } diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index a9a26f3d719..93f3c57fec6 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1269,11 +1269,15 @@ struct InfoCollector void visitReturn(Return* curr) { addResult(curr->value); } + void visitContNew(ContNew* curr) { + // TODO: optimize when possible + addRoot(curr); + } void visitContBind(ContBind* curr) { // TODO: optimize when possible addRoot(curr); } - void visitContNew(ContNew* curr) { + void visitSuspend(Suspend* curr) { // TODO: optimize when possible addRoot(curr); } @@ -1281,7 +1285,11 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } - void visitSuspend(Suspend* curr) { + void visitResumeThrow(ResumeThrow* curr) { + // TODO: optimize when possible + addRoot(curr); + } + void visitStackSwitch(StackSwitch* curr) { // TODO: optimize when possible addRoot(curr); } diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index ba5640eff7f..7d9f30b6778 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -412,10 +412,16 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStringWTF16Get(StringWTF16Get* curr) {} void visitStringSliceWTF(StringSliceWTF* curr) {} - void visitContBind(ContBind* curr) { WASM_UNREACHABLE("not implemented"); } void visitContNew(ContNew* curr) { WASM_UNREACHABLE("not implemented"); } - void visitResume(Resume* curr) { WASM_UNREACHABLE("not implemented"); } + void visitContBind(ContBind* curr) { WASM_UNREACHABLE("not implemented"); } void visitSuspend(Suspend* curr) { WASM_UNREACHABLE("not implemented"); } + void visitResume(Resume* curr) { WASM_UNREACHABLE("not implemented"); } + void visitResumeThrow(ResumeThrow* curr) { + WASM_UNREACHABLE("not implemented"); + } + void visitStackSwitch(StackSwitch* curr) { + WASM_UNREACHABLE("not implemented"); + } }; } // namespace wasm diff --git a/src/parser/contexts.h b/src/parser/contexts.h index c2e6eebf102..a09433f809d 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -349,6 +349,8 @@ struct NullInstrParserCtx { using ExprT = Ok; using CatchT = Ok; using CatchListT = Ok; + using OnClauseT = Ok; + using OnClauseListT = Ok; using TagLabelListT = Ok; using FieldIdxT = Ok; @@ -442,8 +444,10 @@ struct NullInstrParserCtx { return Ok{}; } - TagLabelListT makeTagLabelList() { return Ok{}; } - void appendTagLabel(TagLabelListT&, TagIdxT, LabelIdxT) {} + OnClauseListT makeOnClauseList() { return Ok{}; } + void appendOnClause(OnClauseListT&, OnClauseT) {} + OnClauseT makeOnLabel(TagIdxT, LabelIdxT) { return Ok{}; } + OnClauseT makeOnSwitch(TagIdxT) { return Ok{}; } void setSrcLoc(const std::vector&) {} @@ -858,12 +862,15 @@ struct NullInstrParserCtx { return Ok{}; } template + Result<> makeContNew(Index, const std::vector&, HeapTypeT) { + return Ok{}; + } + template Result<> makeContBind(Index, const std::vector&, HeapTypeT, HeapTypeT) { return Ok{}; } - template - Result<> makeContNew(Index, const std::vector&, HeapTypeT) { + Result<> makeSuspend(Index, const std::vector&, TagIdxT) { return Ok{}; } template @@ -873,7 +880,17 @@ struct NullInstrParserCtx { const TagLabelListT&) { return Ok{}; } - Result<> makeSuspend(Index, const std::vector&, TagIdxT) { + template + Result<> makeResumeThrow(Index, + const std::vector&, + HeapTypeT, + TagIdxT, + const TagLabelListT&) { + return Ok{}; + } + template + Result<> + makeStackSwitch(Index, const std::vector&, HeapTypeT, TagIdxT) { return Ok{}; } }; @@ -1405,6 +1422,9 @@ struct ParseDefsCtx : TypeParserCtx { using TagLabelListT = std::vector>; + struct OnClauseInfo; + using OnClauseListT = std::vector; + Lexer in; Module& wasm; @@ -1500,9 +1520,20 @@ struct ParseDefsCtx : TypeParserCtx { CatchInfo makeCatchAll(Index label) { return {{}, label, false}; } CatchInfo makeCatchAllRef(Index label) { return {{}, label, true}; } - TagLabelListT makeTagLabelList() { return {}; } - void appendTagLabel(TagLabelListT& tagLabels, Name tag, Index label) { - tagLabels.push_back({tag, label}); + struct OnClauseInfo { + Name tag; + Index label; // unset when isOnSwitch = true. + bool isOnSwitch; + }; + + OnClauseInfo makeOnLabel(Name tag, Index label) { + return {tag, label, false}; + } + OnClauseInfo makeOnSwitch(Name tag) { return {tag, {}, true}; } + + OnClauseListT makeOnClauseList() { return {}; } + void appendOnClause(std::vector& list, OnClauseInfo info) { + list.push_back(info); } Result getHeapTypeFromIdx(Index idx) { @@ -2629,37 +2660,68 @@ struct ParseDefsCtx : TypeParserCtx { return withLoc(pos, irBuilder.makeStringSliceWTF()); } - Result<> makeContBind(Index pos, - const std::vector& annotations, - HeapType contTypeBefore, - HeapType contTypeAfter) { - return withLoc(pos, irBuilder.makeContBind(contTypeBefore, contTypeAfter)); - } - Result<> makeContNew(Index pos, const std::vector& annotations, HeapType type) { return withLoc(pos, irBuilder.makeContNew(type)); } + Result<> makeContBind(Index pos, + const std::vector& annotations, + HeapType sourceType, + HeapType targetType) { + return withLoc(pos, irBuilder.makeContBind(sourceType, targetType)); + } + + Result<> + makeSuspend(Index pos, const std::vector& annotations, Name tag) { + return withLoc(pos, irBuilder.makeSuspend(tag)); + } + Result<> makeResume(Index pos, const std::vector& annotations, HeapType type, - const TagLabelListT& tagLabels) { + const std::vector& resumetable) { std::vector tags; - std::vector labels; - tags.reserve(tagLabels.size()); - labels.reserve(tagLabels.size()); - for (auto& [tag, label] : tagLabels) { - tags.push_back(tag); - labels.push_back(label); + std::vector> labels; + tags.reserve(resumetable.size()); + labels.reserve(resumetable.size()); + for (const OnClauseInfo& info : resumetable) { + tags.push_back(info.tag); + if (info.isOnSwitch) { + labels.push_back(std::nullopt); + } else { + labels.push_back(std::optional(info.label)); + } } return withLoc(pos, irBuilder.makeResume(type, tags, labels)); } - Result<> - makeSuspend(Index pos, const std::vector& annotations, Name tag) { - return withLoc(pos, irBuilder.makeSuspend(tag)); + Result<> makeResumeThrow(Index pos, + const std::vector& annotations, + HeapType type, + Name tag, + const std::vector& resumetable) { + std::vector tags; + std::vector> labels; + tags.reserve(resumetable.size()); + labels.reserve(resumetable.size()); + for (const OnClauseInfo& info : resumetable) { + tags.push_back(info.tag); + if (info.isOnSwitch) { + labels.push_back(std::nullopt); + } else { + labels.push_back(std::optional(info.label)); + } + } + return withLoc(pos, irBuilder.makeResumeThrow(type, tag, tags, labels)); + } + + Result<> makeStackSwitch(Index pos, + const std::vector& annotations, + HeapType type, + Name tag) { + return withLoc(pos, irBuilder.makeStackSwitch(type, tag)); } }; diff --git a/src/parser/parsers.h b/src/parser/parsers.h index ca502355a9d..48f65c497e0 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -309,13 +309,17 @@ Result<> makeStringWTF16Get(Ctx&, Index, const std::vector&); template Result<> makeStringSliceWTF(Ctx&, Index, const std::vector&); template +Result<> makeContNew(Ctx*, Index, const std::vector&); +template Result<> makeContBind(Ctx&, Index, const std::vector&); template -Result<> makeContNew(Ctx*, Index, const std::vector&); +Result<> makeSuspend(Ctx&, Index, const std::vector&); template Result<> makeResume(Ctx&, Index, const std::vector&); template -Result<> makeSuspend(Ctx&, Index, const std::vector&); +Result<> makeResumeThrow(Ctx&, Index, const std::vector&); +template +Result<> makeStackSwitch(Ctx&, Index, const std::vector&); template Result<> ignore(Ctx&, Index, const std::vector&) { @@ -2507,57 +2511,99 @@ Result<> makeStringSliceWTF(Ctx& ctx, return ctx.makeStringSliceWTF(pos, annotations); } +template +Result<> +makeContNew(Ctx& ctx, Index pos, const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); + + return ctx.makeContNew(pos, annotations, *type); +} + // contbind ::= 'cont.bind' typeidx typeidx template Result<> makeContBind(Ctx& ctx, Index pos, const std::vector& annotations) { - auto typeBefore = typeidx(ctx); - CHECK_ERR(typeBefore); + auto sourceType = typeidx(ctx); + CHECK_ERR(sourceType); - auto typeAfter = typeidx(ctx); - CHECK_ERR(typeAfter); + auto targetType = typeidx(ctx); + CHECK_ERR(targetType); - return ctx.makeContBind(pos, annotations, *typeBefore, *typeAfter); + return ctx.makeContBind(pos, annotations, *sourceType, *targetType); } template Result<> -makeContNew(Ctx& ctx, Index pos, const std::vector& annotations) { - auto type = typeidx(ctx); - CHECK_ERR(type); +makeSuspend(Ctx& ctx, Index pos, const std::vector& annotations) { + auto tag = tagidx(ctx); + CHECK_ERR(tag); - return ctx.makeContNew(pos, annotations, *type); + return ctx.makeSuspend(pos, annotations, *tag); } -// resume ::= 'resume' typeidx ('(' 'on' tagidx labelidx ')')* +// resumetable ::= ('(' 'on' tagidx labelidx | 'on' tagidx switch ')')* template -Result<> -makeResume(Ctx& ctx, Index pos, const std::vector& annotations) { - auto type = typeidx(ctx); - CHECK_ERR(type); - - auto tagLabels = ctx.makeTagLabelList(); +Result makeResumeTable(Ctx& ctx) { + auto resumetable = ctx.makeOnClauseList(); while (ctx.in.takeSExprStart("on"sv)) { auto tag = tagidx(ctx); CHECK_ERR(tag); - auto label = labelidx(ctx); - CHECK_ERR(label); - ctx.appendTagLabel(tagLabels, *tag, *label); + if (ctx.in.takeKeyword("switch")) { + ctx.appendOnClause(resumetable, ctx.makeOnSwitch(*tag)); + } else { + auto label = labelidx(ctx); + CHECK_ERR(label); + ctx.appendOnClause(resumetable, ctx.makeOnLabel(*tag, *label)); + } if (!ctx.in.takeRParen()) { return ctx.in.err("expected ')' at end of handler clause"); } } - - return ctx.makeResume(pos, annotations, *type, tagLabels); + return resumetable; } +// resume ::= 'resume' typeidx resumetable template Result<> -makeSuspend(Ctx& ctx, Index pos, const std::vector& annotations) { +makeResume(Ctx& ctx, Index pos, const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); + + auto resumetable = makeResumeTable(ctx); + CHECK_ERR(resumetable); + + return ctx.makeResume(pos, annotations, *type, *resumetable); +} + +// resume_throw ::= 'resume_throw' typeidx tagidx ('(' 'on' tagidx labelidx | +// 'on' tagidx switch ')')* +template +Result<> makeResumeThrow(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); + auto exnTag = tagidx(ctx); + CHECK_ERR(exnTag); + + auto resumetable = makeResumeTable(ctx); + CHECK_ERR(resumetable); + + return ctx.makeResumeThrow(pos, annotations, *type, *exnTag, *resumetable); +} + +// switch ::= 'switch' typeidx tagidx +template +Result<> makeStackSwitch(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); auto tag = tagidx(ctx); CHECK_ERR(tag); - return ctx.makeSuspend(pos, annotations, *tag); + return ctx.makeStackSwitch(pos, annotations, *type, *tag); } // ======= diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index e9f8509fef6..71878c973b3 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -309,7 +309,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { void visitLoop(Loop* curr); void visitTry(Try* curr); void visitTryTable(TryTable* curr); - void visitResume(Resume* curr); + bool maybePrintUnreachableReplacement(Expression* curr, Type type); bool maybePrintUnreachableOrNullReplacement(Expression* curr, Type type); void visitCallRef(CallRef* curr) { @@ -398,6 +398,35 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } + void visitContNew(ContNew* curr) { + if (!maybePrintUnreachableReplacement(curr, curr->type)) { + visitExpression(curr); + } + } + void visitContBind(ContBind* curr) { + if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && + !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + visitExpression(curr); + } + } + void visitResume(Resume* curr) { + if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && + !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + visitExpression(curr); + } + } + void visitResumeThrow(ResumeThrow* curr) { + if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && + !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + visitExpression(curr); + } + } + void visitStackSwitch(StackSwitch* curr) { + if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && + !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + visitExpression(curr); + } + } // Module-level visitors void handleSignature(Function* curr, bool printImplicitNames = false); @@ -2515,35 +2544,66 @@ struct PrintExpressionContents void visitStringSliceWTF(StringSliceWTF* curr) { printMedium(o, "stringview_wtf16.slice"); } + void visitContNew(ContNew* curr) { + assert(curr->type.isContinuation()); + printMedium(o, "cont.new "); + printHeapType(curr->type.getHeapType()); + } void visitContBind(ContBind* curr) { + assert(curr->cont->type.isContinuation() && curr->type.isContinuation()); printMedium(o, "cont.bind "); - printHeapType(curr->contTypeBefore); + printHeapType(curr->cont->type.getHeapType()); o << ' '; - printHeapType(curr->contTypeAfter); + printHeapType(curr->type.getHeapType()); } - void visitContNew(ContNew* curr) { - printMedium(o, "cont.new "); - printHeapType(curr->contType); + void visitSuspend(Suspend* curr) { + printMedium(o, "suspend "); + curr->tag.print(o); } - - void visitResume(Resume* curr) { - printMedium(o, "resume"); - - o << ' '; - printHeapType(curr->contType); - + template + static void handleResumeTable(std::ostream& o, ResumeType* curr) { + static_assert(std::is_base_of::value || + std::is_base_of::value); for (Index i = 0; i < curr->handlerTags.size(); i++) { o << " ("; printMedium(o, "on "); curr->handlerTags[i].print(o); o << ' '; - curr->handlerBlocks[i].print(o); + if (curr->handlerBlocks[i].isNull()) { + o << "switch"; + } else { + curr->handlerBlocks[i].print(o); + } o << ')'; } } + void visitResume(Resume* curr) { + assert(curr->cont->type.isContinuation()); + printMedium(o, "resume"); - void visitSuspend(Suspend* curr) { - printMedium(o, "suspend "); + o << ' '; + printHeapType(curr->cont->type.getHeapType()); + + handleResumeTable(o, curr); + } + void visitResumeThrow(ResumeThrow* curr) { + assert(curr->cont->type.isContinuation()); + printMedium(o, "resume_throw"); + + o << ' '; + printHeapType(curr->cont->type.getHeapType()); + o << ' '; + curr->tag.print(o); + + handleResumeTable(o, curr); + } + void visitStackSwitch(StackSwitch* curr) { + assert(curr->cont->type.isContinuation()); + printMedium(o, "switch"); + + o << ' '; + printHeapType(curr->cont->type.getHeapType()); + o << ' '; curr->tag.print(o); } }; @@ -2837,7 +2897,7 @@ void PrintSExpression::visitLoop(Loop* curr) { // The parenthesis wrapping do/catch/catch_all is just a syntax and does not // affect nested depths of instructions within. // -// try-delegate is written in the forded format as +// try-delegate is written in the folded format as // (try // (do // ... @@ -2911,23 +2971,6 @@ void PrintSExpression::visitTryTable(TryTable* curr) { controlFlowDepth--; } -void PrintSExpression::visitResume(Resume* curr) { - controlFlowDepth++; - o << '('; - printExpressionContents(curr); - - incIndent(); - - for (Index i = 0; i < curr->operands.size(); i++) { - printFullLine(curr->operands[i]); - } - - printFullLine(curr->cont); - - controlFlowDepth--; - decIndent(); -} - bool PrintSExpression::maybePrintUnreachableReplacement(Expression* curr, Type type) { // When we cannot print an instruction because the child from which it's diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 074e7c2505a..4dc0cbb444b 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -875,10 +875,12 @@ struct TransferFn : OverriddenVisitor { void visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("TODO"); } void visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); } - void visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); } void visitContNew(ContNew* curr) { WASM_UNREACHABLE("TODO"); } - void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } + void visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); } void visitSuspend(Suspend* curr) { WASM_UNREACHABLE("TODO"); } + void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } + void visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } + void visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } }; struct TypeGeneralizing : WalkerPass> { diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index c7acf1d4618..bff71bb4138 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -103,7 +103,7 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::ExtendedConst, "extended const expressions") .addFeature(FeatureSet::Strings, "strings") .addFeature(FeatureSet::MultiMemory, "multimemory") - .addFeature(FeatureSet::TypedContinuations, "typed continuations") + .addFeature(FeatureSet::StackSwitching, "stack switching") .addFeature(FeatureSet::SharedEverything, "shared-everything threads") .addFeature(FeatureSet::FP16, "float 16 operations") .add("--enable-typed-function-references", diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 0669d978e22..9765afd0cbe 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -393,7 +393,7 @@ extern const char* RelaxedSIMDFeature; extern const char* ExtendedConstFeature; extern const char* StringsFeature; extern const char* MultiMemoryFeature; -extern const char* TypedContinuationsFeature; +extern const char* StackSwitchingFeature; extern const char* SharedEverythingFeature; extern const char* FP16Feature; extern const char* BulkMemoryOptFeature; @@ -1160,12 +1160,17 @@ enum ASTNodes { StringNewLossyUTF8Array = 0xb4, StringEncodeLossyUTF8Array = 0xb6, - // typed continuation opcodes + // stack switching opcodes ContNew = 0xe0, ContBind = 0xe1, Suspend = 0xe2, Resume = 0xe3, - + ResumeThrow = 0xe4, + Switch = 0xe5, // NOTE(dhil): the internal class is known as + // StackSwitch to avoid conflict with the existing + // 'switch table'. + OnLabel = 0x00, // (on $tag $label) + OnSwitch = 0x01 // (on $tag switch) }; enum MemoryAccess { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 0666a489bfe..0e28f3d5a66 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1193,44 +1193,67 @@ class Builder { ret->finalize(); return ret; } - ContBind* makeContBind(HeapType contTypeBefore, - HeapType contTypeAfter, - const std::vector& operands, + ContNew* makeContNew(HeapType type, Expression* func) { + auto* ret = wasm.allocator.alloc(); + ret->type = Type(type, NonNullable); + ret->func = func; + ret->finalize(); + return ret; + } + ContBind* makeContBind(HeapType targetType, + ExpressionList&& operands, Expression* cont) { auto* ret = wasm.allocator.alloc(); - ret->contTypeBefore = contTypeBefore; - ret->contTypeAfter = contTypeAfter; - ret->operands.set(operands); + ret->type = Type(targetType, NonNullable); + ret->operands = std::move(operands); ret->cont = cont; ret->finalize(); return ret; } - ContNew* makeContNew(HeapType contType, Expression* func) { - auto* ret = wasm.allocator.alloc(); - ret->contType = contType; - ret->func = func; - ret->finalize(); + Suspend* makeSuspend(Name tag, const std::vector& args) { + auto* ret = wasm.allocator.alloc(); + ret->tag = tag; + ret->operands.set(args); + ret->finalize(&wasm); return ret; } - Resume* makeResume(HeapType contType, - const std::vector& handlerTags, + Resume* makeResume(const std::vector& handlerTags, const std::vector& handlerBlocks, - const std::vector& operands, + const std::vector& sentTypes, + ExpressionList&& operands, Expression* cont) { auto* ret = wasm.allocator.alloc(); - ret->contType = contType; ret->handlerTags.set(handlerTags); ret->handlerBlocks.set(handlerBlocks); - ret->operands.set(operands); + ret->sentTypes.set(sentTypes); + ret->operands = std::move(operands); ret->cont = cont; - ret->finalize(&wasm); + ret->finalize(); return ret; } - Suspend* makeSuspend(Name tag, const std::vector& args) { - auto* ret = wasm.allocator.alloc(); + ResumeThrow* makeResumeThrow(Name tag, + const std::vector& handlerTags, + const std::vector& handlerBlocks, + const std::vector& sentTypes, + ExpressionList&& operands, + Expression* cont) { + auto* ret = wasm.allocator.alloc(); ret->tag = tag; - ret->operands.set(args); - ret->finalize(&wasm); + ret->handlerTags.set(handlerTags); + ret->handlerBlocks.set(handlerBlocks); + ret->sentTypes.set(sentTypes); + ret->operands = std::move(operands); + ret->cont = cont; + ret->finalize(); + return ret; + } + StackSwitch* + makeStackSwitch(Name tag, ExpressionList&& operands, Expression* cont) { + auto* ret = wasm.allocator.alloc(); + ret->tag = tag; + ret->operands = std::move(operands); + ret->cont = cont; + ret->finalize(); return ret; } diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 623ccbd397e..e40f7a06f02 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -783,17 +783,19 @@ DELEGATE_FIELD_CHILD(StringSliceWTF, start) DELEGATE_FIELD_CHILD(StringSliceWTF, ref) DELEGATE_FIELD_CASE_END(StringSliceWTF) +DELEGATE_FIELD_CASE_START(ContNew) +DELEGATE_FIELD_CHILD(ContNew, func) +DELEGATE_FIELD_CASE_END(ContNew) + DELEGATE_FIELD_CASE_START(ContBind) DELEGATE_FIELD_CHILD(ContBind, cont) DELEGATE_FIELD_CHILD_VECTOR(ContBind, operands) -DELEGATE_FIELD_HEAPTYPE(ContBind, contTypeAfter) -DELEGATE_FIELD_HEAPTYPE(ContBind, contTypeBefore) DELEGATE_FIELD_CASE_END(ContBind) -DELEGATE_FIELD_CASE_START(ContNew) -DELEGATE_FIELD_CHILD(ContNew, func) -DELEGATE_FIELD_HEAPTYPE(ContNew, contType) -DELEGATE_FIELD_CASE_END(ContNew) +DELEGATE_FIELD_CASE_START(Suspend) +DELEGATE_FIELD_CHILD_VECTOR(Suspend, operands) +DELEGATE_FIELD_NAME_KIND(Suspend, tag, ModuleItemKind::Tag) +DELEGATE_FIELD_CASE_END(Suspend) DELEGATE_FIELD_CASE_START(Resume) DELEGATE_FIELD_TYPE_VECTOR(Resume, sentTypes) @@ -801,13 +803,23 @@ DELEGATE_FIELD_CHILD(Resume, cont) DELEGATE_FIELD_CHILD_VECTOR(Resume, operands) DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(Resume, handlerBlocks) DELEGATE_FIELD_NAME_KIND_VECTOR(Resume, handlerTags, ModuleItemKind::Tag) -DELEGATE_FIELD_HEAPTYPE(Resume, contType) DELEGATE_FIELD_CASE_END(Resume) -DELEGATE_FIELD_CASE_START(Suspend) -DELEGATE_FIELD_CHILD_VECTOR(Suspend, operands) -DELEGATE_FIELD_NAME_KIND(Suspend, tag, ModuleItemKind::Tag) -DELEGATE_FIELD_CASE_END(Suspend) +DELEGATE_FIELD_CASE_START(ResumeThrow) +DELEGATE_FIELD_TYPE_VECTOR(ResumeThrow, sentTypes) +DELEGATE_FIELD_CHILD(ResumeThrow, cont) +DELEGATE_FIELD_CHILD_VECTOR(ResumeThrow, operands) +DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(ResumeThrow, handlerBlocks) +DELEGATE_FIELD_NAME_KIND_VECTOR(ResumeThrow, handlerTags, ModuleItemKind::Tag) +DELEGATE_FIELD_NAME_KIND(ResumeThrow, tag, ModuleItemKind::Tag) +DELEGATE_FIELD_CASE_END(ResumeThrow) + +DELEGATE_FIELD_CASE_START(StackSwitch) +DELEGATE_FIELD_CHILD(StackSwitch, cont) +DELEGATE_FIELD_CHILD_VECTOR(StackSwitch, operands) +DELEGATE_FIELD_NAME_KIND(StackSwitch, tag, ModuleItemKind::Tag) +DELEGATE_FIELD_CASE_END(StackSwitch) + DELEGATE_FIELD_MAIN_END diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index eb24feb7b83..4e78de1a56d 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -103,9 +103,11 @@ DELEGATE(StringConcat); DELEGATE(StringEq); DELEGATE(StringWTF16Get); DELEGATE(StringSliceWTF); -DELEGATE(ContBind); DELEGATE(ContNew); -DELEGATE(Resume); +DELEGATE(ContBind); DELEGATE(Suspend); +DELEGATE(Resume); +DELEGATE(ResumeThrow); +DELEGATE(StackSwitch); #undef DELEGATE diff --git a/src/wasm-features.h b/src/wasm-features.h index 20eec56bbab..7ada02e9979 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -46,7 +46,7 @@ struct FeatureSet { ExtendedConst = 1 << 13, Strings = 1 << 14, MultiMemory = 1 << 15, - TypedContinuations = 1 << 16, + StackSwitching = 1 << 16, SharedEverything = 1 << 17, FP16 = 1 << 18, BulkMemoryOpt = 1 << 19, // Just the memory.copy and fill operations @@ -95,8 +95,8 @@ struct FeatureSet { return "strings"; case MultiMemory: return "multimemory"; - case TypedContinuations: - return "typed-continuations"; + case StackSwitching: + return "stack-switching"; case SharedEverything: return "shared-everything"; case FP16: @@ -149,9 +149,7 @@ struct FeatureSet { bool hasExtendedConst() const { return (features & ExtendedConst) != 0; } bool hasStrings() const { return (features & Strings) != 0; } bool hasMultiMemory() const { return (features & MultiMemory) != 0; } - bool hasTypedContinuations() const { - return (features & TypedContinuations) != 0; - } + bool hasStackSwitching() const { return (features & StackSwitching) != 0; } bool hasSharedEverything() const { return (features & SharedEverything) != 0; } @@ -182,7 +180,7 @@ struct FeatureSet { void setExtendedConst(bool v = true) { set(ExtendedConst, v); } void setStrings(bool v = true) { set(Strings, v); } void setMultiMemory(bool v = true) { set(MultiMemory, v); } - void setTypedContinuations(bool v = true) { set(TypedContinuations, v); } + void setStackSwitching(bool v = true) { set(StackSwitching, v); } void setSharedEverything(bool v = true) { set(SharedEverything, v); } void setFP16(bool v = true) { set(FP16, v); } void setBulkMemoryOpt(bool v = true) { set(BulkMemoryOpt, v); } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 186245ab004..2a019513d2a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2617,10 +2617,16 @@ class ConstantExpressionRunner : public ExpressionRunner { } return ExpressionRunner::visitRefAs(curr); } - Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("unimplemented"); } Flow visitContNew(ContNew* curr) { WASM_UNREACHABLE("unimplemented"); } - Flow visitResume(Resume* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("unimplemented"); } Flow visitSuspend(Suspend* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitResume(Resume* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitResumeThrow(ResumeThrow* curr) { + WASM_UNREACHABLE("unimplemented"); + } + Flow visitStackSwitch(StackSwitch* curr) { + WASM_UNREACHABLE("unimplemented"); + } void trap(const char* why) override { throw NonconstantException(); } @@ -4330,10 +4336,12 @@ class ModuleRunnerBase : public ExpressionRunner { multiValues.pop_back(); return ret; } - Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitContNew(ContNew* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSuspend(Suspend* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } void trap(const char* why) override { externalInterface->trap(why); } diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 51c04a446c6..fb2727bf94c 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -233,12 +233,17 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeStringWTF16Get(); Result<> makeStringIterNext(); Result<> makeStringSliceWTF(); - Result<> makeContBind(HeapType contTypeBefore, HeapType contTypeAfter); Result<> makeContNew(HeapType ct); + Result<> makeContBind(HeapType sourceType, HeapType targetType); + Result<> makeSuspend(Name tag); Result<> makeResume(HeapType ct, const std::vector& tags, - const std::vector& labels); - Result<> makeSuspend(Name tag); + const std::vector>& labels); + Result<> makeResumeThrow(HeapType ct, + Name tag, + const std::vector& tags, + const std::vector>& labels); + Result<> makeStackSwitch(HeapType ct, Name tag); // Private functions that must be public for technical reasons. Result<> visitExpression(Expression*); diff --git a/src/wasm-type.h b/src/wasm-type.h index 1e7c0a0ba15..8e481e4e7cf 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -369,6 +369,9 @@ class Type { bool isArray() const { return isRef() && getHeapType().isArray(); } bool isExn() const { return isRef() && getHeapType().isExn(); } bool isString() const { return isRef() && getHeapType().isString(); } + bool isContinuation() const { + return isRef() && getHeapType().isContinuation(); + } bool isDefaultable() const; // TODO: Allow this only for reference types. diff --git a/src/wasm.h b/src/wasm.h index eee81fe106b..f2ecd76873d 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -744,10 +744,13 @@ class Expression { StringEqId, StringWTF16GetId, StringSliceWTFId, - ContBindId, ContNewId, - ResumeId, + ContBindId, SuspendId, + ResumeId, + ResumeThrowId, + // Id for the stack switching `switch` + StackSwitchId, NumExpressionIds }; Id _id; @@ -1960,27 +1963,37 @@ class StringSliceWTF : public SpecificExpression { void finalize(); }; +class ContNew : public SpecificExpression { +public: + ContNew() = default; + ContNew(MixedArena& allocator) {} + + Expression* func; + + void finalize(); +}; + class ContBind : public SpecificExpression { public: ContBind(MixedArena& allocator) : operands(allocator) {} - HeapType contTypeBefore; - HeapType contTypeAfter; ExpressionList operands; Expression* cont; void finalize(); }; -class ContNew : public SpecificExpression { +class Suspend : public SpecificExpression { public: - ContNew() = default; - ContNew(MixedArena& allocator) {} + Suspend(MixedArena& allocator) : operands(allocator) {} - HeapType contType; - Expression* func; + Name tag; + ExpressionList operands; - void finalize(); + // We need access to the module to obtain the signature of the tag, + // which determines this node's type. + // If no module is given, then the type must have been set already. + void finalize(Module* wasm = nullptr); }; class Resume : public SpecificExpression { @@ -1989,17 +2002,21 @@ class Resume : public SpecificExpression { : handlerTags(allocator), handlerBlocks(allocator), operands(allocator), sentTypes(allocator) {} - HeapType contType; + // The following two vectors are to be understood together + // pointwise. That is, the ith component of each vector together + // classifies an on-clause `(on $tag $label)` or `(on $tag + // switch)`. The first vector stores reifies the `$tag` bit of the + // aforementioned syntax... ArenaVector handlerTags; + // ... whilst this vector reifies the `$label` bit of the + // syntax. For `switch` clauses the ith component will be the Empty + // name (i.e. `Name()`). ArenaVector handlerBlocks; ExpressionList operands; Expression* cont; - // When 'Module*' parameter is given, we populate the 'sentTypes' array, so - // that the types can be accessed in other analyses without accessing the - // module. - void finalize(Module* wasm = nullptr); + void finalize(); // sentTypes[i] contains the type of the values that will be sent to the block // handlerBlocks[i] if suspending with tag handlerTags[i]. Not part of the @@ -2009,17 +2026,41 @@ class Resume : public SpecificExpression { ArenaVector sentTypes; }; -class Suspend : public SpecificExpression { +class ResumeThrow : public SpecificExpression { public: - Suspend(MixedArena& allocator) : operands(allocator) {} + ResumeThrow(MixedArena& allocator) + : handlerTags(allocator), handlerBlocks(allocator), operands(allocator), + sentTypes(allocator) {} Name tag; + // See the comment on `Resume` above. + ArenaVector handlerTags; + ArenaVector handlerBlocks; + ExpressionList operands; + Expression* cont; - // We need access to the module to obtain the signature of the tag, - // which determines this node's type. - // If no module is given, then the type must have been set already. - void finalize(Module* wasm = nullptr); + void finalize(); + + // sentTypes[i] contains the type of the values that will be sent to the block + // handlerBlocks[i] if suspending with tag handlerTags[i]. Not part of the + // instruction's syntax, but stored here for subsequent use. + // This information is cached here in order not to query the module + // every time we query the sent types. + ArenaVector sentTypes; +}; + +class StackSwitch : public SpecificExpression { +public: + StackSwitch(MixedArena& allocator) : operands(allocator) {} + + Name tag; + + ExpressionList operands; + Expression* cont; + + // We need access to the module to obtain the signature of the tag. + void finalize(); }; // Globals diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 0841d4b8093..f9d9225bbc7 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1348,8 +1348,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::StringsFeature; case FeatureSet::MultiMemory: return BinaryConsts::CustomSections::MultiMemoryFeature; - case FeatureSet::TypedContinuations: - return BinaryConsts::CustomSections::TypedContinuationsFeature; + case FeatureSet::StackSwitching: + return BinaryConsts::CustomSections::StackSwitchingFeature; case FeatureSet::SharedEverything: return BinaryConsts::CustomSections::SharedEverythingFeature; case FeatureSet::FP16: @@ -3028,26 +3028,63 @@ Result<> WasmBinaryReader::readInst() { case BinaryConsts::RetCallRef: return builder.makeCallRef(getIndexedHeapType(), code == BinaryConsts::RetCallRef); + case BinaryConsts::ContNew: + return builder.makeContNew(getIndexedHeapType()); case BinaryConsts::ContBind: { auto before = getIndexedHeapType(); auto after = getIndexedHeapType(); return builder.makeContBind(before, after); } - case BinaryConsts::ContNew: - return builder.makeContNew(getIndexedHeapType()); + case BinaryConsts::Suspend: + return builder.makeSuspend(getTagName(getU32LEB())); case BinaryConsts::Resume: { auto type = getIndexedHeapType(); - std::vector tags; - std::vector labels; auto numHandlers = getU32LEB(); + std::vector tags; + std::vector> labels; + tags.reserve(numHandlers); + labels.reserve(numHandlers); for (Index i = 0; i < numHandlers; ++i) { - tags.push_back(getTagName(getU32LEB())); - labels.push_back(getU32LEB()); + uint8_t code = getInt8(); + if (code == BinaryConsts::OnLabel) { + tags.push_back(getTagName(getU32LEB())); + labels.push_back(std::optional{getU32LEB()}); + } else if (code == BinaryConsts::OnSwitch) { + tags.push_back(getTagName(getU32LEB())); + labels.push_back(std::nullopt); + } else { + return Err{"ON opcode expected"}; + } } return builder.makeResume(type, tags, labels); } - case BinaryConsts::Suspend: - return builder.makeSuspend(getTagName(getU32LEB())); + case BinaryConsts::ResumeThrow: { + auto type = getIndexedHeapType(); + auto tag = getTagName(getU32LEB()); + auto numHandlers = getU32LEB(); + std::vector tags; + std::vector> labels; + tags.reserve(numHandlers); + labels.reserve(numHandlers); + for (Index i = 0; i < numHandlers; ++i) { + uint8_t code = getInt8(); + if (code == BinaryConsts::OnLabel) { + tags.push_back(getTagName(getU32LEB())); + labels.push_back(std::optional{getU32LEB()}); + } else if (code == BinaryConsts::OnSwitch) { + tags.push_back(getTagName(getU32LEB())); + labels.push_back(std::nullopt); + } else { + return Err{"ON opcode expected"}; + } + } + return builder.makeResumeThrow(type, tag, tags, labels); + } + case BinaryConsts::Switch: { + auto type = getIndexedHeapType(); + auto tag = getTagName(getU32LEB()); + return builder.makeStackSwitch(type, tag); + } #define BINARY_INT(code) \ case BinaryConsts::I32##code: \ @@ -4887,9 +4924,8 @@ void WasmBinaryReader::readFeatures(size_t payloadLen) { feature = FeatureSet::Strings; } else if (name == BinaryConsts::CustomSections::MultiMemoryFeature) { feature = FeatureSet::MultiMemory; - } else if (name == - BinaryConsts::CustomSections::TypedContinuationsFeature) { - feature = FeatureSet::TypedContinuations; + } else if (name == BinaryConsts::CustomSections::StackSwitchingFeature) { + feature = FeatureSet::StackSwitching; } else if (name == BinaryConsts::CustomSections::SharedEverythingFeature) { feature = FeatureSet::SharedEverything; } else if (name == BinaryConsts::CustomSections::FP16Feature) { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 9371046c304..603768d55cd 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -698,6 +698,35 @@ struct IRBuilder::ChildPopper ConstraintCollector{builder, children}.visitTupleExtract(curr, arity); return popConstrainedChildren(children); } + + Result<> visitContBind(ContBind* curr, + std::optional src = std::nullopt, + std::optional dest = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitContBind(curr, src, dest); + return popConstrainedChildren(children); + } + + Result<> visitResume(Resume* curr, + std::optional ct = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitResume(curr, ct); + return popConstrainedChildren(children); + } + + Result<> visitResumeThrow(ResumeThrow* curr, + std::optional ct = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitResumeThrow(curr, ct); + return popConstrainedChildren(children); + } + + Result<> visitStackSwitch(StackSwitch* curr, + std::optional ct = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitStackSwitch(curr, ct); + return popConstrainedChildren(children); + } }; Result<> IRBuilder::visit(Expression* curr) { @@ -2278,76 +2307,187 @@ Result<> IRBuilder::makeStringSliceWTF() { return Ok{}; } -Result<> IRBuilder::makeContBind(HeapType contTypeBefore, - HeapType contTypeAfter) { - if (!contTypeBefore.isContinuation() || !contTypeAfter.isContinuation()) { +Result<> IRBuilder::makeContNew(HeapType type) { + if (!type.isContinuation()) { + return Err{"expected continuation type"}; + } + ContNew curr; + curr.type = Type(type, NonNullable); + CHECK_ERR(visitContNew(&curr)); + + push(builder.makeContNew(type, curr.func)); + return Ok{}; +} + +Result<> IRBuilder::makeContBind(HeapType sourceType, HeapType targetType) { + if (!sourceType.isContinuation() || !targetType.isContinuation()) { return Err{"expected continuation types"}; } ContBind curr(wasm.allocator); - curr.contTypeBefore = contTypeBefore; - curr.contTypeAfter = contTypeAfter; - size_t paramsBefore = - contTypeBefore.getContinuation().type.getSignature().params.size(); - size_t paramsAfter = - contTypeAfter.getContinuation().type.getSignature().params.size(); - if (paramsBefore < paramsAfter) { + + curr.type = Type(targetType, NonNullable); + size_t sourceParams = + sourceType.getContinuation().type.getSignature().params.size(); + size_t targetParams = + targetType.getContinuation().type.getSignature().params.size(); + if (sourceParams < targetParams) { return Err{"incompatible continuation types in cont.bind: source type " + - contTypeBefore.toString() + - " has fewer parameters than destination " + - contTypeAfter.toString()}; + sourceType.toString() + " has fewer parameters than target " + + targetType.toString()}; } - curr.operands.resize(paramsBefore - paramsAfter); - CHECK_ERR(visitContBind(&curr)); + curr.operands.resize(sourceParams - targetParams); + CHECK_ERR(ChildPopper{*this}.visitContBind(&curr, sourceType, targetType)); + CHECK_ERR(validateTypeAnnotation(sourceType, curr.cont)); + CHECK_ERR(validateTypeAnnotation(targetType, &curr)); + + push(builder.makeContBind(targetType, std::move(curr.operands), curr.cont)); + return Ok{}; +} + +Result<> IRBuilder::makeSuspend(Name tag) { + Suspend curr(wasm.allocator); + curr.tag = tag; + curr.operands.resize(wasm.getTag(tag)->params().size()); + CHECK_ERR(visitSuspend(&curr)); std::vector operands(curr.operands.begin(), curr.operands.end()); - push( - builder.makeContBind(contTypeBefore, contTypeAfter, operands, curr.cont)); + push(builder.makeSuspend(tag, operands)); return Ok{}; } -Result<> IRBuilder::makeContNew(HeapType ct) { +struct ResumeTable { + std::vector targets; + std::vector sentTypes; +}; + +static Result +makeResumeTable(const std::vector>& labels, + std::function(Index)> getLabelName, + std::function(Index)> getLabelType) { + std::vector targets; + targets.reserve(labels.size()); + + std::vector sentTypes; + sentTypes.reserve(sentTypes.size()); + + for (Index i = 0; i < labels.size(); i++) { + Name target; + Type sentType; + if (labels[i].has_value()) { + // (on $tag $label) clause + Index labelIndex = labels[i].value(); + Result name = getLabelName(labelIndex); + CHECK_ERR(name); + target = *name; + + Result targetType = getLabelType(labelIndex); + CHECK_ERR(targetType); + if (targetType->isContinuation()) { + sentType = *targetType; + } else if (targetType->isTuple() && + targetType->getTuple().back().isContinuation()) { + // The continuation type is expected to be the last element of + // a multi-valued block. + sentType = *targetType; + } else { + return Err{"expected continuation type"}; + } + } else { + // (on $tag switch) clause + target = Name(); + sentType = Type::none; + } + targets.push_back(target); + sentTypes.push_back(sentType); + } + return ResumeTable{std::move(targets), std::move(sentTypes)}; +} + +Result<> +IRBuilder::makeResume(HeapType ct, + const std::vector& tags, + const std::vector>& labels) { + if (tags.size() != labels.size()) { + return Err{"the sizes of tags and labels must be equal"}; + } if (!ct.isContinuation()) { return Err{"expected continuation type"}; } - ContNew curr; - curr.contType = ct; - CHECK_ERR(visitContNew(&curr)); - push(builder.makeContNew(ct, curr.func)); + Resume curr(wasm.allocator); + auto contSig = ct.getContinuation().type.getSignature(); + curr.operands.resize(contSig.params.size()); + + Result resumetable = makeResumeTable( + labels, + [this](Index i) { return this->getLabelName(i); }, + [this](Index i) { return this->getLabelType(i); }); + CHECK_ERR(resumetable); + CHECK_ERR(ChildPopper{*this}.visitResume(&curr, ct)); + CHECK_ERR(validateTypeAnnotation(ct, curr.cont)); + + push(builder.makeResume(tags, + resumetable->targets, + resumetable->sentTypes, + std::move(curr.operands), + curr.cont)); + return Ok{}; } -Result<> IRBuilder::makeResume(HeapType ct, - const std::vector& tags, - const std::vector& labels) { +Result<> +IRBuilder::makeResumeThrow(HeapType ct, + Name tag, + const std::vector& tags, + const std::vector>& labels) { + if (tags.size() != labels.size()) { + return Err{"the sizes of tags and labels must be equal"}; + } if (!ct.isContinuation()) { return Err{"expected continuation type"}; } - Resume curr(wasm.allocator); - curr.contType = ct; - curr.operands.resize(ct.getContinuation().type.getSignature().params.size()); - CHECK_ERR(visitResume(&curr)); - std::vector labelNames; - labelNames.reserve(labels.size()); - for (auto label : labels) { - auto name = getLabelName(label); - CHECK_ERR(name); - labelNames.push_back(*name); - } - std::vector operands(curr.operands.begin(), curr.operands.end()); - push(builder.makeResume(ct, tags, labelNames, operands, curr.cont)); + ResumeThrow curr(wasm.allocator); + curr.tag = tag; + curr.operands.resize(wasm.getTag(tag)->params().size()); + + Result resumetable = makeResumeTable( + labels, + [this](Index i) { return this->getLabelName(i); }, + [this](Index i) { return this->getLabelType(i); }); + CHECK_ERR(resumetable); + CHECK_ERR(ChildPopper{*this}.visitResumeThrow(&curr, ct)); + CHECK_ERR(validateTypeAnnotation(ct, curr.cont)); + + push(builder.makeResumeThrow(tag, + tags, + resumetable->targets, + resumetable->sentTypes, + std::move(curr.operands), + curr.cont)); return Ok{}; } -Result<> IRBuilder::makeSuspend(Name tag) { - Suspend curr(wasm.allocator); +Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { + if (!ct.isContinuation()) { + return Err{"expected continuation type"}; + } + StackSwitch curr(wasm.allocator); curr.tag = tag; - curr.operands.resize(wasm.getTag(tag)->params().size()); - CHECK_ERR(visitSuspend(&curr)); + auto nparams = ct.getContinuation().type.getSignature().params.size(); + if (nparams < 1) { + return Err{"arity mismatch: the continuation argument must have, at least, " + "unary arity"}; + } - std::vector operands(curr.operands.begin(), curr.operands.end()); - push(builder.makeSuspend(tag, operands)); + // The continuation argument of the continuation is synthetic, + // i.e. it is provided by the runtime. + curr.operands.resize(nparams - 1); + + CHECK_ERR(ChildPopper{*this}.visitStackSwitch(&curr, ct)); + CHECK_ERR(validateTypeAnnotation(ct, curr.cont)); + + push(builder.makeStackSwitch(tag, std::move(curr.operands), curr.cont)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index dc360b94420..060b01b04ee 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2662,31 +2662,70 @@ void BinaryInstWriter::visitStringSliceWTF(StringSliceWTF* curr) { << U32LEB(BinaryConsts::StringViewWTF16Slice); } -void BinaryInstWriter::visitContBind(ContBind* curr) { - o << int8_t(BinaryConsts::ContBind); - parent.writeIndexedHeapType(curr->contTypeBefore); - parent.writeIndexedHeapType(curr->contTypeAfter); -} - void BinaryInstWriter::visitContNew(ContNew* curr) { o << int8_t(BinaryConsts::ContNew); - parent.writeIndexedHeapType(curr->contType); + parent.writeIndexedHeapType(curr->type.getHeapType()); +} + +void BinaryInstWriter::visitSuspend(Suspend* curr) { + o << int8_t(BinaryConsts::Suspend) << U32LEB(parent.getTagIndex(curr->tag)); +} + +void BinaryInstWriter::visitContBind(ContBind* curr) { + assert(curr->cont->type.isContinuation() && curr->type.isContinuation()); + o << int8_t(BinaryConsts::ContBind); + parent.writeIndexedHeapType(curr->cont->type.getHeapType()); + parent.writeIndexedHeapType(curr->type.getHeapType()); } void BinaryInstWriter::visitResume(Resume* curr) { + assert(curr->cont->type.isContinuation()); o << int8_t(BinaryConsts::Resume); - parent.writeIndexedHeapType(curr->contType); + parent.writeIndexedHeapType(curr->cont->type.getHeapType()); size_t handlerNum = curr->handlerTags.size(); o << U32LEB(handlerNum); for (size_t i = 0; i < handlerNum; i++) { - o << U32LEB(parent.getTagIndex(curr->handlerTags[i])) - << U32LEB(getBreakIndex(curr->handlerBlocks[i])); + if (curr->handlerBlocks[i].isNull()) { + // on switch + o << int8_t(BinaryConsts::OnSwitch) + << U32LEB(parent.getTagIndex(curr->handlerTags[i])); + } else { + // on label + o << int8_t(BinaryConsts::OnLabel) + << U32LEB(parent.getTagIndex(curr->handlerTags[i])) + << U32LEB(getBreakIndex(curr->handlerBlocks[i])); + } } } -void BinaryInstWriter::visitSuspend(Suspend* curr) { - o << int8_t(BinaryConsts::Suspend) << U32LEB(parent.getTagIndex(curr->tag)); +void BinaryInstWriter::visitResumeThrow(ResumeThrow* curr) { + assert(curr->cont->type.isContinuation()); + o << int8_t(BinaryConsts::ResumeThrow); + parent.writeIndexedHeapType(curr->cont->type.getHeapType()); + o << U32LEB(parent.getTagIndex(curr->tag)); + + size_t handlerNum = curr->handlerTags.size(); + o << U32LEB(handlerNum); + for (size_t i = 0; i < handlerNum; i++) { + if (curr->handlerBlocks[i].isNull()) { + // on switch + o << int8_t(BinaryConsts::OnSwitch) + << U32LEB(parent.getTagIndex(curr->handlerTags[i])); + } else { + // on label + o << int8_t(BinaryConsts::OnLabel) + << U32LEB(parent.getTagIndex(curr->handlerTags[i])) + << U32LEB(getBreakIndex(curr->handlerBlocks[i])); + } + } +} + +void BinaryInstWriter::visitStackSwitch(StackSwitch* curr) { + assert(curr->cont->type.isContinuation()); + o << int8_t(BinaryConsts::Switch); + parent.writeIndexedHeapType(curr->cont->type.getHeapType()); + o << U32LEB(parent.getTagIndex(curr->tag)); } void BinaryInstWriter::emitScopeEnd(Expression* curr) { diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index b1f74d49177..734c5e4b903 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -84,6 +84,7 @@ template struct RecGroupComparator { case HeapTypeKind::Array: return compare(a.getArray(), b.getArray()); case HeapTypeKind::Cont: + assert(a.isContinuation() && b.isContinuation()); return compare(a.getContinuation(), b.getContinuation()); case HeapTypeKind::Basic: break; @@ -237,6 +238,7 @@ struct RecGroupHasher { hash_combine(digest, hash(type.getArray())); return digest; case HeapTypeKind::Cont: + assert(type.isContinuation()); wasm::rehash(digest, 2381496927); hash_combine(digest, hash(type.getContinuation())); return digest; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 4f341588b6a..454f563f684 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1255,7 +1255,7 @@ FeatureSet HeapType::getFeatures() const { return; case HeapType::cont: case HeapType::nocont: - feats |= FeatureSet::TypedContinuations; + feats |= FeatureSet::StackSwitching; return; } } @@ -1281,7 +1281,7 @@ FeatureSet HeapType::getFeatures() const { feats |= FeatureSet::Multivalue; } } else if (heapType.isContinuation()) { - feats |= FeatureSet::TypedContinuations; + feats |= FeatureSet::StackSwitching; } // In addition, scan their non-ref children, to add dependencies on diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 68951e1759d..8812a0d3f70 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -510,10 +510,12 @@ struct FunctionValidator : public WalkerPass> { void visitStringEq(StringEq* curr); void visitStringWTF16Get(StringWTF16Get* curr); void visitStringSliceWTF(StringSliceWTF* curr); - void visitContBind(ContBind* curr); void visitContNew(ContNew* curr); - void visitResume(Resume* curr); + void visitContBind(ContBind* curr); void visitSuspend(Suspend* curr); + void visitResume(Resume* curr); + void visitResumeThrow(ResumeThrow* curr); + void visitStackSwitch(StackSwitch* curr); void visitFunction(Function* curr); @@ -3607,61 +3609,111 @@ void FunctionValidator::visitStringSliceWTF(StringSliceWTF* curr) { "string operations require reference-types [--enable-strings]"); } -void FunctionValidator::visitContBind(ContBind* curr) { +void FunctionValidator::visitContNew(ContNew* curr) { // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "cont.new requires stack-switching [--enable-stack-switching]"); + shouldBeTrue( - !getModule() || getModule()->features.hasTypedContinuations(), + (curr->type.isContinuation() && + curr->type.getHeapType().getContinuation().type.isSignature()) || + curr->type == Type::unreachable, curr, - "cont.bind requires typed-continuatons [--enable-typed-continuations]"); + "cont.new must be annotated with a continuation type"); +} - shouldBeTrue((curr->contTypeBefore.isContinuation() && - curr->contTypeBefore.getContinuation().type.isSignature()), +void FunctionValidator::visitContBind(ContBind* curr) { + // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), curr, - "invalid first type in ContBind expression"); + "cont.bind requires stack-switching [--enable-stack-switching]"); - shouldBeTrue((curr->contTypeAfter.isContinuation() && - curr->contTypeAfter.getContinuation().type.isSignature()), + shouldBeTrue( + (curr->cont->type.isContinuation() && + curr->cont->type.getHeapType().getContinuation().type.isSignature()) || + curr->cont->type == Type::unreachable, + curr, + "the first type annotation on cont.bind must be a continuation type"); + + shouldBeTrue( + (curr->type.isContinuation() && + curr->type.getHeapType().getContinuation().type.isSignature()) || + curr->type == Type::unreachable, + curr, + "the second type annotation on cont.bind must be a continuation type"); +} + +void FunctionValidator::visitSuspend(Suspend* curr) { + // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), curr, - "invalid second type in ContBind expression"); + "suspend requires stack-switching [--enable-stack-switching]"); } -void FunctionValidator::visitContNew(ContNew* curr) { +void FunctionValidator::visitResume(Resume* curr) { // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "resume requires stack-switching [--enable-stack-switching]"); + shouldBeTrue( - !getModule() || getModule()->features.hasTypedContinuations(), + curr->sentTypes.size() == curr->handlerBlocks.size(), curr, - "cont.new requires typed-continuatons [--enable-typed-continuations]"); + "sentTypes cache in resume instruction has not been initialized"); - shouldBeTrue((curr->contType.isContinuation() && - curr->contType.getContinuation().type.isSignature()), - curr, - "invalid type in ContNew expression"); + shouldBeTrue( + (curr->cont->type.isContinuation() && + curr->cont->type.getHeapType().getContinuation().type.isSignature()) || + curr->type == Type::unreachable, + curr, + "resume must be annotated with a continuation type"); } -void FunctionValidator::visitResume(Resume* curr) { +void FunctionValidator::visitResumeThrow(ResumeThrow* curr) { // TODO implement actual type-checking shouldBeTrue( - !getModule() || getModule()->features.hasTypedContinuations(), + !getModule() || (getModule()->features.hasExceptionHandling() && + getModule()->features.hasStackSwitching()), curr, - "resume requires typed-continuatons [--enable-typed-continuations]"); + "resume_throw requires exception handling [--enable-exception-handling] " + "and stack-switching [--enable-stack-switching]"); shouldBeTrue( curr->sentTypes.size() == curr->handlerBlocks.size(), curr, - "sentTypes cache in Resume instruction has not been initialized"); + "sentTypes cache in resume_throw instruction has not been initialized"); - shouldBeTrue((curr->contType.isContinuation() && - curr->contType.getContinuation().type.isSignature()), - curr, - "invalid type in Resume expression"); + shouldBeTrue( + (curr->cont->type.isContinuation() && + curr->cont->type.getHeapType().getContinuation().type.isSignature()) || + curr->type == Type::unreachable, + curr, + "resume_throw must be annotated with a continuation type"); + + auto* tag = getModule()->getTagOrNull(curr->tag); + if (!shouldBeTrue(!!tag, curr, "resume_throw must be annotated with a tag")) { + return; + } } -void FunctionValidator::visitSuspend(Suspend* curr) { +void FunctionValidator::visitStackSwitch(StackSwitch* curr) { // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "switch requires stack-switching [--enable-stack-switching]"); + shouldBeTrue( - !getModule() || getModule()->features.hasTypedContinuations(), + (curr->cont->type.isContinuation() && + curr->cont->type.getHeapType().getContinuation().type.isSignature()) || + curr->type == Type::unreachable, curr, - "suspend requires typed-continuations [--enable-typed-continuations]"); + "switch must be annotated with a continuation type"); + + auto* tag = getModule()->getTagOrNull(curr->tag); + if (!shouldBeTrue(!!tag, curr, "switch must be annotated with a tag")) { + return; + } } void FunctionValidator::visitFunction(Function* curr) { @@ -4165,10 +4217,10 @@ static void validateTags(Module& module, ValidationInfo& info) { } for (auto& curr : module.tags) { if (curr->results() != Type::none) { - info.shouldBeTrue(module.features.hasTypedContinuations(), + info.shouldBeTrue(module.features.hasStackSwitching(), curr->name, - "Tags with result types require typed continuations " - "feature [--enable-typed-continuations]"); + "Tags with result types require stack switching " + "feature [--enable-stack-switching]"); } if (curr->params().isTuple()) { info.shouldBeTrue( diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 94454456288..c8a82311c30 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -54,7 +54,7 @@ const char* RelaxedSIMDFeature = "relaxed-simd"; const char* ExtendedConstFeature = "extended-const"; const char* StringsFeature = "strings"; const char* MultiMemoryFeature = "multimemory"; -const char* TypedContinuationsFeature = "typed-continuations"; +const char* StackSwitchingFeature = "stack-switching"; const char* SharedEverythingFeature = "shared-everything"; const char* FP16Feature = "fp16"; const char* BulkMemoryOptFeature = "bulk-memory-opt"; @@ -1378,79 +1378,71 @@ void StringSliceWTF::finalize() { } } +void ContNew::finalize() { + if (func->type == Type::unreachable) { + type = Type::unreachable; + } +} + void ContBind::finalize() { if (cont->type == Type::unreachable) { type = Type::unreachable; - } else if (!handleUnreachableOperands(this)) { - type = Type(contTypeAfter, NonNullable); + return; + } + if (handleUnreachableOperands(this)) { + return; } } -void ContNew::finalize() { - if (func->type == Type::unreachable) { - type = Type::unreachable; - } else { - type = Type(contType, NonNullable); +void Suspend::finalize(Module* wasm) { + if (!handleUnreachableOperands(this) && wasm) { + auto tag = wasm->getTag(this->tag); + type = tag->results(); } } -static void populateResumeSentTypes(Resume* curr, Module* wasm) { - if (!wasm) { +void Resume::finalize() { + if (cont->type == Type::unreachable) { + type = Type::unreachable; + return; + } + if (handleUnreachableOperands(this)) { return; } + assert(this->cont->type.isContinuation()); const Signature& contSig = - curr->contType.getContinuation().type.getSignature(); - - // Let $tag be a tag with type [tgp*] -> [tgr*]. Let $ct be a continuation - // type (cont $ft), where $ft is [ctp*] -> [ctr*]. Then an instruction (resume - // $ct ... (tag $tag $block) ... ) causes $block to receive values of the - // following types when suspending to $tag: tgp* (ref $ct') where ct' = (cont - // $ft') and ft' = [tgr*] -> [ctr*]. - // - auto& ctrs = contSig.results; - curr->sentTypes.clear(); - curr->sentTypes.resize(curr->handlerTags.size()); - for (Index i = 0; i < curr->handlerTags.size(); i++) { - auto tag = wasm->getTag(curr->handlerTags[i]); - - auto tgps = tag->params(); - auto tgrs = tag->results(); - - HeapType ftPrime{Signature(tgrs, ctrs)}; - HeapType ctPrime{Continuation(ftPrime)}; - Type ctPrimeRef(ctPrime, Nullability::NonNullable); - - if (tgps.size() > 0) { - TypeList sentValueTypes; - sentValueTypes.reserve(tgps.size() + 1); - - sentValueTypes.insert(sentValueTypes.begin(), tgps.begin(), tgps.end()); - sentValueTypes.push_back(ctPrimeRef); - curr->sentTypes[i] = Type(sentValueTypes); - } else { - curr->sentTypes[i] = ctPrimeRef; - } - } + this->cont->type.getHeapType().getContinuation().type.getSignature(); + type = contSig.results; } -void Resume::finalize(Module* wasm) { +void ResumeThrow::finalize() { if (cont->type == Type::unreachable) { type = Type::unreachable; - } else if (!handleUnreachableOperands(this)) { - const Signature& contSig = - this->contType.getContinuation().type.getSignature(); - type = contSig.results; + return; + } + if (handleUnreachableOperands(this)) { + return; } - populateResumeSentTypes(this, wasm); + assert(this->cont->type.isContinuation()); + const Signature& contSig = + this->cont->type.getHeapType().getContinuation().type.getSignature(); + type = contSig.results; } -void Suspend::finalize(Module* wasm) { - if (!handleUnreachableOperands(this) && wasm) { - auto tag = wasm->getTag(this->tag); - type = tag->results(); +void StackSwitch::finalize() { + if (cont->type == Type::unreachable) { + type = Type::unreachable; + return; + } + if (handleUnreachableOperands(this)) { + return; } + + assert(this->cont->type.isContinuation()); + type = + this->cont->type.getHeapType().getContinuation().type.getSignature().params; } size_t Function::getNumParams() { return getParams().size(); } diff --git a/src/wasm2js.h b/src/wasm2js.h index fc0b2f6cff8..9a4416068c5 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2416,11 +2416,15 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, ValueBuilder::makeCall(ABI::wasm2js::TRAP)); } + Ref visitContNew(ContNew* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitContBind(ContBind* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); } - Ref visitContNew(ContNew* curr) { + Ref visitSuspend(Suspend* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); } @@ -2428,7 +2432,11 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } - Ref visitSuspend(Suspend* curr) { + Ref visitResumeThrow(ResumeThrow* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } + Ref visitStackSwitch(StackSwitch* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); } diff --git a/test/lit/basic/stack_switching.wast b/test/lit/basic/stack_switching.wast new file mode 100644 index 00000000000..541c200ecec --- /dev/null +++ b/test/lit/basic/stack_switching.wast @@ -0,0 +1,947 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + ;; CHECK-TEXT: (type $f1 (func)) + + ;; CHECK-TEXT: (type $k1 (cont $f1)) + + ;; CHECK-TEXT: (type $2 (func (param (ref $k1)))) + + ;; CHECK-TEXT: (type $ft1 (func (param i32))) + + ;; CHECK-TEXT: (type $ct1 (sub (cont $ft1))) + + ;; CHECK-TEXT: (type $ft (func (param i32) (result i32))) + ;; CHECK-BIN: (type $f1 (func)) + + ;; CHECK-BIN: (type $k1 (cont $f1)) + + ;; CHECK-BIN: (type $2 (func (param (ref $k1)))) + + ;; CHECK-BIN: (type $ft1 (func (param i32))) + + ;; CHECK-BIN: (type $ct1 (sub (cont $ft1))) + + ;; CHECK-BIN: (type $ft (func (param i32) (result i32))) + (type $ft (func (param i32) (result i32))) + ;; CHECK-TEXT: (type $ct (cont $ft)) + ;; CHECK-BIN: (type $ct (cont $ft)) + (type $ct (cont $ft)) + + ;; CHECK-TEXT: (type $7 (func (param (ref $ct)) (result (ref $ct)))) + + ;; CHECK-TEXT: (type $8 (func (param contref nullcontref (ref cont) (ref nocont)) (result contref))) + + ;; CHECK-TEXT: (type $9 (func (param (ref $ct1)))) + + ;; CHECK-TEXT: (type $ct0 (sub (cont $f1))) + + ;; CHECK-TEXT: (global $kglo (mut (ref null $k1)) (ref.null nocont)) + + ;; CHECK-TEXT: (global $gglo (ref null $k1) (ref.null nocont)) + + ;; CHECK-TEXT: (elem declare func $f1 $f2 $f3 $fglo $r0 $r1) + + ;; CHECK-TEXT: (tag $exn (type $f1)) + + ;; CHECK-TEXT: (tag $e1 (type $f1)) + + ;; CHECK-TEXT: (tag $e2 (type $f1)) + + ;; CHECK-TEXT: (export "unhandled-1" (func $f1)) + + ;; CHECK-TEXT: (export "unhandled-2" (func $unhandled-2)) + + ;; CHECK-TEXT: (export "unhandled-3" (func $unhandled-3)) + + ;; CHECK-TEXT: (export "handled" (func $handled)) + + ;; CHECK-TEXT: (export "uncaught-1" (func $uncaught-1)) + + ;; CHECK-TEXT: (export "uncaught-2" (func $uncaught-2)) + + ;; CHECK-TEXT: (export "uncaught-3" (func $uncaught-3)) + + ;; CHECK-TEXT: (export "non-linear-1" (func $non-linear-1)) + + ;; CHECK-TEXT: (export "non-linear-2" (func $non-linear-2)) + + ;; CHECK-TEXT: (export "non-linear-3" (func $non-linear-3)) + + ;; CHECK-TEXT: (export "non-linear-4" (func $non-linear-4)) + + ;; CHECK-TEXT: (func $id (type $7) (param $x (ref $ct)) (result (ref $ct)) + ;; CHECK-TEXT-NEXT: (local.get $x) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (type $7 (func (param (ref $ct)) (result (ref $ct)))) + + ;; CHECK-BIN: (type $8 (func (param contref nullcontref (ref cont) (ref nocont)) (result contref))) + + ;; CHECK-BIN: (type $9 (func (param (ref $ct1)))) + + ;; CHECK-BIN: (type $ct0 (sub (cont $f1))) + + ;; CHECK-BIN: (global $kglo (mut (ref null $k1)) (ref.null nocont)) + + ;; CHECK-BIN: (global $gglo (ref null $k1) (ref.null nocont)) + + ;; CHECK-BIN: (elem declare func $f1 $f2 $f3 $fglo $r0 $r1) + + ;; CHECK-BIN: (tag $exn (type $f1)) + + ;; CHECK-BIN: (tag $e1 (type $f1)) + + ;; CHECK-BIN: (tag $e2 (type $f1)) + + ;; CHECK-BIN: (export "unhandled-1" (func $f1)) + + ;; CHECK-BIN: (export "unhandled-2" (func $unhandled-2)) + + ;; CHECK-BIN: (export "unhandled-3" (func $unhandled-3)) + + ;; CHECK-BIN: (export "handled" (func $handled)) + + ;; CHECK-BIN: (export "uncaught-1" (func $uncaught-1)) + + ;; CHECK-BIN: (export "uncaught-2" (func $uncaught-2)) + + ;; CHECK-BIN: (export "uncaught-3" (func $uncaught-3)) + + ;; CHECK-BIN: (export "non-linear-1" (func $non-linear-1)) + + ;; CHECK-BIN: (export "non-linear-2" (func $non-linear-2)) + + ;; CHECK-BIN: (export "non-linear-3" (func $non-linear-3)) + + ;; CHECK-BIN: (export "non-linear-4" (func $non-linear-4)) + + ;; CHECK-BIN: (func $id (type $7) (param $x (ref $ct)) (result (ref $ct)) + ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: ) + (func $id (param $x (ref $ct)) (result (ref $ct)) + (local.get $x) + ) + + ;; CHECK-TEXT: (func $id2 (type $8) (param $w contref) (param $x nullcontref) (param $y (ref cont)) (param $z (ref nocont)) (result contref) + ;; CHECK-TEXT-NEXT: (local.get $z) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $id2 (type $8) (param $w contref) (param $x nullcontref) (param $y (ref cont)) (param $z (ref nocont)) (result contref) + ;; CHECK-BIN-NEXT: (local.get $z) + ;; CHECK-BIN-NEXT: ) + (func $id2 + (param $w contref) + (param $x nullcontref) + (param $y (ref cont)) + (param $z (ref nocont)) + (result contref) + (local.get $z) + ) + + (tag $exn) + (tag $e1) + (tag $e2) + + (type $f1 (func)) + (type $k1 (cont $f1)) + + (rec + (type $f2 (func (param (ref $f3)))) + (type $f3 (func (param (ref $f2)))) + ) + (type $k2 (cont $f2)) + (type $k3 (cont $f3)) + + (type $ft1 (func (param i32))) + (type $ct1 (sub (cont $ft1))) + + (type $ft0 (func)) + (type $ct0 (sub (cont $ft0))) + + (type $f4 (sub (func (result anyref)))) + (type $f5 (sub $f4 (func (result eqref)))) + (type $c4 (sub (cont $f4))) + (type $c5 (sub $c4 (cont $f5))) + + (type $ft2 (func)) + (type $ct2 (cont $ft2)) + + (global $kglo (mut (ref null $ct2)) (ref.null $ct2)) + (global $gglo (ref null $ct2) (ref.null $ct2)) + + ;; CHECK-TEXT: (func $fglo (type $f1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $fglo (type $f1) + ;; CHECK-BIN-NEXT: ) + (func $fglo) + ;; CHECK-BIN-NODEBUG: (type $0 (func)) + + ;; CHECK-BIN-NODEBUG: (type $1 (cont $0)) + + ;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref $1)))) + + ;; CHECK-BIN-NODEBUG: (type $3 (func (param i32))) + + ;; CHECK-BIN-NODEBUG: (type $4 (sub (cont $3))) + + ;; CHECK-BIN-NODEBUG: (type $5 (func (param i32) (result i32))) + + ;; CHECK-BIN-NODEBUG: (type $6 (cont $5)) + + ;; CHECK-BIN-NODEBUG: (type $7 (func (param (ref $6)) (result (ref $6)))) + + ;; CHECK-BIN-NODEBUG: (type $8 (func (param contref nullcontref (ref cont) (ref nocont)) (result contref))) + + ;; CHECK-BIN-NODEBUG: (type $9 (func (param (ref $4)))) + + ;; CHECK-BIN-NODEBUG: (type $10 (sub (cont $0))) + + ;; CHECK-BIN-NODEBUG: (global $global$0 (mut (ref null $1)) (ref.null nocont)) + + ;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $1) (ref.null nocont)) + + ;; CHECK-BIN-NODEBUG: (elem declare func $12 $15 $16 $2 $5 $9) + (elem declare func $fglo) + + (func + (global.set $kglo (cont.new $ct2 (ref.func $fglo)))) + + (func (param $x (ref $ct1)) + (i32.const 123) + (local.get $x) + (cont.bind $ct1 $ct0) + (drop) + ) + + ;; CHECK-TEXT: (func $0 (type $f1) + ;; CHECK-TEXT-NEXT: (global.set $kglo + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $fglo) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + + ;; CHECK-TEXT: (func $1 (type $9) (param $x (ref $ct1)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (cont.bind $ct1 $ct0 + ;; CHECK-TEXT-NEXT: (i32.const 123) + ;; CHECK-TEXT-NEXT: (local.get $x) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + + ;; CHECK-TEXT: (func $f1 (type $f1) + ;; CHECK-TEXT-NEXT: (suspend $e1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $3 (type $f1) + ;; CHECK-BIN-NEXT: (global.set $kglo + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $fglo) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + + ;; CHECK-BIN: (func $4 (type $9) (param $x (ref $ct1)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (cont.bind $ct1 $ct0 + ;; CHECK-BIN-NEXT: (i32.const 123) + ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + + ;; CHECK-BIN: (func $f1 (type $f1) + ;; CHECK-BIN-NEXT: (suspend $e1) + ;; CHECK-BIN-NEXT: ) + (func $f1 (export "unhandled-1") + (suspend $e1) + ) + + ;; CHECK-TEXT: (func $unhandled-2 (type $f1) + ;; CHECK-TEXT-NEXT: (resume $k1 + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $f1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $unhandled-2 (type $f1) + ;; CHECK-BIN-NEXT: (resume $k1 + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $f1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $unhandled-2 (export "unhandled-2") + (resume $k1 (cont.new $k1 (ref.func $f1))) + ) + + ;; CHECK-TEXT: (func $unhandled-3 (type $f1) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $h (result (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 (on $e2 $h) + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $f1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $unhandled-3 (type $f1) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block (result (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 (on $e2 $block) + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $f1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $unhandled-3 (export "unhandled-3") + (block $h (result (ref $k1)) + (resume $k1 (on $e2 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (drop) + ) + + ;; CHECK-TEXT: (func $handled (type $f1) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $h (result (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 (on $e1 $h) + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $f1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $handled (type $f1) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block (result (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 (on $e1 $block) + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $f1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $handled (export "handled") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (drop) + ) + + (elem declare func $f2) + ;; CHECK-TEXT: (func $f2 (type $f1) + ;; CHECK-TEXT-NEXT: (throw $exn) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $f2 (type $f1) + ;; CHECK-BIN-NEXT: (throw $exn) + ;; CHECK-BIN-NEXT: ) + (func $f2 + (throw $exn) + ) + + ;; CHECK-TEXT: (func $uncaught-1 (type $f1) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $h (result (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 (on $e1 $h) + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $f2) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $uncaught-1 (type $f1) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block (result (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 (on $e1 $block) + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $f2) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $uncaught-1 (export "uncaught-1") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f2))) + (unreachable) + ) + (drop) + ) + + ;; CHECK-TEXT: (func $uncaught-2 (type $f1) + ;; CHECK-TEXT-NEXT: (resume_throw $k1 $exn + ;; CHECK-TEXT-NEXT: (block $h (result (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 (on $e1 $h) + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $f1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $uncaught-2 (type $f1) + ;; CHECK-BIN-NEXT: (resume_throw $k1 $exn + ;; CHECK-BIN-NEXT: (block $block (result (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 (on $e1 $block) + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $f1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $uncaught-2 (export "uncaught-2") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (resume_throw $k1 $exn) + ) + + (elem declare func $f3) + ;; CHECK-TEXT: (func $f3 (type $f1) + ;; CHECK-TEXT-NEXT: (call $f4) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $f3 (type $f1) + ;; CHECK-BIN-NEXT: (call $f4) + ;; CHECK-BIN-NEXT: ) + (func $f3 + (call $f4) + ) + ;; CHECK-TEXT: (func $f4 (type $f1) + ;; CHECK-TEXT-NEXT: (suspend $e1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $f4 (type $f1) + ;; CHECK-BIN-NEXT: (suspend $e1) + ;; CHECK-BIN-NEXT: ) + (func $f4 + (suspend $e1) + ) + + ;; CHECK-TEXT: (func $uncaught-3 (type $f1) + ;; CHECK-TEXT-NEXT: (resume_throw $k1 $exn + ;; CHECK-TEXT-NEXT: (block $h (result (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 (on $e1 $h) + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $f3) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $uncaught-3 (type $f1) + ;; CHECK-BIN-NEXT: (resume_throw $k1 $exn + ;; CHECK-BIN-NEXT: (block $block (result (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 (on $e1 $block) + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $f3) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $uncaught-3 (export "uncaught-3") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f3))) + (unreachable) + ) + (resume_throw $k1 $exn) + ) + + (elem declare func $r0 $r1) + ;; CHECK-TEXT: (func $r0 (type $f1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $r0 (type $f1) + ;; CHECK-BIN-NEXT: ) + (func $r0) + + ;; CHECK-TEXT: (func $r1 (type $f1) + ;; CHECK-TEXT-NEXT: (suspend $e1) + ;; CHECK-TEXT-NEXT: (suspend $e1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $r1 (type $f1) + ;; CHECK-BIN-NEXT: (suspend $e1) + ;; CHECK-BIN-NEXT: (suspend $e1) + ;; CHECK-BIN-NEXT: ) + (func $r1 (suspend $e1) (suspend $e1)) + + ;; CHECK-TEXT: (func $nl1 (type $2) (param $k (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 + ;; CHECK-TEXT-NEXT: (local.get $k) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (resume $k1 + ;; CHECK-TEXT-NEXT: (local.get $k) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $nl1 (type $2) (param $k (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 + ;; CHECK-BIN-NEXT: (local.get $k) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (resume $k1 + ;; CHECK-BIN-NEXT: (local.get $k) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $nl1 (param $k (ref $k1)) + (resume $k1 (local.get $k)) + (resume $k1 (local.get $k)) + ) + ;; CHECK-TEXT: (func $nl2 (type $2) (param $k (ref $k1)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $h (result (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 (on $e1 $h) + ;; CHECK-TEXT-NEXT: (local.get $k) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (resume $k1 + ;; CHECK-TEXT-NEXT: (local.get $k) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $nl2 (type $2) (param $k (ref $k1)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block (result (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 (on $e1 $block) + ;; CHECK-BIN-NEXT: (local.get $k) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (resume $k1 + ;; CHECK-BIN-NEXT: (local.get $k) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $nl2 (param $k (ref $k1)) + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (local.get $k)) + (unreachable) + ) + (resume $k1 (local.get $k)) + (unreachable) + ) + ;; CHECK-TEXT: (func $nl3 (type $2) (param $k (ref $k1)) + ;; CHECK-TEXT-NEXT: (local $k' (ref null $k1)) + ;; CHECK-TEXT-NEXT: (local.set $k' + ;; CHECK-TEXT-NEXT: (block $h1 (result (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 (on $e1 $h1) + ;; CHECK-TEXT-NEXT: (local.get $k) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $h2 (result (ref $k1)) + ;; CHECK-TEXT-NEXT: (resume $k1 (on $e1 $h2) + ;; CHECK-TEXT-NEXT: (local.get $k') + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (resume $k1 + ;; CHECK-TEXT-NEXT: (local.get $k') + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $nl3 (type $2) (param $k (ref $k1)) + ;; CHECK-BIN-NEXT: (local $k' (ref null $k1)) + ;; CHECK-BIN-NEXT: (local.set $k' + ;; CHECK-BIN-NEXT: (block $block (result (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 (on $e1 $block) + ;; CHECK-BIN-NEXT: (local.get $k) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block1 (result (ref $k1)) + ;; CHECK-BIN-NEXT: (resume $k1 (on $e1 $block1) + ;; CHECK-BIN-NEXT: (local.get $k') + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (resume $k1 + ;; CHECK-BIN-NEXT: (local.get $k') + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $nl3 (param $k (ref $k1)) + (local $k' (ref null $k1)) + (block $h1 (result (ref $k1)) + (resume $k1 (on $e1 $h1) (local.get $k)) + (unreachable) + ) + (local.set $k') + (block $h2 (result (ref $k1)) + (resume $k1 (on $e1 $h2) (local.get $k')) + (unreachable) + ) + (resume $k1 (local.get $k')) + (unreachable) + ) + ;; CHECK-TEXT: (func $nl4 (type $2) (param $k (ref $k1)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (cont.bind $k1 $k1 + ;; CHECK-TEXT-NEXT: (local.get $k) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (resume $k1 + ;; CHECK-TEXT-NEXT: (local.get $k) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $nl4 (type $2) (param $k (ref $k1)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (cont.bind $k1 $k1 + ;; CHECK-BIN-NEXT: (local.get $k) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (resume $k1 + ;; CHECK-BIN-NEXT: (local.get $k) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $nl4 (param $k (ref $k1)) + (drop (cont.bind $k1 $k1 (local.get $k))) + (resume $k1 (local.get $k)) + ) + + ;; CHECK-TEXT: (func $non-linear-1 (type $f1) + ;; CHECK-TEXT-NEXT: (call $nl1 + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $r0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $non-linear-1 (type $f1) + ;; CHECK-BIN-NEXT: (call $nl1 + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $r0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $non-linear-1 (export "non-linear-1") + (call $nl1 (cont.new $k1 (ref.func $r0))) + ) + ;; CHECK-TEXT: (func $non-linear-2 (type $f1) + ;; CHECK-TEXT-NEXT: (call $nl2 + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $r1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $non-linear-2 (type $f1) + ;; CHECK-BIN-NEXT: (call $nl2 + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $r1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $non-linear-2 (export "non-linear-2") + (call $nl2 (cont.new $k1 (ref.func $r1))) + ) + ;; CHECK-TEXT: (func $non-linear-3 (type $f1) + ;; CHECK-TEXT-NEXT: (call $nl3 + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $r1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $non-linear-3 (type $f1) + ;; CHECK-BIN-NEXT: (call $nl3 + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $r1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $non-linear-3 (export "non-linear-3") + (call $nl3 (cont.new $k1 (ref.func $r1))) + ) + ;; CHECK-TEXT: (func $non-linear-4 (type $f1) + ;; CHECK-TEXT-NEXT: (call $nl4 + ;; CHECK-TEXT-NEXT: (cont.new $k1 + ;; CHECK-TEXT-NEXT: (ref.func $r1) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $non-linear-4 (type $f1) + ;; CHECK-BIN-NEXT: (call $nl4 + ;; CHECK-BIN-NEXT: (cont.new $k1 + ;; CHECK-BIN-NEXT: (ref.func $r1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $non-linear-4 (export "non-linear-4") + (call $nl4 (cont.new $k1 (ref.func $r1))) + ) + +) +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $0)) + +;; CHECK-BIN-NODEBUG: (tag $tag$1 (type $0)) + +;; CHECK-BIN-NODEBUG: (tag $tag$2 (type $0)) + +;; CHECK-BIN-NODEBUG: (export "unhandled-1" (func $5)) + +;; CHECK-BIN-NODEBUG: (export "unhandled-2" (func $6)) + +;; CHECK-BIN-NODEBUG: (export "unhandled-3" (func $7)) + +;; CHECK-BIN-NODEBUG: (export "handled" (func $8)) + +;; CHECK-BIN-NODEBUG: (export "uncaught-1" (func $10)) + +;; CHECK-BIN-NODEBUG: (export "uncaught-2" (func $11)) + +;; CHECK-BIN-NODEBUG: (export "uncaught-3" (func $14)) + +;; CHECK-BIN-NODEBUG: (export "non-linear-1" (func $21)) + +;; CHECK-BIN-NODEBUG: (export "non-linear-2" (func $22)) + +;; CHECK-BIN-NODEBUG: (export "non-linear-3" (func $23)) + +;; CHECK-BIN-NODEBUG: (export "non-linear-4" (func $24)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $7) (param $0 (ref $6)) (result (ref $6)) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $8) (param $0 contref) (param $1 nullcontref) (param $2 (ref cont)) (param $3 (ref nocont)) (result contref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $3) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $3 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (global.set $global$0 +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $2) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $4 (type $9) (param $0 (ref $4)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (cont.bind $4 $10 +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $5 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (suspend $tag$1) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $6 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $5) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $7 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$2 $block) +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $5) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $8 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$1 $block) +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $5) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $9 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (throw $tag$0) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $10 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$1 $block) +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $9) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $11 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (resume_throw $1 $tag$0 +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$1 $block) +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $5) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $12 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (call $13) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $13 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (suspend $tag$1) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $14 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (resume_throw $1 $tag$0 +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$1 $block) +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $12) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $15 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $16 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (suspend $tag$1) +;; CHECK-BIN-NODEBUG-NEXT: (suspend $tag$1) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $17 (type $2) (param $0 (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $18 (type $2) (param $0 (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$1 $block) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $19 (type $2) (param $0 (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (local $1 (ref null $1)) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$1 $block) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block1 (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$1 $block1) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $20 (type $2) (param $0 (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (cont.bind $1 $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $21 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (call $17 +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $15) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $22 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (call $18 +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $16) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $23 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (call $19 +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $16) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $24 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (call $20 +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $16) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/typed_continuations_contbind.wast b/test/lit/basic/stack_switching_contbind.wast similarity index 81% rename from test/lit/basic/typed_continuations_contbind.wast rename to test/lit/basic/stack_switching_contbind.wast index 065d4a7962f..f19cd270ec4 100644 --- a/test/lit/basic/typed_continuations_contbind.wast +++ b/test/lit/basic/stack_switching_contbind.wast @@ -29,6 +29,8 @@ ;; CHECK-TEXT: (type $5 (func (param (ref $ct1)) (result (ref $ct1)))) + ;; CHECK-TEXT: (type $6 (func (result (ref $ct1)))) + ;; CHECK-TEXT: (func $f (type $4) (param $x (ref $ct1)) (result (ref $ct2)) ;; CHECK-TEXT-NEXT: (cont.bind $ct1 $ct2 ;; CHECK-TEXT-NEXT: (i32.const 123) @@ -40,6 +42,8 @@ ;; CHECK-BIN: (type $5 (func (param (ref $ct1)) (result (ref $ct1)))) + ;; CHECK-BIN: (type $6 (func (result (ref $ct1)))) + ;; CHECK-BIN: (func $f (type $4) (param $x (ref $ct1)) (result (ref $ct2)) ;; CHECK-BIN-NEXT: (cont.bind $ct1 $ct2 ;; CHECK-BIN-NEXT: (i32.const 123) @@ -70,6 +74,23 @@ (local.get $x) ) ) + + ;; CHECK-TEXT: (func $k (type $6) (result (ref $ct1)) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable ContBind we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $k (type $6) (result (ref $ct1)) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $k (result (ref $ct1)) + (cont.bind $ct1 $ct1 + (unreachable) + ) + ) ) ;; CHECK-BIN-NODEBUG: (type $0 (func (param i32 i64 i32) (result i32))) @@ -83,6 +104,8 @@ ;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref $1)) (result (ref $1)))) +;; CHECK-BIN-NODEBUG: (type $6 (func (result (ref $1)))) + ;; CHECK-BIN-NODEBUG: (func $0 (type $4) (param $0 (ref $1)) (result (ref $3)) ;; CHECK-BIN-NODEBUG-NEXT: (cont.bind $1 $3 ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) @@ -96,3 +119,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $6) (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/typed_continuations_contnew.wast b/test/lit/basic/stack_switching_contnew.wast similarity index 56% rename from test/lit/basic/typed_continuations_contnew.wast rename to test/lit/basic/stack_switching_contnew.wast index c0539cb9a73..ce945828802 100644 --- a/test/lit/basic/typed_continuations_contnew.wast +++ b/test/lit/basic/stack_switching_contnew.wast @@ -19,15 +19,33 @@ ;; CHECK-TEXT: (type $2 (func (result (ref $ct)))) + ;; CHECK-TEXT: (type $3 (func (result (ref cont)))) + ;; CHECK-TEXT: (elem declare func $g) - ;; CHECK-TEXT: (func $g (type $ft) (param $0 i32) (result i32) - ;; CHECK-TEXT-NEXT: (i32.const 123) + ;; CHECK-TEXT: (func $f (type $2) (result (ref $ct)) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable ContNew we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (type $2 (func (result (ref $ct)))) + ;; CHECK-BIN: (type $3 (func (result (ref cont)))) + ;; CHECK-BIN: (elem declare func $g) + ;; CHECK-BIN: (func $f (type $2) (result (ref $ct)) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $f (result (ref $ct)) + (cont.new $ct (unreachable))) + + ;; CHECK-TEXT: (func $g (type $ft) (param $0 i32) (result i32) + ;; CHECK-TEXT-NEXT: (i32.const 123) + ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $g (type $ft) (param $0 i32) (result i32) ;; CHECK-BIN-NEXT: (i32.const 123) ;; CHECK-BIN-NEXT: ) @@ -40,7 +58,9 @@ ;; CHECK-BIN-NODEBUG: (type $2 (func (result (ref $1)))) - ;; CHECK-BIN-NODEBUG: (elem declare func $0) + ;; CHECK-BIN-NODEBUG: (type $3 (func (result (ref cont)))) + + ;; CHECK-BIN-NODEBUG: (elem declare func $1) (elem declare func $g) ;; CHECK-TEXT: (func $h (type $2) (result (ref $ct)) @@ -57,13 +77,36 @@ (cont.new $ct (ref.func $g)) ) + ;; CHECK-TEXT: (func $k (type $3) (result (ref cont)) + ;; CHECK-TEXT-NEXT: (cont.new $ct + ;; CHECK-TEXT-NEXT: (ref.null nofunc) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $k (type $3) (result (ref cont)) + ;; CHECK-BIN-NEXT: (cont.new $ct + ;; CHECK-BIN-NEXT: (ref.null nofunc) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $k (result (ref cont)) + (cont.new $ct (ref.null nofunc))) + ) -;; CHECK-BIN-NODEBUG: (func $0 (type $0) (param $0 i32) (result i32) +;; CHECK-BIN-NODEBUG: (func $0 (type $2) (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $0) (param $0 i32) (result i32) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $1 (type $2) (result (ref $1)) +;; CHECK-BIN-NODEBUG: (func $2 (type $2) (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $3 (type $3) (result (ref cont)) ;; CHECK-BIN-NODEBUG-NEXT: (cont.new $1 -;; CHECK-BIN-NODEBUG-NEXT: (ref.func $0) +;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/typed_continuations_resume.wast b/test/lit/basic/stack_switching_resume.wast similarity index 100% rename from test/lit/basic/typed_continuations_resume.wast rename to test/lit/basic/stack_switching_resume.wast diff --git a/test/lit/basic/stack_switching_resume_throw.wast b/test/lit/basic/stack_switching_resume_throw.wast new file mode 100644 index 00000000000..64a2ae62b6f --- /dev/null +++ b/test/lit/basic/stack_switching_resume_throw.wast @@ -0,0 +1,163 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + ;; CHECK-TEXT: (type $ft (func (param i32) (result i32))) + ;; CHECK-BIN: (type $ft (func (param i32) (result i32))) + (type $ft (func (param i32) (result i32))) + ;; CHECK-TEXT: (type $ct (cont $ft)) + ;; CHECK-BIN: (type $ct (cont $ft)) + (type $ct (cont $ft)) + + ;; CHECK-TEXT: (type $2 (func (result i32 (ref $ct)))) + + ;; CHECK-TEXT: (type $3 (func (param i64))) + + ;; CHECK-TEXT: (type $4 (func (param (ref $ct)) (result i32))) + + ;; CHECK-TEXT: (type $5 (func (result i32))) + + ;; CHECK-TEXT: (tag $t (type $ft) (param i32) (result i32)) + ;; CHECK-BIN: (type $2 (func (result i32 (ref $ct)))) + + ;; CHECK-BIN: (type $3 (func (param i64))) + + ;; CHECK-BIN: (type $4 (func (param (ref $ct)) (result i32))) + + ;; CHECK-BIN: (type $5 (func (result i32))) + + ;; CHECK-BIN: (tag $t (type $ft) (param i32) (result i32)) + (tag $t (param i32) (result i32)) + ;; CHECK-TEXT: (tag $e (type $3) (param i64)) + ;; CHECK-BIN: (tag $e (type $3) (param i64)) + (tag $e (param i64)) + + ;; CHECK-TEXT: (func $go (type $4) (param $x (ref $ct)) (result i32) + ;; CHECK-TEXT-NEXT: (tuple.extract 2 0 + ;; CHECK-TEXT-NEXT: (block $handler (type $2) (result i32 (ref $ct)) + ;; CHECK-TEXT-NEXT: (return + ;; CHECK-TEXT-NEXT: (resume_throw $ct $e (on $t $handler) + ;; CHECK-TEXT-NEXT: (i64.const 123) + ;; CHECK-TEXT-NEXT: (local.get $x) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $go (type $4) (param $x (ref $ct)) (result i32) + ;; CHECK-BIN-NEXT: (local $scratch (tuple i32 (ref $ct))) + ;; CHECK-BIN-NEXT: (local $scratch_2 i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_2 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block $block (type $2) (result i32 (ref $ct)) + ;; CHECK-BIN-NEXT: (return + ;; CHECK-BIN-NEXT: (resume_throw $ct $e (on $t $block) + ;; CHECK-BIN-NEXT: (i64.const 123) + ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (tuple.extract 2 1 + ;; CHECK-BIN-NEXT: (local.get $scratch) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_2) + ;; CHECK-BIN-NEXT: ) + (func $go (param $x (ref $ct)) (result i32) + (tuple.extract 2 0 + (block $handler (result i32 (ref $ct)) + (return + (resume_throw $ct $e + (on $t $handler) + (i64.const 123) + (local.get $x) + ) + ) + ) + ) + ) + + ;; CHECK-TEXT: (func $unreachable (type $5) (result i32) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable ResumeThrow we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (i64.const 123) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $unreachable (type $5) (result i32) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (i64.const 123) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $unreachable (result i32) + (resume_throw $ct $e + (i64.const 123) + (unreachable) + ) + ) +) +;; CHECK-BIN-NODEBUG: (type $0 (func (param i32) (result i32))) + +;; CHECK-BIN-NODEBUG: (type $1 (cont $0)) + +;; CHECK-BIN-NODEBUG: (type $2 (func (result i32 (ref $1)))) + +;; CHECK-BIN-NODEBUG: (type $3 (func (param i64))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param (ref $1)) (result i32))) + +;; CHECK-BIN-NODEBUG: (type $5 (func (result i32))) + +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $0) (param i32) (result i32)) + +;; CHECK-BIN-NODEBUG: (tag $tag$1 (type $3) (param i64)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $4) (param $0 (ref $1)) (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 (ref $1))) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_2 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_2 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (block $block (type $2) (result i32 (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (return +;; CHECK-BIN-NODEBUG-NEXT: (resume_throw $1 $tag$1 (on $tag$0 $block) +;; CHECK-BIN-NODEBUG-NEXT: (i64.const 123) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_2) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $5) (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (i64.const 123) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/typed_continuations_suspend.wast b/test/lit/basic/stack_switching_suspend.wast similarity index 100% rename from test/lit/basic/typed_continuations_suspend.wast rename to test/lit/basic/stack_switching_suspend.wast diff --git a/test/lit/basic/stack_switching_switch.wast b/test/lit/basic/stack_switching_switch.wast new file mode 100644 index 00000000000..152ef0aedec --- /dev/null +++ b/test/lit/basic/stack_switching_switch.wast @@ -0,0 +1,219 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + (rec + ;; CHECK-TEXT: (rec + ;; CHECK-TEXT-NEXT: (type $ft (func (param i32 (ref null $ct)) (result i32))) + ;; CHECK-BIN: (rec + ;; CHECK-BIN-NEXT: (type $ft (func (param i32 (ref null $ct)) (result i32))) + (type $ft (func (param i32) (param (ref null $ct)) (result i32))) + ;; CHECK-TEXT: (type $ct (cont $ft)) + ;; CHECK-BIN: (type $ct (cont $ft)) + (type $ct (cont $ft))) + + ;; CHECK-TEXT: (type $2 (func (param (ref null $ct)) (result i32))) + + ;; CHECK-TEXT: (type $3 (func (result i32))) + + ;; CHECK-TEXT: (type $4 (func (param (ref $ct)) (result i32))) + + ;; CHECK-TEXT: (tag $t (type $3) (result i32)) + ;; CHECK-BIN: (type $2 (func (param (ref null $ct)) (result i32))) + + ;; CHECK-BIN: (type $3 (func (result i32))) + + ;; CHECK-BIN: (type $4 (func (param (ref $ct)) (result i32))) + + ;; CHECK-BIN: (tag $t (type $3) (result i32)) + (tag $t (result i32)) + + ;; CHECK-TEXT: (func $swap (type $2) (param $k (ref null $ct)) (result i32) + ;; CHECK-TEXT-NEXT: (local $scratch (tuple i32 (ref null $ct))) + ;; CHECK-TEXT-NEXT: (local $scratch_2 i32) + ;; CHECK-TEXT-NEXT: (return + ;; CHECK-TEXT-NEXT: (block (result i32) + ;; CHECK-TEXT-NEXT: (local.set $scratch_2 + ;; CHECK-TEXT-NEXT: (tuple.extract 2 0 + ;; CHECK-TEXT-NEXT: (local.tee $scratch + ;; CHECK-TEXT-NEXT: (switch $ct $t + ;; CHECK-TEXT-NEXT: (i32.const 42) + ;; CHECK-TEXT-NEXT: (local.get $k) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.set $k + ;; CHECK-TEXT-NEXT: (tuple.extract 2 1 + ;; CHECK-TEXT-NEXT: (local.get $scratch) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $scratch_2) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $swap (type $2) (param $k (ref null $ct)) (result i32) + ;; CHECK-BIN-NEXT: (local $scratch i32) + ;; CHECK-BIN-NEXT: (local $scratch_2 i32) + ;; CHECK-BIN-NEXT: (local $3 (ref null $ct)) + ;; CHECK-BIN-NEXT: (local $scratch_4 (tuple i32 (ref null $ct))) + ;; CHECK-BIN-NEXT: (local $scratch_5 i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_2 + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block (result i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_5 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch_4 + ;; CHECK-BIN-NEXT: (switch $ct $t + ;; CHECK-BIN-NEXT: (i32.const 42) + ;; CHECK-BIN-NEXT: (local.get $k) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.set $3 + ;; CHECK-BIN-NEXT: (tuple.extract 2 1 + ;; CHECK-BIN-NEXT: (local.get $scratch_4) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_5) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.set $k + ;; CHECK-BIN-NEXT: (local.get $3) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (return + ;; CHECK-BIN-NEXT: (local.get $scratch_2) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $swap (param $k (ref null $ct)) (result i32) + (switch $ct $t + (i32.const 42) + (local.get $k)) + (local.set $k) + (return)) + + ;; CHECK-TEXT: (func $go (type $4) (param $x (ref $ct)) (result i32) + ;; CHECK-TEXT-NEXT: (resume $ct (on $t switch) + ;; CHECK-TEXT-NEXT: (i32.const 123) + ;; CHECK-TEXT-NEXT: (ref.null nocont) + ;; CHECK-TEXT-NEXT: (local.get $x) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $go (type $4) (param $x (ref $ct)) (result i32) + ;; CHECK-BIN-NEXT: (resume $ct (on $t switch) + ;; CHECK-BIN-NEXT: (i32.const 123) + ;; CHECK-BIN-NEXT: (ref.null nocont) + ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $go (param $x (ref $ct)) (result i32) + (resume $ct + (on $t switch) + (i32.const 123) + (ref.null $ct) + (local.get $x) + ) + ) + + ;; CHECK-TEXT: (func $unreachable (type $2) (param $k (ref null $ct)) (result i32) + ;; CHECK-TEXT-NEXT: (return + ;; CHECK-TEXT-NEXT: (local.tee $k + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable StackSwitch we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (i32.const 42) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $unreachable (type $2) (param $k (ref null $ct)) (result i32) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (i32.const 42) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $unreachable (param $k (ref null $ct)) (result i32) + (switch $ct $t + (i32.const 42) + (unreachable)) + (local.set $k) + (return) + ) +) +;; CHECK-BIN-NODEBUG: (rec +;; CHECK-BIN-NODEBUG-NEXT: (type $0 (func (param i32 (ref null $1)) (result i32))) + +;; CHECK-BIN-NODEBUG: (type $1 (cont $0)) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref null $1)) (result i32))) + +;; CHECK-BIN-NODEBUG: (type $3 (func (result i32))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param (ref $1)) (result i32))) + +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $3) (result i32)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (ref null $1)) (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $1 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $2 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $3 (ref null $1)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 (ref null $1))) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_5 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $2 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $1 +;; CHECK-BIN-NODEBUG-NEXT: (block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_5 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (switch $1 $tag$0 +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 42) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $3 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_5) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $3) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (return +;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $4) (param $0 (ref $1)) (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (resume $1 (on $tag$0 switch) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) +;; CHECK-BIN-NODEBUG-NEXT: (ref.null nocont) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $2) (param $0 (ref null $1)) (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 42) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/typed_continuations.wast b/test/lit/basic/typed_continuations.wast deleted file mode 100644 index 1e024ca0b3e..00000000000 --- a/test/lit/basic/typed_continuations.wast +++ /dev/null @@ -1,68 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: wasm-opt %s -all -o %t.text.wast -g -S -;; RUN: wasm-as %s -all -g -o %t.wasm -;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast -;; RUN: wasm-as %s -all -o %t.nodebug.wasm -;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast -;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT -;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN -;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG - -(module - ;; CHECK-TEXT: (type $ft (func (param i32) (result i32))) - ;; CHECK-BIN: (type $ft (func (param i32) (result i32))) - (type $ft (func (param i32) (result i32))) - ;; CHECK-TEXT: (type $ct (cont $ft)) - ;; CHECK-BIN: (type $ct (cont $ft)) - (type $ct (cont $ft)) - - ;; CHECK-TEXT: (type $2 (func (param (ref $ct)) (result (ref $ct)))) - - ;; CHECK-TEXT: (type $3 (func (param contref nullcontref (ref cont) (ref nocont)) (result contref))) - - ;; CHECK-TEXT: (func $id (type $2) (param $x (ref $ct)) (result (ref $ct)) - ;; CHECK-TEXT-NEXT: (local.get $x) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (type $2 (func (param (ref $ct)) (result (ref $ct)))) - - ;; CHECK-BIN: (type $3 (func (param contref nullcontref (ref cont) (ref nocont)) (result contref))) - - ;; CHECK-BIN: (func $id (type $2) (param $x (ref $ct)) (result (ref $ct)) - ;; CHECK-BIN-NEXT: (local.get $x) - ;; CHECK-BIN-NEXT: ) - (func $id (param $x (ref $ct)) (result (ref $ct)) - (local.get $x) - ) - - ;; CHECK-TEXT: (func $id2 (type $3) (param $w contref) (param $x nullcontref) (param $y (ref cont)) (param $z (ref nocont)) (result contref) - ;; CHECK-TEXT-NEXT: (local.get $z) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $id2 (type $3) (param $w contref) (param $x nullcontref) (param $y (ref cont)) (param $z (ref nocont)) (result contref) - ;; CHECK-BIN-NEXT: (local.get $z) - ;; CHECK-BIN-NEXT: ) - (func $id2 - (param $w contref) - (param $x nullcontref) - (param $y (ref cont)) - (param $z (ref nocont)) - (result contref) - (local.get $z) - ) - -) -;; CHECK-BIN-NODEBUG: (type $0 (func (param i32) (result i32))) - -;; CHECK-BIN-NODEBUG: (type $1 (cont $0)) - -;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref $1)) (result (ref $1)))) - -;; CHECK-BIN-NODEBUG: (type $3 (func (param contref nullcontref (ref cont) (ref nocont)) (result contref))) - -;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (ref $1)) (result (ref $1)) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 contref) (param $1 nullcontref) (param $2 (ref cont)) (param $3 (ref nocont)) (result contref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $3) -;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 7f2e1bc9452..63f4f03adf8 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -116,9 +116,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index 06e2c4a0dfc..2960ddb9ae0 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -123,9 +123,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index d698afe86f0..1efe7ffd82b 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -109,9 +109,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index 5f9f17ff286..1ed447b16b1 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -151,9 +151,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index f83fc5c0f1e..0e3b5f8b166 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -139,9 +139,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index fa68453a76d..bc2d0553376 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -762,9 +762,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 49130d2a9eb..ec039f5b238 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -771,9 +771,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 5116b7bea12..9cd47473841 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -148,9 +148,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 04d74142160..1e4267dc26f 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -248,9 +248,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 34a42d2f4a6..48cac67700e 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -725,9 +725,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt index e6b611d4a4e..2cd2573f10b 100644 --- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt +++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt @@ -14,7 +14,7 @@ --enable-extended-const --enable-strings --enable-multimemory ---enable-typed-continuations +--enable-stack-switching --enable-shared-everything --enable-fp16 --enable-bulk-memory-opt diff --git a/test/unit/test_features.py b/test/unit/test_features.py index 1b91eb255f8..0a232da0fb4 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -54,10 +54,10 @@ def check_gc(self, module, error): self.check_feature(module, error, '--enable-gc', ['--enable-reference-types']) - def check_typed_continuations(self, module, error): - # Typed continuations implies function references (which is provided by + def check_stack_switching(self, module, error): + # Stack switching implies function references (which is provided by # gc in binaryen, and implies reference types) and exceptions - self.check_feature(module, error, '--enable-typed-continuations', + self.check_feature(module, error, '--enable-stack-switching', ['--enable-gc', '--enable-reference-types', '--enable-exception-handling']) def test_v128_signature(self): @@ -296,9 +296,9 @@ def test_tag_results(self): (tag $foo (result i32)) ) ''' - self.check_typed_continuations(module, - 'Tags with result types require typed ' - 'continuations feature [--enable-typed-continuations]') + self.check_stack_switching(module, + 'Tags with result types require stack ' + 'switching feature [--enable-stack-switching]') def test_cont_type(self): module = ''' @@ -310,7 +310,7 @@ def test_cont_type(self): ) ) ''' - self.check_typed_continuations(module, 'all used types should be allowed') + self.check_stack_switching(module, 'all used types should be allowed') def test_call_indirect_overlong(self): # Check that the call-indirect-overlong enable and disable are ignored. @@ -447,7 +447,7 @@ def test_emit_all_features(self): '--enable-extended-const', '--enable-strings', '--enable-multimemory', - '--enable-typed-continuations', + '--enable-stack-switching', '--enable-shared-everything', '--enable-fp16', '--enable-bulk-memory-opt', From 7353da707a96e6cc9911569529a87d3ed0ac1370 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Jan 2025 13:50:51 -0800 Subject: [PATCH 261/622] JSPI: Add a chance for sleep() to not actually sleep (#7244) Rather than return a promise, it returns an integer immediately. --- scripts/fuzz_shell.js | 52 +++++++++++++++------- test/lit/d8/fuzz_shell_sleep.wast | 72 +++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 test/lit/d8/fuzz_shell_sleep.wast diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 1856c815b3e..252ee5629dc 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -224,6 +224,34 @@ function toAddressType(table, index) { return index; } +// Simple deterministic hashing, on an unsigned 32-bit seed. See e.g. +// https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine +var hashSeed; + +function hasHashSeed() { + return hashSeed !== undefined; +} + +function hashCombine(value) { + // hashSeed must be set before we do anything. + assert(hasHashSeed()); + + hashSeed ^= value + 0x9e3779b9 + (hashSeed << 6) + (hashSeed >>> 2); + return hashSeed >>> 0; +} + +// Get a random 32-bit number. This is like hashCombine but does not take a +// parameter. +function randomBits() { + return hashCombine(-1); +} + +// Return true with probability 1 in n. E.g. oneIn(3) returns false 2/3 of the +// time, and true 1/3 of the time. +function oneIn(n) { + return (randomBits() % n) == 0; +} + // Set up the imports. var tempRet0; var imports = { @@ -274,7 +302,10 @@ var imports = { // Sleep a given amount of ms (when JSPI) and return a given id after that. 'sleep': (ms, id) => { - if (!JSPI) { + // Also avoid sleeping even in JSPI mode, rarely, just to add variety + // here. Only do this when we have a hash seed, that is, when we are + // allowing randomness. + if (!JSPI || (hasHashSeed() && oneIn(10))) { return id; } return new Promise((resolve, reject) => { @@ -371,13 +402,6 @@ function build(binary) { } } -// Simple deterministic hashing, on an unsigned 32-bit seed. See e.g. -// https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine -function hashCombine(seed, value) { - seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >>> 2); - return seed >>> 0; -} - // Run the code by calling exports. The optional |ordering| parameter indicates // howe we should order the calls to the exports: if it is not provided, we call // them in the natural order, which allows our output to be compared to other @@ -385,6 +409,8 @@ function hashCombine(seed, value) { // provided, it is a random seed we use to make deterministic choices on // the order of calls. /* async */ function callExports(ordering) { + hashSeed = ordering; + // Call the exports we were told, or if we were not given an explicit list, // call them all. let relevantExports = exportsToCall || exportList; @@ -425,13 +451,12 @@ function hashCombine(seed, value) { task = tasks.pop(); } else { // Pick a random task. - ordering = hashCombine(ordering, tasks.length); - let i = ordering % tasks.length; + let i = hashCombine(tasks.length) % tasks.length; task = tasks.splice(i, 1)[0]; } // Execute the task. - console.log('[fuzz-exec] calling ' + task.name); + console.log(`[fuzz-exec] calling ${task.name}${task.deferred ? ' (after defer)' : ''}`); let result; try { result = task.func(); @@ -451,10 +476,7 @@ function hashCombine(seed, value) { // depending on each other, ensuring certain orders of execution. if (ordering !== undefined && !task.deferred && result && typeof result == 'object' && typeof result.then === 'function') { - // Hash with -1 here, just to get something different than the hashing a - // few lines above. - ordering = hashCombine(ordering, -1); - if (ordering & 1) { + if (randomBits() & 1) { // Defer it for later. Reuse the existing task for simplicity. console.log(`(jspi: defer ${task.name})`); task.func = /* async */ () => { diff --git a/test/lit/d8/fuzz_shell_sleep.wast b/test/lit/d8/fuzz_shell_sleep.wast new file mode 100644 index 00000000000..40bbb6e8289 --- /dev/null +++ b/test/lit/d8/fuzz_shell_sleep.wast @@ -0,0 +1,72 @@ +(module + (import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32))) + + (func $func1 (export "func1") (result i32) + (call $sleep + (i32.const 0) ;; ms (d8 always sleeps 0 anyhow) + (i32.const 1) ;; id + ) + ) + + (func $func2 (export "func2") (result i32) + (call $sleep + (i32.const 0) + (i32.const 2) + ) + ) + + (func $func3 (export "func3") (result i32) + (call $sleep + (i32.const 0) + (i32.const 3) + ) + ) + + (func $func4 (export "func4") (result i32) + (call $sleep + (i32.const 0) + (i32.const 4) + ) + ) + + (func $func5 (export "func5") (result i32) + (call $sleep + (i32.const 0) + (i32.const 5) + ) + ) +) + +;; See fuzz_shell_jspi.wast for how the following works. +;; RUN: echo "JSPI = 1;" > %t.0.js +;; RUN: cat %S/../../../scripts/fuzz_shell.js | node -e "process.stdout.write(require('fs').readFileSync(0, 'utf-8').replace(/[/][*] async [*][/]/g, 'async').replace(/[/][*] await [*][/]/g, 'await'))" >> %t.0.js + +;; Replace the callExports() at the end with a call that has a random seed. +;; RUN: cat %t.0.js | node -e "process.stdout.write(require('fs').readFileSync(0, 'utf-8').replace('callExports()', 'callExports(66)'))" > %t.js + +;; Run that JS shell with our wasm. +;; RUN: wasm-opt %s -o %t.wasm -q +;; RUN: v8 --wasm-staging %t.js -- %t.wasm | filecheck %s +;; +;; We should see a few cases that avoid sleeping: func2, func3, and func4 all +;; return a result immediately, showing they do not sleep. (Note though that +;; func2 is more because we do not have a toplevel await, see comment in +;; fuzz_shell_jspi.wast.) +;; +;; CHECK: [fuzz-exec] calling func2 +;; CHECK: [fuzz-exec] note result: func2 => 2 +;; CHECK: [fuzz-exec] calling func1 +;; CHECK: (jspi: defer func1) +;; CHECK: [fuzz-exec] calling func3 +;; CHECK: [fuzz-exec] note result: func3 => 3 +;; CHECK: [fuzz-exec] calling func1 (after defer) +;; CHECK: (jspi: finish func1) +;; CHECK: [fuzz-exec] note result: func1 => 1 +;; CHECK: [fuzz-exec] calling func5 +;; CHECK: (jspi: defer func5) +;; CHECK: [fuzz-exec] calling func4 +;; CHECK: [fuzz-exec] note result: func4 => 4 +;; CHECK: [fuzz-exec] calling func5 (after defer) +;; CHECK: (jspi: finish func5) +;; CHECK: [fuzz-exec] note result: func5 => 5 + From 36c10d46cac02d5d7acc30b9514701f9fe9345e6 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 29 Jan 2025 10:18:49 -0800 Subject: [PATCH 262/622] Release version 122 (#7248) --- CHANGELOG.md | 13 +++++++++++-- CMakeLists.txt | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f05140491e0..630a5d48e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,17 @@ full changeset diff at the end of each section. Current Trunk ------------- - - `struct.atomic.get`/`struct.atomic.set` now require the threads feature, - `--enable-threads`. (#7185) +v122 +---- + + - The heap type associated with a tag is now preserved through optimization. + (#7220) + - The "typed-continuations" features is renamed "stack-switching" and the + latest instructions are experimentally supported. (#7041) + - WasmGC branches that send extra values can now be parsed via lowering to use + scratch locals. (#7202) + - Add experimental support for atomic struct get and set (#7155) and RMW + (#7225) operations. v121 ---- diff --git a/CMakeLists.txt b/CMakeLists.txt index 54103c7b6c7..47dcd16f84a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.16.3) # to reduce this for compatability with emsdk. set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version") -project(binaryen LANGUAGES C CXX VERSION 121) +project(binaryen LANGUAGES C CXX VERSION 122) include(GNUInstallDirs) # The C++ standard whose features are required to build Binaryen. From d0321bcb1deaee77b50fbea24eb3e344cda7dc14 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jan 2025 13:22:28 -0800 Subject: [PATCH 263/622] Remove stale TODO in Monomorphize.cpp [NFC] (#7249) This was done in #6760 ("monomorphize all the things"). --- src/passes/Monomorphize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/Monomorphize.cpp b/src/passes/Monomorphize.cpp index f05d42445fa..2459acf1796 100644 --- a/src/passes/Monomorphize.cpp +++ b/src/passes/Monomorphize.cpp @@ -24,7 +24,7 @@ // * If a call provides a more refined type than the function declares for a // parameter. // * If a call provides a constant as a parameter. -// * If a call provides a GC allocation as a parameter. TODO +// * If a call provides a GC allocation as a parameter. // * If a call is dropped. TODO also other stuff on the outside, e.g. eqz? // // We realize the benefit by creating a monomorphized (specialized/refined) From 30b7241d2e7c841c22f03e8baf73a523a46fd60d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 29 Jan 2025 13:47:11 -0800 Subject: [PATCH 264/622] Add FuzzTest and use it to fuzz types (#7246) FuzzTest is a state-of-the-art fuzzing framework. It supports writing property-based fuzz targets very similar to googletest unit tests, where the framework provides the arguments to the test function drawn from a given domain. These fuzz tests are run continuously for one second each when running the normal googletest unit tests, but FuzzTest also supports building in "fuzzing mode" where the tests can be run continuously with coverage-guided mutations until they find a bug. Add FuzzTest as a third_party dependency that is not built by default. To build FuzzTest and the fuzz tests that use it, set the CMake variable `BUILD_FUZZTEST=ON`. To build in fuzzing mode, additionally set the CMake variable `FUZZTEST_FUZZING_MODE=ON`. One of FuzzTest's key features is its support for domain combinators, which combine simple domains into more complex domains. For example, the domain `VariantOf(InRange(0, 10), Arbitrary())` produces a std::variant that either holds an integer between 0 and 10 or an arbitrary string. The set of available combinators is powerful enough to build domains for arbitrarily structured types. Use domain combinators to define a domain of WebAssembly type definitions. The implementation of this domain follows the same general structure as the existing heap type fuzzer: it chooses the size of rec groups, then it chooses the supertypes and hierarchies for all the definitions, then it generates the particular definitions. The difference is that all random choices are made by the FuzzTest framework rather than our own code. Whenever the domains of future choices will depend on the outcome of the current choice, we use the `FlatMap` combinator to make a choice from the current domain, then pass it to a continuation that finishes constructing the final domain of types. This leads to strange continuation-passing code, but allows us to recursively construct the domain of type definitions. The current implementation of the type definition domain is not ideal: the tree of choices used to produce a particular set of type definitions is deeper and narrower than it could be. Since a mutation of one choice in the tree requires regenerating and changing the subtree of choices rooted at the changed choice, having a narrower tree than necessary means that small mutations are not as diverse as they could be and having a deeper tree means that many mutations are larger than they could be. The quality of the domain construction will be improved in the future. --- .flake8 | 2 +- .github/workflows/ci.yml | 28 + .gitignore | 2 + .gitmodules | 3 + CMakeLists.txt | 119 ++-- check.py | 4 +- test/gtest/CMakeLists.txt | 19 +- test/gtest/type-builder.cpp | 39 ++ test/gtest/type-domains.cpp | 1189 +++++++++++++++++++++++++++++++++++ test/gtest/type-domains.h | 171 +++++ test/gtest/type-test.h | 1 - third_party/CMakeLists.txt | 24 +- third_party/fuzztest | 1 + 13 files changed, 1537 insertions(+), 65 deletions(-) create mode 100644 test/gtest/type-domains.cpp create mode 100644 test/gtest/type-domains.h create mode 160000 third_party/fuzztest diff --git a/.flake8 b/.flake8 index 4e967e90899..0202f1680f6 100644 --- a/.flake8 +++ b/.flake8 @@ -6,4 +6,4 @@ ignore = E241, ; line break after binary operator W504 -exclude = third_party,./test/emscripten,./test/spec,./test/wasm-install,./test/lit +exclude = third_party,./test/emscripten,./test/spec,./test/wasm-install,./test/lit,./_deps diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bb8aef00c3..2ed88400d8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,6 +147,34 @@ jobs: - name: test run: python check.py --binaryen-bin=out/bin + # Copied and modified from build-clang + build-fuzztest: + name: clang with fuzztest + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - uses: actions/checkout@v4 + with: + submodules: true + - name: install ninja + run: sudo apt-get install ninja-build + - name: install v8 + run: | + npm install jsvu -g + jsvu --os=default --engines=v8 + - name: install Python dev dependencies + run: pip3 install -r requirements-dev.txt + - name: cmake + run: | + mkdir -p out + cmake -S . -B out -G Ninja -DCMAKE_INSTALL_PREFIX=out/install -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DBUILD_FUZZTEST=ON + - name: build + run: cmake --build out -v + - name: test + run: python check.py --binaryen-bin=out/bin + # TODO(sbc): Find a way to reduce the duplicate between these sanitizer jobs build-asan: name: asan diff --git a/.gitignore b/.gitignore index 45a0fcad856..7acc6f9cd76 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ CMakeFiles /.ninja_log /bin/ /lib/ +/_deps/ +/dist/ /config.h /emcc-build compile_commands.json diff --git a/.gitmodules b/.gitmodules index 671d8041992..990b797a0e0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "test/spec/testsuite"] path = test/spec/testsuite url = https://github.com/WebAssembly/testsuite.git +[submodule "third_party/fuzztest"] + path = third_party/fuzztest + url = https://github.com/google/fuzztest diff --git a/CMakeLists.txt b/CMakeLists.txt index 47dcd16f84a..caa840beeb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,8 @@ option(BYN_ENABLE_LTO "Build with LTO" Off) # Turn this off to avoid the dependency on gtest. option(BUILD_TESTS "Build GTest-based tests" ON) +option(BUILD_FUZZTEST "Build fuzztest-based tests and fuzzers" OFF) + # Turn this off to build only the library. option(BUILD_TOOLS "Build tools" ON) @@ -161,8 +163,8 @@ endfunction() function(binaryen_add_executable name sources) add_executable(${name} ${sources}) - target_link_libraries(${name} Threads::Threads) - target_link_libraries(${name} binaryen) + target_link_libraries(${name} PRIVATE Threads::Threads) + target_link_libraries(${name} PRIVATE binaryen) binaryen_setup_rpath(${name}) install(TARGETS ${name} DESTINATION ${CMAKE_INSTALL_BINDIR}) endfunction() @@ -258,7 +260,10 @@ if(MSVC) else() # MSVC add_compile_flag("-fno-omit-frame-pointer") - add_compile_flag("-fno-rtti") + if(NOT BUILD_FUZZTEST) + # fuzztest depends on RTTIs. + add_compile_flag("-fno-rtti") + endif() if(WIN32) add_compile_flag("-D_GNU_SOURCE") add_compile_flag("-D__STDC_FORMAT_MACROS") @@ -276,10 +281,6 @@ else() # MSVC # explicitly undefine it: add_nondebug_compile_flag("-UNDEBUG") endif() - if(NOT APPLE AND NOT "${CMAKE_CXX_FLAGS}" MATCHES "-fsanitize") - # This flag only applies to shared libraries so don't use add_link_flag - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") - endif() endif() if(EMSCRIPTEN) @@ -416,6 +417,25 @@ else() # MSVC add_compile_flag("-Wno-deprecated-declarations") endif() + if(BUILD_FUZZTEST) + add_compile_flag("-DFUZZTEST") + fuzztest_setup_fuzzing_flags() + + # Enabling fuzzing mode turns on sanitizers, which turn on additional + # warnings. To keep the build working, do not treat these warnings as + # errors. + add_compile_flag("-Wno-error=maybe-uninitialized") + add_compile_flag("-Wno-error=uninitialized") + add_compile_flag("-Wno-error=array-bounds") + add_compile_flag("-Wno-error=stringop-overread") + add_compile_flag("-Wno-error=missing-field-initializers") + endif() + + if(NOT APPLE AND NOT "${CMAKE_CXX_FLAGS}" MATCHES "-fsanitize") + # This flag only applies to shared libraries so don't use add_link_flag + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") + endif() + endif() # Declare libbinaryen @@ -483,83 +503,84 @@ endif() if(EMSCRIPTEN) # binaryen.js WebAssembly variant add_executable(binaryen_wasm ${binaryen_SOURCES}) - target_link_libraries(binaryen_wasm binaryen) - target_link_libraries(binaryen_wasm "-sFILESYSTEM") - target_link_libraries(binaryen_wasm "-sEXPORT_NAME=Binaryen") - target_link_libraries(binaryen_wasm "-sNODERAWFS=0") + target_link_libraries(binaryen_wasm PRIVATE binaryen) + target_link_libraries(binaryen_wasm PRIVATE "-sFILESYSTEM") + target_link_libraries(binaryen_wasm PRIVATE "-sEXPORT_NAME=Binaryen") + target_link_libraries(binaryen_wasm PRIVATE "-sNODERAWFS=0") # Do not error on the repeated NODERAWFS argument - target_link_libraries(binaryen_wasm "-Wno-unused-command-line-argument") + target_link_libraries(binaryen_wasm PRIVATE "-Wno-unused-command-line-argument") # Emit a single file for convenience of people using binaryen.js as a library, # so they only need to distribute a single file. if(EMSCRIPTEN_ENABLE_SINGLE_FILE) - target_link_libraries(binaryen_wasm "-sSINGLE_FILE") - endif() - target_link_libraries(binaryen_wasm "-sEXPORT_ES6") - target_link_libraries(binaryen_wasm "-sEXPORTED_RUNTIME_METHODS=stringToUTF8OnStack,stringToAscii") - target_link_libraries(binaryen_wasm "-sEXPORTED_FUNCTIONS=_malloc,_free") - target_link_libraries(binaryen_wasm "--post-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-post.js") - target_link_libraries(binaryen_wasm "-msign-ext") - target_link_libraries(binaryen_wasm "-mbulk-memory") - target_link_libraries(binaryen_wasm optimized "--closure=1") + target_link_libraries(binaryen_wasm PRIVATE "-sSINGLE_FILE") + endif() + target_link_libraries(binaryen_wasm PRIVATE "-sEXPORT_ES6") + target_link_libraries(binaryen_wasm PRIVATE "-sEXPORTED_RUNTIME_METHODS=stringToUTF8OnStack,stringToAscii") + target_link_libraries(binaryen_wasm PRIVATE "-sEXPORTED_FUNCTIONS=_malloc,_free") + target_link_libraries(binaryen_wasm PRIVATE "--post-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-post.js") + target_link_libraries(binaryen_wasm PRIVATE "-msign-ext") + target_link_libraries(binaryen_wasm PRIVATE "-mbulk-memory") + target_link_libraries(binaryen_wasm PRIVATE optimized "--closure=1") # TODO: Fix closure warnings! (#5062) - target_link_libraries(binaryen_wasm optimized "-Wno-error=closure") - target_link_libraries(binaryen_wasm optimized "-flto") - target_link_libraries(binaryen_wasm debug "--profiling") + target_link_libraries(binaryen_wasm PRIVATE optimized "-Wno-error=closure") + target_link_libraries(binaryen_wasm PRIVATE optimized "-flto") + target_link_libraries(binaryen_wasm PRIVATE debug "--profiling") # Avoid catching exit as that can confuse error reporting in Node, # see https://github.com/emscripten-core/emscripten/issues/17228 - target_link_libraries(binaryen_wasm "-sNODEJS_CATCH_EXIT=0") + target_link_libraries(binaryen_wasm PRIVATE "-sNODEJS_CATCH_EXIT=0") install(TARGETS binaryen_wasm DESTINATION ${CMAKE_INSTALL_BINDIR}) # binaryen.js JavaScript variant add_executable(binaryen_js ${binaryen_SOURCES}) - target_link_libraries(binaryen_js binaryen) - target_link_libraries(binaryen_js "-sWASM=0") - target_link_libraries(binaryen_js "-sWASM_ASYNC_COMPILATION=0") + target_link_libraries(binaryen_js PRIVATE binaryen) + target_link_libraries(binaryen_js PRIVATE "-sWASM=0") + target_link_libraries(binaryen_js PRIVATE "-sWASM_ASYNC_COMPILATION=0") + if(${CMAKE_CXX_COMPILER_VERSION} STREQUAL "6.0.1") # only valid with fastcomp and WASM=0 - target_link_libraries(binaryen_js "-sELIMINATE_DUPLICATE_FUNCTIONS") + target_link_libraries(binaryen_js PRIVATE "-sELIMINATE_DUPLICATE_FUNCTIONS") endif() # Disabling filesystem and setting web environment for js_of_ocaml # so it doesn't try to detect the "node" environment if(JS_OF_OCAML) - target_link_libraries(binaryen_js "-sFILESYSTEM=0") - target_link_libraries(binaryen_js "-sENVIRONMENT=web,worker") + target_link_libraries(binaryen_js PRIVATE "-sFILESYSTEM=0") + target_link_libraries(binaryen_js PRIVATE "-sENVIRONMENT=web,worker") else() - target_link_libraries(binaryen_js "-sFILESYSTEM=1") + target_link_libraries(binaryen_js PRIVATE "-sFILESYSTEM=1") endif() - target_link_libraries(binaryen_js "-sNODERAWFS=0") + target_link_libraries(binaryen_js PRIVATE "-sNODERAWFS=0") # Do not error on the repeated NODERAWFS argument - target_link_libraries(binaryen_js "-Wno-unused-command-line-argument") + target_link_libraries(binaryen_js PRIVATE "-Wno-unused-command-line-argument") if(EMSCRIPTEN_ENABLE_SINGLE_FILE) - target_link_libraries(binaryen_js "-sSINGLE_FILE") + target_link_libraries(binaryen_js PRIVATE "-sSINGLE_FILE") endif() - target_link_libraries(binaryen_js "-sEXPORT_NAME=Binaryen") + target_link_libraries(binaryen_js PRIVATE "-sEXPORT_NAME=Binaryen") # Currently, js_of_ocaml can only process ES5 code if(JS_OF_OCAML) - target_link_libraries(binaryen_js "-sEXPORT_ES6=0") + target_link_libraries(binaryen_js PRIVATE "-sEXPORT_ES6=0") else() - target_link_libraries(binaryen_js "-sEXPORT_ES6=1") + target_link_libraries(binaryen_js PRIVATE "-sEXPORT_ES6=1") endif() - target_link_libraries(binaryen_js "-sEXPORTED_RUNTIME_METHODS=stringToUTF8OnStack,stringToAscii") - target_link_libraries(binaryen_js "-sEXPORTED_FUNCTIONS=_malloc,_free") - target_link_libraries(binaryen_js "--post-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-post.js") + target_link_libraries(binaryen_js PRIVATE "-sEXPORTED_RUNTIME_METHODS=stringToUTF8OnStack,stringToAscii") + target_link_libraries(binaryen_js PRIVATE "-sEXPORTED_FUNCTIONS=_malloc,_free") + target_link_libraries(binaryen_js PRIVATE "--post-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-post.js") # js_of_ocaml needs a specified variable with special comment to provide the library to consumers if(JS_OF_OCAML) - target_link_libraries(binaryen_js "--extern-pre-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.jsoo-extern-pre.js") + target_link_libraries(binaryen_js PRIVATE "--extern-pre-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.jsoo-extern-pre.js") endif() - target_link_libraries(binaryen_js optimized "--closure=1") + target_link_libraries(binaryen_js PRIVATE optimized "--closure=1") # Currently, js_of_ocaml can only process ES5 code if(JS_OF_OCAML) - target_link_libraries(binaryen_js optimized "--closure-args=\"--language_out=ECMASCRIPT5\"") + target_link_libraries(binaryen_js PRIVATE optimized "--closure-args=\"--language_out=ECMASCRIPT5\"") endif() # TODO: Fix closure warnings! (#5062) - target_link_libraries(binaryen_js optimized "-Wno-error=closure") - target_link_libraries(binaryen_js optimized "-flto") - target_link_libraries(binaryen_js debug "--profiling") - target_link_libraries(binaryen_js debug "-sASSERTIONS") + target_link_libraries(binaryen_js PRIVATE optimized "-Wno-error=closure") + target_link_libraries(binaryen_js PRIVATE optimized "-flto") + target_link_libraries(binaryen_js PRIVATE debug "--profiling") + target_link_libraries(binaryen_js PRIVATE debug "-sASSERTIONS") # Avoid catching exit as that can confuse error reporting in Node, # see https://github.com/emscripten-core/emscripten/issues/17228 - target_link_libraries(binaryen_js "-sNODEJS_CATCH_EXIT=0") + target_link_libraries(binaryen_js PRIVATE "-sNODEJS_CATCH_EXIT=0") install(TARGETS binaryen_js DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() diff --git a/check.py b/check.py index dfaada0ab93..7385fb0f0cb 100755 --- a/check.py +++ b/check.py @@ -43,11 +43,11 @@ def run_version_tests(): print('[ checking --version ... ]\n') not_executable_suffix = ['.DS_Store', '.txt', '.js', '.ilk', '.pdb', '.dll', '.wasm', '.manifest'] - not_executable_prefix = ['binaryen-lit', 'binaryen-unittests'] + executable_prefix = ['wasm'] bin_files = [os.path.join(shared.options.binaryen_bin, f) for f in os.listdir(shared.options.binaryen_bin)] executables = [f for f in bin_files if os.path.isfile(f) and not any(f.endswith(s) for s in not_executable_suffix) and - not any(os.path.basename(f).startswith(s) for s in not_executable_prefix)] + any(os.path.basename(f).startswith(s) for s in executable_prefix)] executables = sorted(executables) assert len(executables) diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 3a586e00e47..b72094a0204 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -1,4 +1,8 @@ -include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third_party/googletest/googletest/include) +if(BUILD_FUZZTEST) + include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third_party/fuzztest) +else() + include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third_party/googletest/googletest/include) +endif() set(unittest_SOURCES arena.cpp @@ -21,10 +25,21 @@ set(unittest_SOURCES validator.cpp ) +if(BUILD_FUZZTEST) + set(unittest_SOURCES ${unittest_SOURCES} type-domains.cpp) +endif() + # suffix_tree.cpp includes LLVM header using std::iterator (deprecated in C++17) if (NOT MSVC) set_source_files_properties(suffix_tree.cpp PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations) endif() +enable_testing() +include(GoogleTest) binaryen_add_executable(binaryen-unittests "${unittest_SOURCES}") -target_link_libraries(binaryen-unittests gtest gtest_main) +if(BUILD_FUZZTEST) + link_fuzztest(binaryen-unittests) + gtest_discover_tests(binaryen-unittests) +else() + target_link_libraries(binaryen-unittests PRIVATE gtest gtest_main) +endif() diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 514df0c59a0..ac95d0e11b4 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -5,6 +5,10 @@ #include "wasm-type.h" #include "gtest/gtest.h" +#ifdef FUZZTEST +#include "type-domains.h" +#endif + using namespace wasm; TEST_F(TypeTest, TypeBuilderGrowth) { @@ -1056,6 +1060,41 @@ TEST_F(TypeTest, TestHeapTypeRelations) { } } +#ifdef FUZZTEST + +void TestHeapTypeRelationsFuzz(std::pair pair) { + auto [a, b] = pair; + auto lub = HeapType::getLeastUpperBound(a, b); + auto otherLub = HeapType::getLeastUpperBound(b, a); + EXPECT_EQ(lub, otherLub); + if (lub) { + EXPECT_EQ(a.getTop(), b.getTop()); + EXPECT_EQ(a.getBottom(), b.getBottom()); + EXPECT_TRUE(HeapType::isSubType(a, *lub)); + EXPECT_TRUE(HeapType::isSubType(b, *lub)); + } else { + EXPECT_NE(a.getTop(), b.getTop()); + EXPECT_NE(a.getBottom(), b.getBottom()); + } + if (a == b) { + EXPECT_EQ(lub, a); + EXPECT_EQ(lub, b); + } else if (lub && *lub == b) { + EXPECT_TRUE(HeapType::isSubType(a, b)); + EXPECT_FALSE(HeapType::isSubType(b, a)); + } else if (lub && *lub == a) { + EXPECT_FALSE(HeapType::isSubType(a, b)); + EXPECT_TRUE(HeapType::isSubType(b, a)); + } else if (lub) { + EXPECT_FALSE(HeapType::isSubType(a, b)); + EXPECT_FALSE(HeapType::isSubType(b, a)); + } +} +FUZZ_TEST(TypeFuzzTest, TestHeapTypeRelationsFuzz) + .WithDomains(ArbitraryHeapTypePair()); + +#endif // FUZZTEST + TEST_F(TypeTest, TestSubtypeErrors) { Type anyref = Type(HeapType::any, Nullable); Type eqref = Type(HeapType::eq, Nullable); diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp new file mode 100644 index 00000000000..b59bc2a0dd7 --- /dev/null +++ b/test/gtest/type-domains.cpp @@ -0,0 +1,1189 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "type-domains.h" +#include "gtest/gtest.h" + +namespace wasm { + +namespace { + +void printHeapType(std::ostream& o, HeapTypePlan& plan) { + if (auto* type = plan.getHeapType()) { + o << *type; + } else if (auto* i = plan.getIndex()) { + o << *i; + } +} + +void printRef(std::ostream& o, RefPlan& plan) { + o << "(ref "; + if (plan.nullable) { + o << "null "; + } + printHeapType(o, plan.type); + o << ")"; +} + +void printType(std::ostream& o, TypePlan& plan) { + if (auto* type = plan.getNonRef()) { + o << *type; + } else if (auto* ref = plan.getRef()) { + printRef(o, *ref); + } +} + +void printFieldType(std::ostream& o, FieldTypePlan& plan) { + if (auto* packed = plan.getPacked()) { + o << (*packed == Field::i8 ? "i8" : "i16"); + } else if (auto* type = plan.getNonPacked()) { + printType(o, *type); + } +} + +void printField(std::ostream& o, FieldPlan& plan) { + o << "(field "; + if (plan.mutable_) { + o << "(mut "; + } + printFieldType(o, plan.type); + if (plan.mutable_) { + o << ")"; + } + o << ")"; +} + +void printFunc(std::ostream& o, FuncPlan& plan) { + o << "(func"; + if (!plan.first.empty()) { + o << " (param"; + for (auto& type : plan.first) { + o << " "; + printType(o, type); + } + o << ")"; + } + if (!plan.second.empty()) { + o << " (result"; + for (auto& type : plan.second) { + o << " "; + printType(o, type); + } + o << ")"; + } + o << ")"; +} + +void printStruct(std::ostream& o, StructPlan& plan) { + o << "(struct"; + for (auto& field : plan) { + o << " "; + printField(o, field); + } + o << ")"; +} + +void printArray(std::ostream& o, ArrayPlan& plan) { + o << "(array "; + printField(o, plan); + o << ")"; +} + +void printCont(std::ostream& o, ContPlan& plan) { + o << "(cont "; + if (plan) { + o << *plan; + } else { + o << "$fallback"; + } + o << ")"; +} + +void printTypeDef(std::ostream& o, const TypeBuilderPlan& plan, size_t i) { + auto def = plan.defs[i]; + auto super = plan.supertypes[i]; + bool shared = plan.kinds[i].shared; + bool final = plan.kinds[i].final; + o << "(type (; " << i << " ;) "; + if (super || !final) { + o << "(sub "; + if (super) { + o << *super << " "; + } + } + if (shared) { + o << "(shared "; + } + if (auto* func = def.getFunc()) { + printFunc(o, *func); + } else if (auto* struct_ = def.getStruct()) { + printStruct(o, *struct_); + } else if (auto* array = def.getArray()) { + printArray(o, *array); + } else if (auto* cont = def.getCont()) { + printCont(o, *cont); + } else { + WASM_UNREACHABLE("unexpected kind"); + } + if (shared) { + o << ")"; + } + if (super || !final) { + o << ")"; + } + o << ")"; +} + +} // anonymous namespace + +std::ostream& operator<<(std::ostream& o, const TypeBuilderPlan& plan) { + assert(!plan.recGroupSizes.empty()); + o << "size: " << plan.size << ", rec group sizes: { " + << plan.recGroupSizes[0]; + for (size_t i = 1; i < plan.recGroupSizes.size(); ++i) { + o << ", " << plan.recGroupSizes[i]; + } + o << " }"; + + if (plan.supertypes.empty()) { + return o; + } + + auto printKind = [&](size_t i) { + if (plan.kinds[i].final) { + o << "*"; + } + if (plan.kinds[i].shared) { + o << "s"; + } + switch (plan.kinds[i].kind) { + case FuncKind: + o << "f"; + break; + case StructKind: + o << "s"; + break; + case ArrayKind: + o << "a"; + break; + case ContKind: + o << "s"; + break; + } + if (auto super = plan.supertypes[i]) { + o << "(" << *super << ")"; + } + }; + + o << ", kinds: { "; + printKind(0); + for (size_t i = 1; i < plan.size; ++i) { + o << ", "; + printKind(i); + } + o << " }"; + + if (plan.defs.empty()) { + return o; + } + + o << "\n"; + + for (size_t i = 0; i < plan.size; ++i) { + o << " "; + printTypeDef(o, plan, i); + o << "\n"; + } + + return o; +} + +namespace { + +template +using ResultVal = + typename std::invoke_result_t, size_t>::value_type; + +template +using ResultVec = std::vector>; + +template +using AccTuple = std::tuple, size_t, ResultVec>; + +template +fuzztest::Domain> StepMapVector(AccTuple); + +template +fuzztest::Domain> AppendMapVector(AccTuple acc, + ResultVal val) { + auto& [map, vec, i, results] = acc; + results.emplace_back(std::move(val)); + return StepMapVector(std::move(acc)); +} + +template +fuzztest::Domain> StepMapVector(AccTuple acc) { + auto& [map, vec, i, results] = acc; + if (i == vec.size()) { + // Base case. We've generated all the elements. + return fuzztest::Just(std::move(results)); + } + // Apply `map` to get the domain for the next element, then generate an + // element of that domain, append it to `results`, and recurse. + auto elemDomain = map(vec, i++); + return fuzztest::FlatMap( + AppendMapVector, fuzztest::Just(std::move(acc)), elemDomain); +} + +// Given a mapping of (const std::vector&, size_t i) -> Domain and a +// std::vector, apply the mapping elementwise and produce a +// Domain>. +template +fuzztest::Domain> MapVector(Map map, std::vector vec) { + return StepMapVector( + std::make_tuple(map, std::move(vec), size_t(0), ResultVec{})); +} + +// Given a mapping of T -> Domain and a std::vector, apply the mapping +// elementwise and produce a Domain>. This is a shorthand version +// of MapVector for when the output domains depend only on single elements. +template +auto MapElements(Map map, std::vector vec) { + return MapVector([map](std::vector vec, size_t i) { return map(vec[i]); }, + std::move(vec)); +} + +fuzztest::Domain ArbitraryUnsharedTypeKind() { + return fuzztest::ElementOf({FuncKind, StructKind, ArrayKind, ContKind}); +} + +fuzztest::Domain ArbitraryTypeKind() { + // Independently random unshared kind, sharedness, and mutability. + return fuzztest::StructOf(ArbitraryUnsharedTypeKind(), + fuzztest::Arbitrary(), + fuzztest::Arbitrary()); +} + +fuzztest::Domain TypeBuilderPlanSize() { + // Choose a size for the TypeBuilder. + return fuzztest::InRange(size_t(1), MaxTypeBuilderSize); +} + +fuzztest::Domain InitTypeBuilderPlan() { + // Create a TypeBuilderPlan with `size` and `curr` set to the same choice + // of size. `curr` represents how many slots still need a rec group. + return fuzztest::FlatMap( + [](size_t size) { + return fuzztest::Just(TypeBuilderPlan{size, size}); + }, + TypeBuilderPlanSize()); +} + +fuzztest::Domain StepRecGroup(TypeBuilderPlan plan); + +fuzztest::Domain AppendRecGroup(TypeBuilderPlan plan, + size_t newSize) { + // Update `plan` to append a recgroup of size `newSize`, then recurse iff + // there is still size unallocated to a rec group. + plan.curr -= newSize; + plan.recGroupSizes.push_back(newSize); + if (plan.curr == 0) { + return fuzztest::Just(std::move(plan)); + } else { + return StepRecGroup(std::move(plan)); + } +} + +fuzztest::Domain StepRecGroup(TypeBuilderPlan plan) { + // Given a plan that needs more rec groups, choose the size of the next rec + // group based on the available size remaining. Bias toward singleton rec + // groups. + auto remaining = plan.curr; + assert(remaining > 0); + return fuzztest::FlatMap( + AppendRecGroup, + fuzztest::Just(std::move(plan)), + fuzztest::OneOf(fuzztest::Just(size_t(1)), + fuzztest::InRange(size_t(1), remaining))); +} + +fuzztest::Domain ArbitraryRecGroupPlan() { + // Initialize a plan with just a size, then add rec group sizes. + return fuzztest::FlatMap(StepRecGroup, InitTypeBuilderPlan()); +} + +void TestRecGroupPlanSizes(TypeBuilderPlan plan) { + size_t sum = 0; + for (auto size : plan.recGroupSizes) { + sum += size; + } + EXPECT_EQ(plan.size, sum); + EXPECT_EQ(plan.curr, 0); +} +FUZZ_TEST(TypeBuilderDomainsTest, TestRecGroupPlanSizes) + .WithDomains(ArbitraryRecGroupPlan()); + +fuzztest::Domain StepSupertypeAndKind(TypeBuilderPlan plan); +fuzztest::Domain AppendKind(TypeBuilderPlan plan, + TypeKind kind); + +fuzztest::Domain AppendSupertype(TypeBuilderPlan plan, + std::optional super) { + plan.supertypes.push_back(super); + if (super) { + // If there is a supertype, then the current type will inherit its kind. + auto kind = plan.kinds[*super]; + return AppendKind(std::move(plan), kind); + } else { + // Otherwise, we give it an arbitrary kind. + return fuzztest::FlatMap( + AppendKind, fuzztest::Just(std::move(plan)), ArbitraryTypeKind()); + } +} + +fuzztest::Domain AppendKind(TypeBuilderPlan plan, + TypeKind kind) { + // We have chosen the kind either based on the supertype or arbitrarily. + // Either way, set it and then recurse iff there are more supertypes and kinds + // to set. + plan.kinds.push_back(kind); + if (plan.curr == plan.size) { + return fuzztest::Just(std::move(plan)); + } else { + return StepSupertypeAndKind(std::move(plan)); + } +} + +fuzztest::Domain StepSupertypeAndKind(TypeBuilderPlan plan) { + // Collect previous non-final types as possible supertypes. + auto index = plan.curr++; + std::vector possibleSupers; + for (size_t i = 0; i < index; ++i) { + if (!plan.kinds[i].final) { + possibleSupers.push_back(i); + } + } + if (possibleSupers.empty()) { + // No possible supertype. + return AppendSupertype(std::move(plan), std::nullopt); + } else { + // Optionally choose an available supertype. + return fuzztest::FlatMap( + AppendSupertype, + fuzztest::Just(std::move(plan)), + fuzztest::OptionalOf(fuzztest::ElementOf(std::move(possibleSupers)))); + } +} + +fuzztest::Domain ArbitraryAbstractTypeBuilderPlan() { + // Initialize with rec group sizes, then add supertype declarations and type + // kinds. + return fuzztest::FlatMap(StepSupertypeAndKind, ArbitraryRecGroupPlan()); +} + +void TestSupertypesAndKinds(TypeBuilderPlan plan) { + ASSERT_EQ(plan.size, plan.supertypes.size()); + ASSERT_EQ(plan.size, plan.kinds.size()); + for (size_t i = 0; i < plan.size; ++i) { + if (auto super = plan.supertypes[i]) { + EXPECT_LT(*super, i); + EXPECT_EQ(plan.kinds[*super].kind, plan.kinds[i].kind); + EXPECT_EQ(plan.kinds[*super].shared, plan.kinds[i].shared); + EXPECT_FALSE(plan.kinds[*super].final); + } + } +} +FUZZ_TEST(TypeBuilderDomainsTest, TestSupertypesAndKinds) + .WithDomains(ArbitraryAbstractTypeBuilderPlan()); + +fuzztest::Domain InitConcreteTypeBuilderPlan() { + // Reset `curr` to 0 (for simplicity) and initialize `numReferenceable` based + // on the size of the first rec group. + return fuzztest::Map( + [](TypeBuilderPlan plan) { + plan.curr = 0; + plan.numReferenceable = plan.recGroupSizes[0]; + return plan; + }, + ArbitraryAbstractTypeBuilderPlan()); +} + +template +std::vector AvailableMatchingIndices(TypeBuilderPlan plan, Pred pred) { + std::vector matches; + for (size_t i = 0; i < plan.numReferenceable; ++i) { + if (pred(plan.kinds[i].kind, plan.kinds[i].shared)) { + matches.push_back(i); + } + } + return matches; +} + +template +fuzztest::Domain AvailableMatchingOrAbstractHeapType( + TypeBuilderPlan plan, Pred pred, fuzztest::Domain abstract) { + // Look for referenceable indices with kinds matching the predicate and return + // a variant of the indices or the given abstract subtypes. If there are no + // possible indices, just return the abstract subtypes. + auto matches = AvailableMatchingIndices(std::move(plan), pred); + if (matches.empty()) { + return fuzztest::Map([](HeapType type) { return HeapTypePlan{type}; }, + abstract); + } else { + return fuzztest::VariantOf( + abstract, fuzztest::ElementOf(std::move(matches))); + } +} + +std::vector AvailableStrictSubIndices(TypeBuilderPlan plan, + size_t index) { + // Look for direct and indirect subtypes. To find indirect subtypes, keep + // track of all the possible supertypes that are subtypes of `super`. + std::vector matches; + std::vector acceptedSupers(plan.numReferenceable); + acceptedSupers[index] = true; + assert(plan.numReferenceable <= plan.size); + for (size_t i = index + 1; i < plan.numReferenceable; ++i) { + auto otherSuper = plan.supertypes[i]; + if (otherSuper && acceptedSupers[*otherSuper]) { + matches.push_back(i); + acceptedSupers[i] = true; + } + } + return matches; +} + +fuzztest::Domain AvailableStrictSubHeapType(TypeBuilderPlan plan, + HeapTypePlan super) { + // Get an available subtype of super. + if (auto* type = super.getHeapType()) { + auto share = type->getShared(); + bool shared = share == Shared; + + auto matchingOrAbstract = [=](auto pred, auto abstract) { + return AvailableMatchingOrAbstractHeapType( + std::move(plan), + [&](auto kind, bool otherShared) { + return otherShared == shared && pred(kind); + }, + abstract); + }; + + switch (type->getBasic(Unshared)) { + case HeapType::ext: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::noext.getBasic(share))}); + case HeapType::func: + return matchingOrAbstract( + [](auto kind) { return kind == FuncKind; }, + fuzztest::Just(HeapType(HeapTypes::nofunc.getBasic(share)))); + case HeapType::cont: + return matchingOrAbstract( + [](auto kind) { return kind == ContKind; }, + fuzztest::Just(HeapType(HeapTypes::nocont.getBasic(share)))); + case HeapType::any: + return matchingOrAbstract( + [](auto kind) { return kind == StructKind || kind == ArrayKind; }, + fuzztest::ElementOf({HeapType(HeapTypes::eq.getBasic(share)), + HeapType(HeapTypes::i31.getBasic(share)), + HeapType(HeapTypes::string.getBasic(share)), + HeapType(HeapTypes::struct_.getBasic(share)), + HeapType(HeapTypes::array.getBasic(share)), + HeapType(HeapTypes::none.getBasic(share))})); + case HeapType::eq: + return matchingOrAbstract( + [](auto kind) { return kind == StructKind || kind == ArrayKind; }, + fuzztest::ElementOf({HeapType(HeapTypes::i31.getBasic(share)), + HeapType(HeapTypes::struct_.getBasic(share)), + HeapType(HeapTypes::array.getBasic(share)), + HeapType(HeapTypes::none.getBasic(share))})); + case HeapType::struct_: + return matchingOrAbstract( + [](auto kind) { return kind == StructKind; }, + fuzztest::Just(HeapType(HeapTypes::none.getBasic(share)))); + case HeapType::array: + return matchingOrAbstract( + [](auto kind) { return kind == ArrayKind; }, + fuzztest::Just(HeapType(HeapTypes::none.getBasic(share)))); + case HeapType::exn: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::noexn.getBasic(share))}); + case HeapType::string: + case HeapType::i31: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::none.getBasic(share))}); + case HeapType::none: + case HeapType::noext: + case HeapType::nofunc: + case HeapType::nocont: + case HeapType::noexn: + // No strict subtypes, so just return super. + return fuzztest::Just(super); + } + WASM_UNREACHABLE("unexpected type"); + } else if (auto* index = super.getIndex()) { + assert(*index < plan.size); + auto kind = plan.kinds[*index].kind; + auto share = plan.kinds[*index].shared ? Shared : Unshared; + auto matches = AvailableStrictSubIndices(std::move(plan), *index); + HeapType bottom = HeapType::none; + switch (kind) { + case FuncKind: + bottom = HeapTypes::nofunc.getBasic(share); + break; + case StructKind: + case ArrayKind: + bottom = HeapTypes::none.getBasic(share); + break; + case ContKind: + bottom = HeapTypes::nocont.getBasic(share); + break; + } + if (matches.empty()) { + return fuzztest::Just(HeapTypePlan{bottom}); + } else { + return fuzztest::VariantOf( + fuzztest::Just(bottom), fuzztest::ElementOf(std::move(matches))); + } + } else { + WASM_UNREACHABLE("unexpected variant"); + } +} + +fuzztest::Domain +AvailableStrictSuperHeapType(TypeBuilderPlan plan, HeapTypePlan sub) { + if (auto* type = sub.getHeapType()) { + auto share = type->getShared(); + bool shared = share == Shared; + + auto matchingOrAbstract = [&](auto pred, auto abstract) { + return AvailableMatchingOrAbstractHeapType( + std::move(plan), + [&](auto kind, bool otherShared) { + return otherShared == shared && pred(kind); + }, + abstract); + }; + + switch (type->getBasic(Unshared)) { + case HeapType::ext: + case HeapType::func: + case HeapType::cont: + case HeapType::any: + case HeapType::exn: + // No strict supertypes, so just return sub. + return fuzztest::Just(sub); + case HeapType::eq: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::any.getBasic(share))}); + case HeapType::i31: + case HeapType::struct_: + case HeapType::array: + case HeapType::string: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::any.getBasic(share))}); + case HeapType::none: + return matchingOrAbstract( + [](auto kind) { return kind == StructKind || kind == ArrayKind; }, + fuzztest::ElementOf({HeapType(HeapTypes::any.getBasic(share)), + HeapType(HeapTypes::eq.getBasic(share)), + HeapType(HeapTypes::i31.getBasic(share)), + HeapType(HeapTypes::string.getBasic(share)), + HeapType(HeapTypes::struct_.getBasic(share)), + HeapType(HeapTypes::array.getBasic(share))})); + case HeapType::noext: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::ext.getBasic(share))}); + case HeapType::nofunc: + return matchingOrAbstract( + [](auto kind) { return kind == FuncKind; }, + fuzztest::Just(HeapType(HeapTypes::func.getBasic(share)))); + case HeapType::nocont: + return matchingOrAbstract( + [](auto kind) { return kind == ContKind; }, + fuzztest::Just(HeapType(HeapTypes::cont.getBasic(share)))); + case HeapType::noexn: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::exn.getBasic(share))}); + } + WASM_UNREACHABLE("unexpected type"); + } else if (auto* index = sub.getIndex()) { + assert(*index < plan.size); + // Collect indices from the supertype chain as well as abstract supertypes. + auto share = plan.kinds[*index].shared ? Shared : Unshared; + std::vector possibleIndices; + for (auto curr = plan.supertypes[*index]; curr; + curr = plan.supertypes[*curr]) { + possibleIndices.push_back(*curr); + } + std::vector abstract; + switch (plan.kinds[*index].kind) { + case FuncKind: + abstract = {HeapTypes::func.getBasic(share)}; + break; + case StructKind: + abstract = {HeapTypes::any.getBasic(share), + HeapTypes::eq.getBasic(share), + HeapTypes::struct_.getBasic(share)}; + break; + case ArrayKind: + abstract = {HeapTypes::any.getBasic(share), + HeapTypes::eq.getBasic(share), + HeapTypes::array.getBasic(share)}; + break; + case ContKind: + abstract = {HeapTypes::cont.getBasic(share)}; + break; + } + assert(!abstract.empty()); + if (possibleIndices.empty()) { + return fuzztest::Map([](auto type) { return HeapTypePlan{type}; }, + fuzztest::ElementOf(std::move(abstract))); + } else { + return fuzztest::VariantOf( + fuzztest::ElementOf(std::move(abstract)), + fuzztest::ElementOf(std::move(possibleIndices))); + } + } else { + WASM_UNREACHABLE("unexpected variant"); + } +} + +fuzztest::Domain AvailableHeapType(TypeBuilderPlan plan) { + // Any reachable or abstract heap type, constrained to be shared if the type + // definition we are constructing is shared. + // TODO: Allow unshared types in shared function type defs? + bool shared = plan.kinds[plan.curr].shared; + auto abstract = + shared ? ArbitrarySharedAbstractHeapType() : ArbitraryAbstractHeapType(); + return AvailableMatchingOrAbstractHeapType( + plan, + [&](auto kind, bool otherShared) { return !shared || otherShared; }, + abstract); +} + +fuzztest::Domain AvailableSubHeapType(TypeBuilderPlan plan, + HeapTypePlan super) { + // Choose a subtype of `super`, biasing toward `super` itself. + return fuzztest::OneOf(fuzztest::Just(super), + AvailableStrictSubHeapType(std::move(plan), super)); +} + +fuzztest::Domain AvailableSuperHeapType(TypeBuilderPlan plan, + HeapTypePlan sub) { + // Choose a supertype of `sub`, biasing toward `sub` itself. + return fuzztest::OneOf(fuzztest::Just(sub), + AvailableStrictSuperHeapType(std::move(plan), sub)); +} + +fuzztest::Domain AvailableRefType(TypeBuilderPlan plan) { + // Independently random heap type and nullability. + return fuzztest::StructOf(AvailableHeapType(std::move(plan)), + fuzztest::Arbitrary()); +} + +fuzztest::Domain AvailableSubRefType(TypeBuilderPlan plan, + RefPlan super) { + auto heapType = AvailableSubHeapType(std::move(plan), super.type); + if (super.nullable) { + return fuzztest::StructOf(heapType, fuzztest::Arbitrary()); + } else { + return fuzztest::StructOf(heapType, fuzztest::Just(false)); + } +} + +fuzztest::Domain AvailableSuperRefType(TypeBuilderPlan plan, + RefPlan sub) { + auto heapType = AvailableSuperHeapType(std::move(plan), sub.type); + if (sub.nullable) { + return fuzztest::StructOf(heapType, fuzztest::Just(true)); + } else { + return fuzztest::StructOf(heapType, fuzztest::Arbitrary()); + } +} + +fuzztest::Domain AvailableType(TypeBuilderPlan plan) { + // A non-reference types or a reference to an available heap type. + return fuzztest::VariantOf(ArbitraryNonRefType(), + AvailableRefType(std::move(plan))); +} + +fuzztest::Domain AvailableSubType(TypeBuilderPlan plan, + TypePlan super) { + if (auto* type = super.getNonRef()) { + // No subtyping among non-ref types. + return fuzztest::Just(super); + } else if (auto* ref = super.getRef()) { + return fuzztest::Map([](auto ref) { return TypePlan{ref}; }, + AvailableSubRefType(std::move(plan), std::move(*ref))); + } else { + WASM_UNREACHABLE("unexpected variant"); + } +} + +fuzztest::Domain AvailableSuperType(TypeBuilderPlan plan, + TypePlan sub) { + if (auto* type = sub.getNonRef()) { + // No subtyping among non-ref types. + return fuzztest::Just(TypePlan{*type}); + } else if (auto* ref = sub.getRef()) { + return fuzztest::Map( + [](auto ref) { return TypePlan{ref}; }, + AvailableSuperRefType(std::move(plan), std::move(*ref))); + } else { + WASM_UNREACHABLE("unexpected variant"); + } +} + +fuzztest::Domain AvailableFieldType(TypeBuilderPlan plan) { + // A packed type or another available type. + return fuzztest::VariantOf( + fuzztest::ElementOf({Field::i8, Field::i16}), + AvailableType(std::move(plan))); +} + +fuzztest::Domain AvailableSubFieldType(TypeBuilderPlan plan, + FieldTypePlan super) { + if (auto* packed = super.getPacked()) { + // No subtyping on packed types. + return fuzztest::Just(FieldTypePlan{*packed}); + } else if (auto* type = super.getNonPacked()) { + return fuzztest::Map([](auto type) { return FieldTypePlan{type}; }, + AvailableSubType(std::move(plan), *type)); + } else { + WASM_UNREACHABLE("unexpected variant"); + } +} + +fuzztest::Domain AvailableField(TypeBuilderPlan plan) { + // An available field type and a random mutability. + return fuzztest::StructOf(AvailableFieldType(std::move(plan)), + fuzztest::Arbitrary()); +} + +fuzztest::Domain AvailableSubField(TypeBuilderPlan plan, + FieldPlan super) { + if (super.mutable_) { + // Mutable fields cannot be modified in subtypes. + return fuzztest::Just(super); + } + return fuzztest::Map( + [&](auto type) { + return FieldPlan{type, false}; + }, + AvailableSubFieldType(std::move(plan), super.type)); +} + +fuzztest::Domain FuncDef(TypeBuilderPlan plan) { + auto params = + fuzztest::VectorOf(AvailableType(plan)).WithMaxSize(MaxParamsSize); + auto results = fuzztest::VectorOf(AvailableType(std::move(plan))) + .WithMaxSize(MaxResultsSize); + return fuzztest::PairOf(params, results); +} + +fuzztest::Domain SubFuncDef(TypeBuilderPlan plan, FuncPlan super) { + // Params are contravariant and results are covariant. + auto params = MapElements( + [plan](TypePlan type) { return AvailableSuperType(plan, type); }, + super.first); + auto results = MapElements( + [plan = std::move(plan)](TypePlan type) { + return AvailableSubType(std::move(plan), type); + }, + super.second); + return fuzztest::PairOf(params, results); +} + +fuzztest::Domain StructDef(TypeBuilderPlan plan) { + return fuzztest::VectorOf(AvailableField(std::move(plan))) + .WithMaxSize(MaxStructSize); +} + +fuzztest::Domain SubStructDef(TypeBuilderPlan plan, + StructPlan super) { + // First do depth subtyping, where we choose a subtype of each field, then + // maybe add extra fields if there is space. + auto depthSubTypeDomain = MapElements( + [plan](const FieldPlan& field) { return AvailableSubField(plan, field); }, + super); + return fuzztest::FlatMap( + [plan](StructPlan toExtend) -> fuzztest::Domain { + if (toExtend.size() == MaxStructSize) { + // No room to add more fields. + return fuzztest::Just(toExtend); + } + auto extensionDomain = fuzztest::VectorOf(AvailableField(std::move(plan))) + .WithMaxSize(MaxStructSize - toExtend.size()); + return fuzztest::FlatMap( + [toExtend](std::vector extension) { + auto extended = toExtend; + extended.insert(extended.end(), extension.begin(), extension.end()); + return fuzztest::Just(std::move(extended)); + }, + extensionDomain); + }, + depthSubTypeDomain); +} + +fuzztest::Domain ArrayDef(TypeBuilderPlan plan) { + return AvailableField(std::move(plan)); +} + +fuzztest::Domain SubArrayDef(TypeBuilderPlan plan, ArrayPlan super) { + return AvailableSubField(std::move(plan), super); +} + +fuzztest::Domain ContDef(TypeBuilderPlan plan) { + // Find referenceable function types, restricting ourselves to shared + // functions if necessary. + bool shared = plan.kinds[plan.curr].shared; + auto matches = + AvailableMatchingIndices(std::move(plan), [&](auto kind, bool otherShared) { + return kind == FuncKind && (!shared || otherShared); + }); + if (matches.empty()) { + return fuzztest::NullOpt(); + } else { + return fuzztest::NonNull( + fuzztest::OptionalOf(fuzztest::ElementOf(std::move(matches)))); + } +} + +fuzztest::Domain SubContDef(TypeBuilderPlan plan, ContPlan super) { + if (auto index = super) { + // Choose an available subtype of the current continuation's function type, + // biasing toward the current continuation's function type itself. + auto matches = AvailableStrictSubIndices(std::move(plan), *index); + if (matches.empty()) { + // No other function indices available to create a subtype. + return fuzztest::Just(super); + } + return fuzztest::OneOf(fuzztest::Just(super), + fuzztest::NonNull(fuzztest::OptionalOf( + fuzztest::ElementOf(std::move(matches))))); + } else { + // We will not generate subtypes of the fallback function type, so keep it + // unchanged. + return fuzztest::Just(super); + } +} + +fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan); + +template +fuzztest::Domain AppendTypeDef(TypeBuilderPlan plan, T def) { + ++plan.curr; + plan.defs.emplace_back(TypeDefPlan{std::move(def)}); + return StepTypeDefinition(std::move(plan)); +} + +fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan) { + auto index = plan.curr; + if (index == plan.size) { + // We have created all the type defs. + return fuzztest::Just(plan); + } + // If we have moved into a new rec group, update our state accordingly. + if (index > plan.numReferenceable) { + ++plan.currRecGroup; + plan.numReferenceable += plan.recGroupSizes[plan.currRecGroup]; + } + // Look at the type kind to determine what domain to draw the type + // definition from. + auto super = plan.supertypes[index]; + switch (plan.kinds[index].kind) { + case FuncKind: { + auto def = + super ? SubFuncDef(plan, *plan.defs[*super].getFunc()) : FuncDef(plan); + return fuzztest::FlatMap( + AppendTypeDef, fuzztest::Just(std::move(plan)), def); + } + case StructKind: { + auto def = super ? SubStructDef(plan, *plan.defs[*super].getStruct()) + : StructDef(plan); + return fuzztest::FlatMap( + AppendTypeDef, fuzztest::Just(std::move(plan)), def); + } + case ArrayKind: { + auto def = super ? SubArrayDef(plan, *plan.defs[*super].getArray()) + : ArrayDef(plan); + return fuzztest::FlatMap( + AppendTypeDef, fuzztest::Just(std::move(plan)), def); + } + case ContKind: { + auto def = + super ? SubContDef(plan, *plan.defs[*super].getCont()) : ContDef(plan); + return fuzztest::FlatMap( + AppendTypeDef, fuzztest::Just(std::move(plan)), def); + } + } + WASM_UNREACHABLE("unexpected kind"); +} + +std::vector BuildHeapTypes(TypeBuilderPlan plan) { + // Continuation types without reachable function types need a fallback. + TypeBuilder fallbackBuilder(2); + fallbackBuilder[0] = Signature(); + fallbackBuilder[1] = Signature(); + fallbackBuilder[1].setShared(); + auto builtFallbacks = fallbackBuilder.build(); + HeapType contFallback = (*builtFallbacks)[0]; + HeapType sharedContFallback = (*builtFallbacks)[1]; + + TypeBuilder builder(plan.size); + + // Rec groups. + size_t start = 0; + for (auto size : plan.recGroupSizes) { + builder.createRecGroup(start, size); + start += size; + } + + // Map plans onto the builder. + + auto heapType = [&](HeapTypePlan& plan) -> HeapType { + if (auto* type = plan.getHeapType()) { + return *type; + } else if (auto* index = plan.getIndex()) { + return builder[*index]; + } else { + WASM_UNREACHABLE("unexpected variant"); + } + }; + + auto ref = [&](RefPlan& plan) -> Type { + return builder.getTempRefType(heapType(plan.type), + plan.nullable ? Nullable : NonNullable); + }; + + auto type = [&](TypePlan& plan) -> Type { + if (auto* type = plan.getNonRef()) { + return *type; + } else if (auto* r = plan.getRef()) { + return ref(*r); + } else { + WASM_UNREACHABLE("unexpected variant"); + } + }; + + auto field = [&](FieldPlan& plan) -> Field { + if (auto* packed = plan.type.getPacked()) { + return Field(*packed, plan.mutable_ ? Mutable : Immutable); + } else if (auto* t = plan.type.getNonPacked()) { + return Field(type(*t), plan.mutable_ ? Mutable : Immutable); + } else { + WASM_UNREACHABLE("unexpected variant"); + } + }; + + auto func = [&](FuncPlan& plan) -> Signature { + std::vector params, results; + for (auto& t : plan.first) { + params.push_back(type(t)); + } + for (auto& t : plan.second) { + results.push_back(type(t)); + } + return Signature(builder.getTempTupleType(std::move(params)), + builder.getTempTupleType(std::move(results))); + }; + + auto struct_ = [&](StructPlan& plan) -> Struct { + std::vector fields; + for (auto& f : plan) { + fields.push_back(field(f)); + } + return Struct(std::move(fields)); + }; + + auto array = [&](ArrayPlan& plan) -> Array { return Array(field(plan)); }; + + auto cont = [&](ContPlan& plan, bool shared) -> Continuation { + if (plan) { + return Continuation(builder[*plan]); + } + return Continuation(shared ? sharedContFallback : contFallback); + }; + + for (size_t i = 0; i < plan.size; ++i) { + if (auto* f = plan.defs[i].getFunc()) { + builder[i] = func(*f); + } else if (auto* s = plan.defs[i].getStruct()) { + builder[i] = struct_(*s); + } else if (auto* a = plan.defs[i].getArray()) { + builder[i] = array(*a); + } else if (auto* c = plan.defs[i].getCont()) { + builder[i] = cont(*c, plan.kinds[i].shared); + } else { + WASM_UNREACHABLE("unexpected variant"); + } + + if (auto super = plan.supertypes[i]) { + builder[i].subTypeOf(builder[*super]); + } + builder[i].setOpen(!plan.kinds[i].final); + builder[i].setShared(plan.kinds[i].shared ? Shared : Unshared); + } + auto built = builder.build(); + if (auto* err = built.getError()) { + std::cerr << err->index << ": " << err->reason << "\n"; + ; + } + assert(built); + return std::move(*built); +} + +auto ArbitraryDefinedHeapTypesAndPlan() { + return fuzztest::Map( + [](TypeBuilderPlan plan) { + auto types = BuildHeapTypes(plan); + return std::pair(std::move(types), std::move(plan)); + }, + ArbitraryTypeBuilderPlan()); +} + +void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { + auto types = std::move(pair.first); + auto plan = std::move(pair.second); + + ASSERT_EQ(types.size(), plan.size); + + auto checkHeapType = [&](HeapTypePlan& plan, HeapType type) { + if (auto* t = plan.getHeapType()) { + EXPECT_EQ(*t, type); + } else if (auto* i = plan.getIndex()) { + EXPECT_EQ(types[*i], type); + } else { + WASM_UNREACHABLE("unexpected variant"); + } + }; + + auto checkRefType = [&](RefPlan& plan, Type type) { + ASSERT_TRUE(type.isRef()); + checkHeapType(plan.type, type.getHeapType()); + EXPECT_EQ(plan.nullable, type.isNullable()); + }; + + auto checkType = [&](TypePlan& plan, Type type) { + if (auto* t = plan.getNonRef()) { + EXPECT_EQ(*t, type); + } else if (auto* r = plan.getRef()) { + checkRefType(*r, type); + } else { + WASM_UNREACHABLE("unexpected variant"); + } + }; + + auto checkField = [&](FieldPlan& plan, Field field) { + EXPECT_EQ(plan.mutable_, field.mutable_ == Mutable); + if (auto* packed = plan.type.getPacked()) { + EXPECT_TRUE(field.isPacked()); + EXPECT_EQ(field.packedType, *packed); + } else if (auto* t = plan.type.getNonPacked()) { + checkType(*t, field.type); + } + }; + + auto checkFunc = [&](FuncPlan& plan, HeapType type) { + ASSERT_TRUE(type.isSignature()); + auto sig = type.getSignature(); + ASSERT_EQ(plan.first.size(), sig.params.size()); + ASSERT_EQ(plan.second.size(), sig.results.size()); + for (size_t i = 0; i < plan.first.size(); ++i) { + checkType(plan.first[i], sig.params[i]); + } + for (size_t i = 0; i < plan.second.size(); ++i) { + checkType(plan.second[i], sig.results[i]); + } + }; + + auto checkStruct = [&](StructPlan& plan, HeapType type) { + ASSERT_TRUE(type.isStruct()); + const auto& fields = type.getStruct().fields; + ASSERT_EQ(plan.size(), fields.size()); + for (size_t i = 0; i < plan.size(); ++i) { + checkField(plan[i], fields[i]); + } + }; + + auto checkArray = [&](ArrayPlan& plan, HeapType type) { + ASSERT_TRUE(type.isArray()); + checkField(plan, type.getArray().element); + }; + + auto checkCont = [&](ContPlan& plan, HeapType type) { + ASSERT_TRUE(type.isContinuation()); + if (plan) { + EXPECT_EQ(types[*plan], type.getContinuation().type); + } + }; + + auto checkDef = [&](TypeDefPlan& plan, HeapType type) { + if (auto* f = plan.getFunc()) { + checkFunc(*f, type); + } else if (auto* s = plan.getStruct()) { + checkStruct(*s, type); + } else if (auto* a = plan.getArray()) { + checkArray(*a, type); + } else if (auto* c = plan.getCont()) { + checkCont(*c, type); + } else { + WASM_UNREACHABLE("unexpected variant"); + } + }; + + for (size_t i = 0; i < plan.size; ++i) { + EXPECT_EQ(plan.kinds[i].shared, types[i].isShared()); + EXPECT_EQ(plan.kinds[i].final, !types[i].isOpen()); + if (auto super = plan.supertypes[i]) { + auto supertype = types[i].getDeclaredSuperType(); + ASSERT_TRUE(supertype); + EXPECT_EQ(types[*super], *supertype); + } else { + EXPECT_FALSE(types[i].getDeclaredSuperType()); + } + checkDef(plan.defs[i], types[i]); + } +} +FUZZ_TEST(TypeBuilderDomainsTest, TestBuiltTypes) + .WithDomains(ArbitraryDefinedHeapTypesAndPlan()); + +} // anonymous namespace + +fuzztest::Domain ArbitraryTypeBuilderPlan() { + // Initialize an abstract type builder plan, then add concrete type definition + // plans. + return fuzztest::FlatMap(StepTypeDefinition, InitConcreteTypeBuilderPlan()); +} + +fuzztest::Domain> ArbitraryDefinedHeapTypes() { + return fuzztest::Map(BuildHeapTypes, ArbitraryTypeBuilderPlan()); +} + +fuzztest::Domain> ArbitraryHeapTypePair() { + return fuzztest::FlatMap( + [](auto types) { + auto typeDomain = fuzztest::OneOf(fuzztest::ElementOf(types), + ArbitraryAbstractHeapType()); + return fuzztest::PairOf(typeDomain, typeDomain); + }, + ArbitraryDefinedHeapTypes()); +} + +} // namespace wasm diff --git a/test/gtest/type-domains.h b/test/gtest/type-domains.h new file mode 100644 index 00000000000..17ad7fe9530 --- /dev/null +++ b/test/gtest/type-domains.h @@ -0,0 +1,171 @@ +/* + * Copyright 2025 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_test_gtest_type_domains_h +#define wasm_test_gtest_type_domains_h + +#include "fuzztest/fuzztest.h" +#include "wasm-type.h" + +#ifndef FUZZTEST +#error "BUILD_FUZZTEST should be enabled" +#endif + +namespace wasm { + +inline fuzztest::Domain ArbitraryUnsharedAbstractHeapType() { + return fuzztest::ElementOf({ + HeapTypes::ext, + HeapTypes::func, + HeapTypes::cont, + HeapTypes::any, + HeapTypes::eq, + HeapTypes::i31, + HeapTypes::struct_, + HeapTypes::array, + HeapTypes::exn, + HeapTypes::string, + HeapTypes::none, + HeapTypes::noext, + HeapTypes::nofunc, + HeapTypes::nocont, + HeapTypes::noexn, + }); +} + +inline fuzztest::Domain ArbitrarySharedAbstractHeapType() { + return fuzztest::ReversibleMap( + [](HeapType ht) { return HeapType(ht.getBasic(Shared)); }, + [](HeapType ht) { + return ht.isShared() + ? std::optional{std::tuple{HeapType(ht.getBasic(Unshared))}} + : std::nullopt; + }, + ArbitraryUnsharedAbstractHeapType()); +} + +inline fuzztest::Domain ArbitraryAbstractHeapType() { + return fuzztest::OneOf(ArbitraryUnsharedAbstractHeapType(), + ArbitrarySharedAbstractHeapType()); +} + +inline fuzztest::Domain ArbitraryNonRefType() { + return fuzztest::ElementOf( + std::vector{Type::i32, Type::i64, Type::f32, Type::f64, Type::v128}); +} + +enum UnsharedTypeKind { FuncKind, StructKind, ArrayKind, ContKind }; + +struct TypeKind { + UnsharedTypeKind kind; + bool shared; + bool final; +}; + +struct HeapTypePlan : std::variant { + HeapType* getHeapType() { return std::get_if(this); } + size_t* getIndex() { return std::get_if(this); } +}; + +struct RefPlan { + HeapTypePlan type; + bool nullable; +}; + +struct TypePlan : std::variant { + Type* getNonRef() { return std::get_if(this); } + RefPlan* getRef() { return std::get_if(this); } +}; + +struct FieldTypePlan : std::variant { + Field::PackedType* getPacked() { + return std::get_if(this); + } + TypePlan* getNonPacked() { return std::get_if(this); } +}; + +struct FieldPlan { + FieldTypePlan type; + bool mutable_; +}; + +using FuncPlan = std::pair, std::vector>; +using StructPlan = std::vector; +using ArrayPlan = FieldPlan; +// If there is no available func type definition, this will be nullopt and we +// will have to use a default fallback. +using ContPlan = std::optional; + +struct TypeDefPlan : std::variant { + FuncPlan* getFunc() { return std::get_if(this); } + StructPlan* getStruct() { return std::get_if(this); } + ArrayPlan* getArray() { return std::get_if(this); } + ContPlan* getCont() { return std::get_if(this); } +}; + +struct TypeBuilderPlan { + // Index variable for controlling recursion during construction. + size_t curr; + + // RecGroupPlan contents. + size_t size; + std::vector recGroupSizes; + + // AbstractTypeBuilderPlan contents. + std::vector> supertypes; + std::vector kinds; + + // TypeBuilderPlan contents. + size_t currRecGroup = 0; + size_t numReferenceable = 0; + std::vector defs; + + // Built types. + std::vector types; + + friend std::ostream& operator<<(std::ostream& o, const TypeBuilderPlan& plan); +}; + +static constexpr size_t MaxTypeBuilderSize = 8; +static constexpr size_t MaxParamsSize = 4; +static constexpr size_t MaxResultsSize = 2; +static constexpr size_t MaxStructSize = 8; + +fuzztest::Domain ArbitraryTypeBuilderPlan(); + +fuzztest::Domain> ArbitraryDefinedHeapTypes(); + +fuzztest::Domain> ArbitraryHeapTypePair(); + +// FuzzTest only supports extending the printer via AbslStringify, but we +// usually define operator<< for our custom printing. Add a generic +// implementation of AbslStringify enabled for anything in the wasm namespace +// that implements operator<< as expected. +template constexpr bool type_exists = true; + +template +void AbslStringify( + Sink& sink, + const T& val, + std::enable_if_t, bool> = false) { + std::stringstream ss; + ss << val; + sink.Append(ss.str()); +} + +} // namespace wasm + +#endif // wasm_test_gtest_type_domains_h diff --git a/test/gtest/type-test.h b/test/gtest/type-test.h index f029b8027b1..2eb0e9594bf 100644 --- a/test/gtest/type-test.h +++ b/test/gtest/type-test.h @@ -6,7 +6,6 @@ // Helper test fixture for managing the global type system state. class TypeTest : public ::testing::Test { - protected: void TearDown() override { wasm::destroyAllTypesForTestingPurposesOnly(); } diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index b55797db770..fde5276d7da 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -1,13 +1,17 @@ -if(BUILD_LLVM_DWARF) - add_subdirectory(llvm-project) -endif() - -include_directories( - googletest/googletest - googletest/googletest/include -) - -if(BUILD_TESTS) +if(BUILD_FUZZTEST) + add_subdirectory(fuzztest) +elseif(BUILD_TESTS) + # fuzztest includes gtest, but if we're not building fuzztest, build gtest ourselves. add_library(gtest STATIC googletest/googletest/src/gtest-all.cc) add_library(gtest_main STATIC googletest/googletest/src/gtest_main.cc) + target_compile_options(gtest PRIVATE "-fno-rtti") + target_compile_options(gtest_main PRIVATE "-fno-rtti") + include_directories( + googletest/googletest + googletest/googletest/include + ) +endif() + +if(BUILD_LLVM_DWARF) + add_subdirectory(llvm-project) endif() diff --git a/third_party/fuzztest b/third_party/fuzztest new file mode 160000 index 00000000000..5bbbddfc241 --- /dev/null +++ b/third_party/fuzztest @@ -0,0 +1 @@ +Subproject commit 5bbbddfc241c8c87902d4a80cda5697dd8c20199 From 9498d5e22048a186e35fff7a28630280d2c21b1f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jan 2025 14:17:16 -0800 Subject: [PATCH 265/622] [Parser] Avoid an internal assertion when a function's type does not match params (#7247) The parser trusted the type when calling `setLocalName`, but that method asserts of the local index is invalid. Avoid that assertion so we reach the proper error message later. --- src/parser/contexts.h | 5 ++++- test/lit/parse-error-func-param-type.wast | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/lit/parse-error-func-param-type.wast diff --git a/src/parser/contexts.h b/src/parser/contexts.h index a09433f809d..e9eb708ac9c 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1325,7 +1325,10 @@ struct ParseModuleTypesCtx : TypeParserCtx, return in.err(pos, "expected signature type"); } f->type = type.type; - for (Index i = 0; i < type.names.size(); ++i) { + // If we are provided with too many names (more than the function has), we + // will error on that later when we check the signature matches the type. + // For now, avoid asserting in setLocalName. + for (Index i = 0; i < std::min(type.names.size(), f->getNumLocals()); ++i) { if (type.names[i].is()) { f->setLocalName(i, type.names[i]); } diff --git a/test/lit/parse-error-func-param-type.wast b/test/lit/parse-error-func-param-type.wast new file mode 100644 index 00000000000..04068eca859 --- /dev/null +++ b/test/lit/parse-error-func-param-type.wast @@ -0,0 +1,10 @@ +;; This function's type does not match the param we define for it. + +;; RUN: not wasm-opt %s 2>&1 | filecheck %s +;; CHECK: Fatal: 9:10: error: type does not match provided signature + +(module + (type $0 (func)) + + (func $0 (type $0) (param $var$0 i32)) +) From 92ad9599a6c56dc0d0972a28d4c2258890538cc8 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 30 Jan 2025 08:34:00 -0800 Subject: [PATCH 266/622] Fix a text parser assertion on bad immediates (#7252) We previously asserted that the end of a nested expression is the same when we parse it with a null context just to find its children and when we parse it for real. It turns out that it is possible for the two end positions to be different when the instruction is invalid in a way that only the real parse catches. Return a normal error instead of asserting because it is possible for invalid input to trigger this condition. Fixes #7251. --- src/parser/parsers.h | 7 +++++++ test/lit/parse-bad-optional-memidx.wast | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 test/lit/parse-bad-optional-memidx.wast diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 48f65c497e0..c90e2c01d1f 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -1005,6 +1005,13 @@ template MaybeResult<> foldedinstr(Ctx& ctx) { auto inst = plaininstr(ctx, std::move(info.annotations)); assert(inst && "unexpectedly failed to parse instruction"); CHECK_ERR(inst); + // We have already parsed the instruction, so we generally know where it + // ends. But there may have been some invalid extra immediates (e.g. + // invalid memory indices) that we only realize are invalid now that we've + // parsed the instruction for real. + if (ctx.in.getPos() != *info.end) { + return ctx.in.err("expected end of instruction"); + } assert(ctx.in.getPos() == *info.end && "expected end of instruction"); continue; } diff --git a/test/lit/parse-bad-optional-memidx.wast b/test/lit/parse-bad-optional-memidx.wast new file mode 100644 index 00000000000..57438d6d2fd --- /dev/null +++ b/test/lit/parse-bad-optional-memidx.wast @@ -0,0 +1,17 @@ +;; Regression test for a parser bug where the invalid memory index followed by +;; another immediate caused an assertion failure. + +;; RUN: not wasm-opt -all %s 2>&1 | filecheck %s + +;; CHECK: Fatal: 12:22: error: expected end of instruction + +(module + (memory 1 1) + + (func $v128.load16_lane1 (param $0 i32) (param $1 v128) (result v128) + (v128.load16_lane 1 0 ;; invalid memory index + (local.get $0) + (local.get $1) + ) + ) +) \ No newline at end of file From e7abb04408b7ec536a0a8a6149768deb584a698d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 30 Jan 2025 12:27:29 -0800 Subject: [PATCH 267/622] [NFC] Remove redundant assertion (#7253) This should have been removed in #7252. --- src/parser/parsers.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parser/parsers.h b/src/parser/parsers.h index c90e2c01d1f..4ba88fd8bf2 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -1012,7 +1012,6 @@ template MaybeResult<> foldedinstr(Ctx& ctx) { if (ctx.in.getPos() != *info.end) { return ctx.in.err("expected end of instruction"); } - assert(ctx.in.getPos() == *info.end && "expected end of instruction"); continue; } From d26ad8218d69016d3b8f9740a2bee06878220494 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jan 2025 12:43:41 -0800 Subject: [PATCH 268/622] Generalize extract_wasms.py (#7254) Rather than pattern-match the very specific form we emit in ClusterFuzz testcases, support any Uint8Array that contains what look like wasm contents. This allows us to also process Fuzzilli testcases. --- scripts/clusterfuzz/extract_wasms.py | 44 ++++++++++++++++++++-------- test/lit/scripts/extract_wasms.lit | 28 ++++++++++++++++++ 2 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 test/lit/scripts/extract_wasms.lit diff --git a/scripts/clusterfuzz/extract_wasms.py b/scripts/clusterfuzz/extract_wasms.py index 9f364d7cc34..c1cc429eeb6 100644 --- a/scripts/clusterfuzz/extract_wasms.py +++ b/scripts/clusterfuzz/extract_wasms.py @@ -14,13 +14,16 @@ # limitations under the License. ''' -Wasm extractor for testcases generated by the ClusterFuzz run.py script. Usage: +Wasm extractor for testcases generated by the ClusterFuzz run.py script. This is +general enough to also handle Fuzzilli output. + +Usage: extract_wasms.py INFILE.js OUTFILE That will find embedded wasm files in INFILE.js, of the form - var .. = new Uint8Array([..wasm_contents..]); + new Uint8Array([..wasm_contents..]); and extract them into OUTFILE.0.wasm, OUTFILE.1.wasm, etc. It also emits OUTFILE.js which will no longer contain the embedded contents, after which the @@ -50,24 +53,41 @@ def get_wasm_filename(): js = f.read() -def repl(text): +def repl(match): + text = match.group(0) + # We found something of the form # - # var binary = new Uint8Array([..binary data as numbers..]); + # new Uint8Array([..binary data as numbers..]); # - # Parse out the numbers into a binary wasm file. - numbers = text.groups()[0] + # See if the numbers are the beginnings of a wasm file, "\0asm". If so, we + # assume it is wasm. (We are careful here because Fuzzilli output can + # contain normal JavaScript Typed Arrays, which we do not want to touch.) + numbers = match.groups()[0] numbers = numbers.split(',') - numbers = [int(n) for n in numbers] + + try: + # Handle both base 10 and 16 by passing in base 0. + parsed = [int(n, 0) for n in numbers] + binary = bytes(parsed) + except ValueError: + # Not wasm; return the existing text. + return text + + if binary[:4] != b'\0asm': + return text + + # It is wasm. Parse out the numbers into a binary wasm file. with open(get_wasm_filename(), 'wb') as f: - f.write(bytes(numbers)) + f.write(binary) - # Replace it with nothing. - return '' + # Replace the Uint8Array with undefined + a comment. + return 'undefined /* extracted wasm */' -# Replace the wasm files and write them out. -js = re.sub(r'var \w+ = new Uint8Array\(\[([\d,]+)\]\)', repl, js) +# Replace the wasm files and write them out. We investigate any new Uint8Array +# on an array of values like [100, 200] or [0x61, 0x6D, 0x6a] etc. +js = re.sub(r'new Uint8Array\(\[([\d,x a-fA-F]+)\]\)', repl, js) # Write out the new JS. with open(f'{out}.js', 'w') as f: diff --git a/test/lit/scripts/extract_wasms.lit b/test/lit/scripts/extract_wasms.lit new file mode 100644 index 00000000000..fee45071bbd --- /dev/null +++ b/test/lit/scripts/extract_wasms.lit @@ -0,0 +1,28 @@ +;; Test extracting wasm files from JS. + +;; A proper wasm start sequence (\0asm), so we will extract it. +;; RUN: echo "good1(new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01]));" > %t.js + +;; A difference in the second byte, so we won't. +;; RUN: echo "bad1(new Uint8Array([0x00, 0xff, 0x73, 0x6D, 0x01]));" >> %t.js + +;; The last byte is unparseable as an integer, so we won't. +;; RUN: echo "bad2(new Uint8Array([0x00, 0x61, 0x73, 0x6D, 6Dx0]));" >> %t.js + +;; This is not a Uint8Array, so we do nothing. +;; RUN: echo "bad3(new Uint16Array([0x00, 0x61, 0x73, 0x6D, 0x01]));" >> %t.js + +;; Another proper one. Note the second number is in base 10, which works too, +;; & there is various odd whitespace which we also ignore. +;; RUN: echo "good2(new Uint8Array([0x00,97, 0x73, 0x6D,0x01]));" >> %t.js + +;; RUN: python %S/../../../scripts/clusterfuzz/extract_wasms.py %t.js %t.out +;; RUN: cat %t.out.js | filecheck %s +;; +;; We extracted the good but not the bad. +;; CHECK: good1(undefined /* extracted wasm */) +;; CHECK: bad1(new Uint8Array +;; CHECK: bad2(new Uint8Array +;; CHECK: bad3(new Uint16Array +;; CHECK: good2(undefined /* extracted wasm */) + From e6292646810ed1d0d5f801dd0dda3fb4186f0fd5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jan 2025 16:16:15 -0800 Subject: [PATCH 269/622] Properly error on a return in a non-function scope (#7256) Without this we get an assertion later on, child-typer.h:720: Assertion `func' failed. Returns use the function to find the return values, so like local get and set, we must error early on lacking a function. --- src/wasm/wasm-ir-builder.cpp | 3 +++ test/lit/parse-error-return-nofunc.wast | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 test/lit/parse-error-return-nofunc.wast diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 603768d55cd..78e622b3cb9 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1698,6 +1698,9 @@ Result<> IRBuilder::makeDrop() { } Result<> IRBuilder::makeReturn() { + if (!func) { + return Err{"return is only valid in a function context"}; + } Return curr; CHECK_ERR(visitReturn(&curr)); push(builder.makeReturn(curr.value)); diff --git a/test/lit/parse-error-return-nofunc.wast b/test/lit/parse-error-return-nofunc.wast new file mode 100644 index 00000000000..2d745c3e097 --- /dev/null +++ b/test/lit/parse-error-return-nofunc.wast @@ -0,0 +1,10 @@ +;; We should error properly on a return in a non-function scope + +;; RUN: not wasm-opt %s 2>&1 | filecheck %s +;; CHECK: Fatal: 8:5: error: return is only valid in a function context + +(module + (elem + (return) + ) +) From f05cf1d7343712803afa49dc3f546290e98c8699 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 30 Jan 2025 19:38:24 -0800 Subject: [PATCH 270/622] [ci] Switch from qemu to native github arm64 runner (#7258) --- .github/workflows/ci.yml | 4 ++-- .github/workflows/create_release.yml | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ed88400d8d..e7b5eae71db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,7 +218,7 @@ jobs: # Keep in sync with build_release.yml build-alpine: name: alpine - runs-on: ubuntu-latest + runs-on: ubuntu-24.04-arm steps: - uses: actions/setup-python@v5 with: @@ -228,7 +228,7 @@ jobs: submodules: true - name: start docker run: | - docker run -w /src -dit --name alpine -v $PWD:/src node:lts-alpine + docker run -w /src -dit --platform=linux/arm64 --name alpine -v $PWD:/src node:lts-alpine echo 'docker exec alpine "$@";' > ./alpine.sh chmod +x ./alpine.sh diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 8cff451c779..b0e7f290664 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -105,10 +105,10 @@ jobs: # Note: Alpine uses musl libc. build-alpine: name: alpine - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: - docker_platform: [amd64, arm64] + os: [ubuntu-latest, ubuntu-24.04-arm] steps: - uses: actions/setup-python@v1 with: @@ -116,13 +116,10 @@ jobs: - uses: actions/checkout@v1 with: submodules: true - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - if: matrix.docker_platform != 'amd64' - name: start docker run: | - docker run -w /src -dit --platform=linux/${{ matrix.docker_platform }} --name alpine -v $PWD:/src node:lts-alpine + docker run -w /src -dit --name alpine -v $PWD:/src node:lts-alpine echo 'docker exec alpine "$@";' > ./alpine.sh chmod +x ./alpine.sh From 6fe5103ab58a4eb751998d13768a0f25795a0de6 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Fri, 31 Jan 2025 18:43:36 -0800 Subject: [PATCH 271/622] [Interpreter] i32.sub (#7259) Building on top of #7227, i32.sub is implemented and tested. --- src/interpreter/interpreter.cpp | 3 +++ test/gtest/interpreter.cpp | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index d60030b69ba..42a83f73786 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -94,6 +94,9 @@ struct ExpressionInterpreter : OverriddenVisitor { if (curr->op == AddInt32) { push(lhs.add(rhs)); return {}; + } else if (curr->op == SubInt32) { + push(lhs.sub(rhs)); + return {}; } WASM_UNREACHABLE("TODO"); } diff --git a/test/gtest/interpreter.cpp b/test/gtest/interpreter.cpp index 1e6f6cdd139..399d46912e9 100644 --- a/test/gtest/interpreter.cpp +++ b/test/gtest/interpreter.cpp @@ -25,7 +25,7 @@ using namespace wasm; -TEST(InterpreterTest, Add) { +TEST(InterpreterTest, AddI32) { Module wasm; IRBuilder builder(wasm); @@ -41,3 +41,20 @@ TEST(InterpreterTest, Add) { EXPECT_EQ(results, expected); } + +TEST(InterpreterTest, SubI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(SubInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(-1))}; + + EXPECT_EQ(results, expected); +} From 758cb173773845b1abbfea6bf9ba773be79ae35a Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 3 Feb 2025 09:43:02 -0800 Subject: [PATCH 272/622] [ci] Attempt to fix create_release for arm64 linux (#7266) This was supposed to be part of #7258 --- .github/workflows/create_release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index b0e7f290664..73b91cbfb5b 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -119,7 +119,10 @@ jobs: - name: start docker run: | - docker run -w /src -dit --name alpine -v $PWD:/src node:lts-alpine + if [[ "${{ matrix.docker_platform }}" == "ubuntu-24.04-arm" ]]; then + platform="--platform=linux/arm64" + fi + docker run -w /src -dit $platform --name alpine -v $PWD:/src node:lts-alpine echo 'docker exec alpine "$@";' > ./alpine.sh chmod +x ./alpine.sh From d67da8ea9be8ef29ddb8a81266c6b2dad949ffb9 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 3 Feb 2025 12:35:35 -0800 Subject: [PATCH 273/622] [ci] Bump setup_python version in create_release.yml (#7267) Hopefully this will fix the current failure: ``` Run actions/setup-python@v1 with: python-version: 3.x architecture: x64 Error: Version 3.x with arch x64 not found ``` --- .github/workflows/create_release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 73b91cbfb5b..3229fd83f5e 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -110,7 +110,7 @@ jobs: matrix: os: [ubuntu-latest, ubuntu-24.04-arm] steps: - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v5 with: python-version: '3.x' - uses: actions/checkout@v1 @@ -180,7 +180,7 @@ jobs: run: shell: bash steps: - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v5 with: python-version: '3.x' - uses: actions/checkout@v1 From 659cdc1a5c23b3c7bea26174078de1f5a4216215 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 3 Feb 2025 13:26:51 -0800 Subject: [PATCH 274/622] [NFC] Update lit test output (#7265) Some of the expected output lines had drifted from what the auto-update script generates. Fix them so the changes don't inadvertently make their way into some unrelated PR. --- test/lit/help/wasm-metadce.test | 4 ++-- test/lit/help/wasm-opt.test | 4 ++-- test/lit/help/wasm2js.test | 4 ++-- test/lit/passes/gto-removals.wast | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index bc2d0553376..5d01bc62c43 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -762,9 +762,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-stack-switching Enable stack switching +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-stack-switching Disable stack switching +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index ec039f5b238..2636423d95b 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -771,9 +771,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-stack-switching Enable stack switching +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-stack-switching Disable stack switching +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 48cac67700e..6e9c7732a3a 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -725,9 +725,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-stack-switching Enable stack switching +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-stack-switching Disable stack switching +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index e02430a3634..e1c85c5a0f6 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -1631,15 +1631,15 @@ ;; A struct with a pop, which requires EH fixups to avoid popping in a nested ;; block. (module - (type $i32 (func (param i32))) - ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct)) - (type $struct (struct (field (mut i32)))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $i32 (func (param i32))) + (type $i32 (func (param i32))) + + (type $struct (struct (field (mut i32)))) ;; CHECK: (tag $tag (type $i32) (param i32)) (tag $tag (type $i32) (param i32)) From 4d415592792063877c70cc0256c064d1ea23b85a Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Mon, 3 Feb 2025 13:54:02 -0800 Subject: [PATCH 275/622] [Interpreter] i32.mul (#7268) Building on top of #7227, i32.mul is implemented and tested. --- src/interpreter/interpreter.cpp | 21 +++++++++++++-------- test/gtest/interpreter.cpp | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 42a83f73786..2197c67079c 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -90,15 +90,20 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitBinary(Binary* curr) { auto rhs = pop(); auto lhs = pop(); - // TODO: switch-case over all operations. - if (curr->op == AddInt32) { - push(lhs.add(rhs)); - return {}; - } else if (curr->op == SubInt32) { - push(lhs.sub(rhs)); - return {}; + // TODO: add support for all operations. + switch (curr->op) { + case AddInt32: + push(lhs.add(rhs)); + return {}; + case SubInt32: + push(lhs.sub(rhs)); + return {}; + case MulInt32: + push(lhs.mul(rhs)); + return {}; + default: + WASM_UNREACHABLE("TODO"); } - WASM_UNREACHABLE("TODO"); } Flow visitSelect(Select* curr) { WASM_UNREACHABLE("TODO"); } Flow visitDrop(Drop* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/test/gtest/interpreter.cpp b/test/gtest/interpreter.cpp index 399d46912e9..418d61530a7 100644 --- a/test/gtest/interpreter.cpp +++ b/test/gtest/interpreter.cpp @@ -58,3 +58,20 @@ TEST(InterpreterTest, SubI32) { EXPECT_EQ(results, expected); } + +TEST(InterpreterTest, MulI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(MulInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(2))}; + + EXPECT_EQ(results, expected); +} From b2000aee984d0a3d868e70de14c1007ff3a70c8d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Feb 2025 15:51:25 -0800 Subject: [PATCH 276/622] Parser: Error on tuples in signatures (#7270) Without this, we reach an assertion when we construct the Type for the params or results, as Types accept lists with single values only. --- src/parser/contexts.h | 8 ++++++++ test/lit/parse-bad-tuple-in-sig.wast | 10 ++++++++++ 2 files changed, 18 insertions(+) create mode 100644 test/lit/parse-bad-tuple-in-sig.wast diff --git a/src/parser/contexts.h b/src/parser/contexts.h index e9eb708ac9c..55601b174c5 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1215,6 +1215,14 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { resultTypes = *results; } + for (auto& v : {paramTypes, resultTypes}) { + for (auto t : v) { + if (!t.isSingle()) { + return in.err("tuple types not allowed in signature"); + } + } + } + auto sig = Signature(Type(paramTypes), Type(resultTypes)); auto [it, inserted] = sigTypes.insert({sig, HeapType::func}); if (inserted) { diff --git a/test/lit/parse-bad-tuple-in-sig.wast b/test/lit/parse-bad-tuple-in-sig.wast new file mode 100644 index 00000000000..d806d3b73dc --- /dev/null +++ b/test/lit/parse-bad-tuple-in-sig.wast @@ -0,0 +1,10 @@ +;; Test that tuple types in signatures lead to errors + +;; RUN: not wasm-opt %s 2>&1 | filecheck %s + +;; CHECK: Fatal: 9:1: error: tuple types not allowed in signature + +(module + (func $tuple-in-sig (param (tuple i32 i32)) + ) +) From b35228f216bb7ed928c26061c847429c7029026b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 3 Feb 2025 16:24:20 -0800 Subject: [PATCH 277/622] Update GTO and TypeRefining for Struct RMW operations (#7262) Both passes use StructUtils::StructScanner to analyze struct operations. Add support for RMW operations to this utility and update its users to provide the new `noteRMW` hook. Test that GTO and TypeRefining optimizations work as expected in the presence of RMW operations, but leave a proper implementation in ConstantFieldPropagation to a later PR. To allow TypeRefining to refine field types based only on the "replacement" operand and not the "expected" operand of cmpxchg operations, update validation to allow the "expected" field to be a supertype of the accessed field type as long as it is still equality comparable. https://github.com/WebAssembly/shared-everything-threads/pull/92 clarifies this intended typing in the upstream proposal. --- scripts/test/fuzzing.py | 2 + src/ir/struct-utils.h | 55 ++++++- src/passes/ConstantFieldPropagation.cpp | 7 + src/passes/GlobalTypeOptimization.cpp | 5 + src/passes/TypeRefining.cpp | 5 + src/wasm/wasm-validator.cpp | 23 +-- test/lit/passes/gto-removals-rmw.wast | 76 ++++++++++ test/lit/passes/type-refining-rmw.wast | 184 ++++++++++++++++++++++++ 8 files changed, 341 insertions(+), 16 deletions(-) create mode 100644 test/lit/passes/gto-removals-rmw.wast create mode 100644 test/lit/passes/type-refining-rmw.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 78b78a089d8..6ffa18ffc4a 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -84,6 +84,8 @@ 'shared-structs.wast', 'heap2local-rmw.wast', 'optimize-instructions-struct-rmw.wast', + 'gto-removals-rmw.wast', + 'type-refining-rmw.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index 150061720f8..e1726c05671 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -118,6 +118,10 @@ struct FunctionStructValuesMap // // void noteDefault(Type fieldType, HeapType type, Index index, T& info); // +// * Note a RMW operation on a field. TODO: Pass more useful information here. +// +// void noteRMW(Expression* expr, HeapType type, Index index, T& info); +// // * Note a copied value (read from this field and written to the same, possibly // in another object). Note that we require that the two types (the one read // from, and written to) are identical; allowing subtyping is possible, but @@ -140,6 +144,8 @@ struct StructScanner bool modifiesBinaryenIR() override { return false; } + SubType& self() { return *static_cast(this); } + StructScanner(FunctionStructValuesMap& functionNewInfos, FunctionStructValuesMap& functionSetGetInfos) : functionNewInfos(functionNewInfos), @@ -157,8 +163,7 @@ struct StructScanner auto& infos = functionNewInfos[this->getFunction()][heapType]; for (Index i = 0; i < fields.size(); i++) { if (curr->isWithDefault()) { - static_cast(this)->noteDefault( - fields[i].type, heapType, i, infos[i]); + self().noteDefault(fields[i].type, heapType, i, infos[i]); } else { noteExpressionOrCopy(curr->operands[i], heapType, i, infos[i]); } @@ -187,10 +192,48 @@ struct StructScanner auto heapType = type.getHeapType(); auto index = curr->index; - static_cast(this)->noteRead( - heapType, - index, - functionSetGetInfos[this->getFunction()][heapType][index]); + self().noteRead(heapType, + index, + functionSetGetInfos[this->getFunction()][heapType][index]); + } + + void visitStructRMW(StructRMW* curr) { + auto type = curr->ref->type; + if (type == Type::unreachable || type.isNull()) { + return; + } + + auto heapType = type.getHeapType(); + auto index = curr->index; + auto& info = + functionSetGetInfos[this->getFunction()][type.getHeapType()][index]; + + if (curr->op == RMWXchg) { + // An xchg is really like a read and write combined. + self().noteRead(heapType, index, info); + noteExpressionOrCopy(curr->value, heapType, index, info); + return; + } + + // Otherwise we don't have a simple expression to describe the written + // value, so fall back to noting an opaque RMW. + self().noteRMW(curr->value, heapType, index, info); + } + + void visitStructCmpxchg(StructCmpxchg* curr) { + auto type = curr->ref->type; + if (type == Type::unreachable || type.isNull()) { + return; + } + + auto heapType = type.getHeapType(); + auto index = curr->index; + auto& info = + functionSetGetInfos[this->getFunction()][type.getHeapType()][curr->index]; + + // A cmpxchg is like a read and conditional write. + self().noteRead(heapType, index, info); + noteExpressionOrCopy(curr->replacement, heapType, index, info); } void diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 36aebf3a49f..6f7ba8eb33b 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -444,6 +444,13 @@ struct PCVScanner // Reads do not interest us. } + void noteRMW(Expression* expr, + HeapType type, + Index index, + PossibleConstantValues& info) { + WASM_UNREACHABLE("TODO"); + } + BoolFunctionStructValuesMap& functionCopyInfos; }; diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 3d006468fc7..1a532b423ef 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -95,6 +95,11 @@ struct FieldInfoScanner void noteRead(HeapType type, Index index, FieldInfo& info) { info.noteRead(); } + + void noteRMW(Expression* expr, HeapType type, Index index, FieldInfo& info) { + info.noteRead(); + info.noteWrite(); + } }; struct GlobalTypeOptimization : public Pass { diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index c37a105ece7..401275fcba1 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -76,6 +76,11 @@ struct FieldInfoScanner // Nothing to do for a read, we just care about written values. } + void noteRMW(Expression* expr, HeapType type, Index index, FieldInfo& info) { + // We must not refine past the RMW value type. + info.note(expr->type); + } + Properties::FallthroughBehavior getFallthroughBehavior() { // Looking at fallthrough values may be dangerous here, because it ignores // intermediate steps. Consider this: diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 8812a0d3f70..3f67906f105 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3154,20 +3154,23 @@ void FunctionValidator::visitStructCmpxchg(StructCmpxchg* curr) { field.mutable_, Mutable, curr, "struct.atomic.rmw field must be mutable"); shouldBeFalse( field.isPacked(), curr, "struct.atomic.rmw field must not be packed"); - bool isEq = - field.type.isRef() && - Type::isSubType( - field.type, - Type(HeapTypes::eq.getBasic(field.type.getHeapType().getShared()), - Nullable)); - if (!shouldBeTrue(field.type == Type::i32 || field.type == Type::i64 || isEq, - curr, - "struct.atomic.rmw field type invalid for operation")) { + + Type expectedExpectedType; + if (field.type == Type::i32) { + expectedExpectedType = Type::i32; + } else if (field.type == Type::i64) { + expectedExpectedType = Type::i64; + } else if (field.type.isRef()) { + expectedExpectedType = Type( + HeapTypes::eq.getBasic(field.type.getHeapType().getShared()), Nullable); + } else { + shouldBeTrue( + false, curr, "struct.atomic.rmw field type invalid for operation"); return; } shouldBeSubType( curr->expected->type, - field.type, + expectedExpectedType, curr, "struct.atomic.rmw.cmpxchg expected value must have the proper type"); shouldBeSubType( diff --git a/test/lit/passes/gto-removals-rmw.wast b/test/lit/passes/gto-removals-rmw.wast new file mode 100644 index 00000000000..7d759a6727f --- /dev/null +++ b/test/lit/passes/gto-removals-rmw.wast @@ -0,0 +1,76 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --gto --closed-world -all -S -o - | filecheck %s + +;; RMW ops are combined reads and writes, so they keep fields alive and mutable. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $rmw (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.add $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw (param (ref $A)) (result i32) + (struct.atomic.rmw.add $A 0 + (local.get 0) + (i32.const 0) + ) + ) +) + +;; Even when it is a copy from a field to itself, xchg keeps the field alive. +;; Optimizing this is left to ConstantFieldPropagation. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A) (ref $A)) (result i32))) + + ;; CHECK: (func $xchg (type $1) (param $0 (ref $A)) (param $1 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.atomic.get $A 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $xchg (param (ref $A) (ref $A)) (result i32) + (struct.atomic.rmw.xchg $A 0 + (local.get 0) + (struct.atomic.get $A 0 + (local.get 1) + ) + ) + ) +) + +;; Same with cmpxchg. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A) i32 (ref $A)) (result i32))) + + ;; CHECK: (func $cmpxchg (type $1) (param $0 (ref $A)) (param $1 i32) (param $2 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (struct.atomic.get $A 0 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg (param (ref $A) i32 (ref $A)) (result i32) + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (struct.atomic.get $A 0 + (local.get 2) + ) + ) + ) +) diff --git a/test/lit/passes/type-refining-rmw.wast b/test/lit/passes/type-refining-rmw.wast new file mode 100644 index 00000000000..a4bb0a85cae --- /dev/null +++ b/test/lit/passes/type-refining-rmw.wast @@ -0,0 +1,184 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-refining -S -o - | filecheck %s + +;; Reference fields accessed by RMW ops can be optimized. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) + (type $null (shared (struct (field (mut (ref null (shared any))))))) + + ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) + (type $i31 (shared (struct (field (mut (ref null (shared any))))))) + + ;; CHECK: (type $super (sub (shared (struct (field (mut (ref $sub))))))) + (type $super (sub (shared (struct (field (mut (ref null $super))))))) + + ;; CHECK: (type $sub (sub $super (shared (struct (field (mut (ref $sub))))))) + (type $sub (sub $super (shared (struct (field (mut (ref null $super))))))) + ) + + ;; CHECK: (type $4 (func (param (ref $null)) (result (ref null (shared any))))) + + ;; CHECK: (type $5 (func (param (ref $i31)) (result (ref null (shared any))))) + + ;; CHECK: (type $6 (func (param (ref $sub)) (result (ref null $super)))) + + ;; CHECK: (func $xchg-null (type $4) (param $0 (ref $null)) (result (ref null (shared any))) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $null 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $xchg-null (param (ref $null)) (result (ref null (shared any))) + (struct.atomic.rmw.xchg $null 0 + (local.get 0) + (ref.null (shared none)) + ) + ) + + ;; CHECK: (func $xchg-i31 (type $5) (param $0 (ref $i31)) (result (ref null (shared any))) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i31 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $xchg-i31 (param (ref $i31)) (result (ref null (shared any))) + ;; Acqrel ordering shouldn't make a difference. + (struct.atomic.rmw.xchg acqrel acqrel $i31 0 + (local.get 0) + (ref.i31_shared + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $xchg-self (type $6) (param $0 (ref $sub)) (result (ref null $super)) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $sub 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $xchg-self (param (ref $sub)) (result (ref null $super)) + (struct.atomic.rmw.xchg $sub 0 + (local.get 0) + (local.get 0) + ) + ) +) + +;; Cmpxchg works as well. Note that the field can be more refined than the +;; "expected" operand. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) + (type $null (shared (struct (field (mut (ref null (shared eq))))))) + + ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) + (type $i31 (shared (struct (field (mut (ref null (shared eq))))))) + + ;; CHECK: (type $super (sub (shared (struct (field (mut (ref $sub))))))) + (type $super (sub (shared (struct (field (mut (ref null $super))))))) + + ;; CHECK: (type $sub (sub $super (shared (struct (field (mut (ref $sub))))))) + (type $sub (sub $super (shared (struct (field (mut (ref null $super))))))) + ) + + ;; CHECK: (type $4 (func (param (ref $null) (ref (shared eq))) (result (ref null (shared any))))) + + ;; CHECK: (type $5 (func (param (ref $i31) (ref (shared eq))) (result (ref null (shared any))))) + + ;; CHECK: (type $6 (func (param (ref $sub) (ref (shared eq))) (result (ref null $super)))) + + ;; CHECK: (func $cmpxchg-null (type $4) (param $0 (ref $null)) (param $1 (ref (shared eq))) (result (ref null (shared any))) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $null 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-null (param (ref $null) (ref (shared eq))) (result (ref null (shared any))) + (struct.atomic.rmw.cmpxchg $null 0 + (local.get 0) + (local.get 1) + (ref.null (shared none)) + ) + ) + + ;; CHECK: (func $cmpxchg-i31 (type $5) (param $0 (ref $i31)) (param $1 (ref (shared eq))) (result (ref null (shared any))) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $i31 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-i31 (param (ref $i31) (ref (shared eq))) (result (ref null (shared any))) + ;; Acqrel ordering shouldn't make a difference. + (struct.atomic.rmw.cmpxchg acqrel acqrel $i31 0 + (local.get 0) + (local.get 1) + (ref.i31_shared + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $cmpxchg-self (type $6) (param $0 (ref $sub)) (param $1 (ref (shared eq))) (result (ref null $super)) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $sub 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg-self (param (ref $sub) (ref (shared eq))) (result (ref null $super)) + (struct.atomic.rmw.cmpxchg $sub 0 + (local.get 0) + (local.get 1) + (local.get 0) + ) + ) +) + +;; Non-reference fields should not cause problems. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (field (mut i32))))) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (type $2 (func (param (ref $A) i32) (result i32))) + + ;; CHECK: (func $rmw (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.add $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw (param (ref $A)) (result i32) + (struct.atomic.rmw.add $A 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $cmpxchg (type $2) (param $0 (ref $A)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cmpxchg (param (ref $A) i32) (result i32) + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (i32.const 1) + ) + ) +) From b4c6cb94c3a9d61e0627eb316175b56bf703ae2a Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 4 Feb 2025 08:17:36 -0800 Subject: [PATCH 278/622] [ci] Cancel CI jobs when new commits are added to a PR (#7269) This seems like it should really be the default. --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7b5eae71db..c56a0b9cff7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ on: - kripken/* pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: lint: From e25eb7f057fc39ea9fb1274f9d7bf9c315a9e4c2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Feb 2025 08:48:44 -0800 Subject: [PATCH 279/622] [NFC] Make fuzzing params mutable (#7257) This only makes them mutable, but does not actually mutate them in any way. Specifically they are now stored in an RAII context, currently a single global one, but that later changes will be able to modify. --- src/tools/CMakeLists.txt | 1 + src/tools/fuzzing.h | 86 +++++++++++++++++++++++++++--- src/tools/fuzzing/fuzzing.cpp | 90 +++++++++++++++++--------------- src/tools/fuzzing/heap-types.cpp | 12 +++-- src/tools/fuzzing/parameters.cpp | 53 +++++++++++++++++++ src/tools/fuzzing/parameters.h | 80 ---------------------------- 6 files changed, 190 insertions(+), 132 deletions(-) create mode 100644 src/tools/fuzzing/parameters.cpp delete mode 100644 src/tools/fuzzing/parameters.h diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index c73797f54c2..96335926dcd 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -3,6 +3,7 @@ FILE(GLOB fuzzing_HEADERS fuzzing/*h) set(fuzzing_SOURCES fuzzing/fuzzing.cpp fuzzing/heap-types.cpp + fuzzing/parameters.cpp fuzzing/random.cpp ${fuzzing_HEADERS} ) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index b878b0ae615..63f399f949f 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -19,12 +19,6 @@ // This is helpful for fuzzing. // -/* -high chance for set at start of loop - high chance of get of a set local in the scope of that scope - high chance of a tee in that case => loop var -*/ - #include "ir/branch-utils.h" #include "ir/struct-utils.h" #include "support/insert_ordered.h" @@ -61,9 +55,68 @@ struct BinaryArgs { Expression* c; }; +// params + +struct FuzzParams { + // The maximum amount of params to each function. + int MAX_PARAMS; + + // The maximum amount of vars in each function. + int MAX_VARS; + + // The maximum number of globals in a module. + int MAX_GLOBALS; + + // The maximum number of tuple elements. + int MAX_TUPLE_SIZE; + + // The maximum number of struct fields. + int MAX_STRUCT_SIZE; + + // The maximum number of elements in an array. + int MAX_ARRAY_SIZE; + + // The number of nontrivial heap types to generate. + int MIN_HEAPTYPES; + int MAX_HEAPTYPES; + + // some things require luck, try them a few times + int TRIES; + + // beyond a nesting limit, greatly decrease the chance to continue to nest + int NESTING_LIMIT; + + // the maximum size of a block + int BLOCK_FACTOR; + + // the memory that we use, a small portion so that we have a good chance of + // looking at writes (we also look outside of this region with small + // probability) this should be a power of 2 + Address USABLE_MEMORY; + + // the number of runtime iterations (function calls, loop backbranches) we + // allow before we stop execution with a trap, to prevent hangs. 0 means + // no hang protection. + int HANG_LIMIT; + + // the maximum amount of new GC types (structs, etc.) to create + int MAX_NEW_GC_TYPES; + + // the maximum amount of catches in each try (not including a catch-all, if + // present). + int MAX_TRY_CATCHES; + + FuzzParams() { setDefaults(); } + + void setDefaults(); +}; + // main reader class TranslateToFuzzReader { + static constexpr size_t VeryImportant = 4; + static constexpr size_t Important = 2; + public: TranslateToFuzzReader(Module& wasm, std::vector&& input, @@ -179,6 +232,27 @@ class TranslateToFuzzReader { FunctionCreationContext* funcContext = nullptr; + // The fuzzing parameters we use. This may change from function to function or + // even in a more refined manner, so we use an RAII context to manage it. + struct FuzzParamsContext : public FuzzParams { + TranslateToFuzzReader& parent; + + FuzzParamsContext* old; + + FuzzParamsContext(TranslateToFuzzReader& parent) + : parent(parent), old(parent.fuzzParams) { + parent.fuzzParams = this; + } + + ~FuzzParamsContext() { parent.fuzzParams = old; } + }; + + FuzzParamsContext* fuzzParams = nullptr; + + // The default global context we use throughout the process (unless it is + // overridden using another context in an RAII manner). + std::unique_ptr globalParams; + public: int nesting = 0; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 50191045fed..11fa12d5ba8 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -23,7 +23,6 @@ #include "ir/type-updating.h" #include "support/string.h" #include "tools/fuzzing/heap-types.h" -#include "tools/fuzzing/parameters.h" namespace wasm { @@ -49,6 +48,8 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, if (wasm.features.hasSIMD()) { loggableTypes.push_back(Type::v128); } + + globalParams = std::make_unique(*this); } TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, @@ -299,7 +300,7 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { } void TranslateToFuzzReader::build() { - if (HANG_LIMIT > 0) { + if (fuzzParams->HANG_LIMIT > 0) { prepareHangLimitSupport(); } if (allowMemory) { @@ -324,7 +325,7 @@ void TranslateToFuzzReader::build() { auto* func = addFunction(); addInvocations(func); } - if (HANG_LIMIT > 0) { + if (fuzzParams->HANG_LIMIT > 0) { addHangLimitSupport(); } if (allowMemory) { @@ -364,7 +365,7 @@ void TranslateToFuzzReader::setupMemory() { segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(i)), false); segment->isPassive = bool(upTo(2)); - size_t segSize = upTo(USABLE_MEMORY * 2); + size_t segSize = upTo(fuzzParams->USABLE_MEMORY * 2); segment->data.resize(segSize); for (size_t j = 0; j < segSize; j++) { segment->data[j] = upTo(512); @@ -385,7 +386,7 @@ void TranslateToFuzzReader::setupMemory() { builder.makeConst(Literal::makeFromInt32(0, memory->addressType)); segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(0)), false); - auto num = upTo(USABLE_MEMORY * 2); + auto num = upTo(fuzzParams->USABLE_MEMORY * 2); for (size_t i = 0; i < num; i++) { auto value = upTo(512); segment->data.push_back(value >= 256 ? 0 : (value & 0xff)); @@ -405,8 +406,8 @@ void TranslateToFuzzReader::setupHeapTypes() { // For GC, also generate random types. if (wasm.features.hasGC()) { - auto generator = - HeapTypeGenerator::create(random, wasm.features, upTo(MAX_NEW_GC_TYPES)); + auto generator = HeapTypeGenerator::create( + random, wasm.features, upTo(fuzzParams->MAX_NEW_GC_TYPES)); auto result = generator.builder.build(); if (auto* err = result.getError()) { Fatal() << "Failed to build heap types: " << err->reason << " at index " @@ -601,7 +602,7 @@ void TranslateToFuzzReader::setupGlobals() { } // Create new random globals. - for (size_t index = upTo(MAX_GLOBALS); index > 0; --index) { + for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); // Prefer immutable ones as they can be used in global.gets in other // globals, for more interesting patterns. @@ -682,7 +683,7 @@ void TranslateToFuzzReader::finalizeMemory() { memory->initial, Address((maxOffset + Memory::kPageSize - 1) / Memory::kPageSize)); } - memory->initial = std::max(memory->initial, USABLE_MEMORY); + memory->initial = std::max(memory->initial, fuzzParams->USABLE_MEMORY); // Avoid an unlimited memory size, which would make fuzzing very difficult // as different VMs will run out of system memory in different ways. if (memory->max == Memory::kUnlimitedSize) { @@ -783,10 +784,11 @@ void TranslateToFuzzReader::prepareHangLimitSupport() { } void TranslateToFuzzReader::addHangLimitSupport() { - auto glob = builder.makeGlobal(HANG_LIMIT_GLOBAL, - Type::i32, - builder.makeConst(int32_t(HANG_LIMIT)), - Builder::Mutable); + auto glob = + builder.makeGlobal(HANG_LIMIT_GLOBAL, + Type::i32, + builder.makeConst(int32_t(fuzzParams->HANG_LIMIT)), + Builder::Mutable); wasm.addGlobal(std::move(glob)); } @@ -974,7 +976,7 @@ void TranslateToFuzzReader::addHashMemorySupport() { contents.push_back( builder.makeLocalSet(0, builder.makeConst(uint32_t(5381)))); auto zero = Literal::makeFromInt32(0, wasm.memories[0]->addressType); - for (Index i = 0; i < USABLE_MEMORY; i++) { + for (Index i = 0; i < fuzzParams->USABLE_MEMORY; i++) { contents.push_back(builder.makeLocalSet( 0, builder.makeBinary( @@ -1032,7 +1034,7 @@ TranslateToFuzzReader::FunctionCreationContext::~FunctionCreationContext() { // fixup to ensure we validate. TypeUpdating::handleNonDefaultableLocals(func, parent.wasm); - if (HANG_LIMIT > 0) { + if (parent.fuzzParams->HANG_LIMIT > 0) { parent.addHangLimitChecks(func); } assert(breakableStack.empty()); @@ -1048,10 +1050,10 @@ Expression* TranslateToFuzzReader::makeHangLimitCheck() { builder.makeIf( builder.makeUnary(UnaryOp::EqZInt32, builder.makeGlobalGet(HANG_LIMIT_GLOBAL, Type::i32)), - builder.makeSequence( - builder.makeGlobalSet(HANG_LIMIT_GLOBAL, - builder.makeConst(int32_t(HANG_LIMIT))), - builder.makeUnreachable())), + builder.makeSequence(builder.makeGlobalSet(HANG_LIMIT_GLOBAL, + builder.makeConst(int32_t( + fuzzParams->HANG_LIMIT))), + builder.makeUnreachable())), builder.makeGlobalSet( HANG_LIMIT_GLOBAL, builder.makeBinary(BinaryOp::SubInt32, @@ -1161,7 +1163,7 @@ Function* TranslateToFuzzReader::addFunction() { func->name = Names::getValidFunctionName(wasm, "func"); FunctionCreationContext context(*this, func); assert(funcContext->typeLocals.empty()); - Index numParams = upToSquared(MAX_PARAMS); + Index numParams = upToSquared(fuzzParams->MAX_PARAMS); std::vector params; params.reserve(numParams); for (Index i = 0; i < numParams; i++) { @@ -1171,7 +1173,7 @@ Function* TranslateToFuzzReader::addFunction() { auto paramType = Type(params); auto resultType = getControlFlowType(); func->type = Signature(paramType, resultType); - Index numVars = upToSquared(MAX_VARS); + Index numVars = upToSquared(fuzzParams->MAX_VARS); for (Index i = 0; i < numVars; i++) { auto type = getConcreteType(); if (!TypeUpdating::canHandleAsLocal(type)) { @@ -1803,8 +1805,9 @@ Expression* TranslateToFuzzReader::make(Type type) { return makeTrivial(type); } // When we should stop, emit something small (but not necessarily trivial). - if (random.finished() || nesting >= 5 * NESTING_LIMIT || // hard limit - (nesting >= NESTING_LIMIT && !oneIn(3))) { + if (random.finished() || + nesting >= 5 * fuzzParams->NESTING_LIMIT || // hard limit + (nesting >= fuzzParams->NESTING_LIMIT && !oneIn(3))) { if (type.isConcrete()) { if (oneIn(2)) { return makeConst(type); @@ -2031,11 +2034,11 @@ Expression* TranslateToFuzzReader::makeBlock(Type type) { ret->type = type; // so we have it during child creation ret->name = makeLabel(); funcContext->breakableStack.push_back(ret); - Index num = upToSquared(BLOCK_FACTOR - 1); // we add another later - if (nesting >= NESTING_LIMIT / 2) { + Index num = upToSquared(fuzzParams->BLOCK_FACTOR - 1); // we add another later + if (nesting >= fuzzParams->NESTING_LIMIT / 2) { // smaller blocks past the limit num /= 2; - if (nesting >= NESTING_LIMIT && oneIn(2)) { + if (nesting >= fuzzParams->NESTING_LIMIT && oneIn(2)) { // smaller blocks past the limit num /= 2; } @@ -2106,7 +2109,7 @@ Expression* TranslateToFuzzReader::makeCondition() { Expression* TranslateToFuzzReader::makeMaybeBlock(Type type) { // if past the limit, prefer not to emit blocks - if (nesting >= NESTING_LIMIT || oneIn(3)) { + if (nesting >= fuzzParams->NESTING_LIMIT || oneIn(3)) { return make(type); } else { return makeBlock(type); @@ -2153,7 +2156,7 @@ Expression* TranslateToFuzzReader::makeTry(Type type) { auto* body = make(type); std::vector catchTags; std::vector catchBodies; - auto numTags = upTo(MAX_TRY_CATCHES); + auto numTags = upTo(fuzzParams->MAX_TRY_CATCHES); std::unordered_set usedTags; for (Index i = 0; i < numTags; i++) { if (wasm.tags.empty()) { @@ -2215,7 +2218,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) { std::vector catchTags; std::vector catchDests; std::vector catchRefs; - auto numCatches = upTo(MAX_TRY_CATCHES); + auto numCatches = upTo(fuzzParams->MAX_TRY_CATCHES); for (Index i = 0; i <= numCatches; i++) { Name tagName; Type tagType; @@ -2240,7 +2243,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) { // also accept a target that is nullable. vec.push_back(Type(HeapType::exn, NonNullable)); auto tagTypeWithExn = Type(vec); - int tries = TRIES; + int tries = fuzzParams->TRIES; while (tries-- > 0) { auto* target = pick(funcContext->breakableStack); auto dest = getTargetName(target); @@ -2272,7 +2275,7 @@ Expression* TranslateToFuzzReader::makeBreak(Type type) { condition = makeCondition(); } // we need to find a proper target to break to; try a few times - int tries = TRIES; + int tries = fuzzParams->TRIES; while (tries-- > 0) { auto* target = pick(funcContext->breakableStack); auto name = getTargetName(target); @@ -2346,7 +2349,7 @@ Expression* TranslateToFuzzReader::makeBreak(Type type) { } Expression* TranslateToFuzzReader::makeCall(Type type) { - int tries = TRIES; + int tries = fuzzParams->TRIES; bool isReturn; while (tries-- > 0) { Function* target = funcContext->func; @@ -2420,9 +2423,9 @@ Expression* TranslateToFuzzReader::makeCallRef(Type type) { // look for a call target with the right type Function* target; bool isReturn; - size_t i = 0; + decltype(fuzzParams->TRIES) i = 0; while (1) { - if (i == TRIES || wasm.functions.empty()) { + if (i == fuzzParams->TRIES || wasm.functions.empty()) { // We can't find a proper target, give up. return makeTrivial(type); } @@ -2585,10 +2588,14 @@ Expression* TranslateToFuzzReader::makePointer() { if (!allowOOB || !oneIn(10)) { if (wasm.memories[0]->is64()) { ret = builder.makeBinary( - AndInt64, ret, builder.makeConst(int64_t(USABLE_MEMORY - 1))); + AndInt64, + ret, + builder.makeConst(int64_t(fuzzParams->USABLE_MEMORY - 1))); } else { ret = builder.makeBinary( - AndInt32, ret, builder.makeConst(int32_t(USABLE_MEMORY - 1))); + AndInt32, + ret, + builder.makeConst(int32_t(fuzzParams->USABLE_MEMORY - 1))); } } return ret; @@ -3316,7 +3323,7 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { // will only stop here when we exceed the nesting and reach a nullable one. // (This assumes there is a nullable one, that is, that the types are // inhabitable.) - const auto LIMIT = NESTING_LIMIT + 1; + const auto LIMIT = fuzzParams->NESTING_LIMIT + 1; AutoNester nester(*this); if (type.isNullable() && (random.finished() || nesting >= LIMIT || oneIn(LIMIT - nesting + 1))) { @@ -3384,7 +3391,8 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { if (!element.type.isDefaultable() || oneIn(2)) { init = makeChild(element.type); } - auto* count = builder.makeConst(int32_t(upTo(MAX_ARRAY_SIZE))); + auto* count = + builder.makeConst(int32_t(upTo(fuzzParams->MAX_ARRAY_SIZE))); return builder.makeArrayNew(type.getHeapType(), count, init); } case HeapTypeKind::Cont: @@ -4011,7 +4019,7 @@ Expression* TranslateToFuzzReader::makeSwitch(Type type) { return make(type); } // we need to find proper targets to break to; try a bunch - int tries = TRIES; + int tries = fuzzParams->TRIES; std::vector names; Type valueType = Type::unreachable; while (tries-- > 0) { @@ -4471,7 +4479,7 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { // to, we can then either drop ourselves or wrap ourselves in a block + // another value, so that we return the proper thing here (which is done below // in fixFlowingType). - int tries = TRIES; + int tries = fuzzParams->TRIES; Name targetName; Type targetType; while (--tries >= 0) { @@ -4939,7 +4947,7 @@ Type TranslateToFuzzReader::getMVPType() { Type TranslateToFuzzReader::getTupleType() { std::vector elements; - size_t maxElements = 2 + upTo(MAX_TUPLE_SIZE - 1); + size_t maxElements = 2 + upTo(fuzzParams->MAX_TUPLE_SIZE - 1); for (size_t i = 0; i < maxElements; ++i) { auto type = getSingleConcreteType(); // Don't add a non-defaultable type into a tuple, as currently we can't diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index b4833475a2c..6255ab42799 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -19,8 +19,8 @@ #include "ir/gc-type-utils.h" #include "ir/subtypes.h" #include "support/insert_ordered.h" +#include "tools/fuzzing.h" #include "tools/fuzzing/heap-types.h" -#include "tools/fuzzing/parameters.h" namespace wasm { @@ -54,6 +54,8 @@ struct HeapTypeGeneratorImpl { // The index of the type we are currently generating. Index index = 0; + FuzzParams params; + HeapTypeGeneratorImpl(Random& rand, FeatureSet features, size_t n) : result{TypeBuilder(n), std::vector>(n), @@ -216,7 +218,7 @@ struct HeapTypeGeneratorImpl { } Type generateTupleType(Shareability share) { - std::vector types(2 + rand.upTo(MAX_TUPLE_SIZE - 1)); + std::vector types(2 + rand.upTo(params.MAX_TUPLE_SIZE - 1)); for (auto& type : types) { type = generateSingleType(share); } @@ -234,7 +236,7 @@ struct HeapTypeGeneratorImpl { } Signature generateSignature() { - std::vector types(rand.upToSquared(MAX_PARAMS)); + std::vector types(rand.upToSquared(params.MAX_PARAMS)); for (auto& type : types) { type = generateSingleType(Unshared); } @@ -252,7 +254,7 @@ struct HeapTypeGeneratorImpl { } Struct generateStruct(Shareability share) { - std::vector fields(rand.upTo(MAX_STRUCT_SIZE + 1)); + std::vector fields(rand.upTo(params.MAX_STRUCT_SIZE + 1)); for (auto& field : fields) { field = generateField(share); } @@ -595,7 +597,7 @@ struct HeapTypeGeneratorImpl { fields.push_back(generateSubField(field)); } // Width subtyping - Index extra = rand.upTo(MAX_STRUCT_SIZE + 1 - fields.size()); + Index extra = rand.upTo(params.MAX_STRUCT_SIZE + 1 - fields.size()); for (Index i = 0; i < extra; ++i) { fields.push_back(generateField(share)); } diff --git a/src/tools/fuzzing/parameters.cpp b/src/tools/fuzzing/parameters.cpp new file mode 100644 index 00000000000..3220f9625d3 --- /dev/null +++ b/src/tools/fuzzing/parameters.cpp @@ -0,0 +1,53 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "tools/fuzzing.h" +#include "wasm.h" + +namespace wasm { + +void FuzzParams::setDefaults() { + MAX_PARAMS = 10; + + MAX_VARS = 20; + + MAX_GLOBALS = 30; + + MAX_TUPLE_SIZE = 6; + + MAX_STRUCT_SIZE = 6; + + MAX_ARRAY_SIZE = 100; + + MIN_HEAPTYPES = 4; + MAX_HEAPTYPES = 20; + + TRIES = 10; + + NESTING_LIMIT = 11; + + BLOCK_FACTOR = 5; + + USABLE_MEMORY = 16; + + HANG_LIMIT = 100; + + MAX_NEW_GC_TYPES = 25; + + MAX_TRY_CATCHES = 4; +} + +} // namespace wasm diff --git a/src/tools/fuzzing/parameters.h b/src/tools/fuzzing/parameters.h deleted file mode 100644 index eede193a7f3..00000000000 --- a/src/tools/fuzzing/parameters.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2021 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. - */ - -// Constants that control fuzzing. - -#ifndef wasm_tools_fuzzing_parameters_h -#define wasm_tools_fuzzing_parameters_h - -#include "wasm.h" - -namespace wasm { - -// The maximum amount of params to each function. -constexpr int MAX_PARAMS = 10; - -// The maximum amount of vars in each function. -constexpr int MAX_VARS = 20; - -// The maximum number of globals in a module. -constexpr int MAX_GLOBALS = 30; - -// The maximum number of tuple elements. -constexpr int MAX_TUPLE_SIZE = 6; - -// The maximum number of struct fields. -static const int MAX_STRUCT_SIZE = 6; - -// The maximum number of elements in an array. -static const int MAX_ARRAY_SIZE = 100; - -// The number of nontrivial heap types to generate. -constexpr int MIN_HEAPTYPES = 4; -constexpr int MAX_HEAPTYPES = 20; - -// some things require luck, try them a few times -constexpr int TRIES = 10; - -// beyond a nesting limit, greatly decrease the chance to continue to nest -constexpr int NESTING_LIMIT = 11; - -// the maximum size of a block -constexpr int BLOCK_FACTOR = 5; - -// the memory that we use, a small portion so that we have a good chance of -// looking at writes (we also look outside of this region with small -// probability) this should be a power of 2 -constexpr Address USABLE_MEMORY = 16; - -// the number of runtime iterations (function calls, loop backbranches) we -// allow before we stop execution with a trap, to prevent hangs. 0 means -// no hang protection. -constexpr int HANG_LIMIT = 100; - -// the maximum amount of new GC types (structs, etc.) to create -constexpr int MAX_NEW_GC_TYPES = 25; - -// the maximum amount of catches in each try (not including a catch-all, if -// present). -constexpr int MAX_TRY_CATCHES = 4; - -// -constexpr size_t VeryImportant = 4; -constexpr size_t Important = 2; - -} // namespace wasm - -#endif // wasm_tools_fuzzing_parameters_h From bce11cfe4f83bb4dc7baed3e8f00536953398d1d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 4 Feb 2025 11:59:28 -0800 Subject: [PATCH 280/622] Update ConstantFieldPropagation for struct RMW ops (#7263) RMW operations generally modify the field they access, so they inhibit constant field propagation. The exceptions are xchg and cmpxchg operations, which are like sets (conditional sets in the case of cmpxchg) that happen to do gets as well. Just like for sets, we can still optimize when the value written by xchg or cmpxchg happens to be either the same constant value we know is already in the field or is a copy from some instance of the same field. There is potential for further optimization when we know an xchg or cmpxchg sets a value that was read from some other field that we know has the same constant value, but that's a missing optimization for plain sets as well. There is also further optimization potential when we can reason that a cmpxchg that would write a different value will never perform the write because the comparison would never succeed. Add tests with TODOs so we remember these potential optimizations in the future. --- scripts/test/fuzzing.py | 1 + src/passes/ConstantFieldPropagation.cpp | 190 +++-- test/lit/passes/cfp-rmw.wast | 896 ++++++++++++++++++++++++ 3 files changed, 1039 insertions(+), 48 deletions(-) create mode 100644 test/lit/passes/cfp-rmw.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 6ffa18ffc4a..270a9cf6b80 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -86,6 +86,7 @@ 'optimize-instructions-struct-rmw.wast', 'gto-removals-rmw.wast', 'type-refining-rmw.wast', + 'cfp-rmw.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 6f7ba8eb33b..0ad1d659cbe 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -113,29 +113,90 @@ struct FunctionOptimizer : public WalkerPass> { : propagatedInfos(propagatedInfos), subTypes(subTypes), rawNewInfos(rawNewInfos), refTest(refTest) {} - void visitStructGet(StructGet* curr) { + template std::optional getRelevantHeapType(T* curr) { auto type = curr->ref->type; if (type == Type::unreachable) { - return; + return std::nullopt; } auto heapType = type.getHeapType(); if (!heapType.isStruct()) { + return std::nullopt; + } + return heapType; + } + + PossibleConstantValues getInfo(HeapType type, Index index) { + if (auto it = propagatedInfos.find(type); it != propagatedInfos.end()) { + // There is information on this type, fetch it. + return it->second[index]; + } + return PossibleConstantValues{}; + } + + // Returns a block dropping the `ref` operand of the argument. + template Block* makeRefDroppingBlock(T* curr) { + Builder builder(*getModule()); + return builder.blockify( + builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref))); + } + + // If an optimized access is sequentially consistent, then it synchronizes + // with other threads at least by participating in the global order of + // sequentially consistent operations. Preserve that effect by replacing the + // access with a fence. + Block* maybeAddFence(Block* block, MemoryOrder order) { + assert(order != MemoryOrder::AcqRel); + if (order == MemoryOrder::SeqCst) { + block->list.push_back(Builder(*getModule()).makeAtomicFence()); + } + return block; + } + + // Given information about a constant value, and the struct type and + // StructGet/RMW/Cmpxchg that reads it, create an expression for that value. + template + Expression* + makeExpression(const PossibleConstantValues& info, HeapType type, T* curr) { + auto* value = info.makeExpression(*getModule()); + auto field = GCTypeUtils::getField(type, curr->index); + assert(field); + // Apply packing, if needed. + if constexpr (std::is_same_v) { + value = + Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule()); + } + // Check if the value makes sense. The analysis below flows values around + // without considering where they are placed, that is, when we see a parent + // type can contain a value in a field then we assume a child may as well + // (which in general it can, e.g., using a reference to the parent, we can + // write that value to it, but the reference might actually point to a + // child instance). If we tracked the types of fields then we might avoid + // flowing values into places they cannot reside, like when a child field is + // a subtype, and so we could ignore things not refined enough for it (GUFA + // does a better job at this). For here, just check we do not break + // validation, and if we do, then we've inferred the only possible value is + // an impossible one, making the code unreachable. + if (!Type::isSubType(value->type, field->type)) { + Builder builder(*getModule()); + value = builder.makeSequence(builder.makeDrop(value), + builder.makeUnreachable()); + } + return value; + } + + void visitStructGet(StructGet* curr) { + auto type = getRelevantHeapType(curr); + if (!type) { return; } + auto heapType = *type; Builder builder(*getModule()); // Find the info for this field, and see if we can optimize. First, see if // there is any information for this heap type at all. If there isn't, it is // as if nothing was ever noted for that field. - PossibleConstantValues info; - assert(!info.hasNoted()); - auto iter = propagatedInfos.find(heapType); - if (iter != propagatedInfos.end()) { - // There is information on this type, fetch it. - info = iter->second[curr->index]; - } - + PossibleConstantValues info = getInfo(heapType, curr->index); if (!info.hasNoted()) { // This field is never written at all. That means that we do not even // construct any data of this type, and so it is a logic error to reach @@ -176,48 +237,78 @@ struct FunctionOptimizer : public WalkerPass> { // constant value. (Leave it to further optimizations to get rid of the // ref.) auto* value = makeExpression(info, heapType, curr); - auto* replacement = builder.blockify( - builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref))); - // If this get is sequentially consistent, then it synchronizes with other - // threads at least by participating in the global order of sequentially - // consistent operations. Preserve that effect by replacing the access with - // a fence. - assert(curr->order != MemoryOrder::AcqRel); - if (curr->order == MemoryOrder::SeqCst) { - replacement = builder.blockify(replacement, builder.makeAtomicFence()); + auto* replacement = makeRefDroppingBlock(curr); + replacement = maybeAddFence(replacement, curr->order); + replacement->list.push_back(value); + replacement->type = value->type; + replaceCurrent(replacement); + changed = true; + } + + template + std::optional> + shouldOptimizeRMW(T* curr) { + auto type = getRelevantHeapType(curr); + if (!type) { + return std::nullopt; } - replaceCurrent(builder.blockify(replacement, value)); + auto heapType = *type; + + // Get the info about the field. Since RMWs can only copy or mutate the + // value, we always have something recorded. + PossibleConstantValues info = getInfo(heapType, curr->index); + assert(info.hasNoted() && "unexpected lack of info for RMW"); + + if (curr->order == MemoryOrder::AcqRel) { + // See comment on visitStructGet for why we don't optimize here. + return std::nullopt; + } + + if (!info.isConstant()) { + // Optimizing using ref.test is not an option here because that only works + // on immutable fields, but RMW operations always access mutable fields. + return std::nullopt; + } + + // We can optimize. + return std::pair(heapType, info); + } + + void visitStructRMW(StructRMW* curr) { + auto typeAndInfo = shouldOptimizeRMW(curr); + if (!typeAndInfo) { + return; + } + // Only xchg allows the field to have a constant value. + assert(curr->op == RMWXchg && "unexpected op"); + auto& [type, info] = *typeAndInfo; + Builder builder(*getModule()); + auto* value = makeExpression(info, type, curr); + auto* replacement = makeRefDroppingBlock(curr); + replacement->list.push_back(builder.makeDrop(curr->value)); + replacement = maybeAddFence(replacement, curr->order); + replacement->list.push_back(value); + replacement->type = value->type; + replaceCurrent(replacement); changed = true; } - // Given information about a constant value, and the struct type and StructGet - // that reads it, create an expression for that value. - Expression* makeExpression(const PossibleConstantValues& info, - HeapType type, - StructGet* curr) { - auto* value = info.makeExpression(*getModule()); - auto field = GCTypeUtils::getField(type, curr->index); - assert(field); - // Apply packing, if needed. - value = - Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule()); - // Check if the value makes sense. The analysis below flows values around - // without considering where they are placed, that is, when we see a parent - // type can contain a value in a field then we assume a child may as well - // (which in general it can, e.g., using a reference to the parent, we can - // write that value to it, but the reference might actually point to a - // child instance). If we tracked the types of fields then we might avoid - // flowing values into places they cannot reside, like when a child field is - // a subtype, and so we could ignore things not refined enough for it (GUFA - // does a better job at this). For here, just check we do not break - // validation, and if we do, then we've inferred the only possible value is - // an impossible one, making the code unreachable. - if (!Type::isSubType(value->type, field->type)) { - Builder builder(*getModule()); - value = builder.makeSequence(builder.makeDrop(value), - builder.makeUnreachable()); + void visitStructCmpxchg(StructCmpxchg* curr) { + auto typeAndInfo = shouldOptimizeRMW(curr); + if (!typeAndInfo) { + return; } - return value; + auto& [type, info] = *typeAndInfo; + Builder builder(*getModule()); + auto* value = makeExpression(info, type, curr); + auto* replacement = makeRefDroppingBlock(curr); + replacement->list.push_back(builder.makeDrop(curr->expected)); + replacement->list.push_back(builder.makeDrop(curr->replacement)); + replacement = maybeAddFence(replacement, curr->order); + replacement->list.push_back(value); + replacement->type = value->type; + replaceCurrent(replacement); + changed = true; } void optimizeUsingRefTest(StructGet* curr) { @@ -448,7 +539,10 @@ struct PCVScanner HeapType type, Index index, PossibleConstantValues& info) { - WASM_UNREACHABLE("TODO"); + // In general RMWs will modify the value of the field, so there is no single + // constant value. We could in principle try to recognize no-op RMWs like + // adds of 0, but we leave that for OptimizeInstructions for simplicity. + info.noteUnknown(); } BoolFunctionStructValuesMap& functionCopyInfos; diff --git a/test/lit/passes/cfp-rmw.wast b/test/lit/passes/cfp-rmw.wast new file mode 100644 index 00000000000..85f36c0f1d8 --- /dev/null +++ b/test/lit/passes/cfp-rmw.wast @@ -0,0 +1,896 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --cfp -all -S -o - | filecheck %s + +;; RMW ops are generally writes that inhibit optimization. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (result (ref $A)))) + + ;; CHECK: (type $2 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $1) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + (func $init (result (ref $A)) + (struct.new_default $A) + ) + + ;; CHECK: (func $rmw (type $2) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.add $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw (param (ref $A)) (result i32) + ;; This RMW mutates the field. + (drop + (struct.atomic.rmw.add $A 0 + (local.get 0) + (i32.const 1) + ) + ) + ;; So this get is not optimizable. + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; RMW xchg operations are optimizable like normal reads and writes, though. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (type $2 (func (result (ref $A)))) + + ;; CHECK: (func $init (type $2) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + (func $init (result (ref $A)) + (struct.new_default $A) + ) + + ;; CHECK: (func $rmw-xchg (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $rmw-xchg (param (ref $A)) (result i32) + ;; This xchg does not change the value, so allows optimization. Other RMW + ;; ops that cannot change values are optimized in OptimizeInstructions + ;; instead. + (struct.atomic.rmw.xchg $A 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-xchg-fallthrough (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $rmw-xchg-fallthrough (param (ref $A)) (result i32) + ;; Same, and it works even with fallthrough. + (struct.atomic.rmw.xchg $A 0 + (local.get 0) + (block (result i32) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $rmw-xchg-acqrel (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-acqrel (param (ref $A)) (result i32) + ;; Making the accesses acqrel instead of seqcst means that the replacement + ;; fence could be more expensive than the original op, so we don't optimize. + (struct.atomic.rmw.xchg acqrel acqrel $A 0 + (local.get 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $get (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + ;; This get is optimizable. + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; A RMW xchg copy is still optimizable, too. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A) (ref $A)) (result i32))) + + ;; CHECK: (type $2 (func (result (ref $A)))) + + ;; CHECK: (type $3 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $2) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + (func $init (result (ref $A)) + (struct.new_default $A) + ) + + ;; CHECK: (func $rmw-xchg-copy (type $1) (param $0 (ref $A)) (param $1 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $rmw-xchg-copy (param (ref $A) (ref $A)) (result i32) + ;; This is a copy from one A to another, so allows optimization. + (struct.atomic.rmw.xchg $A 0 + (local.get 0) + (struct.atomic.get $A 0 + (local.get 1) + ) + ) + ) + + ;; CHECK: (func $rmw-xchg-copy-fallthrough (type $1) (param $0 (ref $A)) (param $1 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $rmw-xchg-copy-fallthrough (param (ref $A) (ref $A)) (result i32) + ;; Same, and it works even with fallthrough. + (struct.atomic.rmw.xchg $A 0 + (local.get 0) + (block (result i32) + (struct.atomic.get $A 0 + (local.get 1) + ) + ) + ) + ) + + ;; CHECK: (func $rmw-xchg-copy-acqrel (type $1) (param $0 (ref $A)) (param $1 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.atomic.get acqrel $A 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-copy-acqrel (param (ref $A) (ref $A)) (result i32) + ;; Making the accesses acqrel instead of seqcst means that the replacement + ;; fence could be more expensive than the original op, so we don't optimize. + (struct.atomic.rmw.xchg acqrel acqrel $A 0 + (local.get 0) + (struct.atomic.get acqrel $A 0 + (local.get 1) + ) + ) + ) + + ;; CHECK: (func $get (type $3) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + ;; This get is optimizable. + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; Not all rmw.xchg instructions are optimizable, though. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (func $init (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new_default $A) + ) + ) + + ;; CHECK: (func $rmw-xchg-mutate (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-mutate (param (ref $A)) (result i32) + ;; The xchg mutates $A, so it has more than one value and cannot be + ;; optimized out. + (struct.atomic.rmw.xchg $A 0 + (local.get 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $get (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; "Copies" across types are not optimizable. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + ;; CHECK: (type $B (shared (struct (field i32)))) + (type $B (shared (struct i32))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new_default $A) + ) + (drop + (struct.new $B + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $rmw-xchg-no-copy (type $3) (param $0 (ref $A)) (param $1 (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-no-copy (param (ref $A) (ref $B)) (result i32) + ;; The xchg mutates $A, so it has more than one value and cannot be + ;; optimized out (but the get of $B is optimized). + (struct.atomic.rmw.xchg $A 0 + (local.get 0) + (struct.atomic.get $B 0 + (local.get 1) + ) + ) + ) + + ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; In principle this version of the previous case is optimizable because the +;; values are the same, but we don't optimize it yet. TODO: optimize this. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + ;; CHECK: (type $B (shared (struct (field i32)))) + (type $B (shared (struct i32))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new_default $A) + ) + ;; Now this is a struct.new_default to make the values match. + (drop + (struct.new_default $B) + ) + ) + + ;; CHECK: (func $rmw-xchg-copy-value (type $3) (param $0 (ref $A)) (param $1 (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-xchg-copy-value (param (ref $A) (ref $B)) (result i32) + ;; The xchg uses different types, but cannot change the value. + (struct.atomic.rmw.xchg $A 0 + (local.get 0) + (struct.atomic.get $B 0 + (local.get 1) + ) + ) + ) + + ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; Similarly, cmpxchg can be optimized. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A) i32) (result i32))) + + ;; CHECK: (type $2 (func (result (ref $A)))) + + ;; CHECK: (type $3 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $2) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + (func $init (result (ref $A)) + (struct.new_default $A) + ) + + ;; CHECK: (func $rmw-cmpxchg (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg (param (ref $A) i32) (result i32) + ;; This cmpxchg does not change the value, so allows optimization. + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (i32.const 0) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-fallthrough (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-fallthrough (param (ref $A) i32) (result i32) + ;; Same, and it works even with fallthrough. + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (block (result i32) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-acqrel (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-acqrel (param (ref $A) i32) (result i32) + ;; Making the accesses acqrel instead of seqcst means that the replacement + ;; fence could be more expensive than the original op, so we don't optimize. + (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 + (local.get 0) + (local.get 1) + (i32.const 0) + ) + ) + + ;; CHECK: (func $get (type $3) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + ;; This get is optimizable. + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; cmpxchg copies can be optimized. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A) i32) (result i32))) + + ;; CHECK: (type $2 (func (result (ref $A)))) + + ;; CHECK: (type $3 (func (param (ref $A) i32 (ref $A)) (result i32))) + + ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $2) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + (func $init (result (ref $A)) + (struct.new_default $A) + ) + + ;; CHECK: (func $rmw-cmpxchg (type $3) (param $0 (ref $A)) (param $1 i32) (param $2 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg (param (ref $A) i32 (ref $A)) (result i32) + ;; This cmpxchg copies the field, so does not change the value. + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (struct.atomic.get $A 0 + (local.get 2) + ) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-fallthrough (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-fallthrough (param (ref $A) i32) (result i32) + ;; Same, and it works even with fallthrough. + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (block (result i32) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-acqrel (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-acqrel (param (ref $A) i32) (result i32) + ;; Making the accesses acqrel instead of seqcst means that the replacement + ;; fence could be more expensive than the original op, so we don't optimize. + (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 + (local.get 0) + (local.get 1) + (i32.const 0) + ) + ) + + ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + ;; This get is optimizable. + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; Mutating cmpxchg cannot be optimized. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (result (ref $A)))) + + ;; CHECK: (type $2 (func (param (ref $A) i32) (result i32))) + + ;; CHECK: (type $3 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $1) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + (func $init (result (ref $A)) + (struct.new_default $A) + ) + + ;; CHECK: (func $rmw-cmpxchg-mutate (type $2) (param $0 (ref $A)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-mutate (param (ref $A) i32) (result i32) + ;; This cmpxchg changes the field if it writes, so cannot be optimized. + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (i32.const 1) + ) + ) + + ;; CHECK: (func $get (type $3) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + ;; This get is not optimizable. + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; Cmpxchg "copies" across types are not optimizable. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + ;; CHECK: (type $B (shared (struct (field i32)))) + (type $B (shared (struct i32))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref $A) i32 (ref $B)) (result i32))) + + ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new_default $A) + ) + (drop + (struct.new $B + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-no-copy (type $3) (param $0 (ref $A)) (param $1 i32) (param $2 (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-no-copy (param (ref $A) i32 (ref $B)) (result i32) + ;; The cmpxchg mutates $A, so it has more than one value and cannot be + ;; optimized out (but the get of $B is optimized). + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (struct.atomic.get $B 0 + (local.get 2) + ) + ) + ) + + ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; In principle this version of the previous case is optimizable because the +;; values are the same, but we don't optimize it yet. TODO: optimize this. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + ;; CHECK: (type $B (shared (struct (field i32)))) + (type $B (shared (struct i32))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref $A) i32 (ref $B)) (result i32))) + + ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new_default $A) + ) + ;; Now this is a struct.new_default to make the values match. + (drop + (struct.new_default $B) + ) + ) + + ;; CHECK: (func $rmw-cmpxchg-copy-value (type $3) (param $0 (ref $A)) (param $1 i32) (param $2 (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (atomic.fence) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-copy-value (param (ref $A) i32 (ref $B)) (result i32) + ;; The cmpxchg uses different types, but cannot change the value. + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (local.get 1) + (struct.atomic.get $B 0 + (local.get 2) + ) + ) + ) + + ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + (struct.get $A 0 + (local.get 0) + ) + ) +) + +;; In principle this version can also be optimized because the cmpxchg will +;; never perform a write. TODO: optimize this. +(module + ;; CHECK: (type $A (shared (struct (field (mut i32))))) + (type $A (shared (struct (mut i32)))) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (type $2 (func (result (ref $A)))) + + ;; CHECK: (func $init (type $2) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + (func $init (result (ref $A)) + (struct.new_default $A) + ) + + ;; CHECK: (func $rmw-cmpxchg-mutate (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rmw-cmpxchg-mutate (param (ref $A)) (result i32) + ;; This cmpxchg will never change the field because the compare will never + ;; succeed. + (struct.atomic.rmw.cmpxchg $A 0 + (local.get 0) + (i32.const 1) + (i32.const 1) + ) + ) + + ;; CHECK: (func $get (type $1) (param $0 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param (ref $A)) (result i32) + ;; This get is optimizable in principle. + (struct.get $A 0 + (local.get 0) + ) + ) +) From b0a453c3eb259bc61105261a0406a790148a7479 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 4 Feb 2025 13:37:28 -0800 Subject: [PATCH 281/622] Optimize acquire-release operations in CFP (#7264) We previously avoided optimizing acquire-release operations in ConstantFieldPropagation to avoid having to replace them with acquire-release fences, which are potentially more expensive than normal acquire-release operations. However, in #7168 we reasoned that optimized acquire gets of constant fields do not need to be replaced with fences because they never necessarily synchronize with a set on another thread; a get can be considered to read the same constant value from before the set. Apply that reasoning to CFP and start optimizing acquire-release operations without replacing them with fences. --- src/passes/ConstantFieldPropagation.cpp | 19 ++------ test/lit/passes/cfp-rmw.wast | 65 ++++++++++++++++++------- test/lit/passes/cfp.wast | 12 +++-- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 0ad1d659cbe..1d5318b0099 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -143,9 +143,11 @@ struct FunctionOptimizer : public WalkerPass> { // If an optimized access is sequentially consistent, then it synchronizes // with other threads at least by participating in the global order of // sequentially consistent operations. Preserve that effect by replacing the - // access with a fence. + // access with a fence. On the other hand, if we're optimizing an + // acquire-release operation, then we know the accessed field is constant and + // will not be modified, so the operation does not necessarily synchronize + // with other threads and no fence is required. Block* maybeAddFence(Block* block, MemoryOrder order) { - assert(order != MemoryOrder::AcqRel); if (order == MemoryOrder::SeqCst) { block->list.push_back(Builder(*getModule()).makeAtomicFence()); } @@ -214,14 +216,6 @@ struct FunctionOptimizer : public WalkerPass> { return; } - if (curr->order == MemoryOrder::AcqRel) { - // Removing an acquire get and preserving its synchronization properties - // would require inserting an acquire fence, but the fence would have - // stronger synchronization properties so might be more expensive. - // Instead, just skip the optimization. - return; - } - // If the value is not a constant, then it is unknown and we must give up // on simply applying a constant. However, we can try to use a ref.test, if // that is allowed. @@ -259,11 +253,6 @@ struct FunctionOptimizer : public WalkerPass> { PossibleConstantValues info = getInfo(heapType, curr->index); assert(info.hasNoted() && "unexpected lack of info for RMW"); - if (curr->order == MemoryOrder::AcqRel) { - // See comment on visitStructGet for why we don't optimize here. - return std::nullopt; - } - if (!info.isConstant()) { // Optimizing using ref.test is not an option here because that only works // on immutable fields, but RMW operations always access mutable fields. diff --git a/test/lit/passes/cfp-rmw.wast b/test/lit/passes/cfp-rmw.wast index 85f36c0f1d8..cd1ff374b22 100644 --- a/test/lit/passes/cfp-rmw.wast +++ b/test/lit/passes/cfp-rmw.wast @@ -107,14 +107,19 @@ ) ;; CHECK: (func $rmw-xchg-acqrel (type $1) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $A 0 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-xchg-acqrel (param (ref $A)) (result i32) - ;; Making the accesses acqrel instead of seqcst means that the replacement - ;; fence could be more expensive than the original op, so we don't optimize. + ;; Making the accesses acqrel instead of seqcst means that we don't need a + ;; fence when we optimize. (struct.atomic.rmw.xchg acqrel acqrel $A 0 (local.get 0) (i32.const 0) @@ -220,16 +225,26 @@ ) ;; CHECK: (func $rmw-xchg-copy-acqrel (type $1) (param $0 (ref $A)) (param $1 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (struct.atomic.get acqrel $A 0 - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-xchg-copy-acqrel (param (ref $A) (ref $A)) (result i32) - ;; Making the accesses acqrel instead of seqcst means that the replacement - ;; fence could be more expensive than the original op, so we don't optimize. + ;; Making the accesses acqrel instead of seqcst means that we don't need a + ;; fence when we optimize. (struct.atomic.rmw.xchg acqrel acqrel $A 0 (local.get 0) (struct.atomic.get acqrel $A 0 @@ -511,15 +526,22 @@ ) ;; CHECK: (func $rmw-cmpxchg-acqrel (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-cmpxchg-acqrel (param (ref $A) i32) (result i32) - ;; Making the accesses acqrel instead of seqcst means that the replacement - ;; fence could be more expensive than the original op, so we don't optimize. + ;; Acqrel accesses to constant fields do not synchronize with anything, so + ;; we can optimize without fences. (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 (local.get 0) (local.get 1) @@ -626,15 +648,22 @@ ) ;; CHECK: (func $rmw-cmpxchg-acqrel (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-cmpxchg-acqrel (param (ref $A) i32) (result i32) - ;; Making the accesses acqrel instead of seqcst means that the replacement - ;; fence could be more expensive than the original op, so we don't optimize. + ;; Acqrel accesses to constant fields do not synchronize with anything, so + ;; we can optimize without fences. (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 (local.get 0) (local.get 1) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index e674fdc4b19..97fd500c3aa 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2870,8 +2870,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.atomic.get acqrel $shared 0 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -2893,8 +2898,7 @@ ) ) (drop - ;; This is not optimized because we wouldn't want to replace it with a - ;; stronger acquire fence. + ;; This can be optimzied and does not require a fence. (struct.atomic.get acqrel $shared 0 (local.get 0) ) From 8878eb5890c9b628b4a9408d946b53a28c1566ba Mon Sep 17 00:00:00 2001 From: mcbarton Date: Wed, 5 Feb 2025 18:23:14 +0000 Subject: [PATCH 282/622] Update Google Test from 1.10.0 to 1.15.2 (#7275) The Google Test which binaryen uses is a commit sometime after version 1.10.0 looking at the readme. This PR updates the submodule to the latest release version, and pins it to the 1.15.2 release. --- third_party/googletest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/googletest b/third_party/googletest index e2239ee6043..b514bdc898e 160000 --- a/third_party/googletest +++ b/third_party/googletest @@ -1 +1 @@ -Subproject commit e2239ee6043f73722e7aa812a459f54a28552929 +Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 From a019ec8688e805926b8cad735ef0d91f05cbd370 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Feb 2025 15:35:34 -0800 Subject: [PATCH 283/622] Fuzzer: Generate ThrowRef, and avoid unneeded traps with it (#7279) We generate a `throw_ref` by generating an exnref for it to throw. To avoid that causing lots of traps due to not having an exnref, add some logic to generate an exnref (by making a try that throws and catches). --- src/tools/fuzzing.h | 1 + src/tools/fuzzing/fuzzing.cpp | 26 ++++++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 63f399f949f..82a9f86b0a4 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -483,6 +483,7 @@ class TranslateToFuzzReader { Expression* makeArrayBulkMemoryOp(Type type); Expression* makeI31Get(Type type); Expression* makeThrow(Type type); + Expression* makeThrowRef(Type type); Expression* makeMemoryInit(); Expression* makeDataDrop(); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 11fa12d5ba8..bc981a49f53 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1992,7 +1992,7 @@ Expression* TranslateToFuzzReader::_makeunreachable() { &Self::makeSwitch, &Self::makeDrop, &Self::makeReturn) - .add(FeatureSet::ExceptionHandling, &Self::makeThrow) + .add(FeatureSet::ExceptionHandling, &Self::makeThrow, &Self::makeThrowRef) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef); return (this->*pick(options))(Type::unreachable); } @@ -3250,12 +3250,17 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return builder.makeArrayNewFixed(ht, {}); } case HeapType::exn: { - auto null = builder.makeRefNull(HeapTypes::exn.getBasic(share)); - if (!type.isNullable()) { - assert(funcContext); - return builder.makeRefAs(RefAsNonNull, null); + // If nullable, we can emit a null. If not, generate an exnref using a + // throw in a try_table. + if (type.isNullable() && oneIn(2)) { + return builder.makeRefNull(HeapTypes::exn.getBasic(share)); } - return null; + + // Make a catch_all_ref to a block. + auto* throww = makeThrow(Type::unreachable); + auto label = makeLabel(); + auto* tryy = builder.makeTryTable(throww, {Name()}, {label}, {true}); + return builder.makeBlock(label, tryy); } case HeapType::string: { // In non-function contexts all we can do is string.const. @@ -4809,6 +4814,15 @@ Expression* TranslateToFuzzReader::makeThrow(Type type) { return builder.makeThrow(tag, operands); } +Expression* TranslateToFuzzReader::makeThrowRef(Type type) { + assert(type == Type::unreachable); + // Use a nullable type here to avoid the risk of trapping (when we find no way + // to make a non-nullable ref, we end up fixing validation with + // ref.as_non_null of a null, which validates but traps). + auto* ref = make(Type(HeapType::exn, Nullable)); + return builder.makeThrowRef(ref); +} + Expression* TranslateToFuzzReader::makeMemoryInit() { if (!allowMemory) { return makeTrivial(Type::none); From 088b10369aa076d21600083a98c4085a9ba3d8f6 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Feb 2025 11:21:46 -0700 Subject: [PATCH 284/622] Fix optimization of seqcst RMWs in OptimizeInstructions (#7280) We previously reasoned that RMWs that do not change in-memory values could be optimized to just atomic gets because they did not necessarily synchronize with subsequent atomic reads of the same location. This is not correct for seqcst RMWs, though, since subsequent seqcst reads in the total order of seqcst operations are _required_ to read from and synchronize with these RMWs, even though they do not change the in-memory values. Fix the optimization to only work on acquire-release RMWs and RMWs to unshared memory. --- src/passes/OptimizeInstructions.cpp | 40 +++-- .../optimize-instructions-struct-rmw.wast | 153 +++++++++--------- 2 files changed, 105 insertions(+), 88 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index c58630a8e7a..6737704f3fb 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1872,14 +1872,27 @@ struct OptimizeInstructions return; } + // We generally can't optimize seqcst RMWs on shared memory because they can + // act as both the source and sink of synchronization edges, even if they + // don't modify the in-memory value. + if (curr->ref->type.getHeapType().isShared() && + curr->order == MemoryOrder::SeqCst) { + return; + } + Builder builder(*getModule()); - // Even when the RMW access is to shared memory, we can optimize out the - // modify and write parts if we know that the modified value is the same as - // the original value. This is valid because reads from writes that don't - // change the in-memory value can be considered to be reads from the - // previous write to the same location instead. That means there is no read - // that necessarily synchronizes with the write. + // This RMW is either to non-shared memory or has acquire-release ordering. + // In the former case, it trivially does not synchronize with other threads + // and we can optimize to our heart's content. In the latter case, if we + // know the RMW does not change the value in memory, then we can consider + // all subsequent reads as reading from the previous write rather than from + // this RMW op, which means this RMW does not synchronize with later reads + // and we can optimize out the write part. This optimization wouldn't be + // valid for sequentially consistent RMW ops because the next reads from + // this location in the total order of seqcst ops would have to be + // considered to be reading from this RMW and therefore would synchronize + // with it. auto* value = Properties::getFallthrough(curr->value, getPassOptions(), *getModule()); if (Properties::isSingleConstantExpression(value)) { @@ -1909,6 +1922,7 @@ struct OptimizeInstructions } } + // No further optimizations possible on RMWs to shared memory. if (curr->ref->type.getHeapType().isShared()) { return; } @@ -1990,9 +2004,17 @@ struct OptimizeInstructions Builder builder(*getModule()); - // Just like other RMW operations, cmpxchg can be optimized to just a read - // if it is known not to change the in-memory value. This is the case when - // `expected` and `replacement` are known to be the same. + // As with other RMW operations, we cannot optimize if the RMW is + // sequentially consistent and to shared memory. + if (curr->ref->type.getHeapType().isShared() && + curr->order == MemoryOrder::SeqCst) { + return; + } + + // Just like other RMW operations, unshared or release-acquire cmpxchg can + // be optimized to just a read if it is known not to change the in-memory + // value. This is the case when `expected` and `replacement` are known to be + // the same. if (areConsecutiveInputsEqual(curr->expected, curr->replacement)) { auto* ref = getResultOfFirst( curr->ref, diff --git a/test/lit/passes/optimize-instructions-struct-rmw.wast b/test/lit/passes/optimize-instructions-struct-rmw.wast index 1a9bcec6c41..1a94dc89d5d 100644 --- a/test/lit/passes/optimize-instructions-struct-rmw.wast +++ b/test/lit/passes/optimize-instructions-struct-rmw.wast @@ -70,7 +70,7 @@ ) ;; CHECK: (func $rmw-add-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) @@ -81,28 +81,28 @@ ;; CHECK-NEXT: ) (func $rmw-add-i32-ident (param (ref null $i32)) (result i32) ;; This can be optimized to just an atomic load. - (struct.atomic.rmw.add $i32 0 + (struct.atomic.rmw.add acqrel acqrel $i32 0 (local.get 0) (i32.const 0) ) ) ;; CHECK: (func $rmw-add-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.add $i32 0 + ;; CHECK-NEXT: (struct.atomic.rmw.add acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-add-i32-noident (param (ref null $i32)) (result i32) ;; But this cannot be optimized at all. - (struct.atomic.rmw.add $i32 0 + (struct.atomic.rmw.add acqrel acqrel $i32 0 (local.get 0) (i32.const 1) ) ) ;; CHECK: (func $rmw-sub-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) @@ -112,27 +112,27 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-sub-i32-ident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.sub $i32 0 + (struct.atomic.rmw.sub acqrel acqrel $i32 0 (local.get 0) (i32.const 0) ) ) ;; CHECK: (func $rmw-sub-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.sub $i32 0 + ;; CHECK-NEXT: (struct.atomic.rmw.sub acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-sub-i32-noident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.sub $i32 0 + (struct.atomic.rmw.sub acqrel acqrel $i32 0 (local.get 0) (i32.const 1) ) ) ;; CHECK: (func $rmw-and-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const -1) @@ -142,27 +142,27 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-and-i32-ident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.and $i32 0 + (struct.atomic.rmw.and acqrel acqrel $i32 0 (local.get 0) (i32.const -1) ) ) ;; CHECK: (func $rmw-and-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.and $i32 0 + ;; CHECK-NEXT: (struct.atomic.rmw.and acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-and-i32-noident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.and $i32 0 + (struct.atomic.rmw.and acqrel acqrel $i32 0 (local.get 0) (i32.const 0) ) ) ;; CHECK: (func $rmw-or-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) @@ -172,27 +172,27 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-or-i32-ident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.or $i32 0 + (struct.atomic.rmw.or acqrel acqrel $i32 0 (local.get 0) (i32.const 0) ) ) ;; CHECK: (func $rmw-or-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.or $i32 0 + ;; CHECK-NEXT: (struct.atomic.rmw.or acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const -1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-or-i32-noident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.or $i32 0 + (struct.atomic.rmw.or acqrel acqrel $i32 0 (local.get 0) (i32.const -1) ) ) ;; CHECK: (func $rmw-xor-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) @@ -202,27 +202,27 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-xor-i32-ident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.xor $i32 0 + (struct.atomic.rmw.xor acqrel acqrel $i32 0 (local.get 0) (i32.const 0) ) ) ;; CHECK: (func $rmw-xor-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.xor $i32 0 + ;; CHECK-NEXT: (struct.atomic.rmw.xor acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const -1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-xor-i32-noident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.xor $i32 0 + (struct.atomic.rmw.xor acqrel acqrel $i32 0 (local.get 0) (i32.const -1) ) ) ;; CHECK: (func $rmw-xchg-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $i32 0 + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (struct.get $i32 0 ;; CHECK-NEXT: (local.get $0) @@ -231,7 +231,7 @@ ;; CHECK-NEXT: ) (func $rmw-xchg-i32-ident (param (ref null $i32)) (result i32) ;; TODO: Optimize this case. - (struct.atomic.rmw.xchg $i32 0 + (struct.atomic.rmw.xchg acqrel acqrel $i32 0 (local.get 0) (struct.get $i32 0 (local.get 0) @@ -240,20 +240,20 @@ ) ;; CHECK: (func $rmw-xchg-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $i32 0 + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-xchg-i32-noident (param (ref null $i32)) (result i32) - (struct.atomic.rmw.xchg $i32 0 + (struct.atomic.rmw.xchg acqrel acqrel $i32 0 (local.get 0) (i32.const 0) ) ) ;; CHECK: (func $cmpxchg-i32-ident (type $6) (param $0 (ref null $i32)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (struct.atomic.get $i32 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -268,7 +268,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cmpxchg-i32-ident (param (ref null $i32) i32) (result i32) - (struct.atomic.rmw.cmpxchg $i32 0 + (struct.atomic.rmw.cmpxchg acqrel acqrel $i32 0 (local.get 0) (local.get 1) (local.get 1) @@ -276,14 +276,14 @@ ) ;; CHECK: (func $cmpxchg-i32-noident (type $6) (param $0 (ref null $i32)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $i32 0 + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cmpxchg-i32-noident (param (ref null $i32) i32) (result i32) - (struct.atomic.rmw.cmpxchg $i32 0 + (struct.atomic.rmw.cmpxchg acqrel acqrel $i32 0 (local.get 0) (i32.const 0) (i32.const 1) @@ -291,7 +291,7 @@ ) ;; CHECK: (func $rmw-add-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.const 0) @@ -302,28 +302,28 @@ ;; CHECK-NEXT: ) (func $rmw-add-i64-ident (param (ref null $i64)) (result i64) ;; This can be optimized to just an atomic load. - (struct.atomic.rmw.add $i64 0 + (struct.atomic.rmw.add acqrel acqrel $i64 0 (local.get 0) (i64.const 0) ) ) ;; CHECK: (func $rmw-add-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.rmw.add $i64 0 + ;; CHECK-NEXT: (struct.atomic.rmw.add acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-add-i64-noident (param (ref null $i64)) (result i64) ;; But this cannot be optimized at all. - (struct.atomic.rmw.add $i64 0 + (struct.atomic.rmw.add acqrel acqrel $i64 0 (local.get 0) (i64.const 1) ) ) ;; CHECK: (func $rmw-sub-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.const 0) @@ -333,27 +333,27 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-sub-i64-ident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.sub $i64 0 + (struct.atomic.rmw.sub acqrel acqrel $i64 0 (local.get 0) (i64.const 0) ) ) ;; CHECK: (func $rmw-sub-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.rmw.sub $i64 0 + ;; CHECK-NEXT: (struct.atomic.rmw.sub acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-sub-i64-noident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.sub $i64 0 + (struct.atomic.rmw.sub acqrel acqrel $i64 0 (local.get 0) (i64.const 1) ) ) ;; CHECK: (func $rmw-and-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.const -1) @@ -363,27 +363,27 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-and-i64-ident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.and $i64 0 + (struct.atomic.rmw.and acqrel acqrel $i64 0 (local.get 0) (i64.const -1) ) ) ;; CHECK: (func $rmw-and-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.rmw.and $i64 0 + ;; CHECK-NEXT: (struct.atomic.rmw.and acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-and-i64-noident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.and $i64 0 + (struct.atomic.rmw.and acqrel acqrel $i64 0 (local.get 0) (i64.const 0) ) ) ;; CHECK: (func $rmw-or-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.const 0) @@ -393,27 +393,27 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-or-i64-ident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.or $i64 0 + (struct.atomic.rmw.or acqrel acqrel $i64 0 (local.get 0) (i64.const 0) ) ) ;; CHECK: (func $rmw-or-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.rmw.or $i64 0 + ;; CHECK-NEXT: (struct.atomic.rmw.or acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const -1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-or-i64-noident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.or $i64 0 + (struct.atomic.rmw.or acqrel acqrel $i64 0 (local.get 0) (i64.const -1) ) ) ;; CHECK: (func $rmw-xor-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.const 0) @@ -423,27 +423,27 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-xor-i64-ident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.xor $i64 0 + (struct.atomic.rmw.xor acqrel acqrel $i64 0 (local.get 0) (i64.const 0) ) ) ;; CHECK: (func $rmw-xor-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.rmw.xor $i64 0 + ;; CHECK-NEXT: (struct.atomic.rmw.xor acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const -1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-xor-i64-noident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.xor $i64 0 + (struct.atomic.rmw.xor acqrel acqrel $i64 0 (local.get 0) (i64.const -1) ) ) ;; CHECK: (func $rmw-xchg-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $i64 0 + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (struct.get $i64 0 ;; CHECK-NEXT: (local.get $0) @@ -452,7 +452,7 @@ ;; CHECK-NEXT: ) (func $rmw-xchg-i64-ident (param (ref null $i64)) (result i64) ;; TODO: Optimize this case. - (struct.atomic.rmw.xchg $i64 0 + (struct.atomic.rmw.xchg acqrel acqrel $i64 0 (local.get 0) (struct.get $i64 0 (local.get 0) @@ -461,20 +461,20 @@ ) ;; CHECK: (func $rmw-xchg-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $i64 0 + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-xchg-i64-noident (param (ref null $i64)) (result i64) - (struct.atomic.rmw.xchg $i64 0 + (struct.atomic.rmw.xchg acqrel acqrel $i64 0 (local.get 0) (i64.const 0) ) ) ;; CHECK: (func $cmpxchg-i64-ident (type $11) (param $0 (ref null $i64)) (param $1 i64) (result i64) - ;; CHECK-NEXT: (struct.atomic.get $i64 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -489,7 +489,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cmpxchg-i64-ident (param (ref null $i64) i64) (result i64) - (struct.atomic.rmw.cmpxchg $i64 0 + (struct.atomic.rmw.cmpxchg acqrel acqrel $i64 0 (local.get 0) (local.get 1) (local.get 1) @@ -497,14 +497,14 @@ ) ;; CHECK: (func $cmpxchg-i64-noident (type $11) (param $0 (ref null $i64)) (param $1 i64) (result i64) - ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $i64 0 + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: (i64.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cmpxchg-i64-noident (param (ref null $i64) i64) (result i64) - (struct.atomic.rmw.cmpxchg $i64 0 + (struct.atomic.rmw.cmpxchg acqrel acqrel $i64 0 (local.get 0) (i64.const 0) (i64.const 1) @@ -512,7 +512,7 @@ ) ;; CHECK: (func $rmw-xchg-ref-ident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (struct.get $struct 0 ;; CHECK-NEXT: (local.get $0) @@ -521,7 +521,7 @@ ;; CHECK-NEXT: ) (func $rmw-xchg-ref-ident (param (ref null $struct)) (result (ref null $struct)) ;; TODO: Optimize this case. - (struct.atomic.rmw.xchg $struct 0 + (struct.atomic.rmw.xchg acqrel acqrel $struct 0 (local.get 0) (struct.get $struct 0 (local.get 0) @@ -530,20 +530,20 @@ ) ;; CHECK: (func $rmw-xchg-ref-noident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-xchg-ref-noident (param (ref null $struct)) (result (ref null $struct)) - (struct.atomic.rmw.xchg $struct 0 + (struct.atomic.rmw.xchg acqrel acqrel $struct 0 (local.get 0) (local.get 0) ) ) ;; CHECK: (func $cmpxchg-ref-ident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) - ;; CHECK-NEXT: (struct.atomic.get $struct 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $struct 0 ;; CHECK-NEXT: (block (result (ref null $struct)) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -558,7 +558,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cmpxchg-ref-ident (param (ref null $struct)) (result (ref null $struct)) - (struct.atomic.rmw.cmpxchg $struct 0 + (struct.atomic.rmw.cmpxchg acqrel acqrel $struct 0 (local.get 0) (local.get 0) (local.get 0) @@ -566,7 +566,7 @@ ) ;; CHECK: (func $cmpxchg-ref-ident-null (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) - ;; CHECK-NEXT: (struct.atomic.get $struct 0 + ;; CHECK-NEXT: (struct.atomic.get acqrel $struct 0 ;; CHECK-NEXT: (block (result (ref null $struct)) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -581,7 +581,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cmpxchg-ref-ident-null (param (ref null $struct)) (result (ref null $struct)) - (struct.atomic.rmw.cmpxchg $struct 0 + (struct.atomic.rmw.cmpxchg acqrel acqrel $struct 0 (local.get 0) (ref.null (shared none)) (ref.null (shared none)) @@ -589,33 +589,29 @@ ) ;; CHECK: (func $cmpxchg-ref-noident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) - ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (ref.null (shared none)) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cmpxchg-ref-noident (param (ref null $struct)) (result (ref null $struct)) - (struct.atomic.rmw.cmpxchg $struct 0 + (struct.atomic.rmw.cmpxchg acqrel acqrel $struct 0 (local.get 0) (ref.null (shared none)) (local.get 0) ) ) - ;; CHECK: (func $rmw-add-i32-acqrel-ident (type $9) (param $0 (ref null $i32)) (result i32) - ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 - ;; CHECK-NEXT: (block (result (ref null $i32)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK: (func $rmw-add-i32-seqcst-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.add $i32 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $rmw-add-i32-acqrel-ident (param (ref null $i32)) (result i32) - ;; Check that acqrel rmws are optimized to acquire gets. - (struct.atomic.rmw.add acqrel acqrel $i32 0 + (func $rmw-add-i32-seqcst-ident (param (ref null $i32)) (result i32) + ;; Check that seqcst rmws are not optimized to acquire gets. + (struct.atomic.rmw.add $i32 0 (local.get 0) (i32.const 0) ) @@ -632,7 +628,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rmw-add-i32-unshared-ident (param (ref null $unshared-i32)) (result i32) - ;; Check just one unshared case to make sure we do the same identity + ;; Check just one unshared seqcst case to make sure we do the same identity ;; optimizations tested above. (struct.atomic.rmw.add $unshared-i32 0 (local.get 0) @@ -640,7 +636,6 @@ ) ) - ;; CHECK: (func $cmpxchg-i32-unshared-ident (type $13) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.get $unshared-i32 0 ;; CHECK-NEXT: (block (result (ref null $unshared-i32)) From 1de13c3d3a2c31a6131033f8d4747aca7b7c5a34 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Feb 2025 11:22:12 -0700 Subject: [PATCH 285/622] Do not emit fences when peephole optimizing struct RMWs (#7281) We previously reasoned that we had to emit fences when optimizing sequentially consistent RMWs to unshared memory to preserve the RMWs' effect on the total order of sequentially consistent operations. However, that total order alone is insufficient to force any read event to observe some other write event; that requires a synchronization edge to be formed via an atomic write and read pair. RMWs to unshared memory can never be part of such a synchronization edge, so removing them cannot introduce new behaviors. Improve our optimization to stop emitting the unnecessary fences. --- src/passes/OptimizeInstructions.cpp | 12 ------------ .../optimize-instructions-struct-rmw.wast | 18 +----------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 6737704f3fb..c8c192685a2 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1981,12 +1981,6 @@ struct OptimizeInstructions newVal, MemoryOrder::Unordered)); - // We must maintain this operation's effect on the global order of seqcst - // operations. - if (curr->order == MemoryOrder::SeqCst) { - block->list.push_back(builder.makeAtomicFence()); - } - block->list.push_back(builder.makeLocalGet(result, curr->type)); block->type = curr->type; replaceCurrent(block); @@ -2061,12 +2055,6 @@ struct OptimizeInstructions builder.makeLocalGet(replacement, curr->type), MemoryOrder::Unordered))); - // We must maintain this operation's effect on the global order of seqcst - // operations. - if (curr->order == MemoryOrder::SeqCst) { - block->list.push_back(builder.makeAtomicFence()); - } - block->list.push_back(builder.makeLocalGet(result, curr->type)); block->type = curr->type; replaceCurrent(block); diff --git a/test/lit/passes/optimize-instructions-struct-rmw.wast b/test/lit/passes/optimize-instructions-struct-rmw.wast index 1a94dc89d5d..0eb5da2fcc5 100644 --- a/test/lit/passes/optimize-instructions-struct-rmw.wast +++ b/test/lit/passes/optimize-instructions-struct-rmw.wast @@ -682,7 +682,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-add-i32-lower (param (ref null $unshared-i32)) (result i32) @@ -714,7 +713,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-sub-i32-lower (param (ref null $unshared-i32)) (result i32) @@ -746,7 +744,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-and-i32-lower (param (ref null $unshared-i32)) (result i32) @@ -778,7 +775,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-or-i32-lower (param (ref null $unshared-i32)) (result i32) @@ -810,7 +806,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-xor-i32-lower (param (ref null $unshared-i32)) (result i32) @@ -839,7 +834,6 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-xchg-i32-lower (param (ref null $unshared-i32)) (result i32) @@ -879,7 +873,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) (func $cmpxchg-i32-lower (param (ref null $unshared-i32)) (result i32) @@ -912,7 +905,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-add-i64-lower (param (ref null $unshared-i64)) (result i64) @@ -944,7 +936,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-sub-i64-lower (param (ref null $unshared-i64)) (result i64) @@ -976,7 +967,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-and-i64-lower (param (ref null $unshared-i64)) (result i64) @@ -1008,7 +998,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-or-i64-lower (param (ref null $unshared-i64)) (result i64) @@ -1040,7 +1029,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-xor-i64-lower (param (ref null $unshared-i64)) (result i64) @@ -1069,7 +1057,6 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-xchg-i64-lower (param (ref null $unshared-i64)) (result i64) @@ -1109,7 +1096,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) (func $cmpxchg-i64-lower (param (ref null $unshared-i64)) (result i64) @@ -1139,7 +1125,6 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-xchg-ref-lower (param (ref null $unshared-struct)) (result (ref null $unshared-struct)) @@ -1179,7 +1164,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) (func $cmpxchg-ref-lower (param (ref null $unshared-struct)) (result (ref null $unshared-struct)) @@ -1215,7 +1199,7 @@ ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-add-i32-acqrel (param (ref null $unshared-i32)) (result i32) - ;; Check that the lowering of an acqrel RMW does not have a fence. + ;; Check that the lowering of an acqrel RMW works the same way. (struct.atomic.rmw.add acqrel acqrel $unshared-i32 0 (local.get 0) (i32.const 1) From 3fcfec49c3c75b10a50fa76afdb6023cb6d6c25a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Feb 2025 13:30:31 -0800 Subject: [PATCH 286/622] Fuzzer: Use a WebAssembly.Tag from JS (#7277) We now create a Tag in JS and import it in the wasm. We can then use that tag from JS when we throw (only some of the time, controlled by a new parameter to the throwing import). --- scripts/fuzz_shell.js | 16 ++++- src/tools/execution-results.h | 20 +++++- src/tools/fuzzing/fuzzing.cpp | 34 ++++++--- test/lit/d8/fuzz_shell_exceptions.wast | 35 +++++++++ test/lit/exec/fuzzing-api.wast | 22 +++++- ...e-to-fuzz_all-features_metrics_noprint.txt | 72 ++++++++----------- 6 files changed, 143 insertions(+), 56 deletions(-) create mode 100644 test/lit/d8/fuzz_shell_exceptions.wast diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 252ee5629dc..e2e9b34323f 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -268,8 +268,12 @@ var imports = { 'log-v128': logValue, // Throw an exception from JS. - 'throw': () => { - throw 'some JS error'; + 'throw': (which) => { + if (!which) { + throw 'some JS error'; + } else { + throw new WebAssembly.Exception(jsTag, [which]); + } }, // Table operations. @@ -326,8 +330,14 @@ var imports = { }, }; -// If Tags are available, add the import j2wasm expects. +// If Tags are available, add some. if (typeof WebAssembly.Tag !== 'undefined') { + // A tag for general use in the fuzzer. + var jsTag = imports['fuzzing-support']['tag'] = new WebAssembly.Tag({ + 'parameters': ['i32'] + }); + + // This allows j2wasm content to run in the fuzzer. imports['imports'] = { 'j2wasm.ExceptionUtils.tag': new WebAssembly.Tag({ 'parameters': ['externref'] diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index ea822d5477a..ad4f5316cef 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -42,6 +42,9 @@ struct LoggingExternalInterface : public ShellExternalInterface { Name exportedTable; Module& wasm; + // The name of the imported fuzzing tag. + Name fuzzTag; + // The ModuleRunner and this ExternalInterface end up needing links both ways, // so we cannot init this in the constructor. ModuleRunner* instance = nullptr; @@ -55,6 +58,13 @@ struct LoggingExternalInterface : public ShellExternalInterface { break; } } + + for (auto& tag : wasm.tags) { + if (tag->module == "fuzzing-support" && tag->base == "tag") { + fuzzTag = tag->name; + break; + } + } } Literals callImport(Function* import, const Literals& arguments) override { @@ -82,7 +92,15 @@ struct LoggingExternalInterface : public ShellExternalInterface { std::cout << "]\n"; return {}; } else if (import->base == "throw") { - throwEmptyException(); + // Throw something, depending on the value of the argument. 0 means we + // should throw a JS exception, and any other value means we should + // throw a wasm exception (with that value as the payload). + if (arguments[0].geti32() == 0) { + throwEmptyException(); + } else { + auto payload = std::make_shared(fuzzTag, arguments); + throwException(WasmException{Literal(payload)}); + } } else if (import->base == "table-get") { // Check for errors here, duplicating tableLoad(), because that will // trap, and we just want to throw an exception (the same as JS would). diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index bc981a49f53..00181a90b31 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -634,8 +634,8 @@ void TranslateToFuzzReader::setupGlobals() { } void TranslateToFuzzReader::setupTags() { - // As in modifyInitialFunctions(), we can't allow tag imports as it would trap - // when the fuzzing infrastructure doesn't know what to provide. + // As in modifyInitialFunctions(), we can't allow arbitrary tag imports, which + // would trap when the fuzzing infrastructure doesn't know what to provide. for (auto& tag : wasm.tags) { if (tag->imported()) { tag->module = tag->base = Name(); @@ -647,6 +647,15 @@ void TranslateToFuzzReader::setupTags() { for (size_t i = 0; i < num; i++) { addTag(); } + + // Add the fuzzing support tag manually sometimes. + if (oneIn(2)) { + auto tag = builder.makeTag(Names::getValidTagName(wasm, "tag"), + Signature(Type::i32, Type::none)); + tag->module = "fuzzing-support"; + tag->base = "tag"; + wasm.addTag(std::move(tag)); + } } void TranslateToFuzzReader::addTag() { @@ -888,16 +897,14 @@ void TranslateToFuzzReader::addImportCallingSupport() { } void TranslateToFuzzReader::addImportThrowingSupport() { - // Throw some kind of exception from JS. - // TODO: Send an index, which is which exported wasm Tag we should throw, or - // something not exported if out of bounds. First we must also export - // tags sometimes. + // Throw some kind of exception from JS. If we send 0 then a pure JS + // exception is thrown, and any other value is the value in a wasm tag. throwImportName = Names::getValidFunctionName(wasm, "throw"); auto func = std::make_unique(); func->name = throwImportName; func->module = "fuzzing-support"; func->base = "throw"; - func->type = Signature(Type::none, Type::none); + func->type = Signature(Type::i32, Type::none); wasm.addFunction(std::move(func)); } @@ -1067,12 +1074,21 @@ Expression* TranslateToFuzzReader::makeImportLogging() { } Expression* TranslateToFuzzReader::makeImportThrowing(Type type) { + // TODO: This and makeThrow should probably be rare, as they halt the program. + // We throw from the import, so this call appears to be none and not // unreachable. assert(type == Type::none); - // TODO: This and makeThrow should probably be rare, as they halt the program. - return builder.makeCall(throwImportName, {}, Type::none); + // An argument of 0 means to throw a JS exception, and otherwise the value in + // a wasm tag. Emit 0 or non-zero with ~equal probability. + Expression* arg; + if (oneIn(2)) { + arg = builder.makeConst(int32_t(0)); + } else { + arg = makeConst(Type::i32); + } + return builder.makeCall(throwImportName, {arg}, Type::none); } Expression* TranslateToFuzzReader::makeImportTableGet() { diff --git a/test/lit/d8/fuzz_shell_exceptions.wast b/test/lit/d8/fuzz_shell_exceptions.wast new file mode 100644 index 00000000000..ed3b070104b --- /dev/null +++ b/test/lit/d8/fuzz_shell_exceptions.wast @@ -0,0 +1,35 @@ +;; Test throwing from JS by calling the throw import. + +(module + (import "fuzzing-support" "throw" (func $throw (param i32))) + + (func $throwing-js (export "throwing-js") + ;; Telling JS to throw with arg 0 leads to a JS exception thrown. + (call $throw + (i32.const 0) + ) + ) + + (func $throwing-tag (export "throwing-tag") + ;; A non-0 arg leads to a wasm Tag being thrown. + (call $throw + (i32.const 42) + ) + ) +) + +;; Build to a binary wasm. +;; +;; RUN: wasm-opt %s -o %t.wasm -q + +;; Run in node. +;; +;; RUN: v8 %S/../../../scripts/fuzz_shell.js -- %t.wasm | filecheck %s +;; +;; CHECK: [fuzz-exec] calling throwing-js +;; CHECK: exception thrown: some JS error +;; CHECK: [fuzz-exec] calling throwing-tag +;; CHECK: exception thrown: [object WebAssembly.Exception] + + + diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 8e251b2ed4f..5e6b0633977 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -8,7 +8,7 @@ (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) (import "fuzzing-support" "log-f64" (func $log-f64 (param f64))) - (import "fuzzing-support" "throw" (func $throw)) + (import "fuzzing-support" "throw" (func $throw (param i32))) (import "fuzzing-support" "table-set" (func $table.set (param i32 funcref))) (import "fuzzing-support" "table-get" (func $table.get (param i32) (result funcref))) @@ -21,6 +21,8 @@ (import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32))) + (import "fuzzing-support" "tag" (tag $imported-tag (param i32))) + (table $table 10 20 funcref) ;; Note that the exported table appears first here, but in the binary and in @@ -42,7 +44,19 @@ ;; CHECK: [fuzz-exec] calling throwing ;; CHECK-NEXT: [exception thrown: __private ()] (func $throwing (export "throwing") - (call $throw) + ;; Throwing 0 throws a JS ("private") exception. + (call $throw + (i32.const 0) + ) + ) + + ;; CHECK: [fuzz-exec] calling throwing-tag + ;; CHECK-NEXT: [exception thrown: imported-tag 42] + (func $throwing-tag (export "throwing-tag") + ;; Throwing non-0 throws using the tag we imported. + (call $throw + (i32.const 42) + ) ) ;; CHECK: [fuzz-exec] calling table.setting @@ -315,6 +329,9 @@ ;; CHECK: [fuzz-exec] calling throwing ;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK: [fuzz-exec] calling throwing-tag +;; CHECK-NEXT: [exception thrown: imported-tag 42] + ;; CHECK: [fuzz-exec] calling table.setting ;; CHECK-NEXT: [exception thrown: __private ()] @@ -386,3 +403,4 @@ ;; CHECK-NEXT: [fuzz-exec] comparing table.getting ;; CHECK-NEXT: [fuzz-exec] comparing table.setting ;; CHECK-NEXT: [fuzz-exec] comparing throwing +;; CHECK-NEXT: [fuzz-exec] comparing throwing-tag diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index a702187b204..a32de90e7d4 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,52 +1,42 @@ Metrics total - [exports] : 10 - [funcs] : 9 + [exports] : 15 + [funcs] : 17 [globals] : 4 - [imports] : 8 + [imports] : 11 [memories] : 1 [memory-data] : 112 - [table-data] : 0 + [table-data] : 4 [tables] : 1 - [tags] : 1 - [total] : 553 - [vars] : 38 - ArrayCopy : 2 - ArrayLen : 4 + [tags] : 0 + [total] : 688 + [vars] : 15 ArrayNew : 1 - ArrayNewFixed : 7 - Binary : 69 - Block : 55 - BrOn : 1 - Break : 1 - Call : 23 - Const : 107 - Drop : 7 - GlobalGet : 19 - GlobalSet : 18 - If : 17 - Load : 17 - LocalGet : 66 - LocalSet : 39 - Loop : 1 + ArrayNewFixed : 5 + Binary : 70 + Block : 77 + Call : 43 + Const : 166 + Drop : 8 + GlobalGet : 43 + GlobalSet : 38 + If : 19 + Load : 16 + LocalGet : 45 + LocalSet : 21 + Loop : 3 Nop : 3 - RefAs : 13 - RefFunc : 9 - RefI31 : 2 - RefIsNull : 2 - RefNull : 7 - RefTest : 1 + RefAs : 2 + RefFunc : 28 + RefNull : 8 Return : 5 - SIMDReplace : 1 - Select : 3 + Select : 1 Store : 1 - StringConst : 3 - StringEncode : 1 - StringMeasure : 1 - StructGet : 1 - StructNew : 15 - StructSet : 2 - TupleExtract : 1 + StringConst : 2 + StructNew : 37 + StructSet : 1 + Throw : 1 + TryTable : 3 TupleMake : 3 - Unary : 16 - Unreachable : 9 + Unary : 19 + Unreachable : 19 From 57c9c2537c5609e4eefe681fc1ae76989f7d4057 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 10 Feb 2025 13:26:35 -0800 Subject: [PATCH 287/622] [EH] Fuzzer: Add WebAssembly.JSTag fuzzing (#7283) This JS API represents the tag of JS exceptions. Import it into the wasm so that wasm can catch and throw JS exceptions. Rename the previous "jsTag", which means "wasm tag created in JS" to "wasmTag" to avoid confusion. --- scripts/fuzz_opt.py | 16 ++++++ scripts/fuzz_shell.js | 7 ++- src/tools/execution-results.h | 51 +++++++++++------- src/tools/fuzzing/fuzzing.cpp | 19 ++++--- test/lit/d8/fuzz_shell_exceptions.wast | 6 ++- test/lit/exec/fuzzing-api.wast | 53 ++++++++++++++----- ...e-to-fuzz_all-features_metrics_noprint.txt | 51 +++++++++--------- 7 files changed, 137 insertions(+), 66 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index bf7e1ef480e..823c57780a0 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1372,6 +1372,18 @@ def can_run_on_wasm(self, wasm): return not wasm_notices_export_changes(wasm) +# see https://github.com/WebAssembly/binaryen/issues/6823#issuecomment-2649122032 +# as the interpreter refers to tags by name, two imports of the same Tag give it +# two different names, but they should behave as if they are one. +def wasm_has_duplicate_tags(wasm): + # as with wasm_notices_export_changes, we could be more precise here and + # disassemble the wasm. + binary = open(wasm, 'rb').read() + # check if we import jstag or wasmtag, which are used in the wasm, so any + # duplication may hit the github issue mentioned above. + return binary.count(b'jstag') >= 2 or binary.count(b'wasmtag') >= 2 + + # Tests wasm-merge class Merge(TestCaseHandler): frequency = 0.15 @@ -1424,6 +1436,10 @@ def handle(self, wasm): abspath('second.wasm'), 'second', '-o', merged, '--skip-export-conflicts'] + FEATURE_OPTS + ['-all']) + if wasm_has_duplicate_tags(merged): + note_ignored_vm_run('dupe_tags') + return + # sometimes also optimize the merged module if random.random() < 0.5: opts = get_random_opts() diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index e2e9b34323f..718692498d1 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -272,7 +272,7 @@ var imports = { if (!which) { throw 'some JS error'; } else { - throw new WebAssembly.Exception(jsTag, [which]); + throw new WebAssembly.Exception(wasmTag, [which]); } }, @@ -333,10 +333,13 @@ var imports = { // If Tags are available, add some. if (typeof WebAssembly.Tag !== 'undefined') { // A tag for general use in the fuzzer. - var jsTag = imports['fuzzing-support']['tag'] = new WebAssembly.Tag({ + var wasmTag = imports['fuzzing-support']['wasmtag'] = new WebAssembly.Tag({ 'parameters': ['i32'] }); + // The JSTag that represents a JS tag. + imports['fuzzing-support']['jstag'] = WebAssembly.JSTag; + // This allows j2wasm content to run in the fuzzer. imports['imports'] = { 'j2wasm.ExceptionUtils.tag': new WebAssembly.Tag({ diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index ad4f5316cef..c6cb7ecc571 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -42,8 +42,13 @@ struct LoggingExternalInterface : public ShellExternalInterface { Name exportedTable; Module& wasm; - // The name of the imported fuzzing tag. - Name fuzzTag; + // The name of the imported fuzzing tag for wasm. + Name wasmTag; + + // The name of the imported tag for js exceptions. If it is not imported, we + // use a default name here (which should differentiate it from any wasm + // exceptions). + Name jsTag = "__private"; // The ModuleRunner and this ExternalInterface end up needing links both ways, // so we cannot init this in the constructor. @@ -60,9 +65,12 @@ struct LoggingExternalInterface : public ShellExternalInterface { } for (auto& tag : wasm.tags) { - if (tag->module == "fuzzing-support" && tag->base == "tag") { - fuzzTag = tag->name; - break; + if (tag->module == "fuzzing-support") { + if (tag->base == "wasmtag") { + wasmTag = tag->name; + } else if (tag->base == "jstag") { + jsTag = tag->name; + } } } } @@ -96,29 +104,29 @@ struct LoggingExternalInterface : public ShellExternalInterface { // should throw a JS exception, and any other value means we should // throw a wasm exception (with that value as the payload). if (arguments[0].geti32() == 0) { - throwEmptyException(); + throwJSException(); } else { - auto payload = std::make_shared(fuzzTag, arguments); + auto payload = std::make_shared(wasmTag, arguments); throwException(WasmException{Literal(payload)}); } } else if (import->base == "table-get") { // Check for errors here, duplicating tableLoad(), because that will // trap, and we just want to throw an exception (the same as JS would). if (!exportedTable) { - throwEmptyException(); + throwJSException(); } auto index = arguments[0].getUnsigned(); if (index >= tables[exportedTable].size()) { - throwEmptyException(); + throwJSException(); } return {tableLoad(exportedTable, index)}; } else if (import->base == "table-set") { if (!exportedTable) { - throwEmptyException(); + throwJSException(); } auto index = arguments[0].getUnsigned(); if (index >= tables[exportedTable].size()) { - throwEmptyException(); + throwJSException(); } tableStore(exportedTable, index, arguments[1]); return {}; @@ -172,21 +180,24 @@ struct LoggingExternalInterface : public ShellExternalInterface { return {}; } - void throwEmptyException() { - // Use a hopefully private tag. - auto payload = std::make_shared("__private", Literals{}); + void throwJSException() { + // JS exceptions contain an externref, which wasm can't read (so the actual + // value here does not matter). + Literal externref = Literal::makeI31(0, Unshared).externalize(); + Literals arguments = {externref}; + auto payload = std::make_shared(jsTag, arguments); throwException(WasmException{Literal(payload)}); } Literals callExportAsJS(Index index) { if (index >= wasm.exports.size()) { // No export. - throwEmptyException(); + throwJSException(); } auto& exp = wasm.exports[index]; if (exp->kind != ExternalKind::Function) { // No callable export. - throwEmptyException(); + throwJSException(); } return callFunctionAsJS(exp->value); } @@ -194,7 +205,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { Literals callRefAsJS(Literal ref) { if (!ref.isFunction()) { // Not a callable ref. - throwEmptyException(); + throwJSException(); } return callFunctionAsJS(ref.getFunc()); } @@ -210,10 +221,10 @@ struct LoggingExternalInterface : public ShellExternalInterface { // An i64 param can work from JS, but fuzz_shell provides 0, which errors // on attempts to convert it to BigInt. v128 and exnref are disalloewd. if (param == Type::i64 || param == Type::v128 || param.isExn()) { - throwEmptyException(); + throwJSException(); } if (!param.isDefaultable()) { - throwEmptyException(); + throwJSException(); } arguments.push_back(Literal::makeZero(param)); } @@ -224,7 +235,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { // An i64 result is fine: a BigInt will be provided. But v128 and exnref // still error. if (result == Type::v128 || result.isExn()) { - throwEmptyException(); + throwJSException(); } } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 00181a90b31..c853e37f790 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -648,13 +648,20 @@ void TranslateToFuzzReader::setupTags() { addTag(); } - // Add the fuzzing support tag manually sometimes. + // Add the fuzzing support tags manually sometimes. if (oneIn(2)) { - auto tag = builder.makeTag(Names::getValidTagName(wasm, "tag"), - Signature(Type::i32, Type::none)); - tag->module = "fuzzing-support"; - tag->base = "tag"; - wasm.addTag(std::move(tag)); + auto wasmTag = builder.makeTag(Names::getValidTagName(wasm, "wasmtag"), + Signature(Type::i32, Type::none)); + wasmTag->module = "fuzzing-support"; + wasmTag->base = "wasmtag"; + wasm.addTag(std::move(wasmTag)); + + auto externref = Type(HeapType::ext, Nullable); + auto jsTag = builder.makeTag(Names::getValidTagName(wasm, "jstag"), + Signature(externref, Type::none)); + jsTag->module = "fuzzing-support"; + jsTag->base = "jstag"; + wasm.addTag(std::move(jsTag)); } } diff --git a/test/lit/d8/fuzz_shell_exceptions.wast b/test/lit/d8/fuzz_shell_exceptions.wast index ed3b070104b..6b46ee2adfa 100644 --- a/test/lit/d8/fuzz_shell_exceptions.wast +++ b/test/lit/d8/fuzz_shell_exceptions.wast @@ -3,6 +3,10 @@ (module (import "fuzzing-support" "throw" (func $throw (param i32))) + ;; Verify that fuzz_shell.js provides these imports for the wasm. + (import "fuzzing-support" "wasmtag" (tag $imported-wasm-tag (param i32))) + (import "fuzzing-support" "jstag" (tag $imported-js-tag (param externref))) + (func $throwing-js (export "throwing-js") ;; Telling JS to throw with arg 0 leads to a JS exception thrown. (call $throw @@ -20,7 +24,7 @@ ;; Build to a binary wasm. ;; -;; RUN: wasm-opt %s -o %t.wasm -q +;; RUN: wasm-opt %s -o %t.wasm -q -all ;; Run in node. ;; diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 5e6b0633977..0d9eb0c9cc1 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -21,7 +21,8 @@ (import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32))) - (import "fuzzing-support" "tag" (tag $imported-tag (param i32))) + (import "fuzzing-support" "wasmtag" (tag $imported-wasm-tag (param i32))) + (import "fuzzing-support" "jstag" (tag $imported-js-tag (param externref))) (table $table 10 20 funcref) @@ -42,7 +43,7 @@ ) ;; CHECK: [fuzz-exec] calling throwing - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $throwing (export "throwing") ;; Throwing 0 throws a JS ("private") exception. (call $throw @@ -51,7 +52,7 @@ ) ;; CHECK: [fuzz-exec] calling throwing-tag - ;; CHECK-NEXT: [exception thrown: imported-tag 42] + ;; CHECK-NEXT: [exception thrown: imported-wasm-tag 42] (func $throwing-tag (export "throwing-tag") ;; Throwing non-0 throws using the tag we imported. (call $throw @@ -60,7 +61,7 @@ ) ;; CHECK: [fuzz-exec] calling table.setting - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $table.setting (export "table.setting") (call $table.set (i32.const 5) @@ -76,7 +77,7 @@ ;; CHECK: [fuzz-exec] calling table.getting ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $table.getting (export "table.getting") ;; There is a non-null value at 5, and a null at 6. (call $log-i32 @@ -104,7 +105,7 @@ ;; CHECK: [fuzz-exec] calling export.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $export.calling (export "export.calling") ;; At index 0 in the exports we have $logging, so we will do those loggings. (call $call.export @@ -140,7 +141,7 @@ ;; CHECK: [fuzz-exec] calling ref.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $ref.calling (export "ref.calling") ;; This will emit some logging. (call $call.ref @@ -310,6 +311,28 @@ ) ) + ;; CHECK: [fuzz-exec] calling catch-js-tag + ;; CHECK-NEXT: [fuzz-exec] note result: catch-js-tag => 100 + (func $catch-js-tag (export "catch-js-tag") (result i32) + ;; The table.set out of bounds will throw a JS exception, so it will be caught + ;; by the catch here, and we'll return the number at the end. + (drop + (block $out (result externref) + (try_table (catch $imported-js-tag $out) + (call $table.set + (i32.const 9999) + (ref.func $table.setting) + ) + (return + (i32.const -1) + ) + ) + ) + ) + (i32.const 100) + ) + + ;; CHECK: [fuzz-exec] calling do-sleep ;; CHECK-NEXT: [fuzz-exec] note result: do-sleep => 42 ;; CHECK-NEXT: warning: no passes specified, not doing any work @@ -327,23 +350,23 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] ;; CHECK: [fuzz-exec] calling throwing -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling throwing-tag -;; CHECK-NEXT: [exception thrown: imported-tag 42] +;; CHECK-NEXT: [exception thrown: imported-wasm-tag 42] ;; CHECK: [fuzz-exec] calling table.setting -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling table.getting ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling export.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling export.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -354,7 +377,7 @@ ;; CHECK: [fuzz-exec] calling ref.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling ref.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -385,8 +408,12 @@ ;; CHECK: [fuzz-exec] calling ref.calling.trap ;; CHECK-NEXT: [trap unreachable] +;; CHECK: [fuzz-exec] calling catch-js-tag +;; CHECK-NEXT: [fuzz-exec] note result: catch-js-tag => 100 + ;; CHECK: [fuzz-exec] calling do-sleep ;; CHECK-NEXT: [fuzz-exec] note result: do-sleep => 42 +;; CHECK-NEXT: [fuzz-exec] comparing catch-js-tag ;; CHECK-NEXT: [fuzz-exec] comparing do-sleep ;; CHECK-NEXT: [fuzz-exec] comparing export.calling ;; CHECK-NEXT: [fuzz-exec] comparing export.calling.catching diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index a32de90e7d4..d36325f58b8 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,42 +1,45 @@ Metrics total - [exports] : 15 - [funcs] : 17 + [exports] : 14 + [funcs] : 18 [globals] : 4 - [imports] : 11 + [imports] : 12 [memories] : 1 [memory-data] : 112 - [table-data] : 4 + [table-data] : 3 [tables] : 1 [tags] : 0 - [total] : 688 - [vars] : 15 + [total] : 626 + [vars] : 49 ArrayNew : 1 ArrayNewFixed : 5 - Binary : 70 - Block : 77 - Call : 43 - Const : 166 - Drop : 8 - GlobalGet : 43 + ArraySet : 1 + AtomicCmpxchg : 1 + AtomicFence : 1 + Binary : 74 + Block : 79 + Call : 40 + Const : 138 + Drop : 9 + GlobalGet : 42 GlobalSet : 38 - If : 19 + If : 20 Load : 16 - LocalGet : 45 - LocalSet : 21 - Loop : 3 - Nop : 3 + LocalGet : 46 + LocalSet : 22 + Loop : 2 + Nop : 4 RefAs : 2 - RefFunc : 28 - RefNull : 8 - Return : 5 + RefFunc : 8 + RefNull : 4 + Return : 3 Select : 1 Store : 1 - StringConst : 2 - StructNew : 37 + StringConst : 3 + StructNew : 18 StructSet : 1 Throw : 1 - TryTable : 3 + TryTable : 2 TupleMake : 3 - Unary : 19 + Unary : 21 Unreachable : 19 From 7198f0e0ce8d52c0adc02c1a897ae48d8d7e677f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 10 Feb 2025 15:37:57 -0800 Subject: [PATCH 288/622] Fix JS tag fuzzing: throw the same in JS and the binaryen interpreter (#7286) We do not compare exceptions in binaryen (not in the optimizer, where we assume we can reorder traps, and not in the fuzzer, where we assume VMs may have different text for them). But, since we have try-catch in wasm, we can actually end up comparing them, by catching the exception and logging the output. For that reason, we need to throw exactly the same JS exception in #7283, which this fixes. --- scripts/fuzz_shell.js | 4 +++- src/tools/execution-results.h | 3 ++- test/lit/d8/fuzz_shell_exceptions.wast | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 718692498d1..de1ef135202 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -270,8 +270,10 @@ var imports = { // Throw an exception from JS. 'throw': (which) => { if (!which) { - throw 'some JS error'; + // Throw a JS exception. + throw 0; } else { + // Throw a wasm exception. throw new WebAssembly.Exception(wasmTag, [which]); } }, diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index c6cb7ecc571..9ef409a385d 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -182,7 +182,8 @@ struct LoggingExternalInterface : public ShellExternalInterface { void throwJSException() { // JS exceptions contain an externref, which wasm can't read (so the actual - // value here does not matter). + // value here does not matter, but it does need to match what the 'throw' + // import does in fuzz_shell.js, as the fuzzer will do comparisons). Literal externref = Literal::makeI31(0, Unshared).externalize(); Literals arguments = {externref}; auto payload = std::make_shared(jsTag, arguments); diff --git a/test/lit/d8/fuzz_shell_exceptions.wast b/test/lit/d8/fuzz_shell_exceptions.wast index 6b46ee2adfa..da1e7fbe0aa 100644 --- a/test/lit/d8/fuzz_shell_exceptions.wast +++ b/test/lit/d8/fuzz_shell_exceptions.wast @@ -31,7 +31,7 @@ ;; RUN: v8 %S/../../../scripts/fuzz_shell.js -- %t.wasm | filecheck %s ;; ;; CHECK: [fuzz-exec] calling throwing-js -;; CHECK: exception thrown: some JS error +;; CHECK: exception thrown: 0 ;; CHECK: [fuzz-exec] calling throwing-tag ;; CHECK: exception thrown: [object WebAssembly.Exception] From 3ec10245f7b8c4fed4dc314f9710ce63f5a7e1da Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 11 Feb 2025 14:44:40 -0800 Subject: [PATCH 289/622] GUFA: Fix handling of public tags (#7278) When a tag is imported or exported, it might be written to (an exception thrown with a value in the tag) from the outside, so we should not assume it is unreachable. --- src/ir/possible-contents.cpp | 20 ++++ test/lit/passes/gufa-cast-all.wast | 152 +++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 93f3c57fec6..ecf5f513c2b 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2309,6 +2309,26 @@ Flower::Flower(Module& wasm, const PassOptions& options) } } + // Exported/imported tags are modifiable from the outside. TODO: should that + // not be possible in closed world? + std::unordered_set publicTags; + for (auto& tag : wasm.tags) { + if (tag->imported()) { + publicTags.insert(tag->name); + } + } + for (auto& ex : wasm.exports) { + if (ex->kind == ExternalKind::Tag) { + publicTags.insert(ex->value); + } + } + for (auto tag : publicTags) { + auto params = wasm.getTag(tag)->params(); + for (Index i = 0; i < params.size(); i++) { + roots[TagLocation{tag, i}] = PossibleContents::fromType(params[i]); + } + } + #ifdef POSSIBLE_CONTENTS_DEBUG std::cout << "struct phase\n"; #endif diff --git a/test/lit/passes/gufa-cast-all.wast b/test/lit/passes/gufa-cast-all.wast index c5b92ee89da..a3d7901e4d1 100644 --- a/test/lit/passes/gufa-cast-all.wast +++ b/test/lit/passes/gufa-cast-all.wast @@ -146,3 +146,155 @@ ) ) ) + +;; Imported tags may be written to from places we do not see. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "fuzzing-support" "throw" (func $throw (type $0) (param i32))) + (import "fuzzing-support" "throw" (func $throw (param i32))) + ;; CHECK: (import "fuzzing-support" "tag" (tag $tag (type $0) (param i32))) + (import "fuzzing-support" "tag" (tag $tag (param i32))) + + ;; CHECK: (export "func" (func $func)) + + ;; CHECK: (func $func (type $1) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (call $throw + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func (export "func") + (try + (do + (call $throw + (i32.const 1) + ) + ) + (catch $tag + ;; If we thought no value was possible to pop here (if no exception were + ;; created of this tag) then we'd put an unreachable after it. As it is + ;; imported, a value might be there, so we do not. + (drop + (pop i32) + ) + ) + ) + ) +) + +;; As above, but with an exported tag. Also test a tag with multiple params. +(module + ;; CHECK: (type $0 (func (param i32 f64))) + + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (import "fuzzing-support" "throw" (func $throw (type $1) (param i32))) + (import "fuzzing-support" "throw" (func $throw (param i32))) + + ;; CHECK: (tag $tag (type $0) (param i32 f64)) + (tag $tag (param i32 f64)) + + ;; CHECK: (export "func" (func $func)) + + ;; CHECK: (export "tag" (tag $tag)) + (export "tag" (tag $tag)) + + ;; CHECK: (func $func (type $2) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (call $throw + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (pop (tuple i32 f64)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func (export "func") + (try + (do + (call $throw + (i32.const 1) + ) + ) + (catch $tag + ;; Once more, we do not optimize to unreachable here. + (tuple.drop 2 + (pop (tuple i32 f64)) + ) + ) + ) + ) +) + +;; Private tags are optimizable. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "fuzzing-support" "throw" (func $throw (type $0) (param i32))) + (import "fuzzing-support" "throw" (func $throw (param i32))) + + ;; CHECK: (tag $tag (type $0) (param i32)) + (tag $tag (param i32)) + + ;; CHECK: (export "func" (func $func)) + + ;; CHECK: (func $func (type $1) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (call $throw + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func (export "func") + (try + (do + (call $throw + (i32.const 1) + ) + ) + (catch $tag + ;; The tag is neither imported nor exported, so we can optimize to + ;; unreachable. + (drop + (pop i32) + ) + ) + ) + ) +) + From eeed8658fff9333a5844e0064119a36e784ad00b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 12 Feb 2025 12:00:31 -0800 Subject: [PATCH 290/622] [NFC] Update test from old EH to new EH (#7292) Followup to #7278 --- test/lit/passes/gufa-cast-all.wast | 130 +++++++++++++---------------- 1 file changed, 58 insertions(+), 72 deletions(-) diff --git a/test/lit/passes/gufa-cast-all.wast b/test/lit/passes/gufa-cast-all.wast index a3d7901e4d1..e6b64851a4d 100644 --- a/test/lit/passes/gufa-cast-all.wast +++ b/test/lit/passes/gufa-cast-all.wast @@ -161,33 +161,29 @@ ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $1) - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (call $throw - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $tag - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result i32) + ;; CHECK-NEXT: (try_table (catch $tag $block) + ;; CHECK-NEXT: (call $throw + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (export "func") - (try - (do - (call $throw - (i32.const 1) - ) - ) - (catch $tag - ;; If we thought no value was possible to pop here (if no exception were - ;; created of this tag) then we'd put an unreachable after it. As it is - ;; imported, a value might be there, so we do not. - (drop - (pop i32) + (drop + ;; If we thought no i32 value could arrive here (if no exception were + ;; created of this tag) then we'd put an unreachable after it. As it is + ;; imported, a value might be there, so we do not. + (block $block (result i32) + (try_table (catch $tag $block) + (call $throw + (i32.const 1) + ) ) + (return) ) ) ) @@ -195,16 +191,18 @@ ;; As above, but with an exported tag. Also test a tag with multiple params. (module - ;; CHECK: (type $0 (func (param i32 f64))) + ;; CHECK: (type $0 (func (result i32 f64))) + + ;; CHECK: (type $1 (func (param i32 f64))) - ;; CHECK: (type $1 (func (param i32))) + ;; CHECK: (type $2 (func (param i32))) - ;; CHECK: (type $2 (func)) + ;; CHECK: (type $3 (func)) - ;; CHECK: (import "fuzzing-support" "throw" (func $throw (type $1) (param i32))) + ;; CHECK: (import "fuzzing-support" "throw" (func $throw (type $2) (param i32))) (import "fuzzing-support" "throw" (func $throw (param i32))) - ;; CHECK: (tag $tag (type $0) (param i32 f64)) + ;; CHECK: (tag $tag (type $1) (param i32 f64)) (tag $tag (param i32 f64)) ;; CHECK: (export "func" (func $func)) @@ -212,32 +210,28 @@ ;; CHECK: (export "tag" (tag $tag)) (export "tag" (tag $tag)) - ;; CHECK: (func $func (type $2) - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (call $throw - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $tag - ;; CHECK-NEXT: (tuple.drop 2 - ;; CHECK-NEXT: (pop (tuple i32 f64)) + ;; CHECK: (func $func (type $3) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $block (type $0) (result i32 f64) + ;; CHECK-NEXT: (try_table (catch $tag $block) + ;; CHECK-NEXT: (call $throw + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (export "func") - (try - (do - (call $throw - (i32.const 1) - ) - ) - (catch $tag - ;; Once more, we do not optimize to unreachable here. - (tuple.drop 2 - (pop (tuple i32 f64)) + ;; Once more, we do not optimize to unreachable here. + (tuple.drop 2 + (block $block (result i32 f64) + (try_table (catch $tag $block) + (call $throw + (i32.const 1) + ) ) + (return) ) ) ) @@ -258,41 +252,33 @@ ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $1) - ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (call $throw - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $tag - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block $block (result i32) + ;; CHECK-NEXT: (try_table (catch $tag $block) + ;; CHECK-NEXT: (call $throw + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (export "func") - (try - (do - (call $throw - (i32.const 1) - ) - ) - (catch $tag - ;; The tag is neither imported nor exported, so we can optimize to - ;; unreachable. - (drop - (pop i32) + ;; The tag is neither imported nor exported, so we can optimize to + ;; unreachable. + (drop + (block $block (result i32) + (try_table (catch $tag $block) + (call $throw + (i32.const 1) + ) ) + (return) ) ) ) From 0be9bfeba0bf58e51be5e94e2c8e8fd27e189117 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 12 Feb 2025 12:00:56 -0800 Subject: [PATCH 291/622] [EH] Prune exnref-returning/receiving exports (#7290) Like v128, JS VMs will trap on an exnref on the boundary between JS and wasm, so the fuzzer must prune such exports (we run that pruning when we intend to compare d8 to the binaryen interpreter; after the pruning, all exports should behave the same between those VMs). --- src/passes/LegalizeJSInterface.cpp | 5 +++-- .../legalize-and-prune-js-interface.wast | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 01a8c240175..453b649c0bc 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -374,7 +374,7 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { } // The params are allowed to be multivalue, but not the results. Otherwise - // look for SIMD. + // look for SIMD etc. auto sig = func->type.getSignature(); auto illegal = isIllegal(sig.results); illegal = @@ -409,7 +409,8 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { bool isIllegal(Type type) { auto features = type.getFeatures(); - return features.hasSIMD() || features.hasMultivalue(); + return features.hasSIMD() || features.hasMultivalue() || + features.hasExceptionHandling(); } }; diff --git a/test/lit/passes/legalize-and-prune-js-interface.wast b/test/lit/passes/legalize-and-prune-js-interface.wast index 1683818a2d8..0840ddbdd57 100644 --- a/test/lit/passes/legalize-and-prune-js-interface.wast +++ b/test/lit/passes/legalize-and-prune-js-interface.wast @@ -128,11 +128,13 @@ ;; CHECK: (type $3 (func (result i32 i32))) - ;; CHECK: (type $4 (func (param i32))) + ;; CHECK: (type $4 (func (result exnref))) - ;; CHECK: (type $5 (func (param i32 i32) (result i32))) + ;; CHECK: (type $5 (func (param i32))) - ;; CHECK: (import "env" "setTempRet0" (func $setTempRet0 (type $4) (param i32))) + ;; CHECK: (type $6 (func (param i32 i32) (result i32))) + + ;; CHECK: (import "env" "setTempRet0" (func $setTempRet0 (type $5) (param i32))) ;; CHECK: (export "export-64" (func $legalstub$export-64)) @@ -167,9 +169,17 @@ ;; This will be pruned. (unreachable) ) + + ;; CHECK: (func $export-exn (type $4) (result exnref) + ;; CHECK-NEXT: (ref.null noexn) + ;; CHECK-NEXT: ) + (func $export-exn (export "export-exn") (result exnref) + ;; This will be pruned. + (ref.null noexn) + ) ) -;; CHECK: (func $legalstub$export-64 (type $5) (param $0 i32) (param $1 i32) (result i32) +;; CHECK: (func $legalstub$export-64 (type $6) (param $0 i32) (param $1 i32) (result i32) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (call $export-64 From 7664807c1085b817427a19528556092a3c2a0b4c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Feb 2025 09:07:55 -0800 Subject: [PATCH 292/622] [GC][EH] Fuzz exnref in GC structs (#7288) Most of the work here is avoiding shared and non-nullable exnref in various places, as we cannot support those in the fuzzer (I don't see a way to generate shared exnrefs, and non-nullable ones can be created but not in global places, as the way to create them needs a block and a try). --- src/tools/fuzzing/fuzzing.cpp | 23 ++++++++++++------ src/tools/fuzzing/heap-types.cpp | 41 ++++++++++++++++++++++++-------- test/lit/fuzz-types.test | 26 ++++++++++---------- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index c853e37f790..f667e4000ea 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1857,8 +1857,10 @@ Expression* TranslateToFuzzReader::make(Type type) { assert(type == Type::unreachable); ret = _makeunreachable(); } - // We should create the right type of thing. - assert(Type::isSubType(ret->type, type)); + if (!Type::isSubType(ret->type, type)) { + Fatal() << "Did not generate the right subtype of " << type + << ", instead we have " << ret->type << " : " << *ret << '\n'; + } nesting--; return ret; } @@ -3273,13 +3275,13 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return builder.makeArrayNewFixed(ht, {}); } case HeapType::exn: { - // If nullable, we can emit a null. If not, generate an exnref using a - // throw in a try_table. - if (type.isNullable() && oneIn(2)) { + // If nullable, we can emit a null. If there is no function context, then + // we must do so, as the other option is a throw in a block, which are not + // possible outside of functions. + if ((type.isNullable() && oneIn(2)) || !funcContext) { return builder.makeRefNull(HeapTypes::exn.getBasic(share)); } - // Make a catch_all_ref to a block. auto* throww = makeThrow(Type::unreachable); auto label = makeLabel(); auto* tryy = builder.makeTryTable(throww, {Name()}, {label}, {true}); @@ -5101,6 +5103,7 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { case HeapType::array: return pick(HeapTypes::array, HeapTypes::none).getBasic(share); case HeapType::exn: + assert(share == Unshared); return HeapTypes::exn.getBasic(share); case HeapType::string: assert(share == Unshared); @@ -5133,7 +5136,13 @@ Type TranslateToFuzzReader::getSubType(Type type) { } return Type(types); } else if (type.isRef()) { - auto heapType = getSubType(type.getHeapType()); + auto heapType = type.getHeapType(); + // Do not generate non-nullable exnrefs in global positions (they cannot be + // created in wasm, nor imported from JS). + if (!funcContext && heapType.isMaybeShared(HeapType::exn)) { + return type; + } + heapType = getSubType(heapType); auto nullability = getSubType(type.getNullability()); auto subType = Type(heapType, nullability); // We don't want to emit lots of uninhabitable types like (ref none), so diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 6255ab42799..dc6e574c7e9 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -154,20 +154,27 @@ struct HeapTypeGeneratorImpl { HeapType::BasicHeapType generateBasicHeapType(Shareability share) { // Choose bottom types more rarely. - // TODO: string, exn, and cont types + // TODO: string and cont types if (rand.oneIn(16)) { HeapType ht = rand.pick(HeapType::noext, HeapType::nofunc, HeapType::none); return ht.getBasic(share); } - HeapType ht = rand.pick(HeapType::func, - HeapType::ext, - HeapType::any, - HeapType::eq, - HeapType::i31, - HeapType::struct_, - HeapType::array); - if (share == Unshared && features.hasSharedEverything() && rand.oneIn(2)) { + + std::vector options{HeapType::func, + HeapType::ext, + HeapType::any, + HeapType::eq, + HeapType::i31, + HeapType::struct_, + HeapType::array}; + // Avoid shared exn, which we cannot generate. + if (features.hasExceptionHandling() && share == Unshared) { + options.push_back(HeapType::exn); + } + auto ht = rand.pick(options); + if (share == Unshared && features.hasSharedEverything() && + ht != HeapType::exn && rand.oneIn(2)) { share = Shared; } return ht.getBasic(share); @@ -203,7 +210,15 @@ struct HeapTypeGeneratorImpl { Type generateRefType(Shareability share) { auto heapType = generateHeapType(share); - auto nullability = rand.oneIn(2) ? Nullable : NonNullable; + Nullability nullability; + if (heapType.isMaybeShared(HeapType::exn)) { + // Do not generate non-nullable exnrefs for now, as we cannot generate + // them in global positions (they cannot be created in wasm, nor imported + // from JS). + nullability = Nullable; + } else { + nullability = rand.oneIn(2) ? Nullable : NonNullable; + } return builder.getTempRefType(heapType, nullability); } @@ -521,6 +536,12 @@ struct HeapTypeGeneratorImpl { }; Ref generateSubRef(Ref super) { + if (super.type.isMaybeShared(HeapType::exn)) { + // Do not generate non-nullable exnrefs for now, as we cannot generate + // them in global positions (they cannot be created in wasm, nor imported + // from JS). There are also no subtypes to consider, so just return. + return super; + } auto nullability = super.nullability == NonNullable ? NonNullable : rand.oneIn(2) ? Nullable : NonNullable; diff --git a/test/lit/fuzz-types.test b/test/lit/fuzz-types.test index 9c338f35e95..b0cedc0b54f 100644 --- a/test/lit/fuzz-types.test +++ b/test/lit/fuzz-types.test @@ -4,7 +4,7 @@ ;; CHECK-NEXT: Built 20 types: ;; CHECK-NEXT: (rec ;; CHECK-NEXT: (type $0 (sub (struct (field (mut i16)) (field (mut (ref $2))) (field (mut (ref null $2)))))) -;; CHECK-NEXT: (type $1 (sub (func (param (ref $1)) (result f64 (ref $0) f32 (ref null (shared eq)))))) +;; CHECK-NEXT: (type $1 (sub (func (param (ref $1)) (result f64 (ref $0) f32 structref)))) ;; CHECK-NEXT: (type $2 (sub (shared (struct (field (mut (ref null (shared extern)))) (field (mut (ref null $2))))))) ;; CHECK-NEXT: (type $3 (sub (shared (struct)))) ;; CHECK-NEXT: ) @@ -22,21 +22,21 @@ ;; CHECK-NEXT: (type $12 (sub (shared (array (ref $3))))) ;; CHECK-NEXT: (type $13 (sub (shared (func (param (ref null $19) v128) (result (ref null $12)))))) ;; CHECK-NEXT: (type $14 (sub final $12 (shared (array (ref $3))))) -;; CHECK-NEXT: (type $15 (sub (shared (func (param (ref null (shared struct)) i31ref) (result nullfuncref))))) +;; CHECK-NEXT: (type $15 (sub (shared (func (param i31ref (ref $5)) (result i32))))) ;; CHECK-NEXT: (type $16 (sub $5 (array i32))) -;; CHECK-NEXT: (type $17 (sub (func (param v128) (result f64)))) -;; CHECK-NEXT: (type $18 (sub (array (ref $11)))) -;; CHECK-NEXT: (type $19 (shared (array i8))) +;; CHECK-NEXT: (type $17 (sub (func (result (ref $7))))) +;; CHECK-NEXT: (type $18 (sub (array (mut i8)))) +;; CHECK-NEXT: (type $19 (shared (array v128))) ;; CHECK-NEXT: ) -;; CHECK-NEXT: +;; CHECK-NEXT: ;; CHECK-NEXT: Inhabitable types: -;; CHECK-NEXT: +;; CHECK-NEXT: ;; CHECK-NEXT: Built 20 types: ;; CHECK-NEXT: (rec ;; CHECK-NEXT: (type $0 (sub (struct (field (mut i16)) (field (mut (ref $2))) (field (mut (ref null $2)))))) -;; CHECK-NEXT: (type $1 (sub (func (param (ref $1)) (result f64 (ref $0) f32 (ref null (shared eq)))))) +;; CHECK-NEXT: (type $1 (sub (func (param (ref $1)) (result f64 (ref $0) f32 structref)))) ;; CHECK-NEXT: (type $2 (sub (shared (struct (field (mut (ref null (shared extern)))) (field (mut (ref null $2))))))) -;; CHECK-NEXT: (type $3 (sub (shared (struct))) +;; CHECK-NEXT: (type $3 (sub (shared (struct)))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec ;; CHECK-NEXT: (type $4 (sub (array i32))) @@ -52,9 +52,9 @@ ;; CHECK-NEXT: (type $12 (sub (shared (array (ref $3))))) ;; CHECK-NEXT: (type $13 (sub (shared (func (param (ref null $19) v128) (result (ref null $12)))))) ;; CHECK-NEXT: (type $14 (sub final $12 (shared (array (ref $3))))) -;; CHECK-NEXT: (type $15 (sub (shared (func (param (ref null (shared struct)) i31ref) (result nullfuncref))))) +;; CHECK-NEXT: (type $15 (sub (shared (func (param i31ref (ref $5)) (result i32))))) ;; CHECK-NEXT: (type $16 (sub $5 (array i32))) -;; CHECK-NEXT: (type $17 (sub (func (param v128) (result f64)))) -;; CHECK-NEXT: (type $18 (sub (array (ref $11)))) -;; CHECK-NEXT: (type $19 (shared (array i8))) +;; CHECK-NEXT: (type $17 (sub (func (result (ref $7))))) +;; CHECK-NEXT: (type $18 (sub (array (mut i8)))) +;; CHECK-NEXT: (type $19 (shared (array v128))) ;; CHECK-NEXT: ) From fbcd71c240438dd160e35d720f9ce3e8f6236bf5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Feb 2025 17:14:41 -0800 Subject: [PATCH 293/622] Fuzzer: Simplify and improve emitting of unreachable code (#7295) In the earlier days of the fuzzer we would call makeUnary with unreachable sometimes, and then emit an unreachable i32.eqz for example. Later we added mutate() which randomly emits unreachable into places, which gives even better coverage - no need to manually handle unreachability in every single instruction. This PR removes the old form. Also remove the "Important" label from makeUnreachable. The unreachable instruction is now one of many that have that type, and we have no reason to prefer it to others. These changes have the effect of improving the chance to emit interesting instructions with unreachable type, like throw, throw_ref. --- src/tools/fuzzing/fuzzing.cpp | 35 +++--------- test/passes/fuzz_metrics_noprint.bin.txt | 54 +++++++++--------- .../fuzz_metrics_passes_noprint.bin.txt | 54 +++++++++--------- ...e-to-fuzz_all-features_metrics_noprint.txt | 57 ++++++++++--------- 4 files changed, 91 insertions(+), 109 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index f667e4000ea..1dd8bc66d27 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -2000,22 +2000,19 @@ Expression* TranslateToFuzzReader::_makeunreachable() { using Self = TranslateToFuzzReader; auto options = FeatureOptions(); using WeightedOption = decltype(options)::WeightedOption; + // Many instructions can become unreachable if a child is unreachable. We + // create such code in mutate() (see |allowUnreachable| there). The list of + // instructions here are those that necessarily have unreachable type, and are + // only created here (though they might have other variations that are + // reachable, like br has br_if that is created elsewhere, and we have call + // here because of return calls, etc.). options .add(FeatureSet::MVP, - WeightedOption{&Self::makeLocalSet, VeryImportant}, - WeightedOption{&Self::makeBlock, Important}, - WeightedOption{&Self::makeIf, Important}, - WeightedOption{&Self::makeLoop, Important}, WeightedOption{&Self::makeBreak, Important}, - WeightedOption{&Self::makeStore, Important}, - WeightedOption{&Self::makeUnary, Important}, - WeightedOption{&Self::makeBinary, Important}, - WeightedOption{&Self::makeUnreachable, Important}, + &Self::makeUnreachable, &Self::makeCall, &Self::makeCallIndirect, - &Self::makeSelect, &Self::makeSwitch, - &Self::makeDrop, &Self::makeReturn) .add(FeatureSet::ExceptionHandling, &Self::makeThrow, &Self::makeThrowRef) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef); @@ -3568,13 +3565,6 @@ Expression* TranslateToFuzzReader::buildUnary(const UnaryArgs& args) { Expression* TranslateToFuzzReader::makeUnary(Type type) { assert(!type.isTuple()); - if (type == Type::unreachable) { - if (auto* unary = makeUnary(getSingleConcreteType())->dynCast()) { - return builder.makeUnary(unary->op, make(Type::unreachable)); - } - // give up - return makeTrivial(type); - } // There are no unary ops for reference types. // TODO: not quite true if you count struct.new and array.new. if (type.isRef()) { @@ -3789,14 +3779,6 @@ Expression* TranslateToFuzzReader::buildBinary(const BinaryArgs& args) { Expression* TranslateToFuzzReader::makeBinary(Type type) { assert(!type.isTuple()); - if (type == Type::unreachable) { - if (auto* binary = makeBinary(getSingleConcreteType())->dynCast()) { - return buildBinary( - {binary->op, make(Type::unreachable), make(Type::unreachable)}); - } - // give up - return makeTrivial(type); - } // There are no binary ops for reference types. // TODO: Use struct.new if (type.isRef()) { @@ -4077,8 +4059,7 @@ Expression* TranslateToFuzzReader::makeSwitch(Type type) { } Expression* TranslateToFuzzReader::makeDrop(Type type) { - return builder.makeDrop( - make(type == Type::unreachable ? type : getConcreteType())); + return builder.makeDrop(make(getConcreteType())); } Expression* TranslateToFuzzReader::makeReturn(Type type) { diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index e1ade8ab17c..87b7a851019 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 65 - [funcs] : 85 + [exports] : 84 + [funcs] : 115 [globals] : 18 [imports] : 4 [memories] : 1 [memory-data] : 24 - [table-data] : 24 + [table-data] : 49 [tables] : 1 [tags] : 0 - [total] : 8396 - [vars] : 239 - Binary : 573 - Block : 1420 - Break : 276 - Call : 382 - CallIndirect : 56 - Const : 1258 - Drop : 124 - GlobalGet : 731 - GlobalSet : 531 - If : 468 - Load : 129 - LocalGet : 578 - LocalSet : 422 - Loop : 173 - Nop : 134 - RefFunc : 24 - Return : 122 - Select : 58 - Store : 57 - Switch : 6 - Unary : 605 - Unreachable : 269 + [total] : 10300 + [vars] : 325 + Binary : 679 + Block : 1736 + Break : 323 + Call : 340 + CallIndirect : 93 + Const : 1686 + Drop : 129 + GlobalGet : 882 + GlobalSet : 665 + If : 581 + Load : 146 + LocalGet : 738 + LocalSet : 614 + Loop : 211 + Nop : 156 + RefFunc : 49 + Return : 111 + Select : 67 + Store : 83 + Switch : 1 + Unary : 680 + Unreachable : 330 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index ce183485c1e..96475668524 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 84 - [funcs] : 112 + [exports] : 46 + [funcs] : 62 [globals] : 17 [imports] : 4 [memories] : 1 [memory-data] : 11 - [table-data] : 36 + [table-data] : 11 [tables] : 1 [tags] : 0 - [total] : 7833 - [vars] : 292 - Binary : 578 - Block : 1340 - Break : 204 - Call : 414 - CallIndirect : 35 - Const : 1256 - Drop : 107 - GlobalGet : 698 - GlobalSet : 535 - If : 418 - Load : 134 - LocalGet : 502 - LocalSet : 331 - Loop : 149 - Nop : 87 - RefFunc : 36 - Return : 103 - Select : 49 - Store : 70 - Switch : 6 - Unary : 508 - Unreachable : 273 + [total] : 4599 + [vars] : 195 + Binary : 353 + Block : 753 + Break : 106 + Call : 195 + CallIndirect : 39 + Const : 776 + Drop : 77 + GlobalGet : 381 + GlobalSet : 299 + If : 220 + Load : 83 + LocalGet : 346 + LocalSet : 266 + Loop : 83 + Nop : 46 + RefFunc : 11 + Return : 67 + Select : 20 + Store : 33 + Switch : 2 + Unary : 299 + Unreachable : 144 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index d36325f58b8..59274c2dbfb 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,45 +1,46 @@ Metrics total - [exports] : 14 - [funcs] : 18 + [exports] : 13 + [funcs] : 14 [globals] : 4 [imports] : 12 [memories] : 1 [memory-data] : 112 - [table-data] : 3 + [table-data] : 2 [tables] : 1 [tags] : 0 - [total] : 626 - [vars] : 49 - ArrayNew : 1 - ArrayNewFixed : 5 - ArraySet : 1 - AtomicCmpxchg : 1 - AtomicFence : 1 + [total] : 654 + [vars] : 15 + ArrayGet : 1 + ArrayLen : 1 + ArrayNew : 6 + ArrayNewFixed : 11 Binary : 74 - Block : 79 - Call : 40 - Const : 138 - Drop : 9 - GlobalGet : 42 - GlobalSet : 38 - If : 20 - Load : 16 - LocalGet : 46 - LocalSet : 22 - Loop : 2 - Nop : 4 + Block : 64 + Call : 43 + CallIndirect : 1 + Const : 170 + Drop : 7 + GlobalGet : 38 + GlobalSet : 32 + If : 18 + Load : 17 + LocalGet : 42 + LocalSet : 23 + Loop : 3 + Nop : 3 RefAs : 2 - RefFunc : 8 - RefNull : 4 + RefFunc : 7 + RefNull : 7 Return : 3 Select : 1 Store : 1 - StringConst : 3 - StructNew : 18 + StringConst : 1 + StringEncode : 1 + StructNew : 37 StructSet : 1 Throw : 1 TryTable : 2 TupleMake : 3 - Unary : 21 - Unreachable : 19 + Unary : 17 + Unreachable : 16 From 60d5626fc771034e23062f28b5b91abf5d746598 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Feb 2025 17:15:05 -0800 Subject: [PATCH 294/622] [EH] Fuzz catch+rethrow on the JS side (#7287) Add a parameter to call-export, call-ref, where the first bit says "catch and rethrow any exception". This has no observable effect in binaryen, but causes us to use different code paths in VMs (for example, a wasm exception may end up caught in JS, then thrown in JS; passing wasm exnrefs to JS for throwing is not possible otherwise, so this is the closest we can get). --- scripts/fuzz_shell.js | 48 +++++++++++---- src/tools/execution-results.h | 19 +++--- src/tools/fuzzing/fuzzing.cpp | 30 ++++++++-- test/lit/d8/fuzz_shell_exceptions.wast | 2 +- test/lit/exec/fuzzing-api.wast | 83 +++++++++++++++++++++++++- 5 files changed, 157 insertions(+), 25 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index de1ef135202..6a70dc1f9cd 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -170,14 +170,18 @@ function callFunc(func) { return func.apply(null, args); } -// Calls a given function in a try-catch, swallowing JS exceptions, and return 1 -// if we did in fact swallow an exception. Wasm traps are not swallowed (see -// details below). -/* async */ function tryCall(func) { +// Calls a given function in a try-catch. Return 1 if an exception was thrown. +// If |rethrow| is set, and an exception is thrown, it is caught and rethrown. +// Wasm traps are not swallowed (see details below). +/* async */ function tryCall(func, rethrow) { try { /* await */ func(); return 0; } catch (e) { + // The exception must exist, and not behave oddly when we access a + // property on it. (VM bugs could cause errors here.) + e.a; + // We only want to catch exceptions, not wasm traps: traps should still // halt execution. Handling this requires different code in wasm2js, so // check for that first (wasm2js does not define RuntimeError, so use @@ -208,7 +212,11 @@ function callFunc(func) { } } // Otherwise, this is a normal exception we want to catch (a wasm - // exception, or a conversion error on the wasm/JS boundary, etc.). + // exception, or a conversion error on the wasm/JS boundary, etc.). Rethrow + // if we were asked to. + if (rethrow) { + throw e; + } return 1; } } @@ -271,7 +279,7 @@ var imports = { 'throw': (which) => { if (!which) { // Throw a JS exception. - throw 0; + throw new Error('js exception'); } else { // Throw a wasm exception. throw new WebAssembly.Exception(wasmTag, [which]); @@ -287,19 +295,39 @@ var imports = { }, // Export operations. - 'call-export': /* async */ (index) => { - /* await */ callFunc(exportList[index].value); + 'call-export': /* async */ (index, flags) => { + var rethrow = flags & 1; + if (JSPI) { + // TODO: Figure out why JSPI fails here. + rethrow = 0; + } + if (!rethrow) { + /* await */ callFunc(exportList[index].value); + } else { + tryCall(/* async */ () => /* await */ callFunc(exportList[index].value), + rethrow); + } }, 'call-export-catch': /* async */ (index) => { return tryCall(/* async */ () => /* await */ callFunc(exportList[index].value)); }, // Funcref operations. - 'call-ref': /* async */ (ref) => { + 'call-ref': /* async */ (ref, flags) => { // This is a direct function reference, and just like an export, it must // be wrapped for JSPI. ref = wrapExportForJSPI(ref); - /* await */ callFunc(ref); + var rethrow = flags & 1; + if (JSPI) { + // TODO: Figure out why JSPI fails here. + rethrow = 0; + } + if (!rethrow) { + /* await */ callFunc(ref); + } else { + tryCall(/* async */ () => /* await */ callFunc(ref), + rethrow); + } }, 'call-ref-catch': /* async */ (ref) => { ref = wrapExportForJSPI(ref); diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 9ef409a385d..508ea816312 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -132,6 +132,10 @@ struct LoggingExternalInterface : public ShellExternalInterface { return {}; } else if (import->base == "call-export") { callExportAsJS(arguments[0].geti32()); + // The second argument determines if we should catch and rethrow + // exceptions. There is no observable difference in those two modes in + // the binaryen interpreter, so we don't need to do anything. + // Return nothing. If we wanted to return a value we'd need to have // multiple such functions, one for each signature. return {}; @@ -143,9 +147,8 @@ struct LoggingExternalInterface : public ShellExternalInterface { return {Literal(int32_t(1))}; } } else if (import->base == "call-ref") { + // Similar to call-export*, but with a ref. callRefAsJS(arguments[0]); - // Return nothing. If we wanted to return a value we'd need to have - // multiple such functions, one for each signature. return {}; } else if (import->base == "call-ref-catch") { try { @@ -181,11 +184,13 @@ struct LoggingExternalInterface : public ShellExternalInterface { } void throwJSException() { - // JS exceptions contain an externref, which wasm can't read (so the actual - // value here does not matter, but it does need to match what the 'throw' - // import does in fuzz_shell.js, as the fuzzer will do comparisons). - Literal externref = Literal::makeI31(0, Unshared).externalize(); - Literals arguments = {externref}; + // JS exceptions contain an externref. Use the same type of value as a JS + // exception would have, which is a reference to an object, and which will + // print out "object" in the logging from JS. A trivial struct is enough for + // us to log the same thing here. + auto empty = HeapType(Struct{}); + auto inner = Literal(std::make_shared(empty, Literals{}), empty); + Literals arguments = {inner.externalize()}; auto payload = std::make_shared(jsTag, arguments); throwException(WasmException{Literal(payload)}); } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 1dd8bc66d27..bebe67d5d40 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -851,12 +851,16 @@ void TranslateToFuzzReader::addImportCallingSupport() { if (choice & 1) { // Given an export index, call it from JS. + // A second parameter has flags. The first bit determines whether we catch + // and rethrow all exceptions. (This ends up giving us the same signature + // and behavior as when we do not rethrow, so we just add the flags here + // rather than another export.) callExportImportName = Names::getValidFunctionName(wasm, "call-export"); auto func = std::make_unique(); func->name = callExportImportName; func->module = "fuzzing-support"; func->base = "call-export"; - func->type = Signature({Type::i32}, Type::none); + func->type = Signature({Type::i32, Type::i32}, Type::none); wasm.addFunction(std::move(func)); } @@ -884,7 +888,10 @@ void TranslateToFuzzReader::addImportCallingSupport() { func->name = callRefImportName; func->module = "fuzzing-support"; func->base = "call-ref"; - func->type = Signature({Type(HeapType::func, Nullable)}, Type::none); + // As call-export, there is a flags param that allows us to catch+rethrow + // all exceptions. + func->type = + Signature({Type(HeapType::func, Nullable), Type::i32}, Type::none); wasm.addFunction(std::move(func)); } @@ -1135,7 +1142,13 @@ Expression* TranslateToFuzzReader::makeImportCallCode(Type type) { if ((catching && (!exportTarget || oneIn(2))) || (!catching && oneIn(4))) { // Most of the time make a non-nullable funcref, to avoid errors. auto refType = Type(HeapType::func, oneIn(10) ? Nullable : NonNullable); - return builder.makeCall(refTarget, {make(refType)}, type); + std::vector args = {make(refType)}; + if (!catching) { + // Only the first bit matters here, so we can send anything (this is + // future-proof for later bits, and has no downside now). + args.push_back(make(Type::i32)); + } + return builder.makeCall(refTarget, args, type); } } @@ -1163,7 +1176,16 @@ Expression* TranslateToFuzzReader::makeImportCallCode(Type type) { index = builder.makeBinary( RemUInt32, index, builder.makeConst(int32_t(maxIndex))); } - return builder.makeCall(exportTarget, {index}, type); + + // The non-catching variants send a flags argument, which says whether to + // catch+rethrow. + std::vector args = {index}; + if (!catching) { + // Only the first bit matters here, so we can send anything (this is + // future-proof for later bits, and has no downside now). + args.push_back(make(Type::i32)); + } + return builder.makeCall(exportTarget, args, type); } Expression* TranslateToFuzzReader::makeImportSleep(Type type) { diff --git a/test/lit/d8/fuzz_shell_exceptions.wast b/test/lit/d8/fuzz_shell_exceptions.wast index da1e7fbe0aa..02c3ffb469d 100644 --- a/test/lit/d8/fuzz_shell_exceptions.wast +++ b/test/lit/d8/fuzz_shell_exceptions.wast @@ -31,7 +31,7 @@ ;; RUN: v8 %S/../../../scripts/fuzz_shell.js -- %t.wasm | filecheck %s ;; ;; CHECK: [fuzz-exec] calling throwing-js -;; CHECK: exception thrown: 0 +;; CHECK: exception thrown: Error: js exception ;; CHECK: [fuzz-exec] calling throwing-tag ;; CHECK: exception thrown: [object WebAssembly.Exception] diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 0d9eb0c9cc1..2595d7e6e60 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -13,10 +13,10 @@ (import "fuzzing-support" "table-set" (func $table.set (param i32 funcref))) (import "fuzzing-support" "table-get" (func $table.get (param i32) (result funcref))) - (import "fuzzing-support" "call-export" (func $call.export (param i32))) + (import "fuzzing-support" "call-export" (func $call.export (param i32 i32))) (import "fuzzing-support" "call-export-catch" (func $call.export.catch (param i32) (result i32))) - (import "fuzzing-support" "call-ref" (func $call.ref (param funcref))) + (import "fuzzing-support" "call-ref" (func $call.ref (param funcref i32))) (import "fuzzing-support" "call-ref-catch" (func $call.ref.catch (param funcref) (result i32))) (import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32))) @@ -110,10 +110,32 @@ ;; At index 0 in the exports we have $logging, so we will do those loggings. (call $call.export (i32.const 0) + ;; First bit unset in the flags means a normal call. + (i32.const 0) + ) + ;; At index 999 we have nothing, so we'll error. + (call $call.export + (i32.const 999) + (i32.const 0) + ) + ) + + ;; CHECK: [fuzz-exec] calling export.calling.rethrow + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] + (func $export.calling.rethrow (export "export.calling.rethrow") + ;; As above, but the second param is different. + (call $call.export + (i32.const 0) + ;; First bit set in the flags means a catch+rethrow. There is no visible + ;; effect here, but there might be in JS VMs. + (i32.const 1) ) ;; At index 999 we have nothing, so we'll error. (call $call.export (i32.const 999) + (i32.const 1) ) ) @@ -146,10 +168,31 @@ ;; This will emit some logging. (call $call.ref (ref.func $logging) + ;; Normal call. + (i32.const 0) + ) + ;; This will throw. + (call $call.ref + (ref.null func) + (i32.const 0) + ) + ) + + ;; CHECK: [fuzz-exec] calling ref.calling.rethrow + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] + (func $ref.calling.rethrow (export "ref.calling.rethrow") + ;; As with calling an export, when we set the flags to 1 exceptions are + ;; caught and rethrown, but there is no noticeable difference here. + (call $call.ref + (ref.func $logging) + (i32.const 1) ) ;; This will throw. (call $call.ref (ref.null func) + (i32.const 1) ) ) @@ -195,6 +238,7 @@ ;; logging from the function, "12". (call $call.ref (ref.func $legal) + (i32.const 1) ) ) @@ -335,7 +379,6 @@ ;; CHECK: [fuzz-exec] calling do-sleep ;; CHECK-NEXT: [fuzz-exec] note result: do-sleep => 42 - ;; CHECK-NEXT: warning: no passes specified, not doing any work (func $do-sleep (export "do-sleep") (result i32) (call $sleep ;; A ridiculous amount of ms, but in the interpreter it is ignored anyhow. @@ -344,6 +387,24 @@ (i32.const 42) ) ) + + ;; CHECK: [fuzz-exec] calling return-externref-exception + ;; CHECK-NEXT: [fuzz-exec] note result: return-externref-exception => object + ;; CHECK-NEXT: warning: no passes specified, not doing any work + (func $return-externref-exception (export "return-externref-exception") (result externref) + ;; Call JS table.set in a way that throws (on out of bounds). The JS exception + ;; is caught and returned from the function, so we can see what it looks like + ;; to the fuzzer, which should be "object" (an exception object). + (block $block (result externref) + (try_table (catch $imported-js-tag $block) + (call $table.set + (i32.const 99990) + (ref.null func) + ) + ) + (unreachable) + ) + ) ) ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -368,6 +429,11 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] +;; CHECK: [fuzz-exec] calling export.calling.rethrow +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] + ;; CHECK: [fuzz-exec] calling export.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] @@ -379,6 +445,11 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] +;; CHECK: [fuzz-exec] calling ref.calling.rethrow +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] + ;; CHECK: [fuzz-exec] calling ref.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] @@ -413,10 +484,14 @@ ;; CHECK: [fuzz-exec] calling do-sleep ;; CHECK-NEXT: [fuzz-exec] note result: do-sleep => 42 + +;; CHECK: [fuzz-exec] calling return-externref-exception +;; CHECK-NEXT: [fuzz-exec] note result: return-externref-exception => object ;; CHECK-NEXT: [fuzz-exec] comparing catch-js-tag ;; CHECK-NEXT: [fuzz-exec] comparing do-sleep ;; CHECK-NEXT: [fuzz-exec] comparing export.calling ;; CHECK-NEXT: [fuzz-exec] comparing export.calling.catching +;; CHECK-NEXT: [fuzz-exec] comparing export.calling.rethrow ;; CHECK-NEXT: [fuzz-exec] comparing logging ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.catching @@ -426,7 +501,9 @@ ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.illegal-v128 ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.legal ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.legal-result +;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.rethrow ;; CHECK-NEXT: [fuzz-exec] comparing ref.calling.trap +;; CHECK-NEXT: [fuzz-exec] comparing return-externref-exception ;; CHECK-NEXT: [fuzz-exec] comparing table.getting ;; CHECK-NEXT: [fuzz-exec] comparing table.setting ;; CHECK-NEXT: [fuzz-exec] comparing throwing From 2bee4bbb3084dc9734a14c76b567d285d54319b3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Feb 2025 17:20:04 -0800 Subject: [PATCH 295/622] [EH] Fuzzer: Fix infinite loop with nested exnrefs (#7294) When we need an exnref value we may create a try-catch with a throw (so when we catch it, we get an exnref). But if the exception tag contains an exnref, then we will try to create another exnref in the throw, leading to recursion (possibly infinite). To avoid this, just create a trivial tag with no values when we just want a trivial value. --- src/tools/fuzzing.h | 3 +++ src/tools/fuzzing/fuzzing.cpp | 24 +++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 82a9f86b0a4..a3985cab09b 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -200,6 +200,9 @@ class TranslateToFuzzReader { Index numAddedFunctions = 0; + // The name of an empty tag. + Name trivialTag; + // RAII helper for managing the state used to create a single function. struct FunctionCreationContext { TranslateToFuzzReader& parent; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index bebe67d5d40..6d965f2e372 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -4830,10 +4830,28 @@ Expression* TranslateToFuzzReader::makeI31Get(Type type) { Expression* TranslateToFuzzReader::makeThrow(Type type) { assert(type == Type::unreachable); - if (wasm.tags.empty()) { - addTag(); + Tag* tag; + if (trivialNesting) { + // We are nested under a makeTrivial call, so only emit something trivial. + // Get (or create) a trivial tag, so we have no operands (and will not call + // make(), below). Otherwise, we might recurse very deeply if we threw a + // tag that contains an exnref (for which we may end up creating yet another + // throw in a try). + if (!trivialTag) { + auto newTag = builder.makeTag(Names::getValidTagName(wasm, "tag$"), + Signature(Type::none, Type::none)); + tag = wasm.addTag(std::move(newTag)); + trivialTag = tag->name; + } else { + tag = wasm.getTag(trivialTag); + } + } else { + // Get a random tag, adding a random one if necessary. + if (wasm.tags.empty()) { + addTag(); + } + tag = pick(wasm.tags).get(); } - auto* tag = pick(wasm.tags).get(); auto tagType = tag->params(); std::vector operands; for (auto t : tagType) { From 19dd23db3c20214e2e6a73023529c2496f9c2a50 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Feb 2025 18:16:03 -0800 Subject: [PATCH 296/622] [EH] Fuzz exnref in tables (#7289) * Add an exnref table. * Add table operations (also for funcref, while we are at it). * Add exnref to getConcrete(), so there is a chance to emit table.get. --- src/tools/fuzzing.h | 3 + src/tools/fuzzing/fuzzing.cpp | 84 ++++++++++++++++- ...e-to-fuzz_all-features_metrics_noprint.txt | 93 +++++++++++-------- 3 files changed, 139 insertions(+), 41 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index a3985cab09b..b67c3a23cd9 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -161,6 +161,7 @@ class TranslateToFuzzReader { Name HANG_LIMIT_GLOBAL; Name funcrefTableName; + Name exnrefTableName; std::unordered_map logImportNames; Name throwImportName; @@ -465,6 +466,8 @@ class TranslateToFuzzReader { Expression* makeSIMDShift(); Expression* makeSIMDLoad(); Expression* makeBulkMemory(Type type); + Expression* makeTableGet(Type type); + Expression* makeTableSet(Type type); // TODO: support other RefIs variants, and rename this Expression* makeRefIsNull(Type type); Expression* makeRefEq(Type type); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 6d965f2e372..c5aa3fa9f9e 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -546,6 +546,22 @@ void TranslateToFuzzReader::setupTables() { segment->setName(Names::getValidElementSegmentName(wasm, "elem$"), false); wasm.addElementSegment(std::move(segment)); } + + // When EH is enabled, set up an exnref table. + if (wasm.features.hasExceptionHandling()) { + Type exnref = Type(HeapType::exn, Nullable); + Address initial = upTo(10); + Address max = oneIn(2) ? initial + upTo(4) : Memory::kUnlimitedSize; + auto tablePtr = + builder.makeTable(Names::getValidTableName(wasm, "exnref_table"), + exnref, + initial, + max, + Type::i32); // TODO: wasm64 + tablePtr->hasExplicitName = true; + table = wasm.addTable(std::move(tablePtr)); + exnrefTableName = table->name; + } } void TranslateToFuzzReader::setupGlobals() { @@ -1953,6 +1969,14 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { if (heapType.isBasic()) { options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBasicRef); + if (type.isNullable() && funcContext) { + if (heapType == HeapType::func) { + options.add(FeatureSet::ReferenceTypes, &Self::makeTableGet); + } + if (heapType == HeapType::exn) { + options.add(FeatureSet::ExceptionHandling, &Self::makeTableGet); + } + } } else { options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCompoundRef); @@ -2000,6 +2024,7 @@ Expression* TranslateToFuzzReader::_makenone() { &Self::makeGlobalSet) .add(FeatureSet::BulkMemory, &Self::makeBulkMemory) .add(FeatureSet::Atomics, &Self::makeAtomic) + .add(FeatureSet::ReferenceTypes, &Self::makeTableSet) .add(FeatureSet::ExceptionHandling, &Self::makeTry) .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) .add(FeatureSet::ExceptionHandling, &Self::makeImportThrowing) @@ -4418,6 +4443,58 @@ Expression* TranslateToFuzzReader::makeBulkMemory(Type type) { WASM_UNREACHABLE("invalid value"); } +Expression* TranslateToFuzzReader::makeTableGet(Type type) { + // Emit a get from the funcref table (which always exists) or the exnref one + // (which might not, if EH is disabled). + auto makeTableGet = [&](Name tableName) { + auto* table = wasm.getTable(tableName); + // Usually emit in-bounds gets, to avoid trapping, but rarely allow + // anything. + Expression* index; + if (allowOOB && oneIn(10)) { + index = make(table->addressType); + } else { + index = builder.makeConst( + Literal::makeFromInt32(upTo(table->initial), table->addressType)); + } + return builder.makeTableGet(tableName, index, table->type); + }; + if (type.getHeapType() == HeapType::exn) { + return makeTableGet(exnrefTableName); + } else if (type.getHeapType() == HeapType::func) { + return makeTableGet(funcrefTableName); + } else { + WASM_UNREACHABLE("bad TableGet type"); + } +} + +Expression* TranslateToFuzzReader::makeTableSet(Type type) { + assert(type == Type::none); + + // Emit a set to either the funcref table (which always exists) or the exnref + // one (which might not, if EH is disabled). + auto makeTableSet = [&](Name tableName) { + auto* table = wasm.getTable(tableName); + // Usually emit in-bounds sets, to avoid trapping, but rarely allow + // anything. + Expression* index; + if (allowOOB && oneIn(10)) { + index = make(table->addressType); + } else { + index = builder.makeConst( + Literal::makeFromInt32(upTo(table->initial), table->addressType)); + } + auto* value = make(table->type); + return builder.makeTableSet(tableName, index, value); + }; + if (exnrefTableName && oneIn(2)) { + return makeTableSet(exnrefTableName); + } else { + assert(funcrefTableName); + return makeTableSet(funcrefTableName); + } +} + Expression* TranslateToFuzzReader::makeRefIsNull(Type type) { assert(type == Type::i32); assert(wasm.features.hasReferenceTypes()); @@ -4922,8 +4999,8 @@ Type TranslateToFuzzReader::getSingleConcreteType() { auto nullability = getNullability(); return Type(heapType, nullability); } - // Skip (ref func), (ref extern), and (ref i31) for now - // because there is no way to create them in globals. TODO. + // Skip (ref func|extern|i31|exn) because there is no way to create them in + // globals. TODO using WeightedOption = FeatureOptions::WeightedOption; return pick(FeatureOptions() .add(FeatureSet::MVP, @@ -4935,6 +5012,9 @@ Type TranslateToFuzzReader::getSingleConcreteType() { .add(FeatureSet::ReferenceTypes, Type(HeapType::func, Nullable), Type(HeapType::ext, Nullable)) + .add(FeatureSet::ExceptionHandling, + // Type(HeapType::exn, NonNullable), + Type(HeapType::exn, Nullable)) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, // Type(HeapType::func, NonNullable), // Type(HeapType::ext, NonNullable), diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 59274c2dbfb..1b21bb54d81 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,46 +1,61 @@ Metrics total - [exports] : 13 - [funcs] : 14 - [globals] : 4 - [imports] : 12 + [exports] : 9 + [funcs] : 8 + [globals] : 1 + [imports] : 11 [memories] : 1 [memory-data] : 112 - [table-data] : 2 - [tables] : 1 + [table-data] : 0 + [tables] : 2 [tags] : 0 - [total] : 654 - [vars] : 15 - ArrayGet : 1 - ArrayLen : 1 - ArrayNew : 6 - ArrayNewFixed : 11 - Binary : 74 - Block : 64 - Call : 43 - CallIndirect : 1 - Const : 170 - Drop : 7 - GlobalGet : 38 - GlobalSet : 32 - If : 18 - Load : 17 - LocalGet : 42 - LocalSet : 23 - Loop : 3 - Nop : 3 - RefAs : 2 - RefFunc : 7 + [total] : 738 + [vars] : 40 + ArrayCopy : 1 + ArrayFill : 1 + ArrayLen : 3 + ArrayNew : 5 + ArrayNewFixed : 1 + AtomicFence : 2 + AtomicNotify : 2 + AtomicRMW : 1 + Binary : 82 + Block : 80 + BrOn : 4 + Break : 13 + Call : 29 + CallRef : 1 + Const : 134 + Drop : 15 + GlobalGet : 26 + GlobalSet : 26 + I31Get : 3 + If : 26 + Load : 16 + LocalGet : 85 + LocalSet : 51 + Loop : 6 + MemoryFill : 2 + Nop : 5 + Pop : 4 + RefAs : 10 + RefEq : 1 + RefFunc : 2 + RefI31 : 12 + RefIsNull : 1 RefNull : 7 - Return : 3 - Select : 1 + RefTest : 2 + Return : 4 + SIMDExtract : 7 + Select : 7 Store : 1 - StringConst : 1 - StringEncode : 1 - StructNew : 37 - StructSet : 1 - Throw : 1 - TryTable : 2 - TupleMake : 3 - Unary : 17 - Unreachable : 16 + StringConst : 3 + StringEncode : 2 + StringMeasure : 1 + StructGet : 2 + StructNew : 2 + StructSet : 2 + Try : 6 + TryTable : 5 + Unary : 24 + Unreachable : 13 From 32b7a25a5cebd0dfc659d3105c64aefcb38251a0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 14 Feb 2025 13:57:12 -0800 Subject: [PATCH 297/622] [NFC] Remove no longer needed v8 flag to avoid warnings (#7297) Recent v8 prints "warning: unrecognized flag" about it. This warning broke a test. Add some logging to the test as well, to make it easier to debug in the future. --- scripts/fuzz_opt.py | 5 ++++- scripts/test/shared.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 823c57780a0..dbe59eec456 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1718,7 +1718,10 @@ def handle(self, wasm): # Make sure that fuzz_shell.js actually executed all exports from both # wasm files. exports = get_exports(wasm, ['func']) + get_exports(second_wasm, ['func']) - assert output.count(FUZZ_EXEC_CALL_PREFIX) == len(exports) + calls_in_output = output.count(FUZZ_EXEC_CALL_PREFIX) + if calls_in_output == 0: + print(f'warning: no calls in output. output:\n{output}') + assert calls_in_output == len(exports) output = fix_output(output) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 489cfefda09..530f386fddc 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -255,7 +255,6 @@ def has_shell_timeout(): V8_OPTS = [ '--wasm-staging', '--experimental-wasm-compilation-hints', - '--experimental-wasm-memory64', '--experimental-wasm-stringref', '--experimental-wasm-fp16', ] From d9fa84a038baaf0b25fb4822a00083f617b9c657 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 14 Feb 2025 14:26:39 -0800 Subject: [PATCH 298/622] Fuzzer: Adjust parameters by input size, sometimes (#7276) Rather than always have a fixed max of heap types, globals, etc. allow more if the input size is large. This adds variety. There is a cost to this variety, we go from 0.89% ignored runs to 1.1% (that is, slightly more runs contain something that prevents us from doing the work we want, like hitting a host limitation or seeing the testcase just traps). This increase seems small enough to be reasonable, and we only do this half the time, so the old behavior is still being tested at half throughput. --- src/tools/fuzzing/fuzzing.cpp | 50 ++++++++- src/tools/fuzzing/random.h | 10 ++ test/passes/fuzz_metrics_noprint.bin.txt | 60 +++++----- .../fuzz_metrics_passes_noprint.bin.txt | 57 +++++----- ...e-to-fuzz_all-features_metrics_noprint.txt | 105 +++++++++--------- 5 files changed, 165 insertions(+), 117 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index c5aa3fa9f9e..e55d94868ac 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -36,10 +36,6 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, : wasm(wasm), closedWorld(closedWorld), builder(wasm), random(std::move(input), wasm.features) { - // Half the time add no unreachable code so that we'll execute the most code - // as possible with no early exits. - allowAddingUnreachableCode = oneIn(2); - // - funcref cannot be logged because referenced functions can be inlined or // removed during optimization // - there's no point in logging anyref because it is opaque @@ -49,7 +45,53 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, loggableTypes.push_back(Type::v128); } + // Setup params. Start with the defaults. globalParams = std::make_unique(*this); + + // Some of the time, adjust parameters based on the size, e.g. allowing more + // heap types in larger inputs, etc. + if (random.oneIn(2)) { + // A typical random wasm input from fuzz_opt.py is fairly large (to minimize + // the process creation overhead of all the things we run from python), and + // the defaults are tuned to that. This corresponds to INPUT_SIZE_MEAN in + // scripts/fuzz_opt.py + const double MEAN_SIZE = 40 * 1024; + + // As we have not read anything from the input, the remaining size is its + // size. + double size = random.remaining(); + auto ratio = size / MEAN_SIZE; + + auto bits = random.get(); + if (bits & 1) { + fuzzParams->MAX_NEW_GC_TYPES *= ratio; + } + if (bits & 2) { + fuzzParams->MAX_GLOBALS *= ratio; + } + if (bits & 4) { + // Only adjust the limit if there is one. + if (fuzzParams->HANG_LIMIT) { + fuzzParams->HANG_LIMIT *= ratio; + // There is a limit, so keep it non-zero to actually prevent hangs. + fuzzParams->HANG_LIMIT = std::max(fuzzParams->HANG_LIMIT, 1); + } + } + if (bits & 8) { + // Only increase the number of tries. Trying fewer times does not help + // find more interesting patterns. + if (ratio > 1) { + fuzzParams->TRIES *= ratio; + } + } + if (bits & 16) { + fuzzParams->MAX_ARRAY_SIZE *= ratio; + } + } + + // Half the time add no unreachable code so that we'll execute the most code + // as possible with no early exits. + allowAddingUnreachableCode = oneIn(2); } TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, diff --git a/src/tools/fuzzing/random.h b/src/tools/fuzzing/random.h index 845aa2fa580..21182c97e0b 100644 --- a/src/tools/fuzzing/random.h +++ b/src/tools/fuzzing/random.h @@ -63,6 +63,16 @@ class Random { bool finished() { return finishedInput; } + // How many bytes of data remain to be used. + size_t remaining() { + if (finishedInput) { + // We finished it and are cycling through it again (using xorFactor to try + // to improve the entropy). + return 0; + } + return bytes.size() - pos; + } + // Pick from a vector-like container template const typename T::value_type& pick(const T& vec) { assert(!vec.empty()); diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index 87b7a851019..5c7e64baa92 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 84 - [funcs] : 115 - [globals] : 18 - [imports] : 4 + [exports] : 24 + [funcs] : 30 + [globals] : 21 + [imports] : 5 [memories] : 1 - [memory-data] : 24 - [table-data] : 49 + [memory-data] : 5 + [table-data] : 8 [tables] : 1 [tags] : 0 - [total] : 10300 - [vars] : 325 - Binary : 679 - Block : 1736 - Break : 323 - Call : 340 - CallIndirect : 93 - Const : 1686 - Drop : 129 - GlobalGet : 882 - GlobalSet : 665 - If : 581 - Load : 146 - LocalGet : 738 - LocalSet : 614 - Loop : 211 - Nop : 156 - RefFunc : 49 - Return : 111 - Select : 67 - Store : 83 - Switch : 1 - Unary : 680 - Unreachable : 330 + [total] : 3231 + [vars] : 112 + Binary : 266 + Block : 529 + Break : 115 + Call : 122 + CallIndirect : 3 + Const : 529 + Drop : 31 + GlobalGet : 271 + GlobalSet : 190 + If : 181 + Load : 58 + LocalGet : 233 + LocalSet : 177 + Loop : 65 + Nop : 59 + RefFunc : 8 + Return : 31 + Select : 16 + Store : 29 + Switch : 4 + Unary : 220 + Unreachable : 94 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index 96475668524..77fa5dc3218 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,35 +1,34 @@ Metrics total - [exports] : 46 - [funcs] : 62 - [globals] : 17 + [exports] : 39 + [funcs] : 53 + [globals] : 7 [imports] : 4 [memories] : 1 - [memory-data] : 11 - [table-data] : 11 + [memory-data] : 29 + [table-data] : 24 [tables] : 1 [tags] : 0 - [total] : 4599 - [vars] : 195 - Binary : 353 - Block : 753 - Break : 106 - Call : 195 - CallIndirect : 39 - Const : 776 - Drop : 77 - GlobalGet : 381 - GlobalSet : 299 - If : 220 - Load : 83 - LocalGet : 346 - LocalSet : 266 - Loop : 83 - Nop : 46 - RefFunc : 11 - Return : 67 - Select : 20 - Store : 33 - Switch : 2 - Unary : 299 - Unreachable : 144 + [total] : 4272 + [vars] : 162 + Binary : 328 + Block : 670 + Break : 131 + Call : 194 + CallIndirect : 38 + Const : 764 + Drop : 65 + GlobalGet : 364 + GlobalSet : 260 + If : 219 + Load : 80 + LocalGet : 299 + LocalSet : 209 + Loop : 77 + Nop : 41 + RefFunc : 24 + Return : 32 + Select : 34 + Store : 28 + Unary : 284 + Unreachable : 131 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 1b21bb54d81..a145cb716a5 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,61 +1,58 @@ Metrics total - [exports] : 9 - [funcs] : 8 - [globals] : 1 - [imports] : 11 + [exports] : 21 + [funcs] : 26 + [globals] : 4 + [imports] : 12 [memories] : 1 - [memory-data] : 112 - [table-data] : 0 + [memory-data] : 17 + [table-data] : 4 [tables] : 2 - [tags] : 0 - [total] : 738 - [vars] : 40 - ArrayCopy : 1 - ArrayFill : 1 - ArrayLen : 3 - ArrayNew : 5 - ArrayNewFixed : 1 - AtomicFence : 2 + [tags] : 3 + [total] : 960 + [vars] : 45 + ArrayNew : 1 + ArrayNewFixed : 9 AtomicNotify : 2 - AtomicRMW : 1 - Binary : 82 - Block : 80 - BrOn : 4 - Break : 13 - Call : 29 - CallRef : 1 - Const : 134 + Binary : 93 + Block : 156 + BrOn : 2 + Break : 5 + Call : 55 + CallIndirect : 1 + Const : 173 + DataDrop : 1 Drop : 15 - GlobalGet : 26 - GlobalSet : 26 - I31Get : 3 - If : 26 - Load : 16 - LocalGet : 85 - LocalSet : 51 - Loop : 6 - MemoryFill : 2 - Nop : 5 - Pop : 4 - RefAs : 10 - RefEq : 1 - RefFunc : 2 - RefI31 : 12 - RefIsNull : 1 - RefNull : 7 - RefTest : 2 - Return : 4 - SIMDExtract : 7 - Select : 7 - Store : 1 - StringConst : 3 - StringEncode : 2 - StringMeasure : 1 - StructGet : 2 - StructNew : 2 - StructSet : 2 - Try : 6 + GlobalGet : 77 + GlobalSet : 70 + If : 40 + Load : 17 + LocalGet : 48 + LocalSet : 24 + Loop : 10 + MemoryFill : 1 + MemoryInit : 1 + Nop : 12 + Pop : 2 + RefEq : 2 + RefFunc : 4 + RefI31 : 3 + RefNull : 9 + RefTest : 1 + Return : 6 + SIMDExtract : 4 + Select : 2 + Store : 3 + StringConst : 8 + StringEncode : 1 + StringEq : 1 + StringWTF16Get : 1 + StructNew : 4 + TableSet : 1 + Throw : 4 + Try : 1 TryTable : 5 - Unary : 24 - Unreachable : 13 + TupleExtract : 1 + TupleMake : 3 + Unary : 45 + Unreachable : 36 From 2e26ec6e4005f50358d62a54167e27bb13ff52f9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Feb 2025 10:56:33 -0800 Subject: [PATCH 299/622] [Parser] Fix Table64 parsing (#7298) We had some hardcoded i32 indexes, but the table might be table64. Also fix some IRBuilder calls of ChildTyper, that now need to know the table name. --- src/ir/child-typer.h | 30 +++++++++++++------ src/wasm/wasm-ir-builder.cpp | 3 ++ test/lit/passes/roundtrip-table.wast | 44 +++++++++++++++++++++++++--- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 5d2bed102e4..68398fa906b 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -81,6 +81,10 @@ template struct ChildTyper : OverriddenVisitor { note(ptrp, wasm.getMemory(mem)->addressType); } + void noteTableIndex(Expression** indexp, Name table) { + note(indexp, wasm.getTable(table)->addressType); + } + void noteAny(Expression** childp) { self().noteAnyType(childp); } void noteAnyReference(Expression** childp) { @@ -746,10 +750,12 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->right, eqref); } - void visitTableGet(TableGet* curr) { note(&curr->index, Type::i32); } + void visitTableGet(TableGet* curr) { + noteTableIndex(&curr->index, curr->table); + } void visitTableSet(TableSet* curr) { - note(&curr->index, Type::i32); + noteTableIndex(&curr->index, curr->table); note(&curr->value, wasm.getTable(curr->table)->type); } @@ -757,24 +763,30 @@ template struct ChildTyper : OverriddenVisitor { void visitTableGrow(TableGrow* curr) { note(&curr->value, wasm.getTable(curr->table)->type); - note(&curr->delta, Type::i32); + noteTableIndex(&curr->delta, curr->table); } void visitTableFill(TableFill* curr) { auto type = wasm.getTable(curr->table)->type; - note(&curr->dest, Type::i32); + noteTableIndex(&curr->dest, curr->table); note(&curr->value, type); - note(&curr->size, Type::i32); + noteTableIndex(&curr->size, curr->table); } void visitTableCopy(TableCopy* curr) { - note(&curr->dest, Type::i32); - note(&curr->source, Type::i32); - note(&curr->size, Type::i32); + noteTableIndex(&curr->dest, curr->destTable); + noteTableIndex(&curr->source, curr->sourceTable); + + // The size depends on both dest and source. + auto* sourceTable = wasm.getTable(curr->sourceTable); + auto* destTable = wasm.getTable(curr->destTable); + Type sizeType = + sourceTable->is64() && destTable->is64() ? Type::i64 : Type::i32; + note(&curr->size, sizeType); } void visitTableInit(TableInit* curr) { - note(&curr->dest, wasm.getTable(curr->table)->addressType); + noteTableIndex(&curr->dest, curr->table); note(&curr->offset, Type::i32); note(&curr->size, Type::i32); } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 78e622b3cb9..1835949b63e 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1769,6 +1769,7 @@ Result<> IRBuilder::makeRefEq() { Result<> IRBuilder::makeTableGet(Name table) { TableGet curr; + curr.table = table; CHECK_ERR(visitTableGet(&curr)); auto type = wasm.getTable(table)->type; push(builder.makeTableGet(table, curr.index, type)); @@ -1806,6 +1807,8 @@ Result<> IRBuilder::makeTableFill(Name table) { Result<> IRBuilder::makeTableCopy(Name destTable, Name srcTable) { TableCopy curr; + curr.destTable = destTable; + curr.sourceTable = srcTable; CHECK_ERR(visitTableCopy(&curr)); push(builder.makeTableCopy( curr.dest, curr.source, curr.size, destTable, srcTable)); diff --git a/test/lit/passes/roundtrip-table.wast b/test/lit/passes/roundtrip-table.wast index 21eb1197c26..1f45d5f4cc6 100644 --- a/test/lit/passes/roundtrip-table.wast +++ b/test/lit/passes/roundtrip-table.wast @@ -1,11 +1,47 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s -all --roundtrip -S -o - +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s (module + ;; CHECK: (type $functype (func)) (type $functype (func)) - (table $0 48 funcref) + + ;; CHECK: (table $table 48 funcref) + (table $table 48 funcref) + ;; CHECK: (table $table64 i64 96 funcref) + (table $table64 i64 96 funcref) + ;; a type that appears in the table and nowhere else. this test checks that ;; we do not crash during the roundtrip on seeing an unexpected type that ;; collectHeapTypes() did not scan. - (elem (table $0) (i32.const 0) funcref (ref.null $functype)) + (elem (table $table) (i32.const 0) funcref (ref.null $functype)) + + ;; CHECK: (elem $0 (table $table) (i32.const 0) funcref (item (ref.null nofunc))) + + ;; CHECK: (func $set (type $functype) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $table64 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set + (table.set $table64 + (i64.const 0) + (block (result funcref) + ;; This unreachable code must not confuse the parser. Specifically the + ;; index should not be replaced with the i32, which would not validate. + (i32.lt_u + (i32.const 0) + (unreachable) + ) + (ref.null nofunc) + ) + ) + ) ) + From 924c32b8b5cef558cb0e08a8d9de5f041b61f1b5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Feb 2025 13:53:57 -0800 Subject: [PATCH 300/622] [NFC] Store function effects on functions (#7302) This fixes an unsettling bug where, with a long-enough sequence of passes, we might create a function A, compute it has no effects, remove that function, create another function with the same but with effects, and think the old effects apply to it. This is basically a danger of using a map from function name to effects. To avoid that, store the effects on function objects themselves. --- src/ir/effects.h | 16 ++++++++-------- src/pass.h | 14 -------------- src/passes/GlobalEffects.cpp | 23 +++++++++-------------- src/passes/pass.cpp | 4 ++-- src/wasm.h | 10 ++++++++++ 5 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/ir/effects.h b/src/ir/effects.h index fa216799729..23290c34005 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -29,8 +29,7 @@ class EffectAnalyzer { public: EffectAnalyzer(const PassOptions& passOptions, Module& module) : ignoreImplicitTraps(passOptions.ignoreImplicitTraps), - trapsNeverHappen(passOptions.trapsNeverHappen), - funcEffectsMap(passOptions.funcEffectsMap), module(module), + trapsNeverHappen(passOptions.trapsNeverHappen), module(module), features(module.features) {} EffectAnalyzer(const PassOptions& passOptions, @@ -47,7 +46,6 @@ class EffectAnalyzer { bool ignoreImplicitTraps; bool trapsNeverHappen; - std::shared_ptr funcEffectsMap; Module& module; FeatureSet features; @@ -523,12 +521,14 @@ class EffectAnalyzer { return; } + // Get the target's effects, if they exist. Note that we must handle the + // case of the function not yet existing (we may be executed in the middle + // of a pass, which may have built up calls but not the targets of those + // calls; in such a case, we do not find the targets and therefore assume + // we know nothing about the effects, which is safe). const EffectAnalyzer* targetEffects = nullptr; - if (parent.funcEffectsMap) { - auto iter = parent.funcEffectsMap->find(curr->target); - if (iter != parent.funcEffectsMap->end()) { - targetEffects = &iter->second; - } + if (auto* target = parent.module.getFunctionOrNull(curr->target)) { + targetEffects = target->effects.get(); } if (curr->isReturn) { diff --git a/src/pass.h b/src/pass.h index daed8b6abc6..0ebd7e6d6bd 100644 --- a/src/pass.h +++ b/src/pass.h @@ -99,11 +99,6 @@ struct InliningOptions { Index partialInliningIfs = 0; }; -// Forward declaration for FuncEffectsMap. -class EffectAnalyzer; - -using FuncEffectsMap = std::unordered_map; - struct PassOptions { friend Pass; @@ -237,15 +232,6 @@ struct PassOptions { // Passes to skip and not run. std::unordered_set passesToSkip; - // Effect info computed for functions. One pass can generate this and then - // other passes later can benefit from it. It is up to the sequence of passes - // to update or discard this when necessary - in particular, when new effects - // are added to a function this must be changed or we may optimize - // incorrectly. However, it is extremely rare for a pass to *add* effects; - // passes normally only remove effects. Passes that do add effects must set - // addsEffects() so the pass runner is aware of them. - std::shared_ptr funcEffectsMap; - // -Os is our default static constexpr const int DEFAULT_OPTIMIZE_LEVEL = 2; static constexpr const int DEFAULT_SHRINK_LEVEL = 1; diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index 47c4d54abfe..fbfb278e93f 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -181,30 +181,25 @@ struct GenerateGlobalEffects : public Pass { } } - // Generate the final data structure, starting from a blank slate where - // nothing is known. - auto& funcEffectsMap = getPassOptions().funcEffectsMap; - funcEffectsMap.reset(); - + // Generate the final data, starting from a blank slate where nothing is + // known. for (auto& [func, info] : analysis.map) { + func->effects.reset(); if (!info.effects) { - // Add no entry to funcEffectsMap, since nothing is known. continue; } - // Only allocate a new funcEffectsMap here when we actually have data for - // it (which might make later effect computation slightly faster, to - // quickly skip the funcEffectsMap code path). - if (!funcEffectsMap) { - funcEffectsMap = std::make_shared(); - } - funcEffectsMap->emplace(func->name, *info.effects); + func->effects = std::make_shared(*info.effects); } } }; struct DiscardGlobalEffects : public Pass { - void run(Module* module) override { getPassOptions().funcEffectsMap.reset(); } + void run(Module* module) override { + for (auto& func : module->functions) { + func->effects.reset(); + } + } }; Pass* createGenerateGlobalEffectsPass() { return new GenerateGlobalEffects(); } diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 5e690790bb4..e2137a8c98b 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -1044,9 +1044,9 @@ void PassRunner::handleAfterEffects(Pass* pass, Function* func) { TypeUpdating::handleNonDefaultableLocals(func, *wasm); } - if (options.funcEffectsMap && pass->addsEffects()) { + if (pass->addsEffects()) { // Effects were added, so discard any computed effects for this function. - options.funcEffectsMap->erase(func->name); + func->effects.reset(); } } diff --git a/src/wasm.h b/src/wasm.h index f2ecd76873d..7458a3d14e3 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2137,6 +2137,9 @@ struct BinaryLocations { std::unordered_map functions; }; +// Forward declaration for FuncEffectsMap. +class EffectAnalyzer; + class Function : public Importable { public: HeapType type = HeapType(Signature()); // parameters and return value @@ -2183,6 +2186,13 @@ class Function : public Importable { delimiterLocations; BinaryLocations::FunctionLocations funcLocation; + // The effects for this function, if they have been computed. We use a shared + // ptr here to avoid compilation errors with the forward-declared + // EffectAnalyzer. + // + // See addsEffects() in pass.h for more details. + std::shared_ptr effects; + // Inlining metadata: whether to disallow full and/or partial inlining (for // details on what those mean, see Inlining.cpp). bool noFullInline = false; From 3339c1f38da5b68ce8bf410773fe4b5eee451ab8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Feb 2025 16:43:43 -0800 Subject: [PATCH 301/622] Fuzzer: Fix handling of JS null exceptions (#7303) We have a test try { .. } catch (e) { e.a; // must be valid } That test will catch various wasm exception issues, but it turns out that it also fails on one specific JS exception: null. It is ok to do `e.a` on numbers, objects, anything really, all except for null. This PR adds a guard for that. --- scripts/fuzz_shell.js | 7 ++++--- test/lit/d8/fuzz_shell_exceptions.wast | 11 +++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 6a70dc1f9cd..3f201b3c812 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -178,9 +178,10 @@ function callFunc(func) { /* await */ func(); return 0; } catch (e) { - // The exception must exist, and not behave oddly when we access a - // property on it. (VM bugs could cause errors here.) - e.a; + // The exception might be a JS null, but otherwise it must be valid to check + // if a property exists on it (VM bugs could cause errors here, specifically + // if a wasm exception is caught here, and it is not represented properly). + if (e !== null) e.a; // We only want to catch exceptions, not wasm traps: traps should still // halt execution. Handling this requires different code in wasm2js, so diff --git a/test/lit/d8/fuzz_shell_exceptions.wast b/test/lit/d8/fuzz_shell_exceptions.wast index 02c3ffb469d..dc835469536 100644 --- a/test/lit/d8/fuzz_shell_exceptions.wast +++ b/test/lit/d8/fuzz_shell_exceptions.wast @@ -20,6 +20,15 @@ (i32.const 42) ) ) + + (func $throwing-jstag-null (export "throwing-jstag-null") + ;; Throwing JSTag leads to the JS side receiving the externref as a JS + ;; value. A null must be handled properly while doing so, without error, and + ;; be logged as a null. + (throw $imported-js-tag + (ref.null noextern) + ) + ) ) ;; Build to a binary wasm. @@ -34,6 +43,8 @@ ;; CHECK: exception thrown: Error: js exception ;; CHECK: [fuzz-exec] calling throwing-tag ;; CHECK: exception thrown: [object WebAssembly.Exception] +;; CHECK: [fuzz-exec] calling throwing-jstag-null +;; CHECK: exception thrown: null From 613ea882e07fb9ed777f46a26678213bce212927 Mon Sep 17 00:00:00 2001 From: Lorenzo Santangelo <48159385+lorsanta@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:49:08 +0100 Subject: [PATCH 302/622] Allow customizing the limit in LimitSegments (#7285) The custom limit can be passed by e.g. --limit-segments --pass-arg=limit-segments@1024 Fixes #7229 --- src/ir/memory-utils.h | 18 ++++++++++-------- src/passes/LimitSegments.cpp | 19 ++++++++++++++++++- test/lit/passes/custom-max-data-segments.wast | 13 +++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 test/lit/passes/custom-max-data-segments.wast diff --git a/src/ir/memory-utils.h b/src/ir/memory-utils.h index d86fb941a3d..fda255caf3a 100644 --- a/src/ir/memory-utils.h +++ b/src/ir/memory-utils.h @@ -46,12 +46,14 @@ inline void ensureExists(Module* wasm) { // Try to merge segments until they fit into web limitations. // Return true if successful. // Does not yet support multimemory -inline bool ensureLimitedSegments(Module& module) { +inline bool +ensureLimitedSegments(Module& module, + Index maxDataSegments = WebLimitations::MaxDataSegments) { if (module.memories.size() > 1) { return false; } auto& dataSegments = module.dataSegments; - if (dataSegments.size() <= WebLimitations::MaxDataSegments) { + if (dataSegments.size() <= maxDataSegments) { return true; } @@ -86,19 +88,19 @@ inline bool ensureLimitedSegments(Module& module) { // check if we have too many dynamic data segments, which we can do nothing // about - if (numDynamic + 1 >= WebLimitations::MaxDataSegments) { + if (numDynamic + 1 >= maxDataSegments) { return false; } // we'll merge constant segments if we must - if (numConstant + numDynamic >= WebLimitations::MaxDataSegments) { - numConstant = WebLimitations::MaxDataSegments - numDynamic - 1; + if (numConstant + numDynamic >= maxDataSegments) { + numConstant = maxDataSegments - numDynamic - 1; [[maybe_unused]] auto num = numConstant + numDynamic; - assert(num == WebLimitations::MaxDataSegments - 1); + assert(num == maxDataSegments - 1); } std::vector> mergedSegments; - mergedSegments.reserve(WebLimitations::MaxDataSegments); + mergedSegments.reserve(maxDataSegments); // drop empty segments and pass through dynamic-offset segments for (auto& segment : dataSegments) { @@ -121,7 +123,7 @@ inline bool ensureLimitedSegments(Module& module) { if (!isRelevant(*segment)) { continue; } - if (mergedSegments.size() + 2 < WebLimitations::MaxDataSegments) { + if (mergedSegments.size() + 2 < maxDataSegments) { mergedSegments.push_back(std::move(segment)); continue; } diff --git a/src/passes/LimitSegments.cpp b/src/passes/LimitSegments.cpp index 2ea95b56ab5..a223c7c12ed 100644 --- a/src/passes/LimitSegments.cpp +++ b/src/passes/LimitSegments.cpp @@ -18,11 +18,28 @@ #include "pass.h" #include "wasm.h" +// +// Attempt to merge segments to fit within a specified limit. +// +// By default this limit is equal to the one commonly used by wasm VMs +// (see wasm-limits.h), but it can be changed with the option below: +// +// --pass-arg=limit-segments@max-data-segments +// +// Specify a custom maximum number of data segments. +// + namespace wasm { struct LimitSegments : public Pass { void run(Module* module) override { - if (!MemoryUtils::ensureLimitedSegments(*module)) { + Index maxDataSegments; + if (hasArgument("limit-segments")) { + maxDataSegments = std::stoul(getArgument("limit-segments", "")); + } else { + maxDataSegments = WebLimitations::MaxDataSegments; + } + if (!MemoryUtils::ensureLimitedSegments(*module, maxDataSegments)) { std::cerr << "Unable to merge segments. " << "wasm VMs may not accept this binary" << std::endl; } diff --git a/test/lit/passes/custom-max-data-segments.wast b/test/lit/passes/custom-max-data-segments.wast new file mode 100644 index 00000000000..701d88fd365 --- /dev/null +++ b/test/lit/passes/custom-max-data-segments.wast @@ -0,0 +1,13 @@ +;; RUN: wasm-opt %s --limit-segments --pass-arg=limit-segments@3 -S -o - | filecheck %s + +;; Test that the data segments custom limit is respected. +(module + (memory 256 256) + ;; CHECK: (data $0 (i32.const 0) "A") + (data (i32.const 0) "A") + ;; CHECK: (data $"" (i32.const 1) "AAAA") + (data (i32.const 1) "A") + (data (i32.const 2) "A") + (data (i32.const 3) "A") + (data (i32.const 4) "A") +) From 3d5a10dcf15aa260d51807e7eae9eb4a6ff17aaf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Feb 2025 12:15:32 -0800 Subject: [PATCH 303/622] Fuzzer: Avoid massive initial sizes for memories (#7305) We generate random segments and then make the memory's initial size big enough to accomodate them, but if the size is massive then we will just OOM anyhow (or even not validate in wasm32 in some cases). To avoid that, put a limit on the maximum initial memory size as influenced by segments. --- src/tools/fuzzing/fuzzing.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index e55d94868ac..75bdcb3b817 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -753,9 +753,18 @@ void TranslateToFuzzReader::finalizeMemory() { maxOffset = maxOffset + offset->value.getInteger(); } } - memory->initial = std::max( - memory->initial, - Address((maxOffset + Memory::kPageSize - 1) / Memory::kPageSize)); + + // Ensure the initial memory can fit the segment (so we don't just trap), + // but only do so when the segment is at a reasonable offset (to avoid + // validation errors on the initial size >= 4GB in wasm32, but also to + // avoid OOM errors on trying to allocate too much initial memory, which is + // annoying in the fuzzer). + Address ONE_GB = 1024 * 1024 * 1024; + if (maxOffset <= ONE_GB) { + memory->initial = std::max( + memory->initial, + Address((maxOffset + Memory::kPageSize - 1) / Memory::kPageSize)); + } } memory->initial = std::max(memory->initial, fuzzParams->USABLE_MEMORY); // Avoid an unlimited memory size, which would make fuzzing very difficult From 26c5502faef4215b85b5369956ec7bf2e93ce953 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Feb 2025 13:18:47 -0800 Subject: [PATCH 304/622] [NFC] Run ClusterFuzz only after some time has passed (#7306) This avoids the annoying experience of seeing ClusterFuzz block for many seconds as you watch the fuzzer begin to run. --- scripts/fuzz_opt.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index dbe59eec456..86d3791e185 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1673,6 +1673,13 @@ def ensure(self): tar.extractall(path=self.clusterfuzz_dir) tar.close() + def can_run_on_wasm(self, wasm): + # Do not run ClusterFuzz in the first seconds of fuzzing: the first time + # it runs is very slow (to build the bundle), which is annoying when you + # are just starting the fuzzer and looking for any obvious problems. + seconds = 30 + return time.time() - start_time > seconds + # Tests linking two wasm files at runtime, and that optimizations do not break # anything. This is similar to Split(), but rather than split a wasm file into @@ -2058,6 +2065,8 @@ def get_random_opts(): print('The v8 shell, d8, must be in the path') sys.exit(1) +start_time = time.time() + if __name__ == '__main__': # if we are given a seed, run exactly that one testcase. otherwise, # run new ones until we fail @@ -2083,7 +2092,6 @@ def get_random_opts(): total_wasm_size = 0 total_input_size = 0 total_input_size_squares = 0 - start_time = time.time() while True: counter += 1 if given_seed is not None: From 379e5ecc28d6a840a20a1df1e243fc0d8694a34d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Feb 2025 14:14:02 -0800 Subject: [PATCH 305/622] Fuzzer: Add an option to preserve imports and exports (#7300) Normally the fuzzer will add new imports (for the JS stuff we can call), and new exports as it adds functions. This adds an option to avoid that, and instead keep the imports and exports fixed. This is useful when we are used to modify an existing fuzzer's testcase, as its connection to the JS side should be considered fixed (and it will run in that fuzzer's JS, not ours). This is added as --fuzz-preserve-imports-exports for wasm-opt. --- CHANGELOG.md | 4 + scripts/fuzz_opt.py | 48 ++++++++- src/tools/fuzzing.h | 10 ++ src/tools/fuzzing/fuzzing.cpp | 98 ++++++++++++------ src/tools/wasm-opt.cpp | 10 ++ test/lit/fuzz-preserve-imports-exports.wast | 49 +++++++++ .../fuzz-preserve-imports-exports.wast.ttf | Bin 0 -> 4023 bytes test/lit/help/wasm-opt.test | 3 + 8 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 test/lit/fuzz-preserve-imports-exports.wast create mode 100644 test/lit/fuzz-preserve-imports-exports.wast.ttf diff --git a/CHANGELOG.md b/CHANGELOG.md index 630a5d48e3d..5a8ce1af093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ full changeset diff at the end of each section. Current Trunk ------------- + - Add an option to preserve imports and exports in the fuzzer (for fuzzer + harnesses where they only want Binaryen to modify their given testcases, not + generate new things in them). + v122 ---- diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 86d3791e185..09ddec2e1f5 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1649,7 +1649,6 @@ def handle(self, wasm): # run, or if the wasm errored during instantiation, which can happen due # to a testcase with a segment out of bounds, say). if output != IGNORE and not output.startswith(INSTANTIATE_ERROR): - assert FUZZ_EXEC_CALL_PREFIX in output def ensure(self): @@ -1756,6 +1755,52 @@ def can_run_on_wasm(self, wasm): return not CLOSED_WORLD and all_disallowed(['shared-everything']) and not NANS +# Test --fuzz-preserve-imports-exports, which never modifies imports or exports. +class PreserveImportsExports(TestCaseHandler): + frequency = 0.1 + + def handle(self, wasm): + # We will later verify that no imports or exports changed, by comparing + # to the unprocessed original text. + original = run([in_bin('wasm-opt'), wasm] + FEATURE_OPTS + ['--print']) + + # We leave if the module has (ref exn) in struct fields (because we have + # no way to generate an exn in a non-function context, and if we picked + # that struct for a global, we'd end up needing a (ref exn) in the + # global scope, which is impossible). The fuzzer is designed to be + # careful not to emit that in testcases, but after the optimizer runs, + # we may end up with struct fields getting refined to that, so we need + # this extra check (which should be hit very rarely). + structs = [line for line in original.split('\n') if '(struct ' in line] + if '(ref exn)' in '\n'.join(structs): + note_ignored_vm_run('has non-nullable exn in struct') + return + + # Generate some random input data. + data = abspath('preserve_input.dat') + make_random_input(random_size(), data) + + # Process the existing wasm file. + processed = run([in_bin('wasm-opt'), data] + FEATURE_OPTS + [ + '-ttf', + '--fuzz-preserve-imports-exports', + '--initial-fuzz=' + wasm, + '--print', + ]) + + def get_relevant_lines(wat): + # Imports and exports are relevant. + lines = [line for line in wat.splitlines() if '(export ' in line or '(import ' in line] + + # Ignore type names, which may vary (e.g. one file may have $5 and + # another may call the same type $17). + lines = [re.sub(r'[(]type [$][0-9a-zA-Z_$]+[)]', '', line) for line in lines] + + return '\n'.join(lines) + + compare(get_relevant_lines(original), get_relevant_lines(processed), 'Preserve') + + # The global list of all test case handlers testcase_handlers = [ FuzzExec(), @@ -1770,6 +1815,7 @@ def can_run_on_wasm(self, wasm): RoundtripText(), ClusterFuzz(), Two(), + PreserveImportsExports(), ] diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index b67c3a23cd9..5e3797247f5 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -128,6 +128,9 @@ class TranslateToFuzzReader { void pickPasses(OptimizationOptions& options); void setAllowMemory(bool allowMemory_) { allowMemory = allowMemory_; } void setAllowOOB(bool allowOOB_) { allowOOB = allowOOB_; } + void setPreserveImportsAndExports(bool preserveImportsAndExports_) { + preserveImportsAndExports = preserveImportsAndExports_; + } void build(); @@ -146,6 +149,13 @@ class TranslateToFuzzReader { // of bounds (which traps in wasm, and is undefined behavior in C). bool allowOOB = true; + // Whether we preserve imports and exports. Normally we add imports (for + // logging and other useful functionality for testing), and add exports of + // functions as we create them. With this set, we add neither imports nor + // exports, which is useful if the tool using us only wants us to mutate an + // existing testcase (using initial-content). + bool preserveImportsAndExports = false; + // Whether we allow the fuzzer to add unreachable code when generating changes // to existing code. This is randomized during startup, but could be an option // like the above options eventually if we find that useful. diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 75bdcb3b817..21459a2ccf7 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -614,10 +614,12 @@ void TranslateToFuzzReader::setupGlobals() { // run the wasm. for (auto& global : wasm.globals) { if (global->imported()) { - // Remove import info from imported globals, and give them a simple - // initializer. - global->module = global->base = Name(); - global->init = makeConst(global->type); + if (!preserveImportsAndExports) { + // Remove import info from imported globals, and give them a simple + // initializer. + global->module = global->base = Name(); + global->init = makeConst(global->type); + } } else { // If the initialization referred to an imported global, it no longer can // point to the same global after we make it a non-imported global unless @@ -695,7 +697,7 @@ void TranslateToFuzzReader::setupTags() { // As in modifyInitialFunctions(), we can't allow arbitrary tag imports, which // would trap when the fuzzing infrastructure doesn't know what to provide. for (auto& tag : wasm.tags) { - if (tag->imported()) { + if (tag->imported() && !preserveImportsAndExports) { tag->module = tag->base = Name(); } } @@ -707,7 +709,7 @@ void TranslateToFuzzReader::setupTags() { } // Add the fuzzing support tags manually sometimes. - if (oneIn(2)) { + if (!preserveImportsAndExports && oneIn(2)) { auto wasmTag = builder.makeTag(Names::getValidTagName(wasm, "wasmtag"), Signature(Type::i32, Type::none)); wasmTag->module = "fuzzing-support"; @@ -779,9 +781,12 @@ void TranslateToFuzzReader::finalizeMemory() { memory->max = std::min(Address(memory->initial + 1), Address(Memory::kMaxSize32)); } - // Avoid an imported memory (which the fuzz harness would need to handle). - for (auto& memory : wasm.memories) { - memory->module = memory->base = Name(); + + if (!preserveImportsAndExports) { + // Avoid an imported memory (which the fuzz harness would need to handle). + for (auto& memory : wasm.memories) { + memory->module = memory->base = Name(); + } } } @@ -826,8 +831,11 @@ void TranslateToFuzzReader::finalizeTable() { assert(ReasonableMaxTableSize <= Table::kMaxSize); table->max = oneIn(2) ? Address(Table::kUnlimitedSize) : table->initial; - // Avoid an imported table (which the fuzz harness would need to handle). - table->module = table->base = Name(); + + if (!preserveImportsAndExports) { + // Avoid an imported table (which the fuzz harness would need to handle). + table->module = table->base = Name(); + } } } @@ -841,8 +849,9 @@ void TranslateToFuzzReader::shuffleExports() { // we emit invokes for a function right after it (so we end up calling the // same code several times in succession, but interleaving it with others may // find more things). But we also keep a good chance for the natural order - // here, as it may help some initial content. - if (wasm.exports.empty() || oneIn(2)) { + // here, as it may help some initial content. Note we cannot do this if we are + // preserving the exports, as their order is something we must maintain. + if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2)) { return; } @@ -881,14 +890,24 @@ void TranslateToFuzzReader::addImportLoggingSupport() { Name baseName = std::string("log-") + type.toString(); func->name = Names::getValidFunctionName(wasm, baseName); logImportNames[type] = func->name; - func->module = "fuzzing-support"; - func->base = baseName; + if (!preserveImportsAndExports) { + func->module = "fuzzing-support"; + func->base = baseName; + } else { + // We cannot add an import, so just make it a trivial function (this is + // simpler than avoiding calls to logging in all the rest of the logic). + func->body = builder.makeNop(); + } func->type = Signature(type, Type::none); wasm.addFunction(std::move(func)); } } void TranslateToFuzzReader::addImportCallingSupport() { + if (preserveImportsAndExports) { + return; + } + if (wasm.features.hasReferenceTypes() && closedWorld) { // In closed world mode we must *remove* the call-ref* imports, if they // exist in the initial content. These are not valid to call in closed-world @@ -983,8 +1002,13 @@ void TranslateToFuzzReader::addImportThrowingSupport() { throwImportName = Names::getValidFunctionName(wasm, "throw"); auto func = std::make_unique(); func->name = throwImportName; - func->module = "fuzzing-support"; - func->base = "throw"; + if (!preserveImportsAndExports) { + func->module = "fuzzing-support"; + func->base = "throw"; + } else { + // As with logging, implement in a trivial way when we cannot add imports. + func->body = builder.makeNop(); + } func->type = Signature(Type::i32, Type::none); wasm.addFunction(std::move(func)); } @@ -999,8 +1023,9 @@ void TranslateToFuzzReader::addImportTableSupport() { } // If a "table" export already exists, skip fuzzing these imports, as the - // current export may not contain a valid table for it. - if (wasm.getExportOrNull("table")) { + // current export may not contain a valid table for it. We also skip if we are + // not adding imports or exports. + if (wasm.getExportOrNull("table") || preserveImportsAndExports) { return; } @@ -1033,8 +1058,9 @@ void TranslateToFuzzReader::addImportTableSupport() { } void TranslateToFuzzReader::addImportSleepSupport() { - if (!oneIn(4)) { - // Fuzz this somewhat rarely, as it may be slow. + // Fuzz this somewhat rarely, as it may be slow, and only when we can add + // imports. + if (preserveImportsAndExports || !oneIn(4)) { return; } @@ -1087,12 +1113,15 @@ void TranslateToFuzzReader::addHashMemorySupport() { auto* body = builder.makeBlock(contents); auto* hasher = wasm.addFunction(builder.makeFunction( "hashMemory", Signature(Type::none, Type::i32), {Type::i32}, body)); - wasm.addExport( - builder.makeExport(hasher->name, hasher->name, ExternalKind::Function)); - // Export memory so JS fuzzing can use it - if (!wasm.getExportOrNull("memory")) { - wasm.addExport(builder.makeExport( - "memory", wasm.memories[0]->name, ExternalKind::Memory)); + + if (!preserveImportsAndExports) { + wasm.addExport( + builder.makeExport(hasher->name, hasher->name, ExternalKind::Function)); + // Export memory so JS fuzzing can use it + if (!wasm.getExportOrNull("memory")) { + wasm.addExport(builder.makeExport( + "memory", wasm.memories[0]->name, ExternalKind::Memory)); + } } } @@ -1340,7 +1369,7 @@ Function* TranslateToFuzzReader::addFunction() { return t.isDefaultable(); }); if (validExportParams && (numAddedFunctions == 0 || oneIn(2)) && - !wasm.getExportOrNull(func->name)) { + !wasm.getExportOrNull(func->name) && !preserveImportsAndExports) { auto* export_ = new Export; export_->name = func->name; export_->value = func->name; @@ -1805,8 +1834,10 @@ void TranslateToFuzzReader::modifyInitialFunctions() { for (Index i = 0; i < wasm.functions.size(); i++) { auto* func = wasm.functions[i].get(); // We can't allow extra imports, as the fuzzing infrastructure wouldn't - // know what to provide. Keep only our own fuzzer imports. - if (func->imported() && func->module == "fuzzing-support") { + // know what to provide. Keep only our own fuzzer imports (or, if we are + // preserving imports, keep them all). + if (func->imported() && + (func->module == "fuzzing-support" || preserveImportsAndExports)) { continue; } FunctionCreationContext context(*this, func); @@ -1907,7 +1938,12 @@ void TranslateToFuzzReader::addInvocations(Function* func) { } body->list.set(invocations); wasm.addFunction(std::move(invoker)); - wasm.addExport(builder.makeExport(name, name, ExternalKind::Function)); + + // Most of the benefit of invocations is lost when we do not add exports for + // them, but still, they might be called by existing functions. + if (!preserveImportsAndExports) { + wasm.addExport(builder.makeExport(name, name, ExternalKind::Function)); + } } Expression* TranslateToFuzzReader::make(Type type) { diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 2f8d225802b..59622d5f938 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -85,6 +85,7 @@ int main(int argc, const char* argv[]) { bool fuzzPasses = false; bool fuzzMemory = true; bool fuzzOOB = true; + bool fuzzPreserveImportsAndExports = false; std::string emitSpecWrapper; std::string emitWasm2CWrapper; std::string inputSourceMapFilename; @@ -178,6 +179,14 @@ int main(int argc, const char* argv[]) { WasmOptOption, Options::Arguments::Zero, [&](Options* o, const std::string& arguments) { fuzzOOB = false; }) + .add("--fuzz-preserve-imports-exports", + "", + "don't add imports and exports in -ttf mode", + WasmOptOption, + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { + fuzzPreserveImportsAndExports = true; + }) .add("--emit-spec-wrapper", "-esw", "Emit a wasm spec interpreter wrapper file that can run the wasm with " @@ -310,6 +319,7 @@ int main(int argc, const char* argv[]) { } reader.setAllowMemory(fuzzMemory); reader.setAllowOOB(fuzzOOB); + reader.setPreserveImportsAndExports(fuzzPreserveImportsAndExports); reader.build(); if (options.passOptions.validate) { if (!WasmValidator().validate(wasm, options.passOptions)) { diff --git a/test/lit/fuzz-preserve-imports-exports.wast b/test/lit/fuzz-preserve-imports-exports.wast new file mode 100644 index 00000000000..8796279f6e4 --- /dev/null +++ b/test/lit/fuzz-preserve-imports-exports.wast @@ -0,0 +1,49 @@ +;; Test the flag to preserve imports and exports in fuzzer generation. + +;; Generate fuzz output using this wat as initial contents, and with the flag to +;; preserve imports and exports. There should be no new imports or exports, and +;; old ones must stay the same. + +;; RUN: wasm-opt %s.ttf --initial-fuzz=%s -all -ttf --fuzz-preserve-imports-exports \ +;; RUN: --metrics -S -o - | filecheck %s --check-prefix=PRESERVE + +;; PRESERVE: [exports] : 1 +;; PRESERVE: [imports] : 5 + +;; [sic] - we do not close ("))") some imports, which have info in the wat +;; which we do not care about. +;; PRESERVE: (import "a" "d" (memory $imemory +;; PRESERVE: (import "a" "e" (table $itable +;; PRESERVE: (import "a" "b" (global $iglobal i32)) +;; PRESERVE: (import "a" "f" (func $ifunc +;; PRESERVE: (import "a" "c" (tag $itag + +;; PRESERVE: (export "foo" (func $foo)) + +;; And, without the flag, we do generate both imports and exports. + +;; RUN: wasm-opt %s.ttf --initial-fuzz=%s -all -ttf \ +;; RUN: --metrics -S -o - | filecheck %s --check-prefix=NORMAL + +;; Rather than hardcode the number here, find two of each. +;; NORMAL: (import +;; NORMAL: (import +;; NORMAL: (export +;; NORMAL: (export + +(module + ;; Existing imports. Note that the fuzzer normally turns imported globals etc. + ;; into normal ones (as the fuzz harness does not know what to provide at + ;; compile time), so we also test that --fuzz-preserve-imports-exports leaves + ;; such imports alone. + (import "a" "b" (global $iglobal i32)) + (import "a" "c" (tag $itag)) + (import "a" "d" (memory $imemory 10 20)) + (import "a" "e" (table $itable 10 20 funcref)) + (import "a" "f" (func $ifunc)) + + ;; One existing export. + (func $foo (export "foo") + ) +) + diff --git a/test/lit/fuzz-preserve-imports-exports.wast.ttf b/test/lit/fuzz-preserve-imports-exports.wast.ttf new file mode 100644 index 0000000000000000000000000000000000000000..922d620d004e4070ddff5e69b942e5153ab4a22e GIT binary patch literal 4023 zcmV;o4@mI6@*xKBgm+@_^UFQg-h><@a_V+EYBGOF?mfF-j78k*(j(8HD11C2SWzdy|PZh18xJJRi>%}yR+{W{w6u=;3m*!-fa#c{TDo4Ky@#2>H4Q_aafy1TU*0h^q^fe@Rw%4* z=8AJY5n-WSj;<@PLf4gw?XlIA*3lBvPi6x2-f@-jTNS_S!WP^czW$-t*6RsujSOk2I^KQ~CJ*O_1H_MciKHeATJGl+`B z8$}iMihi}K&gCPt{y9yGGdVe4VAaS$O3PzcTW5g1mJj35#G#)4nfDK20XKL1SnFkD zY!c%+kxoVLEg22CcPIY|t)-~2L@5y{uW2A=-*Nb%HxU!h@*>!3`kM>w>doL> z=@Wyyz2&*hasg|Ey-Dk1QELlV&Q=abI9B5OULFh1<)Y9K8hXwY%lr*+doaiHVN|L+ zu|FD=jP9BGe~1sPu%a&=-Yo$&4=xE;680Nlsb=)9^|KMfoXISfUeh|qtIk=)w_dr_ z;C18nu!&a2`4vuA3-2#6qj?+{=I*M!03>6~HLagG<|%7?7*0tAIdzk2aMq(>?^-Ao zISeyqf-5)f%yKvQvCiZ;mE*!`I|J>RC$6z#_$0d6B5i-OAFFuW;46S{rBrEe?bf_u z(f82s5tRvEF%C_ZAMq4pYK{Bd6R_`OlSwq=nbPAYX-@f(z`j!4pY8H-xRa6(?Z3B} zd<6$XObS#P0BG*Dxe@wadIpW%tr{@qpN4+_)`hJW58D$|enr!)M2`qo7YO(s>+jUP zSC(BRM>Np1BMH2TbWg&h-X3@9no7l2T(rFzl<;K z;igJ&rRl$-x3I2~n2KFLajt)m-iYTy^s8Fp8Y9)&l^%J+#B3(JZ5;eO+{d{FnZCWu z;J!8eN~LfBy&wQbDQl$OkYo=IpyRr2;jKNvbMr@?l3_epE8GsV!vp$W@{C8vXx*6K zk})bE;-n!2ruEn`sn&C)XUHuY4SFkFcJRuPu<3MXn6E^-wV`O_9^Ic~{BB+~={?JA zpLu$T*7Pn_SQEw&q+mlQ?tWtzVEGR}W11iYr;)YrQBaqZr0}!mI zxil7R#s%Z2a^dHB0t5%uMVZ0 z_Js0-X}2+k_1E-gtQ~q7&3naMqcU6Pg51}x!Q2$O$-2mka7J`w57RZx^z^~qX7b7% zQVU}-M#?)rHEotb*xpJ|ZrJ{z;tqlducF0mK9FTAX~%qVs&y}_n{H~$0GN78jwFDv zXp~Q|Ze}~^+JGF&hm&UJXrHnzHrLFsd4NAY3|OPER&u8pI-(-btCR});^0>I06kZ| z^^U`3>%j84y9KEA{pSVoC7fS(I#^kSpeVTlajFL!!c;crviJn+-YGXOD`z#2Dyj}^ z8Q~K@ZKva#qz7ULZ{9(&|d!m)BuveG9C0@mw>CfD5GqVG_;T-CK z{rh5=EG78j%2a2DM1}xcW_jp5l)Pdy0H)!Z`WB;>TkHOx^RIg#XA-(4u>)HZ@=&KR zSGs!h9|d)zNRofDvcOE}+s_^C&I>7s)||RO1Y9uC7RuOvC*Bo@yk6U~(+YRCcf`H5 zlTmcK0DVyShGRzZ;#ALq!^kgb!8U8TwuGC(-cGP4lBAAfV#{rAcfW?^empn{(-5DZ zwm?IJSn|b%dQ$Z&W3>kP9@pJF$f44jwaq8(V1EGOJs94X0=u< zR?6uMHKl69)NwZ#Eh3v_UE^LXvnBXWNO^Pkj?W)v=uTQgethK=08F#X5M_R4pg+4_SGos_^TTCpWhr3b&yWB3pz#4Uq5y*TflsuTQjS zBBYtZPsf8QZTc?9oFt)sV{ystlu1qjdsyhc)XU(S&%&E1p*ASR*oEzwF?Q(pQ#Zo> zY~Yv*la(b#U=SE3aC!b1y>ho9&b`%OPSl zC3}FOXINJ_Ph_v9c_vuQ1$~d67{2Jes++9gGOc5U_jBZ&WxF=>JN{j*Oc|;li~)F? zy@gxjK}?~#Zl0vsDb;sBf;UD8`QU`4kZO|5CkQ;+ElN`7dyA;s9m#qHz>JWBP_zkg zC|E#lCe;Mu(eipPr-ZQ$YEmkA}|Gcq~9`h$yYG4%G6{0jtNdfB_=O`(&&WL1X%(k>)i$EI`2 z6BLAe7pgZVk*sX-Ht>okW)x9w%Oo@p#o!H$^KweL;-~41ywJ!R%;_<_K?{jzTr7u2 z_k4*zR*_f=micGN3h`xx=qKmEm#+J7zB_a(Eg@;$GH!ZXR-YzYkef{ByciThemKUn{@ zz2D-(yRKCe!SS2MCRXJdCRdhmNR>*QUy-=BA*fZ_p^g#o*Xx%yTNBe$`OlU-aXxje zD9F@;Ok)+4|2O)C9dJk~+Ypq5kVm2mb47ps-yTi0>n+?c=zn*LY%m2>wj=?g;4xZE zeVj?ak!M6?A2-u`A^lBv=0h`^-WLoE7ks}DgabVN;W~ZK8C%UQ@flx%h)>$~Nc4w;UVh~2L%=40grFxRkjf&+?e(hQ>LhBI-4k_+^rcHEH<@a>);%HLNT=}h(dtsP;eJ-c1le+qzQdWO-sI|F(N%Y0K5ZByKN z-l8V}I&+SFAVMG7Wp}pM8~`GF5+?slj6T%;54I@sFE^qCq&TocYm}O5*Ql(e@qRo{ z&?#)a5M65(dy79oShv(Z!{o%Kl_u_iwgpVyA~rmO#H#&oB^e7_50+0V*(03M;s2o( z<(gFt6EKn1T3ouXmQbqOwLbbttOK+8ZIst`=vFY3??67s7)j7R>m&dur(hkJvcTKd zF*p=s&5}gkl}I6dsahWmVFgix$#tf#KD3Tp5BAK#{2-ppr6)r29F_~roxih<*CzLc zjei22ph1{rM!+6}1n#Q9jlR#;)aR|yb2G(W;C4lBO#M7A`@sN@bn{EvjH6J3ND7RG zJ9ea!yYqM$V8@K0I7jvQCBn$_dP`=talWWY7N3k=P#6S`rLS^uCZeLi+LS^vx)TVU zng!v@m%DJ#ddzlkg5aITD{iU>^|G;lc9I;sA@Z$exj{2?>w26a#k}q4cd`qUStxX^ zVtN6>W-$qcZ7Apx1we+MP{Xw>pqgoBVDZ?{&2}M5;t$D9^tD7XaMC?uqZg}FbOk7c z`K86}+V`8bd0;T#NAK;4)hU>YHeicZ_OGA;a857BI!zzG27_}HotR%{(h~QwdeS(- zCZC3Js;ml2FtRA`;#;Kml|?^T20NsvU!&92eIKatO%TdW1LC&N4S(S2d@CmgV^pi3 zbdS5f+FFNm3)&1N322sZaJMf02pk5YMXXPCy+KVykAaZ|V~Sj;l>uxvLfPux5!h?D z3QU^No122J|CuTxMx<=G8PIZ7M=+Xoqn4?3WCaMa!!F)!G9H3i-2(nLtPMv6#~jaT zeD_86?H2TDE5=UTq1qpUuxiH;tqfc1!o`)Ah@LGb(ecqOy~9G&%f)L4%p=G;?WL1o z8T%s6_?He*{c!1Jm~~P9zj2Ym#RH{5%~J;H+8=Y*Ff*uyIn8uUp&z`qJF0yO+ Date: Thu, 20 Feb 2025 16:49:48 -0800 Subject: [PATCH 306/622] Remove unnecessary fences from GSI output (#7308) GlobalStructInference only optimizes gets of immutable fields, so even if those gets are seqcst, they cannot synchronize with any writes. We were being overly conservative by emitting fences in place of optimized seqcst gets before. --- src/passes/GlobalStructInference.cpp | 28 ++++++++-------------------- test/lit/passes/gsi.wast | 12 +++--------- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index f27df3a3c94..89441a53fed 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -462,18 +462,12 @@ struct GlobalStructInference : public Pass { // the early return above) so that only leaves 1 and 2. if (values.size() == 1) { // The case of 1 value is simple: trap if the ref is null, and - // otherwise return the value. We must also fence if the get was - // seqcst. No additional work is necessary for an acquire get because - // there cannot have been any writes to this immutable field that it - // would synchronize with. - Expression* replacement = - builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)); - if (curr->order == MemoryOrder::SeqCst) { - replacement = - builder.blockify(replacement, builder.makeAtomicFence()); - } - replaceCurrent( - builder.blockify(replacement, getReadValue(values[0]))); + // otherwise return the value. Since the field is immutable, there + // cannot have been any writes to it we must synchonize with, so we do + // not need a fence. + replaceCurrent(builder.makeSequence( + builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)), + getReadValue(values[0]))); return; } assert(values.size() == 2); @@ -499,16 +493,10 @@ struct GlobalStructInference : public Pass { // of their execution matters (they may note globals for un-nesting). auto* left = getReadValue(values[0]); auto* right = getReadValue(values[1]); - // Note that we must trap on null, so add a ref.as_non_null here. We - // must also add a fence if this get is seqcst. As before, no extra work - // is necessary for an acquire get because there cannot be a write it - // synchronizes with. + // Note that we must trap on null, so add a ref.as_non_null here. As + // before, the get cannot have synchronized with anything. Expression* getGlobal = builder.makeGlobalGet(checkGlobal, wasm.getGlobal(checkGlobal)->type); - if (curr->order == MemoryOrder::SeqCst) { - getGlobal = - builder.makeSequence(builder.makeAtomicFence(), getGlobal); - } replaceCurrent(builder.makeSelect( builder.makeRefEq(builder.makeRefAs(RefAsNonNull, curr->ref), getGlobal), diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast index 7bc83efd40e..6e15091bdc4 100644 --- a/test/lit/passes/gsi.wast +++ b/test/lit/passes/gsi.wast @@ -2077,10 +2077,7 @@ ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (ref $two)) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (global.get $two-a) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $two-a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2099,8 +2096,7 @@ ) ) (drop - ;; This requires a fence to maintain its effect on the global order of - ;; seqcst operations. + ;; Even though this is seqcst, it still can't synchronize with anything. (struct.atomic.get $two 0 (local.get 0) ) @@ -2135,7 +2131,6 @@ ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2154,8 +2149,7 @@ ) ) (drop - ;; This requires a fence to maintain its effect on the global order of - ;; seqcst operations. + ;; Even though this is seqcst, it still can't synchronize with anything. (struct.atomic.get $two-same 0 (local.get 0) ) From e4f47845f06a8f23a11061cb02496ecd21714a51 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 20 Feb 2025 16:53:06 -0800 Subject: [PATCH 307/622] Remove unnecessary fences from Heap2Local output (#7309) We were previously over conservative when optimizing sequentially consistent accesses to non-escaping structs and emitted fences in their places. Since accesses to non-escaping structs cannot possibly synchronize with accesses in other threads, these fences are not necessary. Stop emitting them. --- src/passes/Heap2Local.cpp | 26 ++++---------------------- test/lit/passes/heap2local-rmw.wast | 18 ------------------ test/lit/passes/heap2local.wast | 7 ++----- 3 files changed, 6 insertions(+), 45 deletions(-) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 56d105c05db..c95fc5d485a 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -857,13 +857,8 @@ struct Struct2Local : PostWalker { builder.makeLocalSet(localIndexes[curr->index], curr->value)); // This struct.set cannot possibly synchronize with other threads via the - // read value, since the struct never escapes this function. But if the set - // is sequentially consistent, it still participates in the global order of - // sequentially consistent operations. Preserve this effect on the global - // ordering by inserting a fence. - if (curr->order == MemoryOrder::SeqCst) { - replacement = builder.blockify(replacement, builder.makeAtomicFence()); - } + // read value, since the struct never escapes this function, so we don't + // need a fence. replaceCurrent(replacement); } @@ -897,11 +892,8 @@ struct Struct2Local : PostWalker { // for other opts to handle. value = Bits::makePackedFieldGet(value, field, curr->signed_, wasm); auto* replacement = builder.blockify(builder.makeDrop(curr->ref)); - // See the note on seqcst struct.set. It is ok to insert the fence before - // the value here since we know the value is just a local.get. - if (curr->order == MemoryOrder::SeqCst) { - replacement = builder.blockify(replacement, builder.makeAtomicFence()); - } + // Just like optimized struct.set, this struct.get cannot synchronize with + // anything, so we don't need a fence. replaceCurrent(builder.blockify(replacement, value)); } @@ -964,11 +956,6 @@ struct Struct2Local : PostWalker { } block->list.push_back(builder.makeLocalSet(local, newVal)); - // See the notes on seqcst struct.get and struct.set. - if (curr->order == MemoryOrder::SeqCst) { - block->list.push_back(builder.makeAtomicFence()); - } - // Unstash the old value. block->list.push_back(builder.makeLocalGet(oldScratch, type)); block->type = type; @@ -1020,11 +1007,6 @@ struct Struct2Local : PostWalker { builder.makeLocalSet( local, builder.makeLocalGet(replacementScratch, type)))); - // See the notes on seqcst struct.get and struct.set. - if (curr->order == MemoryOrder::SeqCst) { - block->list.push_back(builder.makeAtomicFence()); - } - // Unstash the old value. block->list.push_back(builder.makeLocalGet(oldScratch, type)); block->type = type; diff --git a/test/lit/passes/heap2local-rmw.wast b/test/lit/passes/heap2local-rmw.wast index 1301d61eb2e..33fef1cb506 100644 --- a/test/lit/passes/heap2local-rmw.wast +++ b/test/lit/passes/heap2local-rmw.wast @@ -93,7 +93,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-add-i32 (result i32) @@ -127,7 +126,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-sub-i32 (result i32) @@ -161,7 +159,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-and-i32 (result i32) @@ -195,7 +192,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-or-i32 (result i32) @@ -229,7 +225,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-xor-i32 (result i32) @@ -260,7 +255,6 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-xchg-i32 (result i32) @@ -303,7 +297,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-cmpxchg-i32 (result i32) @@ -338,7 +331,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-add-i64 (result i64) @@ -372,7 +364,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-sub-i64 (result i64) @@ -406,7 +397,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-and-i64 (result i64) @@ -440,7 +430,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-or-i64 (result i64) @@ -474,7 +463,6 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-xor-i64 (result i64) @@ -505,7 +493,6 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-xchg-i64 (result i64) @@ -548,7 +535,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-cmpxchg-i64 (result i64) @@ -580,7 +566,6 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) (func $rmw-xchg-ref (param (ref null $struct)) (result (ref null $struct)) @@ -623,7 +608,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) (func $rmw-cmpxchg-ref (param (ref null $struct) (ref null $struct)) (result (ref null $struct)) @@ -661,7 +645,6 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $rmw-acqrel (result i32) - ;; The replacement for an acqrel rmw does not need a fence. (struct.atomic.rmw.add acqrel acqrel $i32 0 (struct.new_default $i32) (i32.const 1) @@ -704,7 +687,6 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) (func $cmpxchg-acqrel (result i32) - ;; The replacement for an acqrel rmw does not need a fence. (struct.atomic.rmw.cmpxchg acqrel acqrel $i32 0 (struct.new_default $i32) (i32.const 1) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 53f1fe289f4..619a33a23c5 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -4678,7 +4678,6 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null (shared none)) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4689,7 +4688,6 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $seqcst @@ -4697,9 +4695,8 @@ (local.set 0 (struct.new_default $struct) ) - ;; seqcst accesses participate in the global ordering of seqcst operations, - ;; so they need to be replaced with a seqcst fence to maintain that - ;; ordering. + ;; seqcst accesses also cannot synchronize with other threads, so we can + ;; still optimize normally. (drop (struct.atomic.get $struct 0 (local.get 0) From 9d5628c99e2049cee7d3752914ae837173afcbe1 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 20 Feb 2025 16:53:42 -0800 Subject: [PATCH 308/622] Remove unnecessary fence in GTO output (#7310) GlobalTypeOptimization removes struct fields that have no reads, then optimizes out any sets of those fields. We were previously being overly conservative and replacing optimized seqcst sets with fences, but in fact those sets cannot possibly synchronize with anything because there are no reads. Stop emitting the unnecessary fences. --- src/passes/GlobalTypeOptimization.cpp | 12 +++--------- test/lit/passes/gto-removals.wast | 18 +++++++----------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 1a532b423ef..bf7d0ae0724 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -532,15 +532,9 @@ struct GlobalTypeOptimization : public Pass { needEHFixups = true; Expression* replacement = builder.makeDrop(builder.makeRefAs(RefAsNonNull, flipped)); - if (curr->order == MemoryOrder::SeqCst) { - // If the removed set is sequentially consistent, we must insert a - // seqcst fence to preserve the effect on the global order of seqcst - // operations. No fence is necessary for release sets because there - // are no reads for them to synchronize with given that we are - // removing the field. - replacement = - builder.makeSequence(replacement, builder.makeAtomicFence()); - } + // We only remove fields with no reads, so if this set is atomic, + // there are no reads it can possibly synchronize with and we do not + // need a fence. replaceCurrent(replacement); } } diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index e1c85c5a0f6..edab804a5ed 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -1593,18 +1593,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (block (result (ref $A)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $sets (param (ref $A)) @@ -1619,8 +1616,7 @@ (local.get 0) (i32.const 1) ) - ;; This requires a fence to keep the effect on the global order of seqcst - ;; operations. + ;; Same with a seqcst set. (struct.atomic.set $A 0 (local.get 0) (i32.const 1) From bf7ff3e2650098d2bfa5dc41ee29fd4afd870ec5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 21 Feb 2025 08:43:41 -0800 Subject: [PATCH 309/622] Adjust the cost of allocations to a much higher value (#7307) VM feedback suggests that struct.new etc. should be avoided when we selectify, as the cost is high even in generational GC implementations. --- src/ir/cost.h | 38 ++++--- test/lit/passes/monomorphize-benefit.wast | 131 +++++++++++----------- test/lit/passes/remove-unused-brs-gc.wast | 65 +++++++++-- 3 files changed, 147 insertions(+), 87 deletions(-) diff --git a/src/ir/cost.h b/src/ir/cost.h index 5be31895287..8c15e2d2acc 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -43,6 +43,10 @@ struct CostAnalyzer : public OverriddenVisitor { // but usually requires some loads and comparisons. static const CostType CastCost = 5; + // Generational GC can be very efficient, but even so allocations have a high + // cost due to shortening the time to the next collection. + static const CostType AllocationCost = 100; + CostType maybeVisit(Expression* curr) { return curr ? visit(curr) : 0; } CostType visitBlock(Block* curr) { @@ -671,11 +675,7 @@ struct CostAnalyzer : public OverriddenVisitor { return base + nullCheckCost(curr->ref) + maybeVisit(curr->ref); } CostType visitStructNew(StructNew* curr) { - // While allocation itself is almost free with generational GC, there is - // at least some baseline cost, plus writing the fields. (If we use default - // values for the fields, then it is possible they are all 0 and if so, we - // can get that almost for free as well, so don't add anything there.) - CostType ret = 4 + curr->operands.size(); + CostType ret = AllocationCost + curr->operands.size(); for (auto* child : curr->operands) { ret += visit(child); } @@ -696,16 +696,16 @@ struct CostAnalyzer : public OverriddenVisitor { visit(curr->expected) + visit(curr->replacement); } CostType visitArrayNew(ArrayNew* curr) { - return 4 + visit(curr->size) + maybeVisit(curr->init); + return AllocationCost + visit(curr->size) + maybeVisit(curr->init); } CostType visitArrayNewData(ArrayNewData* curr) { - return 4 + visit(curr->offset) + visit(curr->size); + return AllocationCost + visit(curr->offset) + visit(curr->size); } CostType visitArrayNewElem(ArrayNewElem* curr) { - return 4 + visit(curr->offset) + visit(curr->size); + return AllocationCost + visit(curr->offset) + visit(curr->size); } CostType visitArrayNewFixed(ArrayNewFixed* curr) { - CostType ret = 4; + CostType ret = AllocationCost; for (auto* child : curr->values) { ret += visit(child); } @@ -743,15 +743,21 @@ struct CostAnalyzer : public OverriddenVisitor { return 8 + visit(curr->ref) + maybeVisit(curr->start) + maybeVisit(curr->end); } - CostType visitStringConst(StringConst* curr) { return 4; } + CostType visitStringConst(StringConst* curr) { + // We do not use AllocationCost here because these will end up imported as + // constants from the outside, after string lowering (and even natively, a + // VM can implement them as global constants). + return 4; + } CostType visitStringMeasure(StringMeasure* curr) { return 6 + visit(curr->ref); } CostType visitStringEncode(StringEncode* curr) { - return 6 + visit(curr->str) + visit(curr->array) + visit(curr->start); + return AllocationCost + 2 + visit(curr->str) + visit(curr->array) + + visit(curr->start); } CostType visitStringConcat(StringConcat* curr) { - return 10 + visit(curr->left) + visit(curr->right); + return AllocationCost + 6 + visit(curr->left) + visit(curr->right); } CostType visitStringEq(StringEq* curr) { // "3" is chosen since strings might or might not be interned in the engine. @@ -765,13 +771,11 @@ struct CostAnalyzer : public OverriddenVisitor { } CostType visitContNew(ContNew* curr) { - // Some arbitrary "high" value, reflecting that this may allocate a stack - return 14 + visit(curr->func); + return AllocationCost + 10 + visit(curr->func); } CostType visitContBind(ContBind* curr) { - // Inspired by struct.new: The only cost of cont.bind is that it may need to - // allocate a buffer to hold the arguments. - CostType ret = 4; + // cont.bind may need to allocate a buffer to hold the arguments. + CostType ret = AllocationCost; ret += visit(curr->cont); for (auto* arg : curr->operands) { ret += visit(arg); diff --git a/test/lit/passes/monomorphize-benefit.wast b/test/lit/passes/monomorphize-benefit.wast index a1b69fbd2f4..682fa686da4 100644 --- a/test/lit/passes/monomorphize-benefit.wast +++ b/test/lit/passes/monomorphize-benefit.wast @@ -947,15 +947,11 @@ ;; THIRD__: (type $3 (func (param anyref i32))) - ;; THIRD__: (type $4 (func (param (ref $A)))) + ;; THIRD__: (type $4 (func (param i32))) - ;; THIRD__: (type $5 (func (param anyref))) + ;; THIRD__: (type $5 (func (param (ref $A)))) - ;; THIRD__: (type $6 (func (param i32) (result (ref $A)))) - - ;; THIRD__: (type $7 (func (param i32))) - - ;; THIRD__: (type $8 (func (result (ref $A)))) + ;; THIRD__: (type $6 (func (param anyref))) ;; THIRD__: (import "a" "b" (func $import (type $1))) ;; TWOTRDS: (type $1 (func)) @@ -964,11 +960,11 @@ ;; TWOTRDS: (type $3 (func (param anyref i32))) - ;; TWOTRDS: (type $4 (func (param (ref $A)))) + ;; TWOTRDS: (type $4 (func (param i32))) - ;; TWOTRDS: (type $5 (func (param anyref))) + ;; TWOTRDS: (type $5 (func (param (ref $A)))) - ;; TWOTRDS: (type $6 (func (param i32))) + ;; TWOTRDS: (type $6 (func (param anyref))) ;; TWOTRDS: (import "a" "b" (func $import (type $1))) ;; HUNDRED: (type $1 (func (param anyref) (result (ref $A)))) @@ -984,8 +980,8 @@ ;; DEFAULT: (import "a" "c" (func $import2 (type $4) (param (ref $A)))) ;; ZERO___: (import "a" "c" (func $import2 (type $8) (param (ref $A)))) - ;; THIRD__: (import "a" "c" (func $import2 (type $4) (param (ref $A)))) - ;; TWOTRDS: (import "a" "c" (func $import2 (type $4) (param (ref $A)))) + ;; THIRD__: (import "a" "c" (func $import2 (type $5) (param (ref $A)))) + ;; TWOTRDS: (import "a" "c" (func $import2 (type $5) (param (ref $A)))) ;; HUNDRED: (import "a" "c" (func $import2 (type $4) (param (ref $A)))) (import "a" "c" (func $import2 (param (ref $A)))) @@ -1173,12 +1169,8 @@ ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) - ;; THIRD__-NEXT: (drop - ;; THIRD__-NEXT: (call $target-long - ;; THIRD__-NEXT: (struct.new $A - ;; THIRD__-NEXT: (local.get $y) - ;; THIRD__-NEXT: ) - ;; THIRD__-NEXT: ) + ;; THIRD__-NEXT: (call $target-long_6 + ;; THIRD__-NEXT: (local.get $y) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: (call $import2 ;; THIRD__-NEXT: (call $target-long @@ -1187,7 +1179,7 @@ ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) - ;; THIRD__-NEXT: (call $target-long_6) + ;; THIRD__-NEXT: (call $target-long_7) ;; THIRD__-NEXT: ) ;; TWOTRDS: (func $calls-long (type $3) (param $x anyref) (param $y i32) ;; TWOTRDS-NEXT: (call $import2 @@ -1207,12 +1199,8 @@ ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) - ;; TWOTRDS-NEXT: (drop - ;; TWOTRDS-NEXT: (call $target-long - ;; TWOTRDS-NEXT: (struct.new $A - ;; TWOTRDS-NEXT: (local.get $y) - ;; TWOTRDS-NEXT: ) - ;; TWOTRDS-NEXT: ) + ;; TWOTRDS-NEXT: (call $target-long_6 + ;; TWOTRDS-NEXT: (local.get $y) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: (call $import2 ;; TWOTRDS-NEXT: (call $target-long @@ -1221,13 +1209,7 @@ ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) - ;; TWOTRDS-NEXT: (drop - ;; TWOTRDS-NEXT: (call $target-long - ;; TWOTRDS-NEXT: (struct.new $A - ;; TWOTRDS-NEXT: (i32.const 42) - ;; TWOTRDS-NEXT: ) - ;; TWOTRDS-NEXT: ) - ;; TWOTRDS-NEXT: ) + ;; TWOTRDS-NEXT: (call $target-long_7) ;; TWOTRDS-NEXT: ) ;; HUNDRED: (func $calls-long (type $2) (param $x anyref) (param $y i32) ;; HUNDRED-NEXT: (call $import2 @@ -1275,9 +1257,9 @@ ;; * Optimize in all cases when the minimum benefit is 0% (except the ;; first call, which is a trivial call context). Removing a cast is ;; enough to justify optimizing. - ;; * In 33% we optimize only the very last case. There we remove both a - ;; cast and a struct.new, which ends up just over 33%. - ;; * In 66% and 100% we optimize nothing at all. + ;; * In 33% and 66% we optimize only two cases. There we remove both a cast + ;; and a struct.new, and the struct.new's high cost makes it worthwhile. + ;; * In 100% we optimize nothing at all. ;; Call with an unknown input and the output is sent to an import. (call $import2 @@ -1379,21 +1361,27 @@ ;; THIRD__-NEXT: (local.get $x) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) - ;; THIRD__-NEXT: (call $target-short_7 + ;; THIRD__-NEXT: (call $target-short_8 ;; THIRD__-NEXT: (local.get $x) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: (call $import2 - ;; THIRD__-NEXT: (call $target-short_8 - ;; THIRD__-NEXT: (local.get $y) + ;; THIRD__-NEXT: (call $target-short + ;; THIRD__-NEXT: (struct.new $A + ;; THIRD__-NEXT: (local.get $y) + ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: (call $target-short_9 ;; THIRD__-NEXT: (local.get $y) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: (call $import2 - ;; THIRD__-NEXT: (call $target-short_10) + ;; THIRD__-NEXT: (call $target-short + ;; THIRD__-NEXT: (struct.new $A + ;; THIRD__-NEXT: (i32.const 42) + ;; THIRD__-NEXT: ) + ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) - ;; THIRD__-NEXT: (call $target-short_11) + ;; THIRD__-NEXT: (call $target-short_10) ;; THIRD__-NEXT: ) ;; TWOTRDS: (func $calls-short (type $3) (param $x anyref) (param $y i32) ;; TWOTRDS-NEXT: (call $import2 @@ -1401,7 +1389,7 @@ ;; TWOTRDS-NEXT: (local.get $x) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) - ;; TWOTRDS-NEXT: (call $target-short_6 + ;; TWOTRDS-NEXT: (call $target-short_8 ;; TWOTRDS-NEXT: (local.get $x) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: (call $import2 @@ -1411,7 +1399,7 @@ ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) - ;; TWOTRDS-NEXT: (call $target-short_7 + ;; TWOTRDS-NEXT: (call $target-short_9 ;; TWOTRDS-NEXT: (local.get $y) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: (call $import2 @@ -1421,7 +1409,7 @@ ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) - ;; TWOTRDS-NEXT: (call $target-short_8) + ;; TWOTRDS-NEXT: (call $target-short_10) ;; TWOTRDS-NEXT: ) ;; HUNDRED: (func $calls-short (type $2) (param $x anyref) (param $y i32) ;; HUNDRED-NEXT: (call $import2 @@ -1466,8 +1454,10 @@ (func $calls-short (param $x anyref) (param $y i32) ;; As above, but now calling the short function: ;; * 0% is the same with the long function: any improvement is enough. - ;; * 33% optimizes them all (but for the first, which is a trivial call - ;; context). + ;; * 33% optimizes some, but not all (the first is a trivial call context; + ;; some of the others do not optimize because the struct.new is + ;; expensive, and if the output goes into another call, we cannot remove + ;; it). ;; * 66% optimizes a few less cases: when the output isn't dropped then we ;; can't do enough work to justify it. ;; * 100% optimizes nothing. @@ -1606,7 +1596,7 @@ ;; ZERO___-NEXT: (nop) ;; ZERO___-NEXT: ) -;; THIRD__: (func $target-long_6 (type $1) +;; THIRD__: (func $target-long_6 (type $4) (param $0 i32) ;; THIRD__-NEXT: (call $import) ;; THIRD__-NEXT: (call $import) ;; THIRD__-NEXT: (call $import) @@ -1615,38 +1605,53 @@ ;; THIRD__-NEXT: (call $import) ;; THIRD__-NEXT: ) -;; THIRD__: (func $target-short_7 (type $5) (param $0 anyref) -;; THIRD__-NEXT: (nop) -;; THIRD__-NEXT: ) - -;; THIRD__: (func $target-short_8 (type $6) (param $0 i32) (result (ref $A)) -;; THIRD__-NEXT: (struct.new $A -;; THIRD__-NEXT: (local.get $0) -;; THIRD__-NEXT: ) +;; THIRD__: (func $target-long_7 (type $1) +;; THIRD__-NEXT: (call $import) +;; THIRD__-NEXT: (call $import) +;; THIRD__-NEXT: (call $import) +;; THIRD__-NEXT: (call $import) +;; THIRD__-NEXT: (call $import) +;; THIRD__-NEXT: (call $import) ;; THIRD__-NEXT: ) -;; THIRD__: (func $target-short_9 (type $7) (param $0 i32) +;; THIRD__: (func $target-short_8 (type $6) (param $0 anyref) ;; THIRD__-NEXT: (nop) ;; THIRD__-NEXT: ) -;; THIRD__: (func $target-short_10 (type $8) (result (ref $A)) -;; THIRD__-NEXT: (struct.new $A -;; THIRD__-NEXT: (i32.const 42) -;; THIRD__-NEXT: ) +;; THIRD__: (func $target-short_9 (type $4) (param $0 i32) +;; THIRD__-NEXT: (nop) ;; THIRD__-NEXT: ) -;; THIRD__: (func $target-short_11 (type $1) +;; THIRD__: (func $target-short_10 (type $1) ;; THIRD__-NEXT: (nop) ;; THIRD__-NEXT: ) -;; TWOTRDS: (func $target-short_6 (type $5) (param $0 anyref) +;; TWOTRDS: (func $target-long_6 (type $4) (param $0 i32) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: ) + +;; TWOTRDS: (func $target-long_7 (type $1) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: ) + +;; TWOTRDS: (func $target-short_8 (type $6) (param $0 anyref) ;; TWOTRDS-NEXT: (nop) ;; TWOTRDS-NEXT: ) -;; TWOTRDS: (func $target-short_7 (type $6) (param $0 i32) +;; TWOTRDS: (func $target-short_9 (type $4) (param $0 i32) ;; TWOTRDS-NEXT: (nop) ;; TWOTRDS-NEXT: ) -;; TWOTRDS: (func $target-short_8 (type $1) +;; TWOTRDS: (func $target-short_10 (type $1) ;; TWOTRDS-NEXT: (nop) ;; TWOTRDS-NEXT: ) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index fa7a6d72739..2ee6171cbcb 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -16,7 +16,10 @@ ;; CHECK: (type $struct-nn (struct (field (ref any)))) (type $struct-nn (struct (field (ref any)))) - ;; CHECK: (func $br_on-if (type $8) (param $0 (ref struct)) + ;; CHECK: (global $struct (ref $struct) (struct.new_default $struct)) + (global $struct (ref $struct) (struct.new $struct)) + + ;; CHECK: (func $br_on-if (type $9) (param $0 (ref struct)) ;; CHECK-NEXT: (block $label ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (select (result (ref struct)) @@ -165,7 +168,7 @@ ) ) - ;; CHECK: (func $nested_br_on_cast (type $9) (result i31ref) + ;; CHECK: (func $nested_br_on_cast (type $10) (result i31ref) ;; CHECK-NEXT: (block $label$1 (result (ref i31)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br $label$1 @@ -644,7 +647,7 @@ ) ) - ;; CHECK: (func $casts-are-costly (type $10) (param $x i32) + ;; CHECK: (func $casts-are-costly (type $8) (param $x i32) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (if (result i32) @@ -789,6 +792,54 @@ ) ) + ;; CHECK: (func $allocations-are-costly (type $8) (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result (ref null $struct)) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $allocations-are-costly (param $x i32) + ;; Allocations are too expensive for us to unconditionalize and selectify + ;; here. + (drop + (if (result anyref) + (local.get $x) + (then + (struct.new $struct) + ) + (else + (ref.null any) + ) + ) + ) + ;; But two nulls are fine. + (drop + (if (result anyref) + (local.get $x) + (then + (ref.null any) + ) + (else + (ref.null any) + ) + ) + ) + ) + ;; CHECK: (func $threading (type $11) (param $x anyref) ;; CHECK-NEXT: (block $outer ;; CHECK-NEXT: (block $inner @@ -868,8 +919,8 @@ ;; CHECK: (func $select-refinalize (type $13) (param $param (ref $struct)) (result (ref struct)) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (select (result (ref $struct)) - ;; CHECK-NEXT: (struct.new_default $struct) - ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (global.get $struct) + ;; CHECK-NEXT: (global.get $struct) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $param) @@ -883,10 +934,10 @@ (if (result (ref struct)) (i32.const 0) (then - (struct.new_default $struct) + (global.get $struct) ) (else - (struct.new_default $struct) + (global.get $struct) ) ) (local.get $param) From 54abcfedb79e71e56548991575c6130dc0999f7b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 21 Feb 2025 10:57:24 -0800 Subject: [PATCH 310/622] Fix CFP to avoid removing synchronization (#7311) ConstantFieldPropagation previously reasoned that if a field has a constant value, then it is not possible for release-acquire operations to use it for synchronization, since the read can be assumed to be from a previous write. This logic was incorrect because reads must read the value written by the last write in the global modification order for the accessed location. In principle it would be possible for CFP to detect fields that have both constant values and also either no acquire reads or no release writes. Such fields cannot possibly be part of synchronization edges and their gets could be optimized normally. For now though, simply do not optimize any gets with acquire or seqcst ordering. Similarly, stop optimizing RMW operations because they can also form synchronization edges. --- src/passes/ConstantFieldPropagation.cpp | 96 +--- test/lit/passes/cfp-rmw.wast | 655 ++---------------------- test/lit/passes/cfp.wast | 24 +- 3 files changed, 66 insertions(+), 709 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 1d5318b0099..48e5a9c7770 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -133,27 +133,6 @@ struct FunctionOptimizer : public WalkerPass> { return PossibleConstantValues{}; } - // Returns a block dropping the `ref` operand of the argument. - template Block* makeRefDroppingBlock(T* curr) { - Builder builder(*getModule()); - return builder.blockify( - builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref))); - } - - // If an optimized access is sequentially consistent, then it synchronizes - // with other threads at least by participating in the global order of - // sequentially consistent operations. Preserve that effect by replacing the - // access with a fence. On the other hand, if we're optimizing an - // acquire-release operation, then we know the accessed field is constant and - // will not be modified, so the operation does not necessarily synchronize - // with other threads and no fence is required. - Block* maybeAddFence(Block* block, MemoryOrder order) { - if (order == MemoryOrder::SeqCst) { - block->list.push_back(Builder(*getModule()).makeAtomicFence()); - } - return block; - } - // Given information about a constant value, and the struct type and // StructGet/RMW/Cmpxchg that reads it, create an expression for that value. template @@ -216,6 +195,16 @@ struct FunctionOptimizer : public WalkerPass> { return; } + if (curr->order != MemoryOrder::Unordered) { + // The analysis we're basing the optimization on is not precise enough to + // rule out the field being used to synchronize between a write of the + // constant value and a subsequent read on another thread. This + // synchronization is possible even when the write does not change the + // value of the field. For now, simply avoid optimizing this case. + // TODO: Track release and acquire operations in the analysis. + return; + } + // If the value is not a constant, then it is unknown and we must give up // on simply applying a constant. However, we can try to use a ref.test, if // that is allowed. @@ -231,69 +220,8 @@ struct FunctionOptimizer : public WalkerPass> { // constant value. (Leave it to further optimizations to get rid of the // ref.) auto* value = makeExpression(info, heapType, curr); - auto* replacement = makeRefDroppingBlock(curr); - replacement = maybeAddFence(replacement, curr->order); - replacement->list.push_back(value); - replacement->type = value->type; - replaceCurrent(replacement); - changed = true; - } - - template - std::optional> - shouldOptimizeRMW(T* curr) { - auto type = getRelevantHeapType(curr); - if (!type) { - return std::nullopt; - } - auto heapType = *type; - - // Get the info about the field. Since RMWs can only copy or mutate the - // value, we always have something recorded. - PossibleConstantValues info = getInfo(heapType, curr->index); - assert(info.hasNoted() && "unexpected lack of info for RMW"); - - if (!info.isConstant()) { - // Optimizing using ref.test is not an option here because that only works - // on immutable fields, but RMW operations always access mutable fields. - return std::nullopt; - } - - // We can optimize. - return std::pair(heapType, info); - } - - void visitStructRMW(StructRMW* curr) { - auto typeAndInfo = shouldOptimizeRMW(curr); - if (!typeAndInfo) { - return; - } - // Only xchg allows the field to have a constant value. - assert(curr->op == RMWXchg && "unexpected op"); - auto& [type, info] = *typeAndInfo; - Builder builder(*getModule()); - auto* value = makeExpression(info, type, curr); - auto* replacement = makeRefDroppingBlock(curr); - replacement->list.push_back(builder.makeDrop(curr->value)); - replacement = maybeAddFence(replacement, curr->order); - replacement->list.push_back(value); - replacement->type = value->type; - replaceCurrent(replacement); - changed = true; - } - - void visitStructCmpxchg(StructCmpxchg* curr) { - auto typeAndInfo = shouldOptimizeRMW(curr); - if (!typeAndInfo) { - return; - } - auto& [type, info] = *typeAndInfo; - Builder builder(*getModule()); - auto* value = makeExpression(info, type, curr); - auto* replacement = makeRefDroppingBlock(curr); - replacement->list.push_back(builder.makeDrop(curr->expected)); - replacement->list.push_back(builder.makeDrop(curr->replacement)); - replacement = maybeAddFence(replacement, curr->order); + auto* replacement = builder.blockify( + builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref))); replacement->list.push_back(value); replacement->type = value->type; replaceCurrent(replacement); diff --git a/test/lit/passes/cfp-rmw.wast b/test/lit/passes/cfp-rmw.wast index cd1ff374b22..02266796a48 100644 --- a/test/lit/passes/cfp-rmw.wast +++ b/test/lit/passes/cfp-rmw.wast @@ -42,9 +42,11 @@ (local.get 0) ) ) + ) -;; RMW xchg operations are optimizable like normal reads and writes, though. +;; RMW xchg operations also cannot be optimized, even if they don't write new +;; values. They may form cross-thread synchronization edges. (module ;; CHECK: (type $A (shared (struct (field (mut i32))))) (type $A (shared (struct (mut i32)))) @@ -61,65 +63,27 @@ ) ;; CHECK: (func $rmw-xchg (type $1) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-xchg (param (ref $A)) (result i32) - ;; This xchg does not change the value, so allows optimization. Other RMW - ;; ops that cannot change values are optimized in OptimizeInstructions - ;; instead. + ;; This xchg does not change the value, but still cannot be optimized. (struct.atomic.rmw.xchg $A 0 (local.get 0) (i32.const 0) ) ) - ;; CHECK: (func $rmw-xchg-fallthrough (type $1) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - (func $rmw-xchg-fallthrough (param (ref $A)) (result i32) - ;; Same, and it works even with fallthrough. - (struct.atomic.rmw.xchg $A 0 - (local.get 0) - (block (result i32) - (i32.const 0) - ) - ) - ) - ;; CHECK: (func $rmw-xchg-acqrel (type $1) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-xchg-acqrel (param (ref $A)) (result i32) - ;; Making the accesses acqrel instead of seqcst means that we don't need a - ;; fence when we optimize. + ;; Making the accesses acqrel instead of seqcst doesn't make a difference. (struct.atomic.rmw.xchg acqrel acqrel $A 0 (local.get 0) (i32.const 0) @@ -135,14 +99,14 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $get (param (ref $A)) (result i32) - ;; This get is optimizable. + ;; This get is still optimizable. (struct.get $A 0 (local.get 0) ) ) ) -;; A RMW xchg copy is still optimizable, too. +;; A RMW xchg copy is also unoptimizable. (module ;; CHECK: (type $A (shared (struct (field (mut i32))))) (type $A (shared (struct (mut i32)))) @@ -161,27 +125,15 @@ ) ;; CHECK: (func $rmw-xchg-copy (type $1) (param $0 (ref $A)) (param $1 (ref $A)) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.atomic.get $A 0 + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-xchg-copy (param (ref $A) (ref $A)) (result i32) - ;; This is a copy from one A to another, so allows optimization. + ;; This is a copy from one A to another, but still cannot be optimized. (struct.atomic.rmw.xchg $A 0 (local.get 0) (struct.atomic.get $A 0 @@ -190,61 +142,16 @@ ) ) - ;; CHECK: (func $rmw-xchg-copy-fallthrough (type $1) (param $0 (ref $A)) (param $1 (ref $A)) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - (func $rmw-xchg-copy-fallthrough (param (ref $A) (ref $A)) (result i32) - ;; Same, and it works even with fallthrough. - (struct.atomic.rmw.xchg $A 0 - (local.get 0) - (block (result i32) - (struct.atomic.get $A 0 - (local.get 1) - ) - ) - ) - ) - ;; CHECK: (func $rmw-xchg-copy-acqrel (type $1) (param $0 (ref $A)) (param $1 (ref $A)) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (struct.atomic.get acqrel $A 0 + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-xchg-copy-acqrel (param (ref $A) (ref $A)) (result i32) - ;; Making the accesses acqrel instead of seqcst means that we don't need a - ;; fence when we optimize. + ;; Making the accesses acqrel instead of seqcst doesn't make a difference. (struct.atomic.rmw.xchg acqrel acqrel $A 0 (local.get 0) (struct.atomic.get acqrel $A 0 @@ -269,193 +176,7 @@ ) ) -;; Not all rmw.xchg instructions are optimizable, though. -(module - ;; CHECK: (type $A (shared (struct (field (mut i32))))) - (type $A (shared (struct (mut i32)))) - - ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $init (type $2) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $init - (drop - (struct.new_default $A) - ) - ) - - ;; CHECK: (func $rmw-xchg-mutate (type $1) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $rmw-xchg-mutate (param (ref $A)) (result i32) - ;; The xchg mutates $A, so it has more than one value and cannot be - ;; optimized out. - (struct.atomic.rmw.xchg $A 0 - (local.get 0) - (i32.const 1) - ) - ) - - ;; CHECK: (func $get (type $1) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $get (param (ref $A)) (result i32) - (struct.get $A 0 - (local.get 0) - ) - ) -) - -;; "Copies" across types are not optimizable. -(module - ;; CHECK: (type $A (shared (struct (field (mut i32))))) - (type $A (shared (struct (mut i32)))) - ;; CHECK: (type $B (shared (struct (field i32)))) - (type $B (shared (struct i32))) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (type $3 (func (param (ref $A) (ref $B)) (result i32))) - - ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) - - ;; CHECK: (func $init (type $2) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $B - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $init - (drop - (struct.new_default $A) - ) - (drop - (struct.new $B - (i32.const 1) - ) - ) - ) - - ;; CHECK: (func $rmw-xchg-no-copy (type $3) (param $0 (ref $A)) (param $1 (ref $B)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $rmw-xchg-no-copy (param (ref $A) (ref $B)) (result i32) - ;; The xchg mutates $A, so it has more than one value and cannot be - ;; optimized out (but the get of $B is optimized). - (struct.atomic.rmw.xchg $A 0 - (local.get 0) - (struct.atomic.get $B 0 - (local.get 1) - ) - ) - ) - - ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $get (param (ref $A)) (result i32) - (struct.get $A 0 - (local.get 0) - ) - ) -) - -;; In principle this version of the previous case is optimizable because the -;; values are the same, but we don't optimize it yet. TODO: optimize this. -(module - ;; CHECK: (type $A (shared (struct (field (mut i32))))) - (type $A (shared (struct (mut i32)))) - ;; CHECK: (type $B (shared (struct (field i32)))) - (type $B (shared (struct i32))) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (type $3 (func (param (ref $A) (ref $B)) (result i32))) - - ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) - - ;; CHECK: (func $init (type $2) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $init - (drop - (struct.new_default $A) - ) - ;; Now this is a struct.new_default to make the values match. - (drop - (struct.new_default $B) - ) - ) - - ;; CHECK: (func $rmw-xchg-copy-value (type $3) (param $0 (ref $A)) (param $1 (ref $B)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.xchg $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $rmw-xchg-copy-value (param (ref $A) (ref $B)) (result i32) - ;; The xchg uses different types, but cannot change the value. - (struct.atomic.rmw.xchg $A 0 - (local.get 0) - (struct.atomic.get $B 0 - (local.get 1) - ) - ) - ) - - ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $get (param (ref $A)) (result i32) - (struct.get $A 0 - (local.get 0) - ) - ) -) - -;; Similarly, cmpxchg can be optimized. +;; Similarly, cmpxchg cannot be optimized. (module ;; CHECK: (type $A (shared (struct (field (mut i32))))) (type $A (shared (struct (mut i32)))) @@ -474,22 +195,14 @@ ) ;; CHECK: (func $rmw-cmpxchg (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-cmpxchg (param (ref $A) i32) (result i32) - ;; This cmpxchg does not change the value, so allows optimization. + ;; This cmpxchg does not change the value, but cannot be optimized. (struct.atomic.rmw.cmpxchg $A 0 (local.get 0) (local.get 1) @@ -497,51 +210,15 @@ ) ) - ;; CHECK: (func $rmw-cmpxchg-fallthrough (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - (func $rmw-cmpxchg-fallthrough (param (ref $A) i32) (result i32) - ;; Same, and it works even with fallthrough. - (struct.atomic.rmw.cmpxchg $A 0 - (local.get 0) - (local.get 1) - (block (result i32) - (i32.const 0) - ) - ) - ) - ;; CHECK: (func $rmw-cmpxchg-acqrel (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-cmpxchg-acqrel (param (ref $A) i32) (result i32) - ;; Acqrel accesses to constant fields do not synchronize with anything, so - ;; we can optimize without fences. + ;; Acqrel accesses do not make a difference. (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 (local.get 0) (local.get 1) @@ -565,51 +242,38 @@ ) ) -;; cmpxchg copies can be optimized. +;; cmpxchg copies also cannot be optimized. (module ;; CHECK: (type $A (shared (struct (field (mut i32))))) (type $A (shared (struct (mut i32)))) - ;; CHECK: (type $1 (func (param (ref $A) i32) (result i32))) + ;; CHECK: (type $1 (func (result (ref $A)))) - ;; CHECK: (type $2 (func (result (ref $A)))) + ;; CHECK: (type $2 (func (param (ref $A) i32 (ref $A)) (result i32))) - ;; CHECK: (type $3 (func (param (ref $A) i32 (ref $A)) (result i32))) + ;; CHECK: (type $3 (func (param (ref $A) i32) (result i32))) ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) - ;; CHECK: (func $init (type $2) (result (ref $A)) + ;; CHECK: (func $init (type $1) (result (ref $A)) ;; CHECK-NEXT: (struct.new_default $A) ;; CHECK-NEXT: ) (func $init (result (ref $A)) (struct.new_default $A) ) - ;; CHECK: (func $rmw-cmpxchg (type $3) (param $0 (ref $A)) (param $1 i32) (param $2 (ref $A)) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop + ;; CHECK: (func $rmw-cmpxchg (type $2) (param $0 (ref $A)) (param $1 i32) (param $2 (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (struct.atomic.get $A 0 + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-cmpxchg (param (ref $A) i32 (ref $A)) (result i32) - ;; This cmpxchg copies the field, so does not change the value. + ;; This cmpxchg copies the field, so does not change the value. It still + ;; cannot be optimized. (struct.atomic.rmw.cmpxchg $A 0 (local.get 0) (local.get 1) @@ -619,51 +283,15 @@ ) ) - ;; CHECK: (func $rmw-cmpxchg-fallthrough (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - (func $rmw-cmpxchg-fallthrough (param (ref $A) i32) (result i32) - ;; Same, and it works even with fallthrough. - (struct.atomic.rmw.cmpxchg $A 0 - (local.get 0) - (local.get 1) - (block (result i32) - (i32.const 0) - ) - ) - ) - - ;; CHECK: (func $rmw-cmpxchg-acqrel (type $1) (param $0 (ref $A)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop + ;; CHECK: (func $rmw-cmpxchg-acqrel (type $3) (param $0 (ref $A)) (param $1 i32) (result i32) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $rmw-cmpxchg-acqrel (param (ref $A) i32) (result i32) - ;; Acqrel accesses to constant fields do not synchronize with anything, so - ;; we can optimize without fences. + ;; Acqrel accesses to constant fields do not make a difference. (struct.atomic.rmw.cmpxchg acqrel acqrel $A 0 (local.get 0) (local.get 1) @@ -687,198 +315,9 @@ ) ) -;; Mutating cmpxchg cannot be optimized. -(module - ;; CHECK: (type $A (shared (struct (field (mut i32))))) - (type $A (shared (struct (mut i32)))) - - ;; CHECK: (type $1 (func (result (ref $A)))) - - ;; CHECK: (type $2 (func (param (ref $A) i32) (result i32))) - - ;; CHECK: (type $3 (func (param (ref $A)) (result i32))) - - ;; CHECK: (func $init (type $1) (result (ref $A)) - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - (func $init (result (ref $A)) - (struct.new_default $A) - ) - - ;; CHECK: (func $rmw-cmpxchg-mutate (type $2) (param $0 (ref $A)) (param $1 i32) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $rmw-cmpxchg-mutate (param (ref $A) i32) (result i32) - ;; This cmpxchg changes the field if it writes, so cannot be optimized. - (struct.atomic.rmw.cmpxchg $A 0 - (local.get 0) - (local.get 1) - (i32.const 1) - ) - ) - - ;; CHECK: (func $get (type $3) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $get (param (ref $A)) (result i32) - ;; This get is not optimizable. - (struct.get $A 0 - (local.get 0) - ) - ) -) - -;; Cmpxchg "copies" across types are not optimizable. -(module - ;; CHECK: (type $A (shared (struct (field (mut i32))))) - (type $A (shared (struct (mut i32)))) - ;; CHECK: (type $B (shared (struct (field i32)))) - (type $B (shared (struct i32))) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (type $3 (func (param (ref $A) i32 (ref $B)) (result i32))) - - ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) - - ;; CHECK: (func $init (type $2) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $B - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $init - (drop - (struct.new_default $A) - ) - (drop - (struct.new $B - (i32.const 1) - ) - ) - ) - - ;; CHECK: (func $rmw-cmpxchg-no-copy (type $3) (param $0 (ref $A)) (param $1 i32) (param $2 (ref $B)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $rmw-cmpxchg-no-copy (param (ref $A) i32 (ref $B)) (result i32) - ;; The cmpxchg mutates $A, so it has more than one value and cannot be - ;; optimized out (but the get of $B is optimized). - (struct.atomic.rmw.cmpxchg $A 0 - (local.get 0) - (local.get 1) - (struct.atomic.get $B 0 - (local.get 2) - ) - ) - ) - - ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $get (param (ref $A)) (result i32) - (struct.get $A 0 - (local.get 0) - ) - ) -) - -;; In principle this version of the previous case is optimizable because the -;; values are the same, but we don't optimize it yet. TODO: optimize this. -(module - ;; CHECK: (type $A (shared (struct (field (mut i32))))) - (type $A (shared (struct (mut i32)))) - ;; CHECK: (type $B (shared (struct (field i32)))) - (type $B (shared (struct i32))) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (type $3 (func (param (ref $A) i32 (ref $B)) (result i32))) - - ;; CHECK: (type $4 (func (param (ref $A)) (result i32))) - - ;; CHECK: (func $init (type $2) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $init - (drop - (struct.new_default $A) - ) - ;; Now this is a struct.new_default to make the values match. - (drop - (struct.new_default $B) - ) - ) - - ;; CHECK: (func $rmw-cmpxchg-copy-value (type $3) (param $0 (ref $A)) (param $1 i32) (param $2 (ref $B)) (result i32) - ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $rmw-cmpxchg-copy-value (param (ref $A) i32 (ref $B)) (result i32) - ;; The cmpxchg uses different types, but cannot change the value. - (struct.atomic.rmw.cmpxchg $A 0 - (local.get 0) - (local.get 1) - (struct.atomic.get $B 0 - (local.get 2) - ) - ) - ) - - ;; CHECK: (func $get (type $4) (param $0 (ref $A)) (result i32) - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $get (param (ref $A)) (result i32) - (struct.get $A 0 - (local.get 0) - ) - ) -) - -;; In principle this version can also be optimized because the cmpxchg will -;; never perform a write. TODO: optimize this. +;; In principle this version can be optimized because the cmpxchg will never +;; perform a write and there are no other writes for it to synchronize with. +;; TODO: optimize this. (module ;; CHECK: (type $A (shared (struct (field (mut i32))))) (type $A (shared (struct (mut i32)))) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 97fd500c3aa..0bc4372ec35 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2870,24 +2870,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (struct.atomic.get acqrel $shared 0 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (atomic.fence) - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (struct.atomic.get $shared 0 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2898,13 +2887,14 @@ ) ) (drop - ;; This can be optimzied and does not require a fence. + ;; This can be optimzied in principle, but our analysis cannot yet prove + ;; there is no synchronization. TODO. (struct.atomic.get acqrel $shared 0 (local.get 0) ) ) (drop - ;; This can be optimized, but requires a seqcst fence. + ;; Same as above. (struct.atomic.get $shared 0 (local.get 0) ) From 3ea7e6f7e7ef6761a4d7e8e670dc19ec39c8a8a6 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 21 Feb 2025 10:57:39 -0800 Subject: [PATCH 311/622] [NFC] Update a comment about synchronization in GUFA (#7312) The comment mentioned that in principle we might be able to optimize atomic struct.gets when we know what the stored value we are loading will be. This is not correct because the write and the read would still create a synchronization edge in that case. Update the comment accordingly. --- src/passes/GUFA.cpp | 5 +---- test/lit/passes/gufa-refs.wast | 9 ++++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 2ca1ac6ef59..a4c92fe81a0 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -153,10 +153,7 @@ struct GUFAOptimizer if (Properties::getMemoryOrder(curr) != MemoryOrder::Unordered) { // This load might synchronize with some store, and if we replaced the // load with a constant or with a load from a global, it would not - // synchronize with that store anymore. Since we know what value the store - // must write, and we know it is the same as every other store to the same - // location, it's possible that optimizing here would be allowable, but - // for now be conservative and do not optimize. + // synchronize with that store anymore. return; } diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index a4878df7b31..6d48d651a5f 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -6171,19 +6171,22 @@ ) (drop ;; This is optimizable. It reads from shared memory, but there is only one - ;; possible value that can be read. + ;; possible value that can be read and it is not atomic, so does not form + ;; a synchronization edge. (struct.get $A 0 (local.get 0) ) ) (drop - ;; We do not (yet) optimize atomic gets. + ;; We do not optimize atomic gets, since they might synchronize with a + ;; write. (struct.atomic.get acqrel $A 0 (local.get 0) ) ) (drop - ;; We do not (yet) optimize atomic gets. + ;; We do not optimize atomic gets, since they might synchronize with a + ;; write. (struct.atomic.get $A 0 (local.get 0) ) From 83c82e56172506572098cbc590e7b0b0d6edcd1f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 21 Feb 2025 14:42:30 -0800 Subject: [PATCH 312/622] [NFC] Make room for exact references in type encoding (#7314) Reserve an additional bit in the encoding of types to represent reference exactness, which is introduced in the custom RTTs proposal: https://github.com/WebAssembly/custom-rtts/blob/main/proposals/custom-rtts/Overview.md#exact-reference-types. Also switch to using constexpr variables for bit manipulations in the type representation rather than using hardcoded numbers. --- src/wasm-type.h | 66 +++++++++++++++++------------ src/wasm/wasm-type.cpp | 2 +- test/example/c-api-kitchen-sink.txt | 22 +++++----- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 8e481e4e7cf..9e3419190d3 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -95,28 +95,32 @@ class HeapType { // should also be passed by value. uintptr_t id; + static constexpr int TypeBits = 3; + static constexpr int UsedBits = TypeBits + 1; + static constexpr int SharedMask = 1 << TypeBits; + public: - // Bits 0 and 1 are used by the Type representation, so need to be left free. - // Bit 2 determines whether the basic heap type is shared (1) or unshared (0). + // Bits 0-2 are used by the Type representation, so need to be left free. + // Bit 3 determines whether the basic heap type is shared (1) or unshared (0). enum BasicHeapType : uint32_t { - ext = 1 << 3, - func = 2 << 3, - cont = 3 << 3, - any = 4 << 3, - eq = 5 << 3, - i31 = 6 << 3, - struct_ = 7 << 3, - array = 8 << 3, - exn = 9 << 3, - string = 10 << 3, - none = 11 << 3, - noext = 12 << 3, - nofunc = 13 << 3, - nocont = 14 << 3, - noexn = 15 << 3, + ext = 1 << UsedBits, + func = 2 << UsedBits, + cont = 3 << UsedBits, + any = 4 << UsedBits, + eq = 5 << UsedBits, + i31 = 6 << UsedBits, + struct_ = 7 << UsedBits, + array = 8 << UsedBits, + exn = 9 << UsedBits, + string = 10 << UsedBits, + none = 11 << UsedBits, + noext = 12 << UsedBits, + nofunc = 13 << UsedBits, + nocont = 14 << UsedBits, + noexn = 15 << UsedBits, }; static constexpr BasicHeapType _last_basic_type = - BasicHeapType(noexn + (1 << 2)); + BasicHeapType(noexn | SharedMask); // BasicHeapType can be implicitly upgraded to HeapType constexpr HeapType(BasicHeapType id) : id(id) {} @@ -214,7 +218,8 @@ class HeapType { // Get the shared or unshared version of this basic heap type. constexpr BasicHeapType getBasic(Shareability share) const { assert(isBasic()); - return BasicHeapType(share == Shared ? (id | 4) : (id & ~4)); + return BasicHeapType(share == Shared ? (id | SharedMask) + : (id & ~SharedMask)); } // (In)equality must be defined for both HeapType and BasicHeapType because it @@ -269,13 +274,17 @@ class Type { // bit 0 set. When that bit is masked off, they are pointers to the underlying // vectors of types. Otherwise, the type is a reference type, and is // represented as a heap type with bit 1 set iff the reference type is - // nullable. + // nullable and bit 2 set iff the reference type is exact. // // Since `Type` is really just a single integer, it should be passed by value. // This is a uintptr_t rather than a TypeID (uint64_t) to save memory on // 32-bit platforms. uintptr_t id; + static constexpr int TupleMask = 1 << 0; + static constexpr int NullMask = 1 << 1; + static constexpr int ExactMask = 1 << 2; + public: enum BasicType : uint32_t { none = 0, @@ -306,7 +315,10 @@ class Type { // Construct from a heap type description. Also covers construction from // Signature, Struct or Array via implicit conversion to HeapType. Type(HeapType heapType, Nullability nullable) - : Type(heapType.getID() | (nullable == Nullable ? 2 : 0)) {} + : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0)) { + assert(heapType.isBasic() || + !(heapType.getID() & (TupleMask | NullMask | ExactMask))); + } // Predicates // Compound Concrete @@ -345,18 +357,18 @@ class Type { // basic case for the underlying implementation. // TODO: Experiment with leaving bit 0 free in basic types. - bool isTuple() const { return !isBasic() && (id & 1); } + bool isTuple() const { return !isBasic() && (id & TupleMask); } const Tuple& getTuple() const { assert(isTuple()); - return *(Tuple*)(id & ~1); + return *(Tuple*)(id & ~TupleMask); } - bool isRef() const { return !isBasic() && !(id & 1); } - bool isNullable() const { return isRef() && (id & 2); } - bool isNonNullable() const { return isRef() && !(id & 2); } + bool isRef() const { return !isBasic() && !(id & TupleMask); } + bool isNullable() const { return isRef() && (id & NullMask); } + bool isNonNullable() const { return isRef() && !(id & NullMask); } HeapType getHeapType() const { assert(isRef()); - return HeapType(id & ~2); + return HeapType(id & ~(NullMask | ExactMask)); } bool isFunction() const { return isRef() && getHeapType().isFunction(); } diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 454f563f684..8710a0619d5 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -876,7 +876,7 @@ bool HeapType::isOpen() const { Shareability HeapType::getShared() const { if (isBasic()) { - return (id & 4) != 0 ? Shared : Unshared; + return (id & SharedMask) != 0 ? Shared : Unshared; } else { return getHeapTypeInfo(*this)->share; } diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index ba4a520268e..2e319129e13 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -20,17 +20,17 @@ BinaryenTypeAuto: -1 BinaryenPackedTypeNotPacked: 0 BinaryenPackedTypeInt8: 1 BinaryenPackedTypeInt16: 2 -BinaryenHeapTypeExt: 8 -BinaryenHeapTypeFunc: 16 -BinaryenHeapTypeAny: 32 -BinaryenHeapTypeEq: 40 -BinaryenHeapTypeI31: 48 -BinaryenHeapTypeStruct: 56 -BinaryenHeapTypeArray: 64 -BinaryenHeapTypeString: 80 -BinaryenHeapTypeNone: 88 -BinaryenHeapTypeNoext: 96 -BinaryenHeapTypeNofunc: 104 +BinaryenHeapTypeExt: 16 +BinaryenHeapTypeFunc: 32 +BinaryenHeapTypeAny: 64 +BinaryenHeapTypeEq: 80 +BinaryenHeapTypeI31: 96 +BinaryenHeapTypeStruct: 112 +BinaryenHeapTypeArray: 128 +BinaryenHeapTypeString: 160 +BinaryenHeapTypeNone: 176 +BinaryenHeapTypeNoext: 192 +BinaryenHeapTypeNofunc: 208 BinaryenFeatureMVP: 0 BinaryenFeatureAtomics: 1 BinaryenFeatureBulkMemory: 16 From f3462570f4b122049d3487eef8affac441d7a7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Mon, 24 Feb 2025 19:11:50 +0100 Subject: [PATCH 313/622] WAT parser: include file name in error messages (#7318) This is especially useful with `wasm-merge` which takes several file as inputs. --- src/parser/lexer.h | 9 ++++++++- src/parser/wat-parser.cpp | 7 +++++++ src/parser/wat-parser.h | 4 +++- src/wasm/wasm-io.cpp | 11 +++++++---- test/lit/parse-bad-optional-memidx.wast | 2 +- test/lit/parse-bad-supertype-8616.wast | 2 +- test/lit/parse-bad-supertype.wast | 2 +- test/lit/parse-bad-tuple-extract-index.wast | 2 +- test/lit/parse-bad-tuple-in-sig.wast | 2 +- test/lit/parse-error-func-param-type.wast | 2 +- test/lit/parse-error-return-nofunc.wast | 2 +- test/lit/parse-error.wast | 2 +- 12 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/parser/lexer.h b/src/parser/lexer.h index 37c3fe04a87..5d5aee392a3 100644 --- a/src/parser/lexer.h +++ b/src/parser/lexer.h @@ -61,11 +61,15 @@ struct Lexer { private: size_t pos = 0; std::vector annotations; + std::optional file; public: std::string_view buffer; - Lexer(std::string_view buffer) : buffer(buffer) { setPos(0); } + Lexer(std::string_view buffer, std::optional file = std::nullopt) + : file(file), buffer(buffer) { + setPos(0); + } size_t getPos() const { return pos; } @@ -169,6 +173,9 @@ struct Lexer { [[nodiscard]] Err err(size_t pos, std::string reason) { std::stringstream msg; + if (file) { + msg << *file << ":"; + } msg << position(pos) << ": error: " << reason; return Err{msg.str()}; } diff --git a/src/parser/wat-parser.cpp b/src/parser/wat-parser.cpp index 0a40decd5ef..26b2bbad06f 100644 --- a/src/parser/wat-parser.cpp +++ b/src/parser/wat-parser.cpp @@ -121,6 +121,13 @@ Result<> doParseModule(Module& wasm, Lexer& input, bool allowExtra) { } // anonymous namespace +Result<> parseModule(Module& wasm, + std::string_view in, + std::optional filename) { + Lexer lexer(in, filename); + return doParseModule(wasm, lexer, false); +} + Result<> parseModule(Module& wasm, std::string_view in) { Lexer lexer(in); return doParseModule(wasm, lexer, false); diff --git a/src/parser/wat-parser.h b/src/parser/wat-parser.h index 0bfb829cb8a..b0074faa6e1 100644 --- a/src/parser/wat-parser.h +++ b/src/parser/wat-parser.h @@ -26,7 +26,9 @@ namespace wasm::WATParser { // Parse a single WAT module. -Result<> parseModule(Module& wasm, std::string_view in); +Result<> parseModule(Module& wasm, + std::string_view in, + std::optional filename = std::nullopt); // Parse a single WAT module that may have other things after it, as in a wast // file. diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp index e1d036cecce..32dd50d100b 100644 --- a/src/wasm/wasm-io.cpp +++ b/src/wasm/wasm-io.cpp @@ -34,8 +34,11 @@ namespace wasm { #define DEBUG_TYPE "writer" -static void readTextData(std::string& input, Module& wasm, IRProfile profile) { - if (auto parsed = WATParser::parseModule(wasm, input); +static void readTextData(std::optional filename, + std::string& input, + Module& wasm, + IRProfile profile) { + if (auto parsed = WATParser::parseModule(wasm, input, filename); auto err = parsed.getErr()) { Fatal() << err->msg; } @@ -44,7 +47,7 @@ static void readTextData(std::string& input, Module& wasm, IRProfile profile) { void ModuleReader::readText(std::string filename, Module& wasm) { BYN_TRACE("reading text from " << filename << "\n"); auto input(read_file(filename, Flags::Text)); - readTextData(input, wasm, profile); + readTextData(filename, input, wasm, profile); } void ModuleReader::readBinaryData(std::vector& input, @@ -114,7 +117,7 @@ void ModuleReader::readStdin(Module& wasm, std::string sourceMapFilename) { std::ostringstream s; s.write(input.data(), input.size()); std::string input_str = s.str(); - readTextData(input_str, wasm, profile); + readTextData(std::nullopt, input_str, wasm, profile); } } diff --git a/test/lit/parse-bad-optional-memidx.wast b/test/lit/parse-bad-optional-memidx.wast index 57438d6d2fd..d3b40905753 100644 --- a/test/lit/parse-bad-optional-memidx.wast +++ b/test/lit/parse-bad-optional-memidx.wast @@ -3,7 +3,7 @@ ;; RUN: not wasm-opt -all %s 2>&1 | filecheck %s -;; CHECK: Fatal: 12:22: error: expected end of instruction +;; CHECK: Fatal: {{.*}}:12:22: error: expected end of instruction (module (memory 1 1) diff --git a/test/lit/parse-bad-supertype-8616.wast b/test/lit/parse-bad-supertype-8616.wast index c7c3dc957d7..c5940e78b16 100644 --- a/test/lit/parse-bad-supertype-8616.wast +++ b/test/lit/parse-bad-supertype-8616.wast @@ -1,6 +1,6 @@ ;; RUN: not wasm-opt %s 2>&1 | filecheck %s -;; CHECK: Fatal: 9:2: error: invalid type: Heap type has an undeclared supertype +;; CHECK: Fatal: {{.*}}:9:2: error: invalid type: Heap type has an undeclared supertype ;; Regression test for a parser bug that caused an assertion failure in this case. (module diff --git a/test/lit/parse-bad-supertype.wast b/test/lit/parse-bad-supertype.wast index 8612bee27f1..208f9c57899 100644 --- a/test/lit/parse-bad-supertype.wast +++ b/test/lit/parse-bad-supertype.wast @@ -2,7 +2,7 @@ ;; RUN: not wasm-opt %s -all 2>&1 | filecheck %s -;; CHECK: Fatal: 8:2: error: invalid type: Heap type has an invalid supertype +;; CHECK: Fatal: {{.*}}:8:2: error: invalid type: Heap type has an invalid supertype (module (type $super (sub (struct i32))) (type $sub (sub $super (struct i64))) diff --git a/test/lit/parse-bad-tuple-extract-index.wast b/test/lit/parse-bad-tuple-extract-index.wast index ac20c5c43f8..d8a820978d5 100644 --- a/test/lit/parse-bad-tuple-extract-index.wast +++ b/test/lit/parse-bad-tuple-extract-index.wast @@ -2,7 +2,7 @@ ;; RUN: not wasm-opt %s 2>&1 | filecheck %s -;; CHECK: Fatal: 9:3: error: tuple index out of bounds +;; CHECK: Fatal: {{.*}}:9:3: error: tuple index out of bounds (module (func diff --git a/test/lit/parse-bad-tuple-in-sig.wast b/test/lit/parse-bad-tuple-in-sig.wast index d806d3b73dc..156a17297dd 100644 --- a/test/lit/parse-bad-tuple-in-sig.wast +++ b/test/lit/parse-bad-tuple-in-sig.wast @@ -2,7 +2,7 @@ ;; RUN: not wasm-opt %s 2>&1 | filecheck %s -;; CHECK: Fatal: 9:1: error: tuple types not allowed in signature +;; CHECK: Fatal: {{.*}}:9:1: error: tuple types not allowed in signature (module (func $tuple-in-sig (param (tuple i32 i32)) diff --git a/test/lit/parse-error-func-param-type.wast b/test/lit/parse-error-func-param-type.wast index 04068eca859..91bd60a10c5 100644 --- a/test/lit/parse-error-func-param-type.wast +++ b/test/lit/parse-error-func-param-type.wast @@ -1,7 +1,7 @@ ;; This function's type does not match the param we define for it. ;; RUN: not wasm-opt %s 2>&1 | filecheck %s -;; CHECK: Fatal: 9:10: error: type does not match provided signature +;; CHECK: Fatal: {{.*}}:9:10: error: type does not match provided signature (module (type $0 (func)) diff --git a/test/lit/parse-error-return-nofunc.wast b/test/lit/parse-error-return-nofunc.wast index 2d745c3e097..c60b4a3b20c 100644 --- a/test/lit/parse-error-return-nofunc.wast +++ b/test/lit/parse-error-return-nofunc.wast @@ -1,7 +1,7 @@ ;; We should error properly on a return in a non-function scope ;; RUN: not wasm-opt %s 2>&1 | filecheck %s -;; CHECK: Fatal: 8:5: error: return is only valid in a function context +;; CHECK: Fatal: {{.*}}:8:5: error: return is only valid in a function context (module (elem diff --git a/test/lit/parse-error.wast b/test/lit/parse-error.wast index c6aeb42ad75..255a495cecf 100644 --- a/test/lit/parse-error.wast +++ b/test/lit/parse-error.wast @@ -1,7 +1,7 @@ ;; Test that parse errors have helpful messages ;; RUN: not wasm-opt %s 2>&1 | filecheck %s -;; CHECK: Fatal: 8:5: error: unrecognized instruction +;; CHECK: Fatal: {{.*}}:8:5: error: unrecognized instruction (module (func $foo From 4de55c174dad0df399bde8672100bf048ed9cc33 Mon Sep 17 00:00:00 2001 From: Roberto Lublinerman Date: Mon, 24 Feb 2025 13:20:35 -0800 Subject: [PATCH 314/622] Prevent precompute of stringview_wtf16.slice splitting unicode codepoints. (#7320) --- src/wasm-interpreter.h | 14 ++++++++++ test/lit/passes/precompute-strings.wast | 37 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2a019513d2a..6b961dec0b1 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2273,6 +2273,20 @@ class ExpressionRunner : public OverriddenVisitor { Literals contents; if (endVal > startVal) { + // Check that the slice is valid; since we can assume that we have a valid + // UTF-16, we only need to check that it did not split surrgate pairs. + auto firstChar = refValues[startVal].getInteger(); + if (firstChar >= 0xDC00 && firstChar <= 0xDFFF) { + // The first char cannot be a low surrogate. + return Flow(NONCONSTANT_FLOW); + } + + auto lastChar = refValues[endVal - 1].getInteger(); + if (lastChar >= 0xD800 && lastChar <= 0xDBFF) { + // The last char cannot be a high surrogate. + return Flow(NONCONSTANT_FLOW); + } + contents.reserve(endVal - startVal); for (size_t i = startVal; i < endVal; i++) { if (i < refValues.size()) { diff --git a/test/lit/passes/precompute-strings.wast b/test/lit/passes/precompute-strings.wast index 9d4ec3a7a24..47eaddd47e0 100644 --- a/test/lit/passes/precompute-strings.wast +++ b/test/lit/passes/precompute-strings.wast @@ -26,6 +26,10 @@ ;; CHECK: (export "slice-unicode" (func $slice-unicode)) + ;; CHECK: (export "slice-invalid-unicode-end" (func $slice-invalid-unicode-end)) + + ;; CHECK: (export "slice-invalid-unicode-begin" (func $slice-invalid-unicode-begin)) + ;; CHECK: (func $eq-no (type $0) (result i32) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -225,6 +229,39 @@ ) ) + ;; CHECK: (func $slice-invalid-unicode-end (type $2) (result (ref string)) + ;; CHECK-NEXT: (stringview_wtf16.slice + ;; CHECK-NEXT: (string.const "a\f0\90\8d\86b") + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $slice-invalid-unicode-end (export "slice-invalid-unicode-end") (result (ref string)) + (stringview_wtf16.slice + ;; a𐍆b + (string.const "a\f0\90\8d\86b") + (i32.const 0) + (i32.const 2) + ) + ) + + ;; CHECK: (func $slice-invalid-unicode-begin (type $2) (result (ref string)) + ;; CHECK-NEXT: (stringview_wtf16.slice + ;; CHECK-NEXT: (string.const "a\f0\90\8d\86b") + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $slice-invalid-unicode-begin (export "slice-invalid-unicode-begin") (result (ref string)) + (stringview_wtf16.slice + ;; a𐍆b + (string.const "a\f0\90\8d\86b") + (i32.const 2) + (i32.const 4) + ) + ) + + ;; CHECK: (func $string.new-mutable (type $3) (result anyref) ;; CHECK-NEXT: (string.new_wtf16_array ;; CHECK-NEXT: (array.new_fixed $array16 4 From 9242718501e69a41250adb52ababb4e084b7dd1d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 24 Feb 2025 13:24:10 -0800 Subject: [PATCH 315/622] Fuzzer: Modify functions late (#7313) Previously we would create a function and then immediately modify it in interesting ways (replace code, move code around, etc.). We also modified any initial functions (in the content we are given to build on top of) at the very start. That was simple, by modifying later we can get some benefits. This PR makes us work in a loop: while more: if random: add function else modify some random function previously added This lets us unify the modification code to one place (rather than have it separate for initial functions and for created functions). Also, by modifying late, we can end up with calls from early functions to late ones, as we may create A and B before modifying A, and end up mutating something into a call to B. Previously we never had such forward calls. For such late modding to work, the FunctionCreationContext needs to be a little smarter, since we don't use a single context for creation and modification - now the modification may happen later. Specifically, it needs to scan the body to see the next label index that is valid to add, which we did not need before. This PR also moves some things out of the context destructor, because now we will run it more than once on the same function, and some things only need to happen once like adding the hang limit checks. --- src/tools/fuzzing.h | 13 +- src/tools/fuzzing/fuzzing.cpp | 190 +++++++++++------- test/passes/fuzz_metrics_noprint.bin.txt | 54 ++--- .../fuzz_metrics_passes_noprint.bin.txt | 53 ++--- ...e-to-fuzz_all-features_metrics_noprint.txt | 90 ++++----- 5 files changed, 227 insertions(+), 173 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 5e3797247f5..383e5af70c1 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -228,10 +228,7 @@ class TranslateToFuzzReader { // type => list of locals with that type std::unordered_map> typeLocals; - FunctionCreationContext(TranslateToFuzzReader& parent, Function* func) - : parent(parent), func(func) { - parent.funcContext = this; - } + FunctionCreationContext(TranslateToFuzzReader& parent, Function* func); ~FunctionCreationContext(); @@ -343,8 +340,14 @@ class TranslateToFuzzReader { Expression* makeImportSleep(Type type); Expression* makeMemoryHashLogging(); - // Function creation + // Function operations. The main processFunctions() loop will call addFunction + // as well as modFunction(). + void processFunctions(); + // Add a new function. Function* addFunction(); + // Modify an existing function. + void modFunction(Function* func); + void addHangLimitChecks(Function* func); // Recombination and mutation diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 21459a2ccf7..1c89383229f 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -362,11 +362,7 @@ void TranslateToFuzzReader::build() { addImportCallingSupport(); addImportSleepSupport(); modifyInitialFunctions(); - // keep adding functions until we run out of input - while (!random.finished()) { - auto* func = addFunction(); - addInvocations(func); - } + processFunctions(); if (fuzzParams->HANG_LIMIT > 0) { addHangLimitSupport(); } @@ -1125,6 +1121,41 @@ void TranslateToFuzzReader::addHashMemorySupport() { } } +TranslateToFuzzReader::FunctionCreationContext::FunctionCreationContext( + TranslateToFuzzReader& parent, Function* func) + : parent(parent), func(func) { + parent.funcContext = this; + + // Note the types of all locals. + computeTypeLocals(); + + // Find the right index for labelIndex: we emit names like label$5, so we need + // the index to be larger than all currently existing. + if (!func->body) { + return; + } + + struct Finder : public PostWalker> { + Index maxIndex = 0; + + void visitExpression(Expression* curr) { + // Note all scope names, and fix up all uses. + BranchUtils::operateOnScopeNameDefs(curr, [&](Name& name) { + if (name.is()) { + if (name.startsWith("label$")) { + auto str = name.toString(); + str = str.substr(6); + Index index = atoi(str.c_str()); + maxIndex = std::max(maxIndex, index + 1); + } + } + }); + } + } finder; + finder.walk(func->body); + labelIndex = finder.maxIndex; +} + TranslateToFuzzReader::FunctionCreationContext::~FunctionCreationContext() { // We must ensure non-nullable locals validate. Later down we'll run // TypeUpdating::handleNonDefaultableLocals which will make them validate by @@ -1151,9 +1182,6 @@ TranslateToFuzzReader::FunctionCreationContext::~FunctionCreationContext() { // fixup to ensure we validate. TypeUpdating::handleNonDefaultableLocals(func, parent.wasm); - if (parent.fuzzParams->HANG_LIMIT > 0) { - parent.addHangLimitChecks(func); - } assert(breakableStack.empty()); assert(hangStack.empty()); parent.funcContext = nullptr; @@ -1296,14 +1324,78 @@ Expression* TranslateToFuzzReader::makeMemoryHashLogging() { return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none); } +void TranslateToFuzzReader::processFunctions() { + // Functions that are eligible for being modded. We only do so once to each + // function, at most, so once we do we remove it from here. + std::vector moddable; + + // Defined initial functions are moddable. + for (auto& func : wasm.functions) { + if (!func->imported()) { + moddable.push_back(func.get()); + } + } + + // Add invocations, which can help execute the code here even if the function + // was not exported (or was exported but with a signature that traps + // immediately, like receiving a non-nullable ref, that the fuzzer can't + // provide from JS). Note we cannot iterate on wasm.functions because + // addInvocations modifies that. + for (auto* func : moddable) { + addInvocations(func); + } + + // We do not want to always mod in the same frequency. Pick a chance to mod a + // function. When the chance is maximal we will mod every single function, and + // immediately after creating it; when the chance is minimal we will not mod + // anything; values in the middle will end up randomly modding some functions, + // at random times (random times are useful because we might create function + // A, then B, then mod A, and since B has already been created, the modding of + // A may lead to calls to B). + const int RESOLUTION = 10; + auto chance = upTo(RESOLUTION + 1); + + // Keep working while we have random data. + while (!random.finished()) { + if (!moddable.empty() && upTo(RESOLUTION) < chance) { + // Mod an existing function. + auto index = upTo(moddable.size()); + auto* func = moddable[index]; + modFunction(func); + + // Remove this function from the vector by swapping the last item to its + // place, and truncating. + moddable[index] = moddable.back(); + moddable.pop_back(); + } else { + // Add a new function + auto* func = addFunction(); + addInvocations(func); + + // It may be modded later, if we allow out-of-bounds: we emit OOB checks + // in the code we just generated, and any changes could break that. + if (allowOOB) { + moddable.push_back(func); + } + } + } + + // At the very end, add hang limit checks (so no modding can override them). + if (fuzzParams->HANG_LIMIT > 0) { + for (auto& func : wasm.functions) { + if (!func->imported()) { + addHangLimitChecks(func.get()); + } + } + } +} + // TODO: return std::unique_ptr Function* TranslateToFuzzReader::addFunction() { LOGGING_PERCENT = upToSquared(100); auto allocation = std::make_unique(); auto* func = allocation.get(); func->name = Names::getValidFunctionName(wasm, "func"); - FunctionCreationContext context(*this, func); - assert(funcContext->typeLocals.empty()); Index numParams = upToSquared(fuzzParams->MAX_PARAMS); std::vector params; params.reserve(numParams); @@ -1322,7 +1414,9 @@ Function* TranslateToFuzzReader::addFunction() { } func->vars.push_back(type); } - context.computeTypeLocals(); + // Generate the function creation context after we filled in locals, which it + // will scan. + FunctionCreationContext context(*this, func); // with small chance, make the body unreachable auto bodyType = func->getResults(); if (oneIn(10)) { @@ -1334,29 +1428,6 @@ Function* TranslateToFuzzReader::addFunction() { } else { func->body = make(bodyType); } - // Our OOB checks are already in the code, and if we recombine/mutate we - // may end up breaking them. TODO: do them after the fact, like with the - // hang limit checks. - if (allowOOB) { - // Notice the locals and their types again, as more may have been added - // during generation of the body. We want to be able to local.get from those - // as well. - // TODO: We could also add a "localize" phase here to stash even more things - // in locals, so that they can be reused. But we would need to be - // careful with non-nullable locals (which error if used before being - // set, or trap if we make them nullable, both of which are bad). - context.computeTypeLocals(); - // Recombinations create duplicate code patterns. - recombine(func); - // Mutations add random small changes, which can subtly break duplicate - // code patterns. - mutate(func); - // TODO: liveness operations on gets, with some prob alter a get to one - // with more possible sets. - // Recombination, mutation, etc. can break validation; fix things up - // after. - fixAfterChanges(func); - } // Add hang limit checks after all other operations on the function body. wasm.addFunction(std::move(allocation)); @@ -1392,6 +1463,20 @@ Function* TranslateToFuzzReader::addFunction() { return func; } +void TranslateToFuzzReader::modFunction(Function* func) { + FunctionCreationContext context(*this, func); + + dropToLog(func); + // TODO: if we add OOB checks after creation, then we can do it on + // initial contents too, and it may be nice to *not* run these + // passes, like we don't run them on new functions. But, we may + // still want to run them some of the time, at least, so that we + // check variations on initial testcases even at the risk of OOB. + recombine(func); + mutate(func); + fixAfterChanges(func); +} + void TranslateToFuzzReader::addHangLimitChecks(Function* func) { // loop limit for (auto* loop : FindAll(func->body).list) { @@ -1822,9 +1907,6 @@ void TranslateToFuzzReader::modifyInitialFunctions() { if (wasm.functions.empty()) { return; } - // Pick a chance to fuzz the contents of a function. - const int RESOLUTION = 10; - auto chance = upTo(RESOLUTION + 1); // Do not iterate directly on wasm.functions itself (that is, avoid // for (x : wasm.functions) // ) as we may add to it as we go through the functions - make() can add new @@ -1840,43 +1922,11 @@ void TranslateToFuzzReader::modifyInitialFunctions() { (func->module == "fuzzing-support" || preserveImportsAndExports)) { continue; } - FunctionCreationContext context(*this, func); if (func->imported()) { + FunctionCreationContext context(*this, func); func->module = func->base = Name(); func->body = make(func->getResults()); } - // Optionally, fuzz the function contents. - if (upTo(RESOLUTION) >= chance) { - dropToLog(func); - // Notice params as well as any locals generated above. - // TODO add some locals? and the rest of addFunction's operations? - context.computeTypeLocals(); - // TODO: if we add OOB checks after creation, then we can do it on - // initial contents too, and it may be nice to *not* run these - // passes, like we don't run them on new functions. But, we may - // still want to run them some of the time, at least, so that we - // check variations on initial testcases even at the risk of OOB. - recombine(func); - mutate(func); - fixAfterChanges(func); - // TODO: This triad of functions appears in another place as well, and - // could be handled by a single function. That function could also - // decide to reorder recombine and mutate or even run more cycles of - // them. - } - } - - // Add invocations, which can help execute the code here even if the function - // was not exported (or was exported but with a signature that traps - // immediately, like receiving a non-nullable ref, that the fuzzer can't - // provide from JS). Note we need to use a temp vector for iteration, as - // addInvocations modifies wasm.functions. - std::vector funcs; - for (auto& func : wasm.functions) { - funcs.push_back(func.get()); - } - for (auto* func : funcs) { - addInvocations(func); } // Remove a start function - the fuzzing harness expects code to run only diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index 5c7e64baa92..1d46c87763d 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 24 - [funcs] : 30 + [exports] : 77 + [funcs] : 111 [globals] : 21 [imports] : 5 [memories] : 1 [memory-data] : 5 - [table-data] : 8 + [table-data] : 45 [tables] : 1 [tags] : 0 - [total] : 3231 - [vars] : 112 - Binary : 266 - Block : 529 - Break : 115 - Call : 122 - CallIndirect : 3 - Const : 529 - Drop : 31 - GlobalGet : 271 - GlobalSet : 190 - If : 181 - Load : 58 - LocalGet : 233 - LocalSet : 177 - Loop : 65 - Nop : 59 - RefFunc : 8 - Return : 31 - Select : 16 - Store : 29 - Switch : 4 - Unary : 220 - Unreachable : 94 + [total] : 9163 + [vars] : 296 + Binary : 620 + Block : 1503 + Break : 288 + Call : 580 + CallIndirect : 101 + Const : 1500 + Drop : 136 + GlobalGet : 772 + GlobalSet : 562 + If : 478 + Load : 126 + LocalGet : 630 + LocalSet : 487 + Loop : 166 + Nop : 78 + RefFunc : 45 + Return : 87 + Select : 75 + Store : 60 + Switch : 2 + Unary : 588 + Unreachable : 279 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index 77fa5dc3218..301f7d664dd 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,34 +1,35 @@ Metrics total - [exports] : 39 - [funcs] : 53 + [exports] : 36 + [funcs] : 64 [globals] : 7 [imports] : 4 [memories] : 1 [memory-data] : 29 - [table-data] : 24 + [table-data] : 19 [tables] : 1 [tags] : 0 - [total] : 4272 - [vars] : 162 - Binary : 328 - Block : 670 - Break : 131 - Call : 194 - CallIndirect : 38 - Const : 764 - Drop : 65 - GlobalGet : 364 - GlobalSet : 260 - If : 219 - Load : 80 - LocalGet : 299 - LocalSet : 209 - Loop : 77 - Nop : 41 - RefFunc : 24 - Return : 32 - Select : 34 - Store : 28 - Unary : 284 - Unreachable : 131 + [total] : 10813 + [vars] : 208 + Binary : 791 + Block : 1709 + Break : 412 + Call : 382 + CallIndirect : 73 + Const : 1793 + Drop : 77 + GlobalGet : 898 + GlobalSet : 637 + If : 561 + Load : 199 + LocalGet : 908 + LocalSet : 616 + Loop : 248 + Nop : 169 + RefFunc : 19 + Return : 89 + Select : 91 + Store : 80 + Switch : 2 + Unary : 747 + Unreachable : 312 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index a145cb716a5..55eb8b3f31a 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,58 +1,58 @@ Metrics total - [exports] : 21 - [funcs] : 26 + [exports] : 16 + [funcs] : 19 [globals] : 4 [imports] : 12 [memories] : 1 [memory-data] : 17 - [table-data] : 4 + [table-data] : 6 [tables] : 2 - [tags] : 3 - [total] : 960 - [vars] : 45 - ArrayNew : 1 - ArrayNewFixed : 9 - AtomicNotify : 2 - Binary : 93 - Block : 156 - BrOn : 2 + [tags] : 2 + [total] : 850 + [vars] : 49 + ArrayNewFixed : 5 + AtomicCmpxchg : 1 + AtomicFence : 2 + AtomicRMW : 1 + Binary : 91 + Block : 112 Break : 5 - Call : 55 + Call : 29 CallIndirect : 1 - Const : 173 - DataDrop : 1 - Drop : 15 - GlobalGet : 77 - GlobalSet : 70 - If : 40 - Load : 17 - LocalGet : 48 - LocalSet : 24 - Loop : 10 + CallRef : 2 + Const : 171 + Drop : 8 + GlobalGet : 55 + GlobalSet : 48 + I31Get : 2 + If : 37 + Load : 26 + LocalGet : 53 + LocalSet : 31 + Loop : 6 + MemoryCopy : 2 MemoryFill : 1 - MemoryInit : 1 - Nop : 12 + Nop : 6 Pop : 2 - RefEq : 2 - RefFunc : 4 - RefI31 : 3 - RefNull : 9 + RefAs : 1 + RefEq : 3 + RefFunc : 11 + RefI31 : 8 + RefIsNull : 1 + RefNull : 5 RefTest : 1 - Return : 6 - SIMDExtract : 4 - Select : 2 - Store : 3 - StringConst : 8 - StringEncode : 1 - StringEq : 1 + Return : 13 + SIMDExtract : 1 + Select : 8 + Store : 2 + StringConst : 12 + StringEq : 4 StringWTF16Get : 1 - StructNew : 4 - TableSet : 1 - Throw : 4 - Try : 1 - TryTable : 5 - TupleExtract : 1 - TupleMake : 3 - Unary : 45 - Unreachable : 36 + StructNew : 6 + Try : 3 + TryTable : 4 + TupleExtract : 3 + TupleMake : 5 + Unary : 37 + Unreachable : 24 From 1d7757c65ab861ec5568748f0cf3a867cf28c44e Mon Sep 17 00:00:00 2001 From: mcbarton Date: Mon, 24 Feb 2025 22:38:25 +0000 Subject: [PATCH 316/622] [ci] Update checkout action in create_release.yml to v4 (#7316) --- .github/workflows/create_release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 3229fd83f5e..f81a5f0b3eb 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -21,7 +21,7 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 with: submodules: true @@ -113,7 +113,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.x' - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 with: submodules: true From 5f767b73d8dc846db532decc64109d3aab63873c Mon Sep 17 00:00:00 2001 From: Roberto Lublinerman Date: Mon, 24 Feb 2025 16:44:13 -0800 Subject: [PATCH 317/622] Move StringSliceWTF16 avoiding non UTF slices fix to precompute.cpp. (#7322) --- src/passes/Precompute.cpp | 32 ++++++++++++++++++++++++++++++++ src/wasm-interpreter.h | 14 -------------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 93f2f1d6947..96870dc02ee 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -240,6 +240,38 @@ class PrecomputingExpressionRunner // string.encode_wtf16_array anyhow.) return Flow(NONCONSTANT_FLOW); } + + Flow visitStringSliceWTF(StringSliceWTF* curr) { + auto flow = Super::visitStringSliceWTF(curr); + if (flow.breaking()) { + return flow; + } + + auto refData = flow.getSingleValue().getGCData(); + if (!refData) { + return Flow(NONCONSTANT_FLOW); + } + + auto& refValues = refData->values; + if (refValues.size() == 0) { + return flow; + } + + // Check that the slice is valid; since we can assume that we have a valid + // UTF-16, we only need to check that it did not split surrogate pairs. + auto firstChar = refValues[0].getInteger(); + if (firstChar >= 0xDC00 && firstChar <= 0xDFFF) { + // The first char cannot be a low surrogate. + return Flow(NONCONSTANT_FLOW); + } + + auto lastChar = refValues[refValues.size() - 1].getInteger(); + if (lastChar >= 0xD800 && lastChar <= 0xDBFF) { + // The last char cannot be a high surrogate. + return Flow(NONCONSTANT_FLOW); + } + return flow; + } }; struct Precompute diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6b961dec0b1..2a019513d2a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2273,20 +2273,6 @@ class ExpressionRunner : public OverriddenVisitor { Literals contents; if (endVal > startVal) { - // Check that the slice is valid; since we can assume that we have a valid - // UTF-16, we only need to check that it did not split surrgate pairs. - auto firstChar = refValues[startVal].getInteger(); - if (firstChar >= 0xDC00 && firstChar <= 0xDFFF) { - // The first char cannot be a low surrogate. - return Flow(NONCONSTANT_FLOW); - } - - auto lastChar = refValues[endVal - 1].getInteger(); - if (lastChar >= 0xD800 && lastChar <= 0xDBFF) { - // The last char cannot be a high surrogate. - return Flow(NONCONSTANT_FLOW); - } - contents.reserve(endVal - startVal); for (size_t i = startVal; i < endVal; i++) { if (i < refValues.size()) { From 1a8fddcd4ae932e590504690c354cf717ac0f143 Mon Sep 17 00:00:00 2001 From: Roberto Lublinerman Date: Tue, 25 Feb 2025 11:01:50 -0800 Subject: [PATCH 318/622] [NFC] Precompute: Don't optimize string literals to string.const if not valid UTF-16 (#7324) This replaces an interpreter override on string slicing. By considering the output of operations, this is more general, and avoids ever emitting an invalid string constant. --- src/passes/Precompute.cpp | 67 ++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 96870dc02ee..2df23f2ae96 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -240,38 +240,6 @@ class PrecomputingExpressionRunner // string.encode_wtf16_array anyhow.) return Flow(NONCONSTANT_FLOW); } - - Flow visitStringSliceWTF(StringSliceWTF* curr) { - auto flow = Super::visitStringSliceWTF(curr); - if (flow.breaking()) { - return flow; - } - - auto refData = flow.getSingleValue().getGCData(); - if (!refData) { - return Flow(NONCONSTANT_FLOW); - } - - auto& refValues = refData->values; - if (refValues.size() == 0) { - return flow; - } - - // Check that the slice is valid; since we can assume that we have a valid - // UTF-16, we only need to check that it did not split surrogate pairs. - auto firstChar = refValues[0].getInteger(); - if (firstChar >= 0xDC00 && firstChar <= 0xDFFF) { - // The first char cannot be a low surrogate. - return Flow(NONCONSTANT_FLOW); - } - - auto lastChar = refValues[refValues.size() - 1].getInteger(); - if (lastChar >= 0xD800 && lastChar <= 0xDBFF) { - // The last char cannot be a high surrogate. - return Flow(NONCONSTANT_FLOW); - } - return flow; - } }; struct Precompute @@ -967,18 +935,17 @@ struct Precompute if (value.isNull()) { return true; } - return canEmitConstantFor(value.type); - } - bool canEmitConstantFor(Type type) { + auto type = value.type; // A function is fine to emit a constant for - we'll emit a RefFunc, which // is compact and immutable, so there can't be a problem. if (type.isFunction()) { return true; } - // We can emit a StringConst for a string constant. + // We can emit a StringConst for a string constant if the string is a + // UTF-16 string. if (type.isString()) { - return true; + return isValidUTF16Literal(value); } // All other reference types cannot be precomputed. Even an immutable GC // reference is not currently something this pass can handle, as it will @@ -991,6 +958,32 @@ struct Precompute return true; } + // TODO: move this logic to src/support/string, and refactor to share code + // with wasm/literal.cpp string printing's conversion from a Literal to a raw + // string. + bool isValidUTF16Literal(const Literal& value) { + bool expectLowSurrogate = false; + for (auto& v : value.getGCData()->values) { + auto c = v.getInteger(); + if (c >= 0xDC00 && c <= 0xDFFF) { + if (expectLowSurrogate) { + expectLowSurrogate = false; + continue; + } + // We got a low surrogate but weren't expecting one. + return false; + } + if (expectLowSurrogate) { + // We are expecting a low surrogate but didn't get one. + return false; + } + if (c >= 0xD800 && c <= 0xDBFF) { + expectLowSurrogate = true; + } + } + return !expectLowSurrogate; + } + // Helpers for partial precomputing. // Given a stack of expressions and the index of an expression in it, find From 7fe07c08c562fe15f1cc08dd053e204a621ba78d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 25 Feb 2025 11:15:36 -0800 Subject: [PATCH 319/622] Fuzzer: Interpose on existing exports (#7321) If the initial wasm we are fuzzing with had an export, then some of the time add more code in that export, interposing before the usual code: (func $foo (export "bar") (result i32) (..code..) ) => (func $foo (export "bar") (result i32) (call $something) ;; new code (..code..) ) We interpose by inserting a call to another function. We already got something like this from modifying functions from initial content, but adding such calls gives a much better chance to execute an interesting amount of new code (calling one of the new functions we generated ourselves, which could contain anything). --- src/tools/fuzzing/fuzzing.cpp | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 1c89383229f..e529706f786 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1336,6 +1336,8 @@ void TranslateToFuzzReader::processFunctions() { } } + auto numInitialExports = wasm.exports.size(); + // Add invocations, which can help execute the code here even if the function // was not exported (or was exported but with a signature that traps // immediately, like receiving a non-nullable ref, that the fuzzer can't @@ -1380,6 +1382,45 @@ void TranslateToFuzzReader::processFunctions() { } } + // Interpose on initial exports. When initial content contains exports, it can + // be useful to add new code that executes in them, rather than just adding + // new exports later. To some extent modifying the initially-exported function + // gives us that, but typically these are small changes, not calls to entirely + // new code (and this is especially important when preserveImportsAndExports, + // as in that mode we do not add new exports, so this interposing is our main + // chance to run new code using the existing exports). + // + // Interpose with a call before the old code. We do a call here so that we end + // up running a useful amount of new code (rather than just make(none) which + // would only emit something local in the current function, and which depends + // on its contents). + // TODO: We could also interpose after, either in functions without results, + // or by saving the results to a temp local as we call. + // + // Specifically, we will call functions, for simplicity, with no params or + // results. Such functions exist in abundance in general, because the + // invocations we add look exactly that way. First, find all such functions, + // and then find places to interpose calls to them. + std::vector noParamsOrResultFuncs; + for (auto& func : wasm.functions) { + if (func->getParams() == Type::none && func->getResults() == Type::none) { + noParamsOrResultFuncs.push_back(func->name); + } + } + if (!noParamsOrResultFuncs.empty()) { + for (Index i = 0; i < numInitialExports; i++) { + auto& exp = wasm.exports[i]; + if (exp->kind == ExternalKind::Function && upTo(RESOLUTION) < chance) { + auto* func = wasm.getFunction(exp->value); + if (!func->imported()) { + auto* call = + builder.makeCall(pick(noParamsOrResultFuncs), {}, Type::none); + func->body = builder.makeSequence(call, func->body); + } + } + } + } + // At the very end, add hang limit checks (so no modding can override them). if (fuzzParams->HANG_LIMIT > 0) { for (auto& func : wasm.functions) { From 9137cbf8826b00ed78735d211a2bfefb4350474b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 25 Feb 2025 12:56:24 -0800 Subject: [PATCH 320/622] [Fuzzing] Add a script to embed wasm files into JS testcases (#7323) This is the inverse of extract_wasms.py: the previous script extracted wasm files from JS, and this one can re-embed them back in. This can be useful when working with ClusterFuzz and Fuzzilli testcases. --- scripts/clusterfuzz/embed_wasms.py | 75 ++++++++++++++++++++++++++++++ test/lit/scripts/embed_wasms.lit | 25 ++++++++++ 2 files changed, 100 insertions(+) create mode 100644 scripts/clusterfuzz/embed_wasms.py create mode 100644 test/lit/scripts/embed_wasms.lit diff --git a/scripts/clusterfuzz/embed_wasms.py b/scripts/clusterfuzz/embed_wasms.py new file mode 100644 index 00000000000..fac00a83d8b --- /dev/null +++ b/scripts/clusterfuzz/embed_wasms.py @@ -0,0 +1,75 @@ +# +# Copyright 2025 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. + +''' +Reverse script for extract_wasms.py: That one extracts wasm files from a +JavaScript testcase (which has wasm files embedded as arrays of numbers), and +this one re-embeds them back. To do so, we use the magic comments that the +extractor uses: it replaces each wasm array with + + 'undefined /* extracted wasm */' + +We simply replace those with the given wasm files, in JS format. + +For example, assume INFILE.js contains two wasm files. Then + + extract_wasms.py INFILE.js OUTFILE + +will emit + + OUTFILE.js, OUTFILE.0.wasm, OUTFILE.1.wasm + +We now have a JS file without the wasm (which includes the magic comments +mentioned before) and one binary wasm file for each wasm. We can now re-embed +them, creating a merged JS file containing JS + wasm, using + + embed_wasms.py OUTFILE.js OUTFILE.0.wasm OUTFILE.1.wasm MERGED.js + +The first argument is the input JS, then the wasm files, then the last argument +is the output JS. +''' + +import re +import sys + +in_js = sys.argv[1] +in_wasms = sys.argv[2:-1] +out_js = sys.argv[-1] + +with open(in_js) as f: + js = f.read() + +wasm_index = 0 + + +def replace_wasm(text): + global wasm_index + wasm_file = in_wasms[wasm_index] + wasm_index += 1 + + with open(wasm_file, 'rb') as f: + wasm = f.read() + + bytes = [str(int(x)) for x in wasm] + bytes = ', '.join(bytes) + + return f'new Uint8Array([{bytes}])' + + +js = re.sub(r'undefined [/][*] extracted wasm [*][/]', replace_wasm, js) + +# Write out the new JS. +with open(out_js, 'w') as f: + f.write(js) diff --git a/test/lit/scripts/embed_wasms.lit b/test/lit/scripts/embed_wasms.lit new file mode 100644 index 00000000000..d5b579bac66 --- /dev/null +++ b/test/lit/scripts/embed_wasms.lit @@ -0,0 +1,25 @@ +;; Test embedding wasm files into JS. + +;; Wasm files replace undefined + magical comments, like these: +;; RUN: echo "good1(undefined /* extracted wasm */);" > %t.js + +;; Slight changes mean we ignore the pattern. +;; RUN: echo "bad(undefinedey /* random stuff */);" >> %t.js + +;; Add a second valid one. +;; RUN: echo "good2(undefined /* extracted wasm */);" >> %t.js + +;; Generate two valid wasm files to embed. +;; RUN: echo "(module)" > %t.1.wat +;; RUN: echo "(module (func $foo))" > %t.2.wat + +;; RUN: wasm-as %t.1.wat -o %t.1.wasm +;; RUN: wasm-as %t.2.wat -o %t.2.wasm + +;; RUN: python %S/../../../scripts/clusterfuzz/embed_wasms.py %t.js %t.1.wasm %t.2.wasm %t.out.js +;; RUN: cat %t.out.js | filecheck %s +;; +;; CHECK: good1(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])); +;; CHECK: bad(undefinedey +;; CHECK: good2(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 10, 4, 1, 2, 0, 11])); + From 609bcec0c1c321d2b4805e341a558679ae710e99 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Tue, 25 Feb 2025 14:48:25 -0800 Subject: [PATCH 321/622] [Interpreter] Float32 (#7325) Building on top of #7227, the following are implemented and tested: - f32.add - f32.sub - f32.mul - f32.div - f32.sqrt - f32.ceil - f32.floor - f32.trunc - f32.nearbyint --- src/interpreter/interpreter.cpp | 35 +++++++- test/gtest/interpreter.cpp | 148 ++++++++++++++++++++++++++++++++ third_party/googletest | 2 +- 3 files changed, 183 insertions(+), 2 deletions(-) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 2197c67079c..1341f88e3ef 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -86,21 +86,54 @@ struct ExpressionInterpreter : OverriddenVisitor { push(curr->value); return {}; } - Flow visitUnary(Unary* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitUnary(Unary* curr) { + auto value = pop(); + switch (curr->op) { + case SqrtFloat32: + push(value.sqrt()); + return {}; + case CeilFloat32: + push(value.ceil()); + return {}; + case FloorFloat32: + push(value.floor()); + return {}; + case TruncFloat32: + push(value.trunc()); + return {}; + case NearestFloat32: + push(value.nearbyint()); + return {}; + default: + WASM_UNREACHABLE("TODO"); + } + } Flow visitBinary(Binary* curr) { auto rhs = pop(); auto lhs = pop(); // TODO: add support for all operations. switch (curr->op) { case AddInt32: + case AddFloat32: push(lhs.add(rhs)); return {}; case SubInt32: + case SubFloat32: push(lhs.sub(rhs)); return {}; case MulInt32: + case MulFloat32: push(lhs.mul(rhs)); return {}; + case DivFloat32: + push(lhs.div(rhs)); + return {}; + case MinFloat32: + push(lhs.min(rhs)); + return {}; + case MaxFloat32: + push(lhs.max(rhs)); + return {}; default: WASM_UNREACHABLE("TODO"); } diff --git a/test/gtest/interpreter.cpp b/test/gtest/interpreter.cpp index 418d61530a7..93639ba2e5e 100644 --- a/test/gtest/interpreter.cpp +++ b/test/gtest/interpreter.cpp @@ -75,3 +75,151 @@ TEST(InterpreterTest, MulI32) { EXPECT_EQ(results, expected); } + +TEST(InterpreterTest, AddF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(0.0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(float(1.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(AddFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(1.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, SubF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(1.0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(float(2.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(SubFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(-1.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, MulF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(1.5))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(float(2.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(MulFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(3.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, DivF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(5.0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(float(2.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(DivFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(2.5))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, SqrtF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(5.0))).getErr()); + ASSERT_FALSE(builder.makeUnary(SqrtFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(2.2360679775))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, CeilF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(1.5))).getErr()); + ASSERT_FALSE(builder.makeUnary(CeilFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(2.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, FloorF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(1.5))).getErr()); + ASSERT_FALSE(builder.makeUnary(FloorFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(1.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, TruncF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(2.017281))).getErr()); + ASSERT_FALSE(builder.makeUnary(TruncFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(2.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, NearF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(2.5))).getErr()); + ASSERT_FALSE(builder.makeUnary(NearestFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(float(2.0))}; + + EXPECT_EQ(results, expected); +} diff --git a/third_party/googletest b/third_party/googletest index b514bdc898e..e2239ee6043 160000 --- a/third_party/googletest +++ b/third_party/googletest @@ -1 +1 @@ -Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 +Subproject commit e2239ee6043f73722e7aa812a459f54a28552929 From 84cac7afb5dacefee8075ec4fcc6432c64ca5d17 Mon Sep 17 00:00:00 2001 From: mcbarton Date: Wed, 26 Feb 2025 19:05:59 +0000 Subject: [PATCH 322/622] [ci] Update Ubuntu 20.04 ci job to Ubuntu 22.04 (#7326) If you pick a recent workflow run (here is the latest run on main https://github.com/WebAssembly/binaryen/actions/runs/13530775040 as of writing this PR) you'll see following at the bottom ![image](https://github.com/user-attachments/assets/81f93f61-28f8-4346-9b8f-f50b239457bf) If you click of the link for this warning you'll be taken here https://github.com/actions/runner-images/issues/11101 . It states that the Ubuntu 20.04 runner was deprecated at the start of this month, and will be fully unsupported by the 1st April . Therefore in my opinion is worth removing the Ubuntu 20.04 job from the ci rather than risk trying to run on an unsupported runner. If you need to keep supporting this version of Ubuntu then a job could be created in a separate PR, which builds inside a Docker container with a Ubuntu 20.04 base image. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c56a0b9cff7..8c98dfb349e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -426,7 +426,7 @@ jobs: build-cxx20: name: c++20 # Make sure we can still build on older Ubuntu - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: CC: "gcc" CXX: "g++" From 4ea373b3214ebd772390d181615dca24d5a6ab08 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Wed, 26 Feb 2025 11:46:32 -0800 Subject: [PATCH 323/622] [Interpreter] Float64 (#7327) Building on top of #7227 , the following are implemented and tested: - f64.add - f64.sub - f64.mul - f64.div - f64.sqrt - f64.ceil - f64.floor - f64.trunc - f64.nearbyint --- src/interpreter/interpreter.cpp | 11 +++ test/gtest/interpreter.cpp | 151 ++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 1341f88e3ef..b07cc328cea 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -90,18 +90,23 @@ struct ExpressionInterpreter : OverriddenVisitor { auto value = pop(); switch (curr->op) { case SqrtFloat32: + case SqrtFloat64: push(value.sqrt()); return {}; case CeilFloat32: + case CeilFloat64: push(value.ceil()); return {}; case FloorFloat32: + case FloorFloat64: push(value.floor()); return {}; case TruncFloat32: + case TruncFloat64: push(value.trunc()); return {}; case NearestFloat32: + case NearestFloat64: push(value.nearbyint()); return {}; default: @@ -115,23 +120,29 @@ struct ExpressionInterpreter : OverriddenVisitor { switch (curr->op) { case AddInt32: case AddFloat32: + case AddFloat64: push(lhs.add(rhs)); return {}; case SubInt32: case SubFloat32: + case SubFloat64: push(lhs.sub(rhs)); return {}; case MulInt32: case MulFloat32: + case MulFloat64: push(lhs.mul(rhs)); return {}; case DivFloat32: + case DivFloat64: push(lhs.div(rhs)); return {}; case MinFloat32: + case MinFloat64: push(lhs.min(rhs)); return {}; case MaxFloat32: + case MaxFloat64: push(lhs.max(rhs)); return {}; default: diff --git a/test/gtest/interpreter.cpp b/test/gtest/interpreter.cpp index 93639ba2e5e..fce15a31e5a 100644 --- a/test/gtest/interpreter.cpp +++ b/test/gtest/interpreter.cpp @@ -25,6 +25,7 @@ using namespace wasm; +// uInt32 TEST(InterpreterTest, AddI32) { Module wasm; IRBuilder builder(wasm); @@ -76,6 +77,7 @@ TEST(InterpreterTest, MulI32) { EXPECT_EQ(results, expected); } +// Float32 TEST(InterpreterTest, AddF32) { Module wasm; IRBuilder builder(wasm); @@ -223,3 +225,152 @@ TEST(InterpreterTest, NearF32) { EXPECT_EQ(results, expected); } + +// Float 64 +TEST(InterpreterTest, AddF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(0.0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(double(1.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(AddFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(1.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, SubF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(1.0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(double(2.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(SubFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(-1.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, MulF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(1.5))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(double(2.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(MulFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(3.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, DivF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(5.0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(double(2.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(DivFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(2.5))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, SqrtF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(5.0))).getErr()); + ASSERT_FALSE(builder.makeUnary(SqrtFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(2.23606797749979))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, CeilF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(1.5))).getErr()); + ASSERT_FALSE(builder.makeUnary(CeilFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(2.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, FloorF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(1.5))).getErr()); + ASSERT_FALSE(builder.makeUnary(FloorFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(1.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, TruncF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(2.017281))).getErr()); + ASSERT_FALSE(builder.makeUnary(TruncFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(2.0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, NearF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(2.5))).getErr()); + ASSERT_FALSE(builder.makeUnary(NearestFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(double(2.0))}; + + EXPECT_EQ(results, expected); +} From 2230db9972b966b6731a63ce1cdd7742ac6bebe3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 26 Feb 2025 12:37:58 -0800 Subject: [PATCH 324/622] Implement exact reference types (#7328) Introduced in the Custom RTTs proposal, exact reference types are referencdes to a particular heap type and not any of its subtypes. The exception is that exact references to the bottom type of each heap type hierarchy are subtypes of exact references to any other type in the hierarchy. This maintains the property that each hierarchy of reference types is a lattice. Implement, construction, equality, hashing, subtyping, LUB, and GLB operations for exact reference types, but do not yet use them in the IR or implement parsing for them. --- src/wasm-type.h | 19 ++- src/wasm/wasm-type.cpp | 110 +++++++++++---- test/gtest/type-builder.cpp | 267 ++++++++++++++++++++++++++++++++++++ 3 files changed, 363 insertions(+), 33 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 9e3419190d3..f09e1e440fe 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -62,6 +62,7 @@ using Tuple = TypeList; enum Nullability { NonNullable, Nullable }; enum Mutability { Immutable, Mutable }; +enum Exactness { Inexact, Exact }; // HeapType name information used for printing. struct TypeNames { @@ -314,10 +315,10 @@ class Type { // Construct from a heap type description. Also covers construction from // Signature, Struct or Array via implicit conversion to HeapType. - Type(HeapType heapType, Nullability nullable) - : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0)) { - assert(heapType.isBasic() || - !(heapType.getID() & (TupleMask | NullMask | ExactMask))); + Type(HeapType heapType, Nullability nullable, Exactness exact = Inexact) + : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | + (exact == Exact ? ExactMask : 0)) { + assert(!(heapType.getID() & (TupleMask | NullMask | ExactMask))); } // Predicates @@ -366,6 +367,8 @@ class Type { bool isRef() const { return !isBasic() && !(id & TupleMask); } bool isNullable() const { return isRef() && (id & NullMask); } bool isNonNullable() const { return isRef() && !(id & NullMask); } + bool isExact() const { return isRef() && (id & ExactMask); } + bool isInexact() const { return isRef() && !(id & ExactMask); } HeapType getHeapType() const { assert(isRef()); return HeapType(id & ~(NullMask | ExactMask)); @@ -390,6 +393,10 @@ class Type { Nullability getNullability() const { return isNullable() ? Nullable : NonNullable; } + Exactness getExactness() const { + assert(isRef()); + return isExact() ? Exact : Inexact; + } private: template bool hasPredicate() { @@ -755,7 +762,9 @@ struct TypeBuilder { // TypeBuilder's HeapTypes. For Ref types, the HeapType may be a temporary // HeapType owned by this builder or a canonical HeapType. Type getTempTupleType(const Tuple&); - Type getTempRefType(HeapType heapType, Nullability nullable); + Type getTempRefType(HeapType heapType, + Nullability nullable, + Exactness exact = Inexact); // Declare the HeapType being built at index `i` to be an immediate subtype of // the given HeapType. diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 8710a0619d5..d35d1537c47 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -776,11 +776,19 @@ Type Type::getLeastUpperBound(Type a, Type b) { return Type(elems); } if (a.isRef() && b.isRef()) { - if (auto heapType = - HeapType::getLeastUpperBound(a.getHeapType(), b.getHeapType())) { + auto heapTypeA = a.getHeapType(); + auto heapTypeB = b.getHeapType(); + if (auto heapType = HeapType::getLeastUpperBound(heapTypeA, heapTypeB)) { auto nullability = (a.isNullable() || b.isNullable()) ? Nullable : NonNullable; - return Type(*heapType, nullability); + auto exactness = (a.isInexact() || b.isInexact()) ? Inexact : Exact; + // The LUB can only be exact if the heap types are the same or one of them + // is bottom. + if (heapTypeA != heapTypeB && !heapTypeA.isBottom() && + !heapTypeB.isBottom()) { + exactness = Inexact; + } + return Type(*heapType, nullability, exactness); } } return Type::none; @@ -814,6 +822,7 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } auto nullability = (a.isNonNullable() || b.isNonNullable()) ? NonNullable : Nullable; + auto exactness = (a.isExact() || b.isExact()) ? Exact : Inexact; HeapType heapType; if (HeapType::isSubType(heapA, heapB)) { heapType = heapA; @@ -822,7 +831,13 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } else { heapType = heapA.getBottom(); } - return Type(heapType, nullability); + // If one of the types is exact, but the GLB heap type is different than its + // heap type, then we must make the GLB heap type bottom. + if ((a.isExact() && heapType != heapA) || + (b.isExact() && heapType != heapB)) { + heapType = heapA.getBottom(); + } + return Type(heapType, nullability, exactness); } const Type& Type::Iterator::operator*() const { @@ -1432,14 +1447,24 @@ bool SubTyper::isSubType(Type a, Type b) { if (a == Type::unreachable) { return true; } - if (a.isRef() && b.isRef()) { - return (a.isNullable() == b.isNullable() || !a.isNullable()) && - isSubType(a.getHeapType(), b.getHeapType()); - } if (a.isTuple() && b.isTuple()) { return isSubType(a.getTuple(), b.getTuple()); } - return false; + if (!a.isRef() || !b.isRef()) { + return false; + } + if (a.isNullable() && !b.isNullable()) { + return false; + } + if (a.isInexact() && !b.isInexact()) { + return false; + } + auto heapTypeA = a.getHeapType(); + auto heapTypeB = b.getHeapType(); + if (b.isExact() && !heapTypeA.isBottom()) { + return heapTypeA == heapTypeB; + } + return isSubType(heapTypeA, heapTypeB); } bool SubTyper::isSubType(HeapType a, HeapType b) { @@ -1586,44 +1611,69 @@ std::ostream& TypePrinter::print(Type type) { } else if (type.isRef()) { auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { + if (type.isExact()) { + os << "(exact "; + } // Print shorthands for certain basic heap types. switch (heapType.getBasic(Unshared)) { case HeapType::ext: - return os << "externref"; + os << "externref"; + break; case HeapType::func: - return os << "funcref"; + os << "funcref"; + break; case HeapType::cont: - return os << "contref"; + os << "contref"; + break; case HeapType::any: - return os << "anyref"; + os << "anyref"; + break; case HeapType::eq: - return os << "eqref"; + os << "eqref"; + break; case HeapType::i31: - return os << "i31ref"; + os << "i31ref"; + break; case HeapType::struct_: - return os << "structref"; + os << "structref"; + break; case HeapType::array: - return os << "arrayref"; + os << "arrayref"; + break; case HeapType::exn: - return os << "exnref"; + os << "exnref"; + break; case HeapType::string: - return os << "stringref"; + os << "stringref"; + break; case HeapType::none: - return os << "nullref"; + os << "nullref"; + break; case HeapType::noext: - return os << "nullexternref"; + os << "nullexternref"; + break; case HeapType::nofunc: - return os << "nullfuncref"; + os << "nullfuncref"; + break; case HeapType::nocont: - return os << "nullcontref"; + os << "nullcontref"; + break; case HeapType::noexn: - return os << "nullexnref"; + os << "nullexnref"; + break; } + if (type.isExact()) { + os << ')'; + } + return os; } os << "(ref "; if (type.isNullable()) { os << "null "; } + if (type.isExact()) { + os << "exact "; + } printHeapTypeName(heapType); os << ')'; } else { @@ -1851,8 +1901,9 @@ size_t RecGroupHasher::hash(Type type) const { return digest; } assert(type.isRef()); - rehash(digest, type.getNullability()); - rehash(digest, hash(type.getHeapType())); + wasm::rehash(digest, type.getNullability()); + wasm::rehash(digest, type.getExactness()); + hash_combine(digest, hash(type.getHeapType())); return digest; } @@ -1974,6 +2025,7 @@ bool RecGroupEquator::eq(Type a, Type b) const { } if (a.isRef() && b.isRef()) { return a.getNullability() == b.getNullability() && + a.getExactness() == b.getExactness() && eq(a.getHeapType(), b.getHeapType()); } return false; @@ -2164,8 +2216,10 @@ Type TypeBuilder::getTempTupleType(const Tuple& tuple) { return impl->tupleStore.insert(tuple); } -Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) { - return Type(type, nullable); +Type TypeBuilder::getTempRefType(HeapType type, + Nullability nullable, + Exactness exact) { + return Type(type, nullable, exact); } void TypeBuilder::setSubType(size_t i, std::optional super) { diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index ac95d0e11b4..feb3b69c652 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -428,6 +428,32 @@ TEST_F(TypeTest, CanonicalizeUses) { EXPECT_NE(built[4], built[6]); } +TEST_F(TypeTest, CanonicalizeExactRefs) { + TypeBuilder builder(4); + + // Types that vary in exactness or nullability of references are different. + Type a = builder.getTempRefType(builder[0], Nullable, Inexact); + Type b = builder.getTempRefType(builder[1], NonNullable, Inexact); + Type c = builder.getTempRefType(builder[2], Nullable, Exact); + Type d = builder.getTempRefType(builder[3], NonNullable, Exact); + + builder[0] = Struct({Field(a, Mutable)}); + builder[1] = Struct({Field(b, Mutable)}); + builder[2] = Struct({Field(c, Mutable)}); + builder[3] = Struct({Field(d, Mutable)}); + + auto result = builder.build(); + ASSERT_TRUE(result); + auto built = *result; + + EXPECT_NE(built[0], built[1]); + EXPECT_NE(built[0], built[2]); + EXPECT_NE(built[0], built[3]); + EXPECT_NE(built[1], built[2]); + EXPECT_NE(built[1], built[3]); + EXPECT_NE(built[2], built[3]); +} + TEST_F(TypeTest, CanonicalizeSelfReferences) { TypeBuilder builder(5); // Single self-reference @@ -1095,6 +1121,247 @@ FUZZ_TEST(TypeFuzzTest, TestHeapTypeRelationsFuzz) #endif // FUZZTEST +TEST_F(TypeTest, TestTypeRelations) { + Type any = Type(HeapType::any, NonNullable, Inexact); + Type nullAny = Type(HeapType::any, Nullable, Inexact); + Type exactAny = Type(HeapType::any, NonNullable, Exact); + Type nullExactAny = Type(HeapType::any, Nullable, Exact); + + HeapType defined = Struct(); + Type def = Type(defined, NonNullable, Inexact); + Type nullDef = Type(defined, Nullable, Inexact); + Type exactDef = Type(defined, NonNullable, Exact); + Type nullExactDef = Type(defined, Nullable, Exact); + + Type none = Type(HeapType::none, NonNullable, Inexact); + Type nullNone = Type(HeapType::none, Nullable, Inexact); + Type exactNone = Type(HeapType::none, NonNullable, Exact); + Type nullExactNone = Type(HeapType::none, Nullable, Exact); + + Type func = Type(HeapType::func, NonNullable, Inexact); + Type nullFunc = Type(HeapType::func, Nullable, Inexact); + Type exactFunc = Type(HeapType::func, NonNullable, Exact); + Type nullExactFunc = Type(HeapType::func, Nullable, Exact); + + Type i32 = Type::i32; + Type unreachable = Type::unreachable; + +#define assertLUB(a, b, lub, glb) \ + { \ + auto lub1 = Type::getLeastUpperBound(a, b); \ + auto lub2 = Type::getLeastUpperBound(b, a); \ + EXPECT_EQ(lub, lub1); \ + EXPECT_EQ(lub1, lub2); \ + if (lub != Type::none) { \ + EXPECT_TRUE(Type::isSubType(a, lub)); \ + EXPECT_TRUE(Type::isSubType(b, lub)); \ + } \ + auto glb1 = Type::getGreatestLowerBound(a, b); \ + auto glb2 = Type::getGreatestLowerBound(b, a); \ + EXPECT_EQ(glb, glb1); \ + EXPECT_EQ(glb, glb2); \ + EXPECT_TRUE(Type::isSubType(glb, a)); \ + EXPECT_TRUE(Type::isSubType(glb, b)); \ + if (a == b) { \ + EXPECT_TRUE(Type::isSubType(a, b)); \ + EXPECT_TRUE(Type::isSubType(b, a)); \ + EXPECT_EQ(lub, a); \ + EXPECT_EQ(glb, a); \ + } else if (lub == b) { \ + EXPECT_TRUE(Type::isSubType(a, b)); \ + EXPECT_FALSE(Type::isSubType(b, a)); \ + EXPECT_EQ(glb, a); \ + } else if (lub == a) { \ + EXPECT_FALSE(Type::isSubType(a, b)); \ + EXPECT_TRUE(Type::isSubType(b, a)); \ + EXPECT_EQ(glb, b); \ + } else if (lub != Type::none) { \ + EXPECT_FALSE(Type::isSubType(a, b)); \ + EXPECT_FALSE(Type::isSubType(b, a)); \ + EXPECT_NE(glb, a); \ + EXPECT_NE(glb, b); \ + } else { \ + EXPECT_FALSE(Type::isSubType(a, b)); \ + EXPECT_FALSE(Type::isSubType(b, a)); \ + } \ + \ + if (a.isRef() && b.isRef()) { \ + auto htA = a.getHeapType(); \ + auto htB = b.getHeapType(); \ + \ + if (lub == Type::none) { \ + EXPECT_NE(htA.getTop(), htB.getTop()); \ + EXPECT_NE(htA.getBottom(), htB.getBottom()); \ + } else { \ + EXPECT_EQ(htA.getTop(), htB.getTop()); \ + EXPECT_EQ(htA.getBottom(), htB.getBottom()); \ + } \ + } \ + } + + assertLUB(any, any, any, any); + assertLUB(any, nullAny, nullAny, any); + assertLUB(any, exactAny, any, exactAny); + assertLUB(any, nullExactAny, nullAny, exactAny); + assertLUB(any, def, any, def); + assertLUB(any, nullDef, nullAny, def); + assertLUB(any, exactDef, any, exactDef); + assertLUB(any, nullExactDef, nullAny, exactDef); + assertLUB(any, none, any, none); + assertLUB(any, nullNone, nullAny, none); + assertLUB(any, exactNone, any, exactNone); + assertLUB(any, nullExactNone, nullAny, exactNone); + assertLUB(any, func, Type(Type::none), unreachable); + assertLUB(any, nullFunc, Type(Type::none), unreachable); + assertLUB(any, exactFunc, Type(Type::none), unreachable); + assertLUB(any, nullExactFunc, Type(Type::none), unreachable); + assertLUB(any, i32, Type(Type::none), unreachable); + assertLUB(any, unreachable, any, unreachable); + + assertLUB(nullAny, nullAny, nullAny, nullAny); + assertLUB(nullAny, exactAny, nullAny, exactAny); + assertLUB(nullAny, nullExactAny, nullAny, nullExactAny); + assertLUB(nullAny, def, nullAny, def); + assertLUB(nullAny, nullDef, nullAny, nullDef); + assertLUB(nullAny, exactDef, nullAny, exactDef); + assertLUB(nullAny, nullExactDef, nullAny, nullExactDef); + assertLUB(nullAny, none, nullAny, none); + assertLUB(nullAny, nullNone, nullAny, nullNone); + assertLUB(nullAny, exactNone, nullAny, exactNone); + assertLUB(nullAny, nullExactNone, nullAny, nullExactNone); + assertLUB(nullAny, func, Type(Type::none), unreachable); + assertLUB(nullAny, nullFunc, Type(Type::none), unreachable); + assertLUB(nullAny, exactFunc, Type(Type::none), unreachable); + assertLUB(nullAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullAny, i32, Type(Type::none), unreachable); + assertLUB(nullAny, unreachable, nullAny, unreachable); + + assertLUB(exactAny, exactAny, exactAny, exactAny); + assertLUB(exactAny, nullExactAny, nullExactAny, exactAny); + assertLUB(exactAny, def, any, exactNone); + assertLUB(exactAny, nullDef, nullAny, exactNone); + assertLUB(exactAny, exactDef, any, exactNone); + assertLUB(exactAny, nullExactDef, nullAny, exactNone); + assertLUB(exactAny, none, any, exactNone); + assertLUB(exactAny, nullNone, nullAny, exactNone); + assertLUB(exactAny, exactNone, exactAny, exactNone); + assertLUB(exactAny, nullExactNone, nullExactAny, exactNone); + assertLUB(exactAny, func, Type(Type::none), unreachable); + assertLUB(exactAny, nullFunc, Type(Type::none), unreachable); + assertLUB(exactAny, exactFunc, Type(Type::none), unreachable); + assertLUB(exactAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactAny, i32, Type(Type::none), unreachable); + assertLUB(exactAny, unreachable, exactAny, unreachable); + + assertLUB(nullExactAny, nullExactAny, nullExactAny, nullExactAny); + assertLUB(nullExactAny, def, nullAny, exactNone); + assertLUB(nullExactAny, nullDef, nullAny, nullExactNone); + assertLUB(nullExactAny, exactDef, nullAny, exactNone); + assertLUB(nullExactAny, nullExactDef, nullAny, nullExactNone); + assertLUB(nullExactAny, none, nullAny, exactNone); + assertLUB(nullExactAny, nullNone, nullAny, nullExactNone); + assertLUB(nullExactAny, exactNone, nullExactAny, exactNone); + assertLUB(nullExactAny, nullExactNone, nullExactAny, nullExactNone); + assertLUB(nullExactAny, func, Type(Type::none), unreachable); + assertLUB(nullExactAny, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, i32, Type(Type::none), unreachable); + assertLUB(nullExactAny, unreachable, nullExactAny, unreachable); + + assertLUB(def, def, def, def); + assertLUB(def, nullDef, nullDef, def); + assertLUB(def, exactDef, def, exactDef); + assertLUB(def, nullExactDef, nullDef, exactDef); + assertLUB(def, none, def, none); + assertLUB(def, nullNone, nullDef, none); + assertLUB(def, exactNone, def, exactNone); + assertLUB(def, nullExactNone, nullDef, exactNone); + assertLUB(def, func, Type(Type::none), unreachable); + assertLUB(def, nullFunc, Type(Type::none), unreachable); + assertLUB(def, exactFunc, Type(Type::none), unreachable); + assertLUB(def, nullExactFunc, Type(Type::none), unreachable); + assertLUB(def, i32, Type(Type::none), unreachable); + assertLUB(def, unreachable, def, unreachable); + + assertLUB(nullDef, nullDef, nullDef, nullDef); + assertLUB(nullDef, exactDef, nullDef, exactDef); + assertLUB(nullDef, nullExactDef, nullDef, nullExactDef); + assertLUB(nullDef, none, nullDef, none); + assertLUB(nullDef, nullNone, nullDef, nullNone); + assertLUB(nullDef, exactNone, nullDef, exactNone); + assertLUB(nullDef, nullExactNone, nullDef, nullExactNone); + assertLUB(nullDef, func, Type(Type::none), unreachable); + assertLUB(nullDef, nullFunc, Type(Type::none), unreachable); + assertLUB(nullDef, exactFunc, Type(Type::none), unreachable); + assertLUB(nullDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullDef, i32, Type(Type::none), unreachable); + assertLUB(nullDef, unreachable, nullDef, unreachable); + + assertLUB(exactDef, exactDef, exactDef, exactDef); + assertLUB(exactDef, nullExactDef, nullExactDef, exactDef); + assertLUB(exactDef, none, def, exactNone); + assertLUB(exactDef, nullNone, nullDef, exactNone); + assertLUB(exactDef, exactNone, exactDef, exactNone); + assertLUB(exactDef, nullExactNone, nullExactDef, exactNone); + assertLUB(exactDef, func, Type(Type::none), unreachable); + assertLUB(exactDef, nullFunc, Type(Type::none), unreachable); + assertLUB(exactDef, exactFunc, Type(Type::none), unreachable); + assertLUB(exactDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactDef, i32, Type(Type::none), unreachable); + assertLUB(exactDef, unreachable, exactDef, unreachable); + + assertLUB(nullExactDef, nullExactDef, nullExactDef, nullExactDef); + assertLUB(nullExactDef, none, nullDef, exactNone); + assertLUB(nullExactDef, nullNone, nullDef, nullExactNone); + assertLUB(nullExactDef, exactNone, nullExactDef, exactNone); + assertLUB(nullExactDef, nullExactNone, nullExactDef, nullExactNone); + assertLUB(nullExactDef, func, Type(Type::none), unreachable); + assertLUB(nullExactDef, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, i32, Type(Type::none), unreachable); + assertLUB(nullExactDef, unreachable, nullExactDef, unreachable); + + assertLUB(none, none, none, none); + assertLUB(none, nullNone, nullNone, none); + assertLUB(none, exactNone, none, exactNone); + assertLUB(none, nullExactNone, nullNone, exactNone); + assertLUB(none, func, Type(Type::none), unreachable); + assertLUB(none, nullFunc, Type(Type::none), unreachable); + assertLUB(none, exactFunc, Type(Type::none), unreachable); + assertLUB(none, nullExactFunc, Type(Type::none), unreachable); + assertLUB(none, i32, Type(Type::none), unreachable); + assertLUB(none, unreachable, none, unreachable); + + assertLUB(nullNone, nullNone, nullNone, nullNone); + assertLUB(nullNone, exactNone, nullNone, exactNone); + assertLUB(nullNone, nullExactNone, nullNone, nullExactNone); + assertLUB(nullNone, func, Type(Type::none), unreachable); + assertLUB(nullNone, nullFunc, Type(Type::none), unreachable); + assertLUB(nullNone, exactFunc, Type(Type::none), unreachable); + assertLUB(nullNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullNone, i32, Type(Type::none), unreachable); + assertLUB(nullNone, unreachable, nullNone, unreachable); + + assertLUB(exactNone, exactNone, exactNone, exactNone); + assertLUB(exactNone, nullExactNone, nullExactNone, exactNone); + assertLUB(exactNone, func, Type(Type::none), unreachable); + assertLUB(exactNone, nullFunc, Type(Type::none), unreachable); + assertLUB(exactNone, exactFunc, Type(Type::none), unreachable); + assertLUB(exactNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactNone, i32, Type(Type::none), unreachable); + assertLUB(exactNone, unreachable, exactNone, unreachable); + + assertLUB(nullExactNone, nullExactNone, nullExactNone, nullExactNone); + assertLUB(nullExactNone, func, Type(Type::none), unreachable); + assertLUB(nullExactNone, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, i32, Type(Type::none), unreachable); + assertLUB(nullExactNone, unreachable, nullExactNone, unreachable); +} + TEST_F(TypeTest, TestSubtypeErrors) { Type anyref = Type(HeapType::any, Nullable); Type eqref = Type(HeapType::eq, Nullable); From f56b5156edc0c1b82dc8d34a1e5384407fbaae09 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Wed, 26 Feb 2025 15:42:32 -0800 Subject: [PATCH 325/622] [Interpreter] I64 (#7329) Building on top of #7227, the following are implemented and tested: - i64.add - i64.sub - i64.mul - i64.eq - i64.ltS - i64.ltU - i64.gtS - i64.gtU While here, I added in relevant cases that leveraged the code added for the above instructions. - i32.eq - f32.eq - f64.eq - i32.ltS - i32.ltU - i32.gtS - i32.ltU --- src/interpreter/interpreter.cpp | 25 ++++ test/gtest/interpreter.cpp | 258 ++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index b07cc328cea..69c670c56a4 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -119,16 +119,19 @@ struct ExpressionInterpreter : OverriddenVisitor { // TODO: add support for all operations. switch (curr->op) { case AddInt32: + case AddInt64: case AddFloat32: case AddFloat64: push(lhs.add(rhs)); return {}; case SubInt32: + case SubInt64: case SubFloat32: case SubFloat64: push(lhs.sub(rhs)); return {}; case MulInt32: + case MulInt64: case MulFloat32: case MulFloat64: push(lhs.mul(rhs)); @@ -137,6 +140,28 @@ struct ExpressionInterpreter : OverriddenVisitor { case DivFloat64: push(lhs.div(rhs)); return {}; + case EqInt32: + case EqInt64: + case EqFloat32: + case EqFloat64: + push(lhs.eq(rhs)); + return {}; + case LtSInt32: + case LtSInt64: + push(lhs.ltS(rhs)); + return {}; + case LtUInt32: + case LtUInt64: + push(lhs.ltU(rhs)); + return {}; + case GtSInt32: + case GtSInt64: + push(lhs.gtS(rhs)); + return {}; + case GtUInt32: + case GtUInt64: + push(lhs.gtU(rhs)); + return {}; case MinFloat32: case MinFloat64: push(lhs.min(rhs)); diff --git a/test/gtest/interpreter.cpp b/test/gtest/interpreter.cpp index fce15a31e5a..1a155dca38b 100644 --- a/test/gtest/interpreter.cpp +++ b/test/gtest/interpreter.cpp @@ -77,6 +77,230 @@ TEST(InterpreterTest, MulI32) { EXPECT_EQ(results, expected); } +TEST(InterpreterTest, EqI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(EqInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, LtUI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(LtUInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(1))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, GtUI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(GtUInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + +// sInt32 +TEST(InterpreterTest, LtSI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(int32_t(-1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(int32_t(-2))).getErr()); + ASSERT_FALSE(builder.makeBinary(LtSInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(int32_t(0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, GtSI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(int32_t(-3))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(int32_t(-4))).getErr()); + ASSERT_FALSE(builder.makeBinary(GtSInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(int32_t(1))}; + + EXPECT_EQ(results, expected); +} + +// uInt64 +TEST(InterpreterTest, AddI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(AddInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(3))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, SubI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(SubInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(-1))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, MulI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(MulInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(2))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, EqI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(EqInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, LtUI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(LtSInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(1))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, GtUI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(GtSInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + +// sInt64 +TEST(InterpreterTest, LtSI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(int64_t(-1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(int64_t(-2))).getErr()); + ASSERT_FALSE(builder.makeBinary(LtSInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(int32_t(0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, GtSI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(int64_t(-3))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(int64_t(-4))).getErr()); + ASSERT_FALSE(builder.makeBinary(GtSInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(int32_t(1))}; + + EXPECT_EQ(results, expected); +} + // Float32 TEST(InterpreterTest, AddF32) { Module wasm; @@ -146,6 +370,23 @@ TEST(InterpreterTest, DivF32) { EXPECT_EQ(results, expected); } +TEST(InterpreterTest, EqF32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(float(1.0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(float(2.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(EqFloat32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + TEST(InterpreterTest, SqrtF32) { Module wasm; IRBuilder builder(wasm); @@ -295,6 +536,23 @@ TEST(InterpreterTest, DivF64) { EXPECT_EQ(results, expected); } +TEST(InterpreterTest, EqF64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(double(1.0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(double(2.0))).getErr()); + ASSERT_FALSE(builder.makeBinary(EqFloat64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + TEST(InterpreterTest, SqrtF64) { Module wasm; IRBuilder builder(wasm); From 7142fa13627c19606966ebb4289e00a8d25c4b1f Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Wed, 26 Feb 2025 17:23:11 -0800 Subject: [PATCH 326/622] [Interpreter] i32/i64 Bitwise Operators (#7332) Building on #7227, implemented and tested the following: - i32/i64.and - i32/i64.or - i32/i64.xor - i32/i64.shl - i32/i64.shrU - i32/i64.shrS - i32/i64.rotL - i32/i64.rotR --- src/interpreter/interpreter.cpp | 32 ++++ test/gtest/interpreter.cpp | 286 +++++++++++++++++++++++++++++++- 2 files changed, 316 insertions(+), 2 deletions(-) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 69c670c56a4..fea5a67ba0f 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -140,6 +140,38 @@ struct ExpressionInterpreter : OverriddenVisitor { case DivFloat64: push(lhs.div(rhs)); return {}; + case AndInt32: + case AndInt64: + push(lhs.and_(rhs)); + return {}; + case OrInt32: + case OrInt64: + push(lhs.or_(rhs)); + return {}; + case XorInt32: + case XorInt64: + push(lhs.xor_(rhs)); + return {}; + case ShlInt32: + case ShlInt64: + push(lhs.shl(rhs)); + return {}; + case ShrUInt32: + case ShrUInt64: + push(lhs.shrU(rhs)); + return {}; + case ShrSInt32: + case ShrSInt64: + push(lhs.shrS(rhs)); + return {}; + case RotLInt32: + case RotLInt64: + push(lhs.rotL(rhs)); + return {}; + case RotRInt32: + case RotRInt64: + push(lhs.rotR(rhs)); + return {}; case EqInt32: case EqInt64: case EqFloat32: diff --git a/test/gtest/interpreter.cpp b/test/gtest/interpreter.cpp index 1a155dca38b..6bb3b9cc302 100644 --- a/test/gtest/interpreter.cpp +++ b/test/gtest/interpreter.cpp @@ -25,7 +25,8 @@ using namespace wasm; -// uInt32 +// Int32 + TEST(InterpreterTest, AddI32) { Module wasm; IRBuilder builder(wasm); @@ -94,6 +95,110 @@ TEST(InterpreterTest, EqI32) { EXPECT_EQ(results, expected); } +TEST(InterpreterTest, AndI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(0))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(AndInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, OrI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(4))).getErr()); + ASSERT_FALSE(builder.makeBinary(OrInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(6))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, XorI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(3))).getErr()); + ASSERT_FALSE(builder.makeBinary(XorInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(2))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, ShlI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(3))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(4))).getErr()); + ASSERT_FALSE(builder.makeBinary(ShlInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(48))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, RotLI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(4))).getErr()); + ASSERT_FALSE(builder.makeBinary(RotLInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(16))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, RotRI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(3))).getErr()); + ASSERT_FALSE(builder.makeBinary(RotRInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(1073741824))}; + + EXPECT_EQ(results, expected); +} + +// uInt32 + TEST(InterpreterTest, LtUI32) { Module wasm; IRBuilder builder(wasm); @@ -128,7 +233,25 @@ TEST(InterpreterTest, GtUI32) { EXPECT_EQ(results, expected); } +TEST(InterpreterTest, ShrUI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(ShrUInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + // sInt32 + TEST(InterpreterTest, LtSI32) { Module wasm; IRBuilder builder(wasm); @@ -163,7 +286,25 @@ TEST(InterpreterTest, GtSI32) { EXPECT_EQ(results, expected); } -// uInt64 +TEST(InterpreterTest, ShrSI32) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint32_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(ShrSInt32).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint32_t(0))}; + + EXPECT_EQ(results, expected); +} + +// Int64 + TEST(InterpreterTest, AddI64) { Module wasm; IRBuilder builder(wasm); @@ -232,6 +373,110 @@ TEST(InterpreterTest, EqI64) { EXPECT_EQ(results, expected); } +TEST(InterpreterTest, AndI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(AndInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(0))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, OrI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(OrInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(3))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, XorI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(XorInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(3))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, ShlI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(ShlInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(4))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, RotLI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(RotLInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(4))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, RotRI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(RotRInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(4611686018427387904))}; + + EXPECT_EQ(results, expected); +} + +// uInt64 + TEST(InterpreterTest, LtUI64) { Module wasm; IRBuilder builder(wasm); @@ -266,7 +511,25 @@ TEST(InterpreterTest, GtUI64) { EXPECT_EQ(results, expected); } +TEST(InterpreterTest, ShrUI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(ShrUInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(0))}; + + EXPECT_EQ(results, expected); +} + // sInt64 + TEST(InterpreterTest, LtSI64) { Module wasm; IRBuilder builder(wasm); @@ -301,7 +564,25 @@ TEST(InterpreterTest, GtSI64) { EXPECT_EQ(results, expected); } +TEST(InterpreterTest, ShrSI64) { + Module wasm; + IRBuilder builder(wasm); + + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(1))).getErr()); + ASSERT_FALSE(builder.makeConst(Literal(uint64_t(2))).getErr()); + ASSERT_FALSE(builder.makeBinary(ShrSInt64).getErr()); + + auto expr = builder.build(); + ASSERT_FALSE(expr.getErr()); + + auto results = Interpreter{}.run(*expr); + std::vector expected{Literal(uint64_t(0))}; + + EXPECT_EQ(results, expected); +} + // Float32 + TEST(InterpreterTest, AddF32) { Module wasm; IRBuilder builder(wasm); @@ -468,6 +749,7 @@ TEST(InterpreterTest, NearF32) { } // Float 64 + TEST(InterpreterTest, AddF64) { Module wasm; IRBuilder builder(wasm); From faaa98509e55d19fb97e851f8b4b20ee5d08ebb0 Mon Sep 17 00:00:00 2001 From: mcbarton Date: Thu, 27 Feb 2025 18:53:10 +0000 Subject: [PATCH 327/622] [ci releases] Fix warning about set-output being deprecated (#7315) Use GITHUB_OUTPUT instead as github docs recommend. --- .github/workflows/create_release.yml | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index f81a5f0b3eb..a71ad6da9d2 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -71,8 +71,8 @@ jobs: tar -czf $TARBALL binaryen-$VERSION # on Windows, MSYS2 will strip the carriage return from CMake output cmake -E sha256sum $TARBALL > $SHASUM - echo "::set-output name=tarball::$TARBALL" - echo "::set-output name=shasum::$SHASUM" + echo "TARBALL=$TARBALL" >> $GITHUB_OUTPUT + echo "SHASUM=$SHASUM" >> $GITHUB_OUTPUT - name: archive-arm64 id: archive-arm64 @@ -87,8 +87,8 @@ jobs: tar -czf $TARBALL binaryen-$VERSION # on Windows, MSYS2 will strip the carriage return from CMake output cmake -E sha256sum $TARBALL > $SHASUM - echo "::set-output name=tarball::$TARBALL" - echo "::set-output name=shasum::$SHASUM" + echo "TARBALL=$TARBALL" >> $GITHUB_OUTPUT + echo "SHASUM=$SHASUM" >> $GITHUB_OUTPUT if: matrix.os == 'macos-latest' - name: upload tarball @@ -96,10 +96,10 @@ jobs: with: draft: true files: | - ${{ steps.archive.outputs.tarball }} - ${{ steps.archive.outputs.shasum }} - ${{ steps.archive-arm64.outputs.tarball }} - ${{ steps.archive-arm64.outputs.shasum }} + ${{ steps.archive.outputs.TARBALL }} + ${{ steps.archive.outputs.SHASUM }} + ${{ steps.archive-arm64.outputs.TARBALL }} + ${{ steps.archive-arm64.outputs.SHASUM }} # Build with gcc 6.3 and run tests on Alpine Linux (inside chroot). # Note: Alpine uses musl libc. @@ -161,16 +161,16 @@ jobs: mv install binaryen-$VERSION tar -czf $TARBALL binaryen-$VERSION cmake -E sha256sum $TARBALL > $SHASUM - echo "::set-output name=tarball::$TARBALL" - echo "::set-output name=shasum::$SHASUM" + echo "TARBALL=$TARBALL" >> $GITHUB_OUTPUT + echo "SHASUM=$SHASUM" >> $GITHUB_OUTPUT - name: upload tarball uses: softprops/action-gh-release@v1 with: draft: true files: | - ${{ steps.archive.outputs.tarball }} - ${{ steps.archive.outputs.shasum }} + ${{ steps.archive.outputs.TARBALL }} + ${{ steps.archive.outputs.SHASUM }} # Build using Emscripten to JavaScript+WebAssembly. build-node: @@ -227,13 +227,13 @@ jobs: cp out/bin/wasm-opt* binaryen-$VERSION/ tar -czf $TARBALL binaryen-$VERSION cmake -E sha256sum $TARBALL > $SHASUM - echo "::set-output name=tarball::$TARBALL" - echo "::set-output name=shasum::$SHASUM" + echo "TARBALL=$TARBALL" >> $GITHUB_OUTPUT + echo "SHASUM=$SHASUM" >> $GITHUB_OUTPUT - name: upload tarball uses: softprops/action-gh-release@v1 with: draft: true files: | - ${{ steps.archive.outputs.tarball }} - ${{ steps.archive.outputs.shasum }} + ${{ steps.archive.outputs.TARBALL }} + ${{ steps.archive.outputs.SHASUM }} From bda29e4da5bbce746504096ea816b8bb22062c27 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 3 Mar 2025 12:19:10 -0800 Subject: [PATCH 328/622] Limit number of function parameters in MergeSimilarFunctions (#7341) MergeSimilarFunctions can merge functions whose bodies differ only the constants they use. These different constants become parameters to the merged function. In extreme cases, the optimization could produce an arbitrary number of new parameters, exceeding implementation limits. Cap the total number of parameters to a merged function at 255. The Web limitation on the number of parameters is 1000, but other implementations, including those on the JVM, cannot handle more than 255. It would be possible to make this limit configurable in the future. --- src/passes/MergeSimilarFunctions.cpp | 10 + src/wasm-limits.h | 5 + .../merge-similar-functions-param-limit.wast | 10116 ++++++++++++++++ 3 files changed, 10131 insertions(+) create mode 100644 test/lit/passes/merge-similar-functions-param-limit.wast diff --git a/src/passes/MergeSimilarFunctions.cpp b/src/passes/MergeSimilarFunctions.cpp index 525d7c38f63..9059e041168 100644 --- a/src/passes/MergeSimilarFunctions.cpp +++ b/src/passes/MergeSimilarFunctions.cpp @@ -84,6 +84,7 @@ #include "pass.h" #include "support/hash.h" #include "support/utilities.h" +#include "wasm-limits.h" #include "wasm.h" #include #include @@ -490,6 +491,15 @@ void EquivalentClass::merge(Module* module, // than the reduced size. bool EquivalentClass::hasMergeBenefit(Module* module, const std::vector& params) { + if (params.size() + primaryFunction->getNumParams() > + MaxSyntheticFunctionParams) { + // It requires too many parameters to merge this equivalence class. In + // principle, we could try splitting the class into smaller classes of + // functions that share more constants with each other, but that could + // be expensive. TODO: investigate splitting the class. + return false; + } + size_t funcCount = functions.size(); Index exprSize = Measurer::measure(primaryFunction->body); size_t thunkCount = funcCount; diff --git a/src/wasm-limits.h b/src/wasm-limits.h index 0e1863fbabc..a509dfa2c00 100644 --- a/src/wasm-limits.h +++ b/src/wasm-limits.h @@ -31,6 +31,11 @@ enum WebLimitations : uint32_t { MaxFunctionParams = 1000 }; +// Web limits allow up to 1000 function parameters, but other implementations, +// such as those on the JVM, only allow up to 255. Do not allow optimizations to +// introduce functions with more than this many parameters. +static constexpr int MaxSyntheticFunctionParams = 255; + } // namespace wasm #endif // wasm_wasm_limits_h diff --git a/test/lit/passes/merge-similar-functions-param-limit.wast b/test/lit/passes/merge-similar-functions-param-limit.wast new file mode 100644 index 00000000000..687b4089809 --- /dev/null +++ b/test/lit/passes/merge-similar-functions-param-limit.wast @@ -0,0 +1,10116 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Check that merge-similar-functions does not introduce functions with more +;; than 255 parameters. 255 is allowed, but 256 is not. + +;; RUN: wasm-opt %s --merge-similar-functions -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) + + ;; CHECK: (func $at-limit-1 + ;; CHECK-NEXT: (call $byn$mgfn-shared$at-limit-1 + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 254) + ;; CHECK-NEXT: (i32.const 253) + ;; CHECK-NEXT: (i32.const 252) + ;; CHECK-NEXT: (i32.const 251) + ;; CHECK-NEXT: (i32.const 250) + ;; CHECK-NEXT: (i32.const 249) + ;; CHECK-NEXT: (i32.const 248) + ;; CHECK-NEXT: (i32.const 247) + ;; CHECK-NEXT: (i32.const 246) + ;; CHECK-NEXT: (i32.const 245) + ;; CHECK-NEXT: (i32.const 244) + ;; CHECK-NEXT: (i32.const 243) + ;; CHECK-NEXT: (i32.const 242) + ;; CHECK-NEXT: (i32.const 241) + ;; CHECK-NEXT: (i32.const 240) + ;; CHECK-NEXT: (i32.const 239) + ;; CHECK-NEXT: (i32.const 238) + ;; CHECK-NEXT: (i32.const 237) + ;; CHECK-NEXT: (i32.const 236) + ;; CHECK-NEXT: (i32.const 235) + ;; CHECK-NEXT: (i32.const 234) + ;; CHECK-NEXT: (i32.const 233) + ;; CHECK-NEXT: (i32.const 232) + ;; CHECK-NEXT: (i32.const 231) + ;; CHECK-NEXT: (i32.const 230) + ;; CHECK-NEXT: (i32.const 229) + ;; CHECK-NEXT: (i32.const 228) + ;; CHECK-NEXT: (i32.const 227) + ;; CHECK-NEXT: (i32.const 226) + ;; CHECK-NEXT: (i32.const 225) + ;; CHECK-NEXT: (i32.const 224) + ;; CHECK-NEXT: (i32.const 223) + ;; CHECK-NEXT: (i32.const 222) + ;; CHECK-NEXT: (i32.const 221) + ;; CHECK-NEXT: (i32.const 220) + ;; CHECK-NEXT: (i32.const 219) + ;; CHECK-NEXT: (i32.const 218) + ;; CHECK-NEXT: (i32.const 217) + ;; CHECK-NEXT: (i32.const 216) + ;; CHECK-NEXT: (i32.const 215) + ;; CHECK-NEXT: (i32.const 214) + ;; CHECK-NEXT: (i32.const 213) + ;; CHECK-NEXT: (i32.const 212) + ;; CHECK-NEXT: (i32.const 211) + ;; CHECK-NEXT: (i32.const 210) + ;; CHECK-NEXT: (i32.const 209) + ;; CHECK-NEXT: (i32.const 208) + ;; CHECK-NEXT: (i32.const 207) + ;; CHECK-NEXT: (i32.const 206) + ;; CHECK-NEXT: (i32.const 205) + ;; CHECK-NEXT: (i32.const 204) + ;; CHECK-NEXT: (i32.const 203) + ;; CHECK-NEXT: (i32.const 202) + ;; CHECK-NEXT: (i32.const 201) + ;; CHECK-NEXT: (i32.const 200) + ;; CHECK-NEXT: (i32.const 199) + ;; CHECK-NEXT: (i32.const 198) + ;; CHECK-NEXT: (i32.const 197) + ;; CHECK-NEXT: (i32.const 196) + ;; CHECK-NEXT: (i32.const 195) + ;; CHECK-NEXT: (i32.const 194) + ;; CHECK-NEXT: (i32.const 193) + ;; CHECK-NEXT: (i32.const 192) + ;; CHECK-NEXT: (i32.const 191) + ;; CHECK-NEXT: (i32.const 190) + ;; CHECK-NEXT: (i32.const 189) + ;; CHECK-NEXT: (i32.const 188) + ;; CHECK-NEXT: (i32.const 187) + ;; CHECK-NEXT: (i32.const 186) + ;; CHECK-NEXT: (i32.const 185) + ;; CHECK-NEXT: (i32.const 184) + ;; CHECK-NEXT: (i32.const 183) + ;; CHECK-NEXT: (i32.const 182) + ;; CHECK-NEXT: (i32.const 181) + ;; CHECK-NEXT: (i32.const 180) + ;; CHECK-NEXT: (i32.const 179) + ;; CHECK-NEXT: (i32.const 178) + ;; CHECK-NEXT: (i32.const 177) + ;; CHECK-NEXT: (i32.const 176) + ;; CHECK-NEXT: (i32.const 175) + ;; CHECK-NEXT: (i32.const 174) + ;; CHECK-NEXT: (i32.const 173) + ;; CHECK-NEXT: (i32.const 172) + ;; CHECK-NEXT: (i32.const 171) + ;; CHECK-NEXT: (i32.const 170) + ;; CHECK-NEXT: (i32.const 169) + ;; CHECK-NEXT: (i32.const 168) + ;; CHECK-NEXT: (i32.const 167) + ;; CHECK-NEXT: (i32.const 166) + ;; CHECK-NEXT: (i32.const 165) + ;; CHECK-NEXT: (i32.const 164) + ;; CHECK-NEXT: (i32.const 163) + ;; CHECK-NEXT: (i32.const 162) + ;; CHECK-NEXT: (i32.const 161) + ;; CHECK-NEXT: (i32.const 160) + ;; CHECK-NEXT: (i32.const 159) + ;; CHECK-NEXT: (i32.const 158) + ;; CHECK-NEXT: (i32.const 157) + ;; CHECK-NEXT: (i32.const 156) + ;; CHECK-NEXT: (i32.const 155) + ;; CHECK-NEXT: (i32.const 154) + ;; CHECK-NEXT: (i32.const 153) + ;; CHECK-NEXT: (i32.const 152) + ;; CHECK-NEXT: (i32.const 151) + ;; CHECK-NEXT: (i32.const 150) + ;; CHECK-NEXT: (i32.const 149) + ;; CHECK-NEXT: (i32.const 148) + ;; CHECK-NEXT: (i32.const 147) + ;; CHECK-NEXT: (i32.const 146) + ;; CHECK-NEXT: (i32.const 145) + ;; CHECK-NEXT: (i32.const 144) + ;; CHECK-NEXT: (i32.const 143) + ;; CHECK-NEXT: (i32.const 142) + ;; CHECK-NEXT: (i32.const 141) + ;; CHECK-NEXT: (i32.const 140) + ;; CHECK-NEXT: (i32.const 139) + ;; CHECK-NEXT: (i32.const 138) + ;; CHECK-NEXT: (i32.const 137) + ;; CHECK-NEXT: (i32.const 136) + ;; CHECK-NEXT: (i32.const 135) + ;; CHECK-NEXT: (i32.const 134) + ;; CHECK-NEXT: (i32.const 133) + ;; CHECK-NEXT: (i32.const 132) + ;; CHECK-NEXT: (i32.const 131) + ;; CHECK-NEXT: (i32.const 130) + ;; CHECK-NEXT: (i32.const 129) + ;; CHECK-NEXT: (i32.const 128) + ;; CHECK-NEXT: (i32.const 127) + ;; CHECK-NEXT: (i32.const 126) + ;; CHECK-NEXT: (i32.const 125) + ;; CHECK-NEXT: (i32.const 124) + ;; CHECK-NEXT: (i32.const 123) + ;; CHECK-NEXT: (i32.const 122) + ;; CHECK-NEXT: (i32.const 121) + ;; CHECK-NEXT: (i32.const 120) + ;; CHECK-NEXT: (i32.const 119) + ;; CHECK-NEXT: (i32.const 118) + ;; CHECK-NEXT: (i32.const 117) + ;; CHECK-NEXT: (i32.const 116) + ;; CHECK-NEXT: (i32.const 115) + ;; CHECK-NEXT: (i32.const 114) + ;; CHECK-NEXT: (i32.const 113) + ;; CHECK-NEXT: (i32.const 112) + ;; CHECK-NEXT: (i32.const 111) + ;; CHECK-NEXT: (i32.const 110) + ;; CHECK-NEXT: (i32.const 109) + ;; CHECK-NEXT: (i32.const 108) + ;; CHECK-NEXT: (i32.const 107) + ;; CHECK-NEXT: (i32.const 106) + ;; CHECK-NEXT: (i32.const 105) + ;; CHECK-NEXT: (i32.const 104) + ;; CHECK-NEXT: (i32.const 103) + ;; CHECK-NEXT: (i32.const 102) + ;; CHECK-NEXT: (i32.const 101) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: (i32.const 99) + ;; CHECK-NEXT: (i32.const 98) + ;; CHECK-NEXT: (i32.const 97) + ;; CHECK-NEXT: (i32.const 96) + ;; CHECK-NEXT: (i32.const 95) + ;; CHECK-NEXT: (i32.const 94) + ;; CHECK-NEXT: (i32.const 93) + ;; CHECK-NEXT: (i32.const 92) + ;; CHECK-NEXT: (i32.const 91) + ;; CHECK-NEXT: (i32.const 90) + ;; CHECK-NEXT: (i32.const 89) + ;; CHECK-NEXT: (i32.const 88) + ;; CHECK-NEXT: (i32.const 87) + ;; CHECK-NEXT: (i32.const 86) + ;; CHECK-NEXT: (i32.const 85) + ;; CHECK-NEXT: (i32.const 84) + ;; CHECK-NEXT: (i32.const 83) + ;; CHECK-NEXT: (i32.const 82) + ;; CHECK-NEXT: (i32.const 81) + ;; CHECK-NEXT: (i32.const 80) + ;; CHECK-NEXT: (i32.const 79) + ;; CHECK-NEXT: (i32.const 78) + ;; CHECK-NEXT: (i32.const 77) + ;; CHECK-NEXT: (i32.const 76) + ;; CHECK-NEXT: (i32.const 75) + ;; CHECK-NEXT: (i32.const 74) + ;; CHECK-NEXT: (i32.const 73) + ;; CHECK-NEXT: (i32.const 72) + ;; CHECK-NEXT: (i32.const 71) + ;; CHECK-NEXT: (i32.const 70) + ;; CHECK-NEXT: (i32.const 69) + ;; CHECK-NEXT: (i32.const 68) + ;; CHECK-NEXT: (i32.const 67) + ;; CHECK-NEXT: (i32.const 66) + ;; CHECK-NEXT: (i32.const 65) + ;; CHECK-NEXT: (i32.const 64) + ;; CHECK-NEXT: (i32.const 63) + ;; CHECK-NEXT: (i32.const 62) + ;; CHECK-NEXT: (i32.const 61) + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: (i32.const 59) + ;; CHECK-NEXT: (i32.const 58) + ;; CHECK-NEXT: (i32.const 57) + ;; CHECK-NEXT: (i32.const 56) + ;; CHECK-NEXT: (i32.const 55) + ;; CHECK-NEXT: (i32.const 54) + ;; CHECK-NEXT: (i32.const 53) + ;; CHECK-NEXT: (i32.const 52) + ;; CHECK-NEXT: (i32.const 51) + ;; CHECK-NEXT: (i32.const 50) + ;; CHECK-NEXT: (i32.const 49) + ;; CHECK-NEXT: (i32.const 48) + ;; CHECK-NEXT: (i32.const 47) + ;; CHECK-NEXT: (i32.const 46) + ;; CHECK-NEXT: (i32.const 45) + ;; CHECK-NEXT: (i32.const 44) + ;; CHECK-NEXT: (i32.const 43) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 41) + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 39) + ;; CHECK-NEXT: (i32.const 38) + ;; CHECK-NEXT: (i32.const 37) + ;; CHECK-NEXT: (i32.const 36) + ;; CHECK-NEXT: (i32.const 35) + ;; CHECK-NEXT: (i32.const 34) + ;; CHECK-NEXT: (i32.const 33) + ;; CHECK-NEXT: (i32.const 32) + ;; CHECK-NEXT: (i32.const 31) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: (i32.const 29) + ;; CHECK-NEXT: (i32.const 28) + ;; CHECK-NEXT: (i32.const 27) + ;; CHECK-NEXT: (i32.const 26) + ;; CHECK-NEXT: (i32.const 25) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: (i32.const 23) + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: (i32.const 21) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 19) + ;; CHECK-NEXT: (i32.const 18) + ;; CHECK-NEXT: (i32.const 17) + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: (i32.const 15) + ;; CHECK-NEXT: (i32.const 14) + ;; CHECK-NEXT: (i32.const 13) + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 9) + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $at-limit-1 + (drop + (i32.const 1) + ) + (drop + (i32.const 2) + ) + (drop + (i32.const 3) + ) + (drop + (i32.const 4) + ) + (drop + (i32.const 5) + ) + (drop + (i32.const 6) + ) + (drop + (i32.const 7) + ) + (drop + (i32.const 8) + ) + (drop + (i32.const 9) + ) + (drop + (i32.const 10) + ) + (drop + (i32.const 11) + ) + (drop + (i32.const 12) + ) + (drop + (i32.const 13) + ) + (drop + (i32.const 14) + ) + (drop + (i32.const 15) + ) + (drop + (i32.const 16) + ) + (drop + (i32.const 17) + ) + (drop + (i32.const 18) + ) + (drop + (i32.const 19) + ) + (drop + (i32.const 20) + ) + (drop + (i32.const 21) + ) + (drop + (i32.const 22) + ) + (drop + (i32.const 23) + ) + (drop + (i32.const 24) + ) + (drop + (i32.const 25) + ) + (drop + (i32.const 26) + ) + (drop + (i32.const 27) + ) + (drop + (i32.const 28) + ) + (drop + (i32.const 29) + ) + (drop + (i32.const 30) + ) + (drop + (i32.const 31) + ) + (drop + (i32.const 32) + ) + (drop + (i32.const 33) + ) + (drop + (i32.const 34) + ) + (drop + (i32.const 35) + ) + (drop + (i32.const 36) + ) + (drop + (i32.const 37) + ) + (drop + (i32.const 38) + ) + (drop + (i32.const 39) + ) + (drop + (i32.const 40) + ) + (drop + (i32.const 41) + ) + (drop + (i32.const 42) + ) + (drop + (i32.const 43) + ) + (drop + (i32.const 44) + ) + (drop + (i32.const 45) + ) + (drop + (i32.const 46) + ) + (drop + (i32.const 47) + ) + (drop + (i32.const 48) + ) + (drop + (i32.const 49) + ) + (drop + (i32.const 50) + ) + (drop + (i32.const 51) + ) + (drop + (i32.const 52) + ) + (drop + (i32.const 53) + ) + (drop + (i32.const 54) + ) + (drop + (i32.const 55) + ) + (drop + (i32.const 56) + ) + (drop + (i32.const 57) + ) + (drop + (i32.const 58) + ) + (drop + (i32.const 59) + ) + (drop + (i32.const 60) + ) + (drop + (i32.const 61) + ) + (drop + (i32.const 62) + ) + (drop + (i32.const 63) + ) + (drop + (i32.const 64) + ) + (drop + (i32.const 65) + ) + (drop + (i32.const 66) + ) + (drop + (i32.const 67) + ) + (drop + (i32.const 68) + ) + (drop + (i32.const 69) + ) + (drop + (i32.const 70) + ) + (drop + (i32.const 71) + ) + (drop + (i32.const 72) + ) + (drop + (i32.const 73) + ) + (drop + (i32.const 74) + ) + (drop + (i32.const 75) + ) + (drop + (i32.const 76) + ) + (drop + (i32.const 77) + ) + (drop + (i32.const 78) + ) + (drop + (i32.const 79) + ) + (drop + (i32.const 80) + ) + (drop + (i32.const 81) + ) + (drop + (i32.const 82) + ) + (drop + (i32.const 83) + ) + (drop + (i32.const 84) + ) + (drop + (i32.const 85) + ) + (drop + (i32.const 86) + ) + (drop + (i32.const 87) + ) + (drop + (i32.const 88) + ) + (drop + (i32.const 89) + ) + (drop + (i32.const 90) + ) + (drop + (i32.const 91) + ) + (drop + (i32.const 92) + ) + (drop + (i32.const 93) + ) + (drop + (i32.const 94) + ) + (drop + (i32.const 95) + ) + (drop + (i32.const 96) + ) + (drop + (i32.const 97) + ) + (drop + (i32.const 98) + ) + (drop + (i32.const 99) + ) + (drop + (i32.const 100) + ) + (drop + (i32.const 101) + ) + (drop + (i32.const 102) + ) + (drop + (i32.const 103) + ) + (drop + (i32.const 104) + ) + (drop + (i32.const 105) + ) + (drop + (i32.const 106) + ) + (drop + (i32.const 107) + ) + (drop + (i32.const 108) + ) + (drop + (i32.const 109) + ) + (drop + (i32.const 110) + ) + (drop + (i32.const 111) + ) + (drop + (i32.const 112) + ) + (drop + (i32.const 113) + ) + (drop + (i32.const 114) + ) + (drop + (i32.const 115) + ) + (drop + (i32.const 116) + ) + (drop + (i32.const 117) + ) + (drop + (i32.const 118) + ) + (drop + (i32.const 119) + ) + (drop + (i32.const 120) + ) + (drop + (i32.const 121) + ) + (drop + (i32.const 122) + ) + (drop + (i32.const 123) + ) + (drop + (i32.const 124) + ) + (drop + (i32.const 125) + ) + (drop + (i32.const 126) + ) + (drop + (i32.const 127) + ) + (drop + (i32.const 128) + ) + (drop + (i32.const 129) + ) + (drop + (i32.const 130) + ) + (drop + (i32.const 131) + ) + (drop + (i32.const 132) + ) + (drop + (i32.const 133) + ) + (drop + (i32.const 134) + ) + (drop + (i32.const 135) + ) + (drop + (i32.const 136) + ) + (drop + (i32.const 137) + ) + (drop + (i32.const 138) + ) + (drop + (i32.const 139) + ) + (drop + (i32.const 140) + ) + (drop + (i32.const 141) + ) + (drop + (i32.const 142) + ) + (drop + (i32.const 143) + ) + (drop + (i32.const 144) + ) + (drop + (i32.const 145) + ) + (drop + (i32.const 146) + ) + (drop + (i32.const 147) + ) + (drop + (i32.const 148) + ) + (drop + (i32.const 149) + ) + (drop + (i32.const 150) + ) + (drop + (i32.const 151) + ) + (drop + (i32.const 152) + ) + (drop + (i32.const 153) + ) + (drop + (i32.const 154) + ) + (drop + (i32.const 155) + ) + (drop + (i32.const 156) + ) + (drop + (i32.const 157) + ) + (drop + (i32.const 158) + ) + (drop + (i32.const 159) + ) + (drop + (i32.const 160) + ) + (drop + (i32.const 161) + ) + (drop + (i32.const 162) + ) + (drop + (i32.const 163) + ) + (drop + (i32.const 164) + ) + (drop + (i32.const 165) + ) + (drop + (i32.const 166) + ) + (drop + (i32.const 167) + ) + (drop + (i32.const 168) + ) + (drop + (i32.const 169) + ) + (drop + (i32.const 170) + ) + (drop + (i32.const 171) + ) + (drop + (i32.const 172) + ) + (drop + (i32.const 173) + ) + (drop + (i32.const 174) + ) + (drop + (i32.const 175) + ) + (drop + (i32.const 176) + ) + (drop + (i32.const 177) + ) + (drop + (i32.const 178) + ) + (drop + (i32.const 179) + ) + (drop + (i32.const 180) + ) + (drop + (i32.const 181) + ) + (drop + (i32.const 182) + ) + (drop + (i32.const 183) + ) + (drop + (i32.const 184) + ) + (drop + (i32.const 185) + ) + (drop + (i32.const 186) + ) + (drop + (i32.const 187) + ) + (drop + (i32.const 188) + ) + (drop + (i32.const 189) + ) + (drop + (i32.const 190) + ) + (drop + (i32.const 191) + ) + (drop + (i32.const 192) + ) + (drop + (i32.const 193) + ) + (drop + (i32.const 194) + ) + (drop + (i32.const 195) + ) + (drop + (i32.const 196) + ) + (drop + (i32.const 197) + ) + (drop + (i32.const 198) + ) + (drop + (i32.const 199) + ) + (drop + (i32.const 200) + ) + (drop + (i32.const 201) + ) + (drop + (i32.const 202) + ) + (drop + (i32.const 203) + ) + (drop + (i32.const 204) + ) + (drop + (i32.const 205) + ) + (drop + (i32.const 206) + ) + (drop + (i32.const 207) + ) + (drop + (i32.const 208) + ) + (drop + (i32.const 209) + ) + (drop + (i32.const 210) + ) + (drop + (i32.const 211) + ) + (drop + (i32.const 212) + ) + (drop + (i32.const 213) + ) + (drop + (i32.const 214) + ) + (drop + (i32.const 215) + ) + (drop + (i32.const 216) + ) + (drop + (i32.const 217) + ) + (drop + (i32.const 218) + ) + (drop + (i32.const 219) + ) + (drop + (i32.const 220) + ) + (drop + (i32.const 221) + ) + (drop + (i32.const 222) + ) + (drop + (i32.const 223) + ) + (drop + (i32.const 224) + ) + (drop + (i32.const 225) + ) + (drop + (i32.const 226) + ) + (drop + (i32.const 227) + ) + (drop + (i32.const 228) + ) + (drop + (i32.const 229) + ) + (drop + (i32.const 230) + ) + (drop + (i32.const 231) + ) + (drop + (i32.const 232) + ) + (drop + (i32.const 233) + ) + (drop + (i32.const 234) + ) + (drop + (i32.const 235) + ) + (drop + (i32.const 236) + ) + (drop + (i32.const 237) + ) + (drop + (i32.const 238) + ) + (drop + (i32.const 239) + ) + (drop + (i32.const 240) + ) + (drop + (i32.const 241) + ) + (drop + (i32.const 242) + ) + (drop + (i32.const 243) + ) + (drop + (i32.const 244) + ) + (drop + (i32.const 245) + ) + (drop + (i32.const 246) + ) + (drop + (i32.const 247) + ) + (drop + (i32.const 248) + ) + (drop + (i32.const 249) + ) + (drop + (i32.const 250) + ) + (drop + (i32.const 251) + ) + (drop + (i32.const 252) + ) + (drop + (i32.const 253) + ) + (drop + (i32.const 254) + ) + (drop + (i32.const 255) + ) + ;; Add nops to increase the benefit of merging + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + ) + + ;; CHECK: (func $at-limit-2 + ;; CHECK-NEXT: (call $byn$mgfn-shared$at-limit-1 + ;; CHECK-NEXT: (i32.const -255) + ;; CHECK-NEXT: (i32.const -254) + ;; CHECK-NEXT: (i32.const -253) + ;; CHECK-NEXT: (i32.const -252) + ;; CHECK-NEXT: (i32.const -251) + ;; CHECK-NEXT: (i32.const -250) + ;; CHECK-NEXT: (i32.const -249) + ;; CHECK-NEXT: (i32.const -248) + ;; CHECK-NEXT: (i32.const -247) + ;; CHECK-NEXT: (i32.const -246) + ;; CHECK-NEXT: (i32.const -245) + ;; CHECK-NEXT: (i32.const -244) + ;; CHECK-NEXT: (i32.const -243) + ;; CHECK-NEXT: (i32.const -242) + ;; CHECK-NEXT: (i32.const -241) + ;; CHECK-NEXT: (i32.const -240) + ;; CHECK-NEXT: (i32.const -239) + ;; CHECK-NEXT: (i32.const -238) + ;; CHECK-NEXT: (i32.const -237) + ;; CHECK-NEXT: (i32.const -236) + ;; CHECK-NEXT: (i32.const -235) + ;; CHECK-NEXT: (i32.const -234) + ;; CHECK-NEXT: (i32.const -233) + ;; CHECK-NEXT: (i32.const -232) + ;; CHECK-NEXT: (i32.const -231) + ;; CHECK-NEXT: (i32.const -230) + ;; CHECK-NEXT: (i32.const -229) + ;; CHECK-NEXT: (i32.const -228) + ;; CHECK-NEXT: (i32.const -227) + ;; CHECK-NEXT: (i32.const -226) + ;; CHECK-NEXT: (i32.const -225) + ;; CHECK-NEXT: (i32.const -224) + ;; CHECK-NEXT: (i32.const -223) + ;; CHECK-NEXT: (i32.const -222) + ;; CHECK-NEXT: (i32.const -221) + ;; CHECK-NEXT: (i32.const -220) + ;; CHECK-NEXT: (i32.const -219) + ;; CHECK-NEXT: (i32.const -218) + ;; CHECK-NEXT: (i32.const -217) + ;; CHECK-NEXT: (i32.const -216) + ;; CHECK-NEXT: (i32.const -215) + ;; CHECK-NEXT: (i32.const -214) + ;; CHECK-NEXT: (i32.const -213) + ;; CHECK-NEXT: (i32.const -212) + ;; CHECK-NEXT: (i32.const -211) + ;; CHECK-NEXT: (i32.const -210) + ;; CHECK-NEXT: (i32.const -209) + ;; CHECK-NEXT: (i32.const -208) + ;; CHECK-NEXT: (i32.const -207) + ;; CHECK-NEXT: (i32.const -206) + ;; CHECK-NEXT: (i32.const -205) + ;; CHECK-NEXT: (i32.const -204) + ;; CHECK-NEXT: (i32.const -203) + ;; CHECK-NEXT: (i32.const -202) + ;; CHECK-NEXT: (i32.const -201) + ;; CHECK-NEXT: (i32.const -200) + ;; CHECK-NEXT: (i32.const -199) + ;; CHECK-NEXT: (i32.const -198) + ;; CHECK-NEXT: (i32.const -197) + ;; CHECK-NEXT: (i32.const -196) + ;; CHECK-NEXT: (i32.const -195) + ;; CHECK-NEXT: (i32.const -194) + ;; CHECK-NEXT: (i32.const -193) + ;; CHECK-NEXT: (i32.const -192) + ;; CHECK-NEXT: (i32.const -191) + ;; CHECK-NEXT: (i32.const -190) + ;; CHECK-NEXT: (i32.const -189) + ;; CHECK-NEXT: (i32.const -188) + ;; CHECK-NEXT: (i32.const -187) + ;; CHECK-NEXT: (i32.const -186) + ;; CHECK-NEXT: (i32.const -185) + ;; CHECK-NEXT: (i32.const -184) + ;; CHECK-NEXT: (i32.const -183) + ;; CHECK-NEXT: (i32.const -182) + ;; CHECK-NEXT: (i32.const -181) + ;; CHECK-NEXT: (i32.const -180) + ;; CHECK-NEXT: (i32.const -179) + ;; CHECK-NEXT: (i32.const -178) + ;; CHECK-NEXT: (i32.const -177) + ;; CHECK-NEXT: (i32.const -176) + ;; CHECK-NEXT: (i32.const -175) + ;; CHECK-NEXT: (i32.const -174) + ;; CHECK-NEXT: (i32.const -173) + ;; CHECK-NEXT: (i32.const -172) + ;; CHECK-NEXT: (i32.const -171) + ;; CHECK-NEXT: (i32.const -170) + ;; CHECK-NEXT: (i32.const -169) + ;; CHECK-NEXT: (i32.const -168) + ;; CHECK-NEXT: (i32.const -167) + ;; CHECK-NEXT: (i32.const -166) + ;; CHECK-NEXT: (i32.const -165) + ;; CHECK-NEXT: (i32.const -164) + ;; CHECK-NEXT: (i32.const -163) + ;; CHECK-NEXT: (i32.const -162) + ;; CHECK-NEXT: (i32.const -161) + ;; CHECK-NEXT: (i32.const -160) + ;; CHECK-NEXT: (i32.const -159) + ;; CHECK-NEXT: (i32.const -158) + ;; CHECK-NEXT: (i32.const -157) + ;; CHECK-NEXT: (i32.const -156) + ;; CHECK-NEXT: (i32.const -155) + ;; CHECK-NEXT: (i32.const -154) + ;; CHECK-NEXT: (i32.const -153) + ;; CHECK-NEXT: (i32.const -152) + ;; CHECK-NEXT: (i32.const -151) + ;; CHECK-NEXT: (i32.const -150) + ;; CHECK-NEXT: (i32.const -149) + ;; CHECK-NEXT: (i32.const -148) + ;; CHECK-NEXT: (i32.const -147) + ;; CHECK-NEXT: (i32.const -146) + ;; CHECK-NEXT: (i32.const -145) + ;; CHECK-NEXT: (i32.const -144) + ;; CHECK-NEXT: (i32.const -143) + ;; CHECK-NEXT: (i32.const -142) + ;; CHECK-NEXT: (i32.const -141) + ;; CHECK-NEXT: (i32.const -140) + ;; CHECK-NEXT: (i32.const -139) + ;; CHECK-NEXT: (i32.const -138) + ;; CHECK-NEXT: (i32.const -137) + ;; CHECK-NEXT: (i32.const -136) + ;; CHECK-NEXT: (i32.const -135) + ;; CHECK-NEXT: (i32.const -134) + ;; CHECK-NEXT: (i32.const -133) + ;; CHECK-NEXT: (i32.const -132) + ;; CHECK-NEXT: (i32.const -131) + ;; CHECK-NEXT: (i32.const -130) + ;; CHECK-NEXT: (i32.const -129) + ;; CHECK-NEXT: (i32.const -128) + ;; CHECK-NEXT: (i32.const -127) + ;; CHECK-NEXT: (i32.const -126) + ;; CHECK-NEXT: (i32.const -125) + ;; CHECK-NEXT: (i32.const -124) + ;; CHECK-NEXT: (i32.const -123) + ;; CHECK-NEXT: (i32.const -122) + ;; CHECK-NEXT: (i32.const -121) + ;; CHECK-NEXT: (i32.const -120) + ;; CHECK-NEXT: (i32.const -119) + ;; CHECK-NEXT: (i32.const -118) + ;; CHECK-NEXT: (i32.const -117) + ;; CHECK-NEXT: (i32.const -116) + ;; CHECK-NEXT: (i32.const -115) + ;; CHECK-NEXT: (i32.const -114) + ;; CHECK-NEXT: (i32.const -113) + ;; CHECK-NEXT: (i32.const -112) + ;; CHECK-NEXT: (i32.const -111) + ;; CHECK-NEXT: (i32.const -110) + ;; CHECK-NEXT: (i32.const -109) + ;; CHECK-NEXT: (i32.const -108) + ;; CHECK-NEXT: (i32.const -107) + ;; CHECK-NEXT: (i32.const -106) + ;; CHECK-NEXT: (i32.const -105) + ;; CHECK-NEXT: (i32.const -104) + ;; CHECK-NEXT: (i32.const -103) + ;; CHECK-NEXT: (i32.const -102) + ;; CHECK-NEXT: (i32.const -101) + ;; CHECK-NEXT: (i32.const -100) + ;; CHECK-NEXT: (i32.const -99) + ;; CHECK-NEXT: (i32.const -98) + ;; CHECK-NEXT: (i32.const -97) + ;; CHECK-NEXT: (i32.const -96) + ;; CHECK-NEXT: (i32.const -95) + ;; CHECK-NEXT: (i32.const -94) + ;; CHECK-NEXT: (i32.const -93) + ;; CHECK-NEXT: (i32.const -92) + ;; CHECK-NEXT: (i32.const -91) + ;; CHECK-NEXT: (i32.const -90) + ;; CHECK-NEXT: (i32.const -89) + ;; CHECK-NEXT: (i32.const -88) + ;; CHECK-NEXT: (i32.const -87) + ;; CHECK-NEXT: (i32.const -86) + ;; CHECK-NEXT: (i32.const -85) + ;; CHECK-NEXT: (i32.const -84) + ;; CHECK-NEXT: (i32.const -83) + ;; CHECK-NEXT: (i32.const -82) + ;; CHECK-NEXT: (i32.const -81) + ;; CHECK-NEXT: (i32.const -80) + ;; CHECK-NEXT: (i32.const -79) + ;; CHECK-NEXT: (i32.const -78) + ;; CHECK-NEXT: (i32.const -77) + ;; CHECK-NEXT: (i32.const -76) + ;; CHECK-NEXT: (i32.const -75) + ;; CHECK-NEXT: (i32.const -74) + ;; CHECK-NEXT: (i32.const -73) + ;; CHECK-NEXT: (i32.const -72) + ;; CHECK-NEXT: (i32.const -71) + ;; CHECK-NEXT: (i32.const -70) + ;; CHECK-NEXT: (i32.const -69) + ;; CHECK-NEXT: (i32.const -68) + ;; CHECK-NEXT: (i32.const -67) + ;; CHECK-NEXT: (i32.const -66) + ;; CHECK-NEXT: (i32.const -65) + ;; CHECK-NEXT: (i32.const -64) + ;; CHECK-NEXT: (i32.const -63) + ;; CHECK-NEXT: (i32.const -62) + ;; CHECK-NEXT: (i32.const -61) + ;; CHECK-NEXT: (i32.const -60) + ;; CHECK-NEXT: (i32.const -59) + ;; CHECK-NEXT: (i32.const -58) + ;; CHECK-NEXT: (i32.const -57) + ;; CHECK-NEXT: (i32.const -56) + ;; CHECK-NEXT: (i32.const -55) + ;; CHECK-NEXT: (i32.const -54) + ;; CHECK-NEXT: (i32.const -53) + ;; CHECK-NEXT: (i32.const -52) + ;; CHECK-NEXT: (i32.const -51) + ;; CHECK-NEXT: (i32.const -50) + ;; CHECK-NEXT: (i32.const -49) + ;; CHECK-NEXT: (i32.const -48) + ;; CHECK-NEXT: (i32.const -47) + ;; CHECK-NEXT: (i32.const -46) + ;; CHECK-NEXT: (i32.const -45) + ;; CHECK-NEXT: (i32.const -44) + ;; CHECK-NEXT: (i32.const -43) + ;; CHECK-NEXT: (i32.const -42) + ;; CHECK-NEXT: (i32.const -41) + ;; CHECK-NEXT: (i32.const -40) + ;; CHECK-NEXT: (i32.const -39) + ;; CHECK-NEXT: (i32.const -38) + ;; CHECK-NEXT: (i32.const -37) + ;; CHECK-NEXT: (i32.const -36) + ;; CHECK-NEXT: (i32.const -35) + ;; CHECK-NEXT: (i32.const -34) + ;; CHECK-NEXT: (i32.const -33) + ;; CHECK-NEXT: (i32.const -32) + ;; CHECK-NEXT: (i32.const -31) + ;; CHECK-NEXT: (i32.const -30) + ;; CHECK-NEXT: (i32.const -29) + ;; CHECK-NEXT: (i32.const -28) + ;; CHECK-NEXT: (i32.const -27) + ;; CHECK-NEXT: (i32.const -26) + ;; CHECK-NEXT: (i32.const -25) + ;; CHECK-NEXT: (i32.const -24) + ;; CHECK-NEXT: (i32.const -23) + ;; CHECK-NEXT: (i32.const -22) + ;; CHECK-NEXT: (i32.const -21) + ;; CHECK-NEXT: (i32.const -20) + ;; CHECK-NEXT: (i32.const -19) + ;; CHECK-NEXT: (i32.const -18) + ;; CHECK-NEXT: (i32.const -17) + ;; CHECK-NEXT: (i32.const -16) + ;; CHECK-NEXT: (i32.const -15) + ;; CHECK-NEXT: (i32.const -14) + ;; CHECK-NEXT: (i32.const -13) + ;; CHECK-NEXT: (i32.const -12) + ;; CHECK-NEXT: (i32.const -11) + ;; CHECK-NEXT: (i32.const -10) + ;; CHECK-NEXT: (i32.const -9) + ;; CHECK-NEXT: (i32.const -8) + ;; CHECK-NEXT: (i32.const -7) + ;; CHECK-NEXT: (i32.const -6) + ;; CHECK-NEXT: (i32.const -5) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: (i32.const -3) + ;; CHECK-NEXT: (i32.const -2) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $at-limit-2 + (drop + (i32.const -1) + ) + (drop + (i32.const -2) + ) + (drop + (i32.const -3) + ) + (drop + (i32.const -4) + ) + (drop + (i32.const -5) + ) + (drop + (i32.const -6) + ) + (drop + (i32.const -7) + ) + (drop + (i32.const -8) + ) + (drop + (i32.const -9) + ) + (drop + (i32.const -10) + ) + (drop + (i32.const -11) + ) + (drop + (i32.const -12) + ) + (drop + (i32.const -13) + ) + (drop + (i32.const -14) + ) + (drop + (i32.const -15) + ) + (drop + (i32.const -16) + ) + (drop + (i32.const -17) + ) + (drop + (i32.const -18) + ) + (drop + (i32.const -19) + ) + (drop + (i32.const -20) + ) + (drop + (i32.const -21) + ) + (drop + (i32.const -22) + ) + (drop + (i32.const -23) + ) + (drop + (i32.const -24) + ) + (drop + (i32.const -25) + ) + (drop + (i32.const -26) + ) + (drop + (i32.const -27) + ) + (drop + (i32.const -28) + ) + (drop + (i32.const -29) + ) + (drop + (i32.const -30) + ) + (drop + (i32.const -31) + ) + (drop + (i32.const -32) + ) + (drop + (i32.const -33) + ) + (drop + (i32.const -34) + ) + (drop + (i32.const -35) + ) + (drop + (i32.const -36) + ) + (drop + (i32.const -37) + ) + (drop + (i32.const -38) + ) + (drop + (i32.const -39) + ) + (drop + (i32.const -40) + ) + (drop + (i32.const -41) + ) + (drop + (i32.const -42) + ) + (drop + (i32.const -43) + ) + (drop + (i32.const -44) + ) + (drop + (i32.const -45) + ) + (drop + (i32.const -46) + ) + (drop + (i32.const -47) + ) + (drop + (i32.const -48) + ) + (drop + (i32.const -49) + ) + (drop + (i32.const -50) + ) + (drop + (i32.const -51) + ) + (drop + (i32.const -52) + ) + (drop + (i32.const -53) + ) + (drop + (i32.const -54) + ) + (drop + (i32.const -55) + ) + (drop + (i32.const -56) + ) + (drop + (i32.const -57) + ) + (drop + (i32.const -58) + ) + (drop + (i32.const -59) + ) + (drop + (i32.const -60) + ) + (drop + (i32.const -61) + ) + (drop + (i32.const -62) + ) + (drop + (i32.const -63) + ) + (drop + (i32.const -64) + ) + (drop + (i32.const -65) + ) + (drop + (i32.const -66) + ) + (drop + (i32.const -67) + ) + (drop + (i32.const -68) + ) + (drop + (i32.const -69) + ) + (drop + (i32.const -70) + ) + (drop + (i32.const -71) + ) + (drop + (i32.const -72) + ) + (drop + (i32.const -73) + ) + (drop + (i32.const -74) + ) + (drop + (i32.const -75) + ) + (drop + (i32.const -76) + ) + (drop + (i32.const -77) + ) + (drop + (i32.const -78) + ) + (drop + (i32.const -79) + ) + (drop + (i32.const -80) + ) + (drop + (i32.const -81) + ) + (drop + (i32.const -82) + ) + (drop + (i32.const -83) + ) + (drop + (i32.const -84) + ) + (drop + (i32.const -85) + ) + (drop + (i32.const -86) + ) + (drop + (i32.const -87) + ) + (drop + (i32.const -88) + ) + (drop + (i32.const -89) + ) + (drop + (i32.const -90) + ) + (drop + (i32.const -91) + ) + (drop + (i32.const -92) + ) + (drop + (i32.const -93) + ) + (drop + (i32.const -94) + ) + (drop + (i32.const -95) + ) + (drop + (i32.const -96) + ) + (drop + (i32.const -97) + ) + (drop + (i32.const -98) + ) + (drop + (i32.const -99) + ) + (drop + (i32.const -100) + ) + (drop + (i32.const -101) + ) + (drop + (i32.const -102) + ) + (drop + (i32.const -103) + ) + (drop + (i32.const -104) + ) + (drop + (i32.const -105) + ) + (drop + (i32.const -106) + ) + (drop + (i32.const -107) + ) + (drop + (i32.const -108) + ) + (drop + (i32.const -109) + ) + (drop + (i32.const -110) + ) + (drop + (i32.const -111) + ) + (drop + (i32.const -112) + ) + (drop + (i32.const -113) + ) + (drop + (i32.const -114) + ) + (drop + (i32.const -115) + ) + (drop + (i32.const -116) + ) + (drop + (i32.const -117) + ) + (drop + (i32.const -118) + ) + (drop + (i32.const -119) + ) + (drop + (i32.const -120) + ) + (drop + (i32.const -121) + ) + (drop + (i32.const -122) + ) + (drop + (i32.const -123) + ) + (drop + (i32.const -124) + ) + (drop + (i32.const -125) + ) + (drop + (i32.const -126) + ) + (drop + (i32.const -127) + ) + (drop + (i32.const -128) + ) + (drop + (i32.const -129) + ) + (drop + (i32.const -130) + ) + (drop + (i32.const -131) + ) + (drop + (i32.const -132) + ) + (drop + (i32.const -133) + ) + (drop + (i32.const -134) + ) + (drop + (i32.const -135) + ) + (drop + (i32.const -136) + ) + (drop + (i32.const -137) + ) + (drop + (i32.const -138) + ) + (drop + (i32.const -139) + ) + (drop + (i32.const -140) + ) + (drop + (i32.const -141) + ) + (drop + (i32.const -142) + ) + (drop + (i32.const -143) + ) + (drop + (i32.const -144) + ) + (drop + (i32.const -145) + ) + (drop + (i32.const -146) + ) + (drop + (i32.const -147) + ) + (drop + (i32.const -148) + ) + (drop + (i32.const -149) + ) + (drop + (i32.const -150) + ) + (drop + (i32.const -151) + ) + (drop + (i32.const -152) + ) + (drop + (i32.const -153) + ) + (drop + (i32.const -154) + ) + (drop + (i32.const -155) + ) + (drop + (i32.const -156) + ) + (drop + (i32.const -157) + ) + (drop + (i32.const -158) + ) + (drop + (i32.const -159) + ) + (drop + (i32.const -160) + ) + (drop + (i32.const -161) + ) + (drop + (i32.const -162) + ) + (drop + (i32.const -163) + ) + (drop + (i32.const -164) + ) + (drop + (i32.const -165) + ) + (drop + (i32.const -166) + ) + (drop + (i32.const -167) + ) + (drop + (i32.const -168) + ) + (drop + (i32.const -169) + ) + (drop + (i32.const -170) + ) + (drop + (i32.const -171) + ) + (drop + (i32.const -172) + ) + (drop + (i32.const -173) + ) + (drop + (i32.const -174) + ) + (drop + (i32.const -175) + ) + (drop + (i32.const -176) + ) + (drop + (i32.const -177) + ) + (drop + (i32.const -178) + ) + (drop + (i32.const -179) + ) + (drop + (i32.const -180) + ) + (drop + (i32.const -181) + ) + (drop + (i32.const -182) + ) + (drop + (i32.const -183) + ) + (drop + (i32.const -184) + ) + (drop + (i32.const -185) + ) + (drop + (i32.const -186) + ) + (drop + (i32.const -187) + ) + (drop + (i32.const -188) + ) + (drop + (i32.const -189) + ) + (drop + (i32.const -190) + ) + (drop + (i32.const -191) + ) + (drop + (i32.const -192) + ) + (drop + (i32.const -193) + ) + (drop + (i32.const -194) + ) + (drop + (i32.const -195) + ) + (drop + (i32.const -196) + ) + (drop + (i32.const -197) + ) + (drop + (i32.const -198) + ) + (drop + (i32.const -199) + ) + (drop + (i32.const -200) + ) + (drop + (i32.const -201) + ) + (drop + (i32.const -202) + ) + (drop + (i32.const -203) + ) + (drop + (i32.const -204) + ) + (drop + (i32.const -205) + ) + (drop + (i32.const -206) + ) + (drop + (i32.const -207) + ) + (drop + (i32.const -208) + ) + (drop + (i32.const -209) + ) + (drop + (i32.const -210) + ) + (drop + (i32.const -211) + ) + (drop + (i32.const -212) + ) + (drop + (i32.const -213) + ) + (drop + (i32.const -214) + ) + (drop + (i32.const -215) + ) + (drop + (i32.const -216) + ) + (drop + (i32.const -217) + ) + (drop + (i32.const -218) + ) + (drop + (i32.const -219) + ) + (drop + (i32.const -220) + ) + (drop + (i32.const -221) + ) + (drop + (i32.const -222) + ) + (drop + (i32.const -223) + ) + (drop + (i32.const -224) + ) + (drop + (i32.const -225) + ) + (drop + (i32.const -226) + ) + (drop + (i32.const -227) + ) + (drop + (i32.const -228) + ) + (drop + (i32.const -229) + ) + (drop + (i32.const -230) + ) + (drop + (i32.const -231) + ) + (drop + (i32.const -232) + ) + (drop + (i32.const -233) + ) + (drop + (i32.const -234) + ) + (drop + (i32.const -235) + ) + (drop + (i32.const -236) + ) + (drop + (i32.const -237) + ) + (drop + (i32.const -238) + ) + (drop + (i32.const -239) + ) + (drop + (i32.const -240) + ) + (drop + (i32.const -241) + ) + (drop + (i32.const -242) + ) + (drop + (i32.const -243) + ) + (drop + (i32.const -244) + ) + (drop + (i32.const -245) + ) + (drop + (i32.const -246) + ) + (drop + (i32.const -247) + ) + (drop + (i32.const -248) + ) + (drop + (i32.const -249) + ) + (drop + (i32.const -250) + ) + (drop + (i32.const -251) + ) + (drop + (i32.const -252) + ) + (drop + (i32.const -253) + ) + (drop + (i32.const -254) + ) + (drop + (i32.const -255) + ) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + ) + + ;; CHECK: (func $over-limit-1 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 17) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 18) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 19) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 21) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 23) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 25) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 26) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 27) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 28) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 29) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 31) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 33) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 34) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 35) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 36) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 37) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 38) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 39) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 41) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 43) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 44) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 45) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 46) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 47) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 48) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 49) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 50) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 51) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 52) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 53) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 54) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 55) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 56) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 57) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 58) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 59) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 61) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 62) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 63) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 65) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 66) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 67) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 68) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 69) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 70) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 71) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 72) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 73) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 74) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 75) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 76) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 77) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 78) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 79) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 80) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 81) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 82) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 83) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 84) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 85) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 86) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 87) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 88) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 89) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 90) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 91) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 92) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 93) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 94) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 95) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 96) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 97) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 98) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 99) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 101) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 102) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 103) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 104) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 105) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 106) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 107) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 108) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 109) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 110) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 111) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 112) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 113) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 114) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 115) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 116) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 117) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 118) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 119) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 120) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 121) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 122) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 123) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 124) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 125) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 126) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 127) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 128) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 129) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 130) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 131) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 132) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 133) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 134) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 135) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 136) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 137) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 138) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 139) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 140) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 141) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 142) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 143) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 144) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 145) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 146) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 147) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 148) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 149) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 150) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 151) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 152) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 153) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 154) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 155) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 156) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 157) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 158) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 160) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 161) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 162) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 163) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 164) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 165) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 166) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 167) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 168) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 169) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 170) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 171) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 172) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 173) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 174) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 175) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 176) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 177) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 178) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 179) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 180) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 181) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 182) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 183) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 184) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 185) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 186) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 187) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 188) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 189) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 190) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 191) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 192) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 193) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 194) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 195) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 196) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 197) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 198) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 199) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 200) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 201) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 202) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 203) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 204) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 205) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 206) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 207) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 208) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 209) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 210) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 211) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 212) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 213) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 214) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 215) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 216) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 217) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 218) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 219) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 220) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 221) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 222) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 223) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 224) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 225) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 226) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 227) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 228) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 229) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 230) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 231) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 232) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 233) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 234) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 235) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 236) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 237) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 238) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 239) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 240) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 241) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 242) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 243) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 244) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 245) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 246) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 247) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 248) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 249) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 250) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 251) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 252) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 253) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 254) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 256) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $over-limit-1 + (drop + (i32.const 1) + ) + (drop + (i32.const 2) + ) + (drop + (i32.const 3) + ) + (drop + (i32.const 4) + ) + (drop + (i32.const 5) + ) + (drop + (i32.const 6) + ) + (drop + (i32.const 7) + ) + (drop + (i32.const 8) + ) + (drop + (i32.const 9) + ) + (drop + (i32.const 10) + ) + (drop + (i32.const 11) + ) + (drop + (i32.const 12) + ) + (drop + (i32.const 13) + ) + (drop + (i32.const 14) + ) + (drop + (i32.const 15) + ) + (drop + (i32.const 16) + ) + (drop + (i32.const 17) + ) + (drop + (i32.const 18) + ) + (drop + (i32.const 19) + ) + (drop + (i32.const 20) + ) + (drop + (i32.const 21) + ) + (drop + (i32.const 22) + ) + (drop + (i32.const 23) + ) + (drop + (i32.const 24) + ) + (drop + (i32.const 25) + ) + (drop + (i32.const 26) + ) + (drop + (i32.const 27) + ) + (drop + (i32.const 28) + ) + (drop + (i32.const 29) + ) + (drop + (i32.const 30) + ) + (drop + (i32.const 31) + ) + (drop + (i32.const 32) + ) + (drop + (i32.const 33) + ) + (drop + (i32.const 34) + ) + (drop + (i32.const 35) + ) + (drop + (i32.const 36) + ) + (drop + (i32.const 37) + ) + (drop + (i32.const 38) + ) + (drop + (i32.const 39) + ) + (drop + (i32.const 40) + ) + (drop + (i32.const 41) + ) + (drop + (i32.const 42) + ) + (drop + (i32.const 43) + ) + (drop + (i32.const 44) + ) + (drop + (i32.const 45) + ) + (drop + (i32.const 46) + ) + (drop + (i32.const 47) + ) + (drop + (i32.const 48) + ) + (drop + (i32.const 49) + ) + (drop + (i32.const 50) + ) + (drop + (i32.const 51) + ) + (drop + (i32.const 52) + ) + (drop + (i32.const 53) + ) + (drop + (i32.const 54) + ) + (drop + (i32.const 55) + ) + (drop + (i32.const 56) + ) + (drop + (i32.const 57) + ) + (drop + (i32.const 58) + ) + (drop + (i32.const 59) + ) + (drop + (i32.const 60) + ) + (drop + (i32.const 61) + ) + (drop + (i32.const 62) + ) + (drop + (i32.const 63) + ) + (drop + (i32.const 64) + ) + (drop + (i32.const 65) + ) + (drop + (i32.const 66) + ) + (drop + (i32.const 67) + ) + (drop + (i32.const 68) + ) + (drop + (i32.const 69) + ) + (drop + (i32.const 70) + ) + (drop + (i32.const 71) + ) + (drop + (i32.const 72) + ) + (drop + (i32.const 73) + ) + (drop + (i32.const 74) + ) + (drop + (i32.const 75) + ) + (drop + (i32.const 76) + ) + (drop + (i32.const 77) + ) + (drop + (i32.const 78) + ) + (drop + (i32.const 79) + ) + (drop + (i32.const 80) + ) + (drop + (i32.const 81) + ) + (drop + (i32.const 82) + ) + (drop + (i32.const 83) + ) + (drop + (i32.const 84) + ) + (drop + (i32.const 85) + ) + (drop + (i32.const 86) + ) + (drop + (i32.const 87) + ) + (drop + (i32.const 88) + ) + (drop + (i32.const 89) + ) + (drop + (i32.const 90) + ) + (drop + (i32.const 91) + ) + (drop + (i32.const 92) + ) + (drop + (i32.const 93) + ) + (drop + (i32.const 94) + ) + (drop + (i32.const 95) + ) + (drop + (i32.const 96) + ) + (drop + (i32.const 97) + ) + (drop + (i32.const 98) + ) + (drop + (i32.const 99) + ) + (drop + (i32.const 100) + ) + (drop + (i32.const 101) + ) + (drop + (i32.const 102) + ) + (drop + (i32.const 103) + ) + (drop + (i32.const 104) + ) + (drop + (i32.const 105) + ) + (drop + (i32.const 106) + ) + (drop + (i32.const 107) + ) + (drop + (i32.const 108) + ) + (drop + (i32.const 109) + ) + (drop + (i32.const 110) + ) + (drop + (i32.const 111) + ) + (drop + (i32.const 112) + ) + (drop + (i32.const 113) + ) + (drop + (i32.const 114) + ) + (drop + (i32.const 115) + ) + (drop + (i32.const 116) + ) + (drop + (i32.const 117) + ) + (drop + (i32.const 118) + ) + (drop + (i32.const 119) + ) + (drop + (i32.const 120) + ) + (drop + (i32.const 121) + ) + (drop + (i32.const 122) + ) + (drop + (i32.const 123) + ) + (drop + (i32.const 124) + ) + (drop + (i32.const 125) + ) + (drop + (i32.const 126) + ) + (drop + (i32.const 127) + ) + (drop + (i32.const 128) + ) + (drop + (i32.const 129) + ) + (drop + (i32.const 130) + ) + (drop + (i32.const 131) + ) + (drop + (i32.const 132) + ) + (drop + (i32.const 133) + ) + (drop + (i32.const 134) + ) + (drop + (i32.const 135) + ) + (drop + (i32.const 136) + ) + (drop + (i32.const 137) + ) + (drop + (i32.const 138) + ) + (drop + (i32.const 139) + ) + (drop + (i32.const 140) + ) + (drop + (i32.const 141) + ) + (drop + (i32.const 142) + ) + (drop + (i32.const 143) + ) + (drop + (i32.const 144) + ) + (drop + (i32.const 145) + ) + (drop + (i32.const 146) + ) + (drop + (i32.const 147) + ) + (drop + (i32.const 148) + ) + (drop + (i32.const 149) + ) + (drop + (i32.const 150) + ) + (drop + (i32.const 151) + ) + (drop + (i32.const 152) + ) + (drop + (i32.const 153) + ) + (drop + (i32.const 154) + ) + (drop + (i32.const 155) + ) + (drop + (i32.const 156) + ) + (drop + (i32.const 157) + ) + (drop + (i32.const 158) + ) + (drop + (i32.const 159) + ) + (drop + (i32.const 160) + ) + (drop + (i32.const 161) + ) + (drop + (i32.const 162) + ) + (drop + (i32.const 163) + ) + (drop + (i32.const 164) + ) + (drop + (i32.const 165) + ) + (drop + (i32.const 166) + ) + (drop + (i32.const 167) + ) + (drop + (i32.const 168) + ) + (drop + (i32.const 169) + ) + (drop + (i32.const 170) + ) + (drop + (i32.const 171) + ) + (drop + (i32.const 172) + ) + (drop + (i32.const 173) + ) + (drop + (i32.const 174) + ) + (drop + (i32.const 175) + ) + (drop + (i32.const 176) + ) + (drop + (i32.const 177) + ) + (drop + (i32.const 178) + ) + (drop + (i32.const 179) + ) + (drop + (i32.const 180) + ) + (drop + (i32.const 181) + ) + (drop + (i32.const 182) + ) + (drop + (i32.const 183) + ) + (drop + (i32.const 184) + ) + (drop + (i32.const 185) + ) + (drop + (i32.const 186) + ) + (drop + (i32.const 187) + ) + (drop + (i32.const 188) + ) + (drop + (i32.const 189) + ) + (drop + (i32.const 190) + ) + (drop + (i32.const 191) + ) + (drop + (i32.const 192) + ) + (drop + (i32.const 193) + ) + (drop + (i32.const 194) + ) + (drop + (i32.const 195) + ) + (drop + (i32.const 196) + ) + (drop + (i32.const 197) + ) + (drop + (i32.const 198) + ) + (drop + (i32.const 199) + ) + (drop + (i32.const 200) + ) + (drop + (i32.const 201) + ) + (drop + (i32.const 202) + ) + (drop + (i32.const 203) + ) + (drop + (i32.const 204) + ) + (drop + (i32.const 205) + ) + (drop + (i32.const 206) + ) + (drop + (i32.const 207) + ) + (drop + (i32.const 208) + ) + (drop + (i32.const 209) + ) + (drop + (i32.const 210) + ) + (drop + (i32.const 211) + ) + (drop + (i32.const 212) + ) + (drop + (i32.const 213) + ) + (drop + (i32.const 214) + ) + (drop + (i32.const 215) + ) + (drop + (i32.const 216) + ) + (drop + (i32.const 217) + ) + (drop + (i32.const 218) + ) + (drop + (i32.const 219) + ) + (drop + (i32.const 220) + ) + (drop + (i32.const 221) + ) + (drop + (i32.const 222) + ) + (drop + (i32.const 223) + ) + (drop + (i32.const 224) + ) + (drop + (i32.const 225) + ) + (drop + (i32.const 226) + ) + (drop + (i32.const 227) + ) + (drop + (i32.const 228) + ) + (drop + (i32.const 229) + ) + (drop + (i32.const 230) + ) + (drop + (i32.const 231) + ) + (drop + (i32.const 232) + ) + (drop + (i32.const 233) + ) + (drop + (i32.const 234) + ) + (drop + (i32.const 235) + ) + (drop + (i32.const 236) + ) + (drop + (i32.const 237) + ) + (drop + (i32.const 238) + ) + (drop + (i32.const 239) + ) + (drop + (i32.const 240) + ) + (drop + (i32.const 241) + ) + (drop + (i32.const 242) + ) + (drop + (i32.const 243) + ) + (drop + (i32.const 244) + ) + (drop + (i32.const 245) + ) + (drop + (i32.const 246) + ) + (drop + (i32.const 247) + ) + (drop + (i32.const 248) + ) + (drop + (i32.const 249) + ) + (drop + (i32.const 250) + ) + (drop + (i32.const 251) + ) + (drop + (i32.const 252) + ) + (drop + (i32.const 253) + ) + (drop + (i32.const 254) + ) + (drop + (i32.const 255) + ) + (drop + (i32.const 256) + ) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + ) + + ;; CHECK: (func $over-limit-2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -17) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -18) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -19) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -21) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -23) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -25) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -26) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -27) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -28) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -29) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -31) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -33) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -34) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -35) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -36) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -37) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -38) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -39) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -40) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -41) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -43) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -44) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -45) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -46) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -47) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -48) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -49) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -50) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -51) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -52) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -53) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -54) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -55) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -56) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -57) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -58) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -59) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -61) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -62) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -63) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -65) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -66) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -67) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -68) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -69) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -70) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -71) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -72) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -73) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -74) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -75) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -76) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -77) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -78) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -79) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -80) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -81) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -82) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -83) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -84) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -85) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -86) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -87) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -88) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -89) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -90) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -91) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -92) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -93) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -94) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -95) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -96) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -97) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -98) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -99) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -101) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -102) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -103) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -104) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -105) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -106) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -107) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -108) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -109) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -110) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -111) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -112) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -113) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -114) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -115) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -116) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -117) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -118) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -119) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -120) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -121) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -122) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -123) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -124) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -125) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -126) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -127) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -128) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -129) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -130) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -131) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -132) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -133) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -134) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -135) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -136) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -137) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -138) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -139) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -140) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -141) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -142) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -143) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -144) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -145) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -146) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -147) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -148) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -149) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -150) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -151) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -152) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -153) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -154) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -155) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -156) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -157) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -158) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -160) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -161) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -162) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -163) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -164) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -165) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -166) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -167) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -168) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -169) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -170) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -171) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -172) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -173) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -174) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -175) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -176) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -177) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -178) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -179) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -180) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -181) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -182) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -183) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -184) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -185) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -186) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -187) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -188) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -189) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -190) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -191) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -192) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -193) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -194) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -195) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -196) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -197) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -198) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -199) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -200) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -201) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -202) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -203) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -204) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -205) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -206) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -207) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -208) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -209) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -210) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -211) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -212) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -213) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -214) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -215) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -216) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -217) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -218) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -219) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -220) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -221) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -222) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -223) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -224) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -225) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -226) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -227) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -228) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -229) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -230) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -231) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -232) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -233) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -234) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -235) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -236) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -237) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -238) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -239) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -240) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -241) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -242) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -243) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -244) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -245) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -246) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -247) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -248) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -249) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -250) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -251) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -252) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -253) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -254) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -256) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $over-limit-2 + (drop + (i32.const -1) + ) + (drop + (i32.const -2) + ) + (drop + (i32.const -3) + ) + (drop + (i32.const -4) + ) + (drop + (i32.const -5) + ) + (drop + (i32.const -6) + ) + (drop + (i32.const -7) + ) + (drop + (i32.const -8) + ) + (drop + (i32.const -9) + ) + (drop + (i32.const -10) + ) + (drop + (i32.const -11) + ) + (drop + (i32.const -12) + ) + (drop + (i32.const -13) + ) + (drop + (i32.const -14) + ) + (drop + (i32.const -15) + ) + (drop + (i32.const -16) + ) + (drop + (i32.const -17) + ) + (drop + (i32.const -18) + ) + (drop + (i32.const -19) + ) + (drop + (i32.const -20) + ) + (drop + (i32.const -21) + ) + (drop + (i32.const -22) + ) + (drop + (i32.const -23) + ) + (drop + (i32.const -24) + ) + (drop + (i32.const -25) + ) + (drop + (i32.const -26) + ) + (drop + (i32.const -27) + ) + (drop + (i32.const -28) + ) + (drop + (i32.const -29) + ) + (drop + (i32.const -30) + ) + (drop + (i32.const -31) + ) + (drop + (i32.const -32) + ) + (drop + (i32.const -33) + ) + (drop + (i32.const -34) + ) + (drop + (i32.const -35) + ) + (drop + (i32.const -36) + ) + (drop + (i32.const -37) + ) + (drop + (i32.const -38) + ) + (drop + (i32.const -39) + ) + (drop + (i32.const -40) + ) + (drop + (i32.const -41) + ) + (drop + (i32.const -42) + ) + (drop + (i32.const -43) + ) + (drop + (i32.const -44) + ) + (drop + (i32.const -45) + ) + (drop + (i32.const -46) + ) + (drop + (i32.const -47) + ) + (drop + (i32.const -48) + ) + (drop + (i32.const -49) + ) + (drop + (i32.const -50) + ) + (drop + (i32.const -51) + ) + (drop + (i32.const -52) + ) + (drop + (i32.const -53) + ) + (drop + (i32.const -54) + ) + (drop + (i32.const -55) + ) + (drop + (i32.const -56) + ) + (drop + (i32.const -57) + ) + (drop + (i32.const -58) + ) + (drop + (i32.const -59) + ) + (drop + (i32.const -60) + ) + (drop + (i32.const -61) + ) + (drop + (i32.const -62) + ) + (drop + (i32.const -63) + ) + (drop + (i32.const -64) + ) + (drop + (i32.const -65) + ) + (drop + (i32.const -66) + ) + (drop + (i32.const -67) + ) + (drop + (i32.const -68) + ) + (drop + (i32.const -69) + ) + (drop + (i32.const -70) + ) + (drop + (i32.const -71) + ) + (drop + (i32.const -72) + ) + (drop + (i32.const -73) + ) + (drop + (i32.const -74) + ) + (drop + (i32.const -75) + ) + (drop + (i32.const -76) + ) + (drop + (i32.const -77) + ) + (drop + (i32.const -78) + ) + (drop + (i32.const -79) + ) + (drop + (i32.const -80) + ) + (drop + (i32.const -81) + ) + (drop + (i32.const -82) + ) + (drop + (i32.const -83) + ) + (drop + (i32.const -84) + ) + (drop + (i32.const -85) + ) + (drop + (i32.const -86) + ) + (drop + (i32.const -87) + ) + (drop + (i32.const -88) + ) + (drop + (i32.const -89) + ) + (drop + (i32.const -90) + ) + (drop + (i32.const -91) + ) + (drop + (i32.const -92) + ) + (drop + (i32.const -93) + ) + (drop + (i32.const -94) + ) + (drop + (i32.const -95) + ) + (drop + (i32.const -96) + ) + (drop + (i32.const -97) + ) + (drop + (i32.const -98) + ) + (drop + (i32.const -99) + ) + (drop + (i32.const -100) + ) + (drop + (i32.const -101) + ) + (drop + (i32.const -102) + ) + (drop + (i32.const -103) + ) + (drop + (i32.const -104) + ) + (drop + (i32.const -105) + ) + (drop + (i32.const -106) + ) + (drop + (i32.const -107) + ) + (drop + (i32.const -108) + ) + (drop + (i32.const -109) + ) + (drop + (i32.const -110) + ) + (drop + (i32.const -111) + ) + (drop + (i32.const -112) + ) + (drop + (i32.const -113) + ) + (drop + (i32.const -114) + ) + (drop + (i32.const -115) + ) + (drop + (i32.const -116) + ) + (drop + (i32.const -117) + ) + (drop + (i32.const -118) + ) + (drop + (i32.const -119) + ) + (drop + (i32.const -120) + ) + (drop + (i32.const -121) + ) + (drop + (i32.const -122) + ) + (drop + (i32.const -123) + ) + (drop + (i32.const -124) + ) + (drop + (i32.const -125) + ) + (drop + (i32.const -126) + ) + (drop + (i32.const -127) + ) + (drop + (i32.const -128) + ) + (drop + (i32.const -129) + ) + (drop + (i32.const -130) + ) + (drop + (i32.const -131) + ) + (drop + (i32.const -132) + ) + (drop + (i32.const -133) + ) + (drop + (i32.const -134) + ) + (drop + (i32.const -135) + ) + (drop + (i32.const -136) + ) + (drop + (i32.const -137) + ) + (drop + (i32.const -138) + ) + (drop + (i32.const -139) + ) + (drop + (i32.const -140) + ) + (drop + (i32.const -141) + ) + (drop + (i32.const -142) + ) + (drop + (i32.const -143) + ) + (drop + (i32.const -144) + ) + (drop + (i32.const -145) + ) + (drop + (i32.const -146) + ) + (drop + (i32.const -147) + ) + (drop + (i32.const -148) + ) + (drop + (i32.const -149) + ) + (drop + (i32.const -150) + ) + (drop + (i32.const -151) + ) + (drop + (i32.const -152) + ) + (drop + (i32.const -153) + ) + (drop + (i32.const -154) + ) + (drop + (i32.const -155) + ) + (drop + (i32.const -156) + ) + (drop + (i32.const -157) + ) + (drop + (i32.const -158) + ) + (drop + (i32.const -159) + ) + (drop + (i32.const -160) + ) + (drop + (i32.const -161) + ) + (drop + (i32.const -162) + ) + (drop + (i32.const -163) + ) + (drop + (i32.const -164) + ) + (drop + (i32.const -165) + ) + (drop + (i32.const -166) + ) + (drop + (i32.const -167) + ) + (drop + (i32.const -168) + ) + (drop + (i32.const -169) + ) + (drop + (i32.const -170) + ) + (drop + (i32.const -171) + ) + (drop + (i32.const -172) + ) + (drop + (i32.const -173) + ) + (drop + (i32.const -174) + ) + (drop + (i32.const -175) + ) + (drop + (i32.const -176) + ) + (drop + (i32.const -177) + ) + (drop + (i32.const -178) + ) + (drop + (i32.const -179) + ) + (drop + (i32.const -180) + ) + (drop + (i32.const -181) + ) + (drop + (i32.const -182) + ) + (drop + (i32.const -183) + ) + (drop + (i32.const -184) + ) + (drop + (i32.const -185) + ) + (drop + (i32.const -186) + ) + (drop + (i32.const -187) + ) + (drop + (i32.const -188) + ) + (drop + (i32.const -189) + ) + (drop + (i32.const -190) + ) + (drop + (i32.const -191) + ) + (drop + (i32.const -192) + ) + (drop + (i32.const -193) + ) + (drop + (i32.const -194) + ) + (drop + (i32.const -195) + ) + (drop + (i32.const -196) + ) + (drop + (i32.const -197) + ) + (drop + (i32.const -198) + ) + (drop + (i32.const -199) + ) + (drop + (i32.const -200) + ) + (drop + (i32.const -201) + ) + (drop + (i32.const -202) + ) + (drop + (i32.const -203) + ) + (drop + (i32.const -204) + ) + (drop + (i32.const -205) + ) + (drop + (i32.const -206) + ) + (drop + (i32.const -207) + ) + (drop + (i32.const -208) + ) + (drop + (i32.const -209) + ) + (drop + (i32.const -210) + ) + (drop + (i32.const -211) + ) + (drop + (i32.const -212) + ) + (drop + (i32.const -213) + ) + (drop + (i32.const -214) + ) + (drop + (i32.const -215) + ) + (drop + (i32.const -216) + ) + (drop + (i32.const -217) + ) + (drop + (i32.const -218) + ) + (drop + (i32.const -219) + ) + (drop + (i32.const -220) + ) + (drop + (i32.const -221) + ) + (drop + (i32.const -222) + ) + (drop + (i32.const -223) + ) + (drop + (i32.const -224) + ) + (drop + (i32.const -225) + ) + (drop + (i32.const -226) + ) + (drop + (i32.const -227) + ) + (drop + (i32.const -228) + ) + (drop + (i32.const -229) + ) + (drop + (i32.const -230) + ) + (drop + (i32.const -231) + ) + (drop + (i32.const -232) + ) + (drop + (i32.const -233) + ) + (drop + (i32.const -234) + ) + (drop + (i32.const -235) + ) + (drop + (i32.const -236) + ) + (drop + (i32.const -237) + ) + (drop + (i32.const -238) + ) + (drop + (i32.const -239) + ) + (drop + (i32.const -240) + ) + (drop + (i32.const -241) + ) + (drop + (i32.const -242) + ) + (drop + (i32.const -243) + ) + (drop + (i32.const -244) + ) + (drop + (i32.const -245) + ) + (drop + (i32.const -246) + ) + (drop + (i32.const -247) + ) + (drop + (i32.const -248) + ) + (drop + (i32.const -249) + ) + (drop + (i32.const -250) + ) + (drop + (i32.const -251) + ) + (drop + (i32.const -252) + ) + (drop + (i32.const -253) + ) + (drop + (i32.const -254) + ) + (drop + (i32.const -255) + ) + (drop + (i32.const -256) + ) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + (nop) + ) +) +;; CHECK: (func $byn$mgfn-shared$at-limit-1 (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 i32) (param $11 i32) (param $12 i32) (param $13 i32) (param $14 i32) (param $15 i32) (param $16 i32) (param $17 i32) (param $18 i32) (param $19 i32) (param $20 i32) (param $21 i32) (param $22 i32) (param $23 i32) (param $24 i32) (param $25 i32) (param $26 i32) (param $27 i32) (param $28 i32) (param $29 i32) (param $30 i32) (param $31 i32) (param $32 i32) (param $33 i32) (param $34 i32) (param $35 i32) (param $36 i32) (param $37 i32) (param $38 i32) (param $39 i32) (param $40 i32) (param $41 i32) (param $42 i32) (param $43 i32) (param $44 i32) (param $45 i32) (param $46 i32) (param $47 i32) (param $48 i32) (param $49 i32) (param $50 i32) (param $51 i32) (param $52 i32) (param $53 i32) (param $54 i32) (param $55 i32) (param $56 i32) (param $57 i32) (param $58 i32) (param $59 i32) (param $60 i32) (param $61 i32) (param $62 i32) (param $63 i32) (param $64 i32) (param $65 i32) (param $66 i32) (param $67 i32) (param $68 i32) (param $69 i32) (param $70 i32) (param $71 i32) (param $72 i32) (param $73 i32) (param $74 i32) (param $75 i32) (param $76 i32) (param $77 i32) (param $78 i32) (param $79 i32) (param $80 i32) (param $81 i32) (param $82 i32) (param $83 i32) (param $84 i32) (param $85 i32) (param $86 i32) (param $87 i32) (param $88 i32) (param $89 i32) (param $90 i32) (param $91 i32) (param $92 i32) (param $93 i32) (param $94 i32) (param $95 i32) (param $96 i32) (param $97 i32) (param $98 i32) (param $99 i32) (param $100 i32) (param $101 i32) (param $102 i32) (param $103 i32) (param $104 i32) (param $105 i32) (param $106 i32) (param $107 i32) (param $108 i32) (param $109 i32) (param $110 i32) (param $111 i32) (param $112 i32) (param $113 i32) (param $114 i32) (param $115 i32) (param $116 i32) (param $117 i32) (param $118 i32) (param $119 i32) (param $120 i32) (param $121 i32) (param $122 i32) (param $123 i32) (param $124 i32) (param $125 i32) (param $126 i32) (param $127 i32) (param $128 i32) (param $129 i32) (param $130 i32) (param $131 i32) (param $132 i32) (param $133 i32) (param $134 i32) (param $135 i32) (param $136 i32) (param $137 i32) (param $138 i32) (param $139 i32) (param $140 i32) (param $141 i32) (param $142 i32) (param $143 i32) (param $144 i32) (param $145 i32) (param $146 i32) (param $147 i32) (param $148 i32) (param $149 i32) (param $150 i32) (param $151 i32) (param $152 i32) (param $153 i32) (param $154 i32) (param $155 i32) (param $156 i32) (param $157 i32) (param $158 i32) (param $159 i32) (param $160 i32) (param $161 i32) (param $162 i32) (param $163 i32) (param $164 i32) (param $165 i32) (param $166 i32) (param $167 i32) (param $168 i32) (param $169 i32) (param $170 i32) (param $171 i32) (param $172 i32) (param $173 i32) (param $174 i32) (param $175 i32) (param $176 i32) (param $177 i32) (param $178 i32) (param $179 i32) (param $180 i32) (param $181 i32) (param $182 i32) (param $183 i32) (param $184 i32) (param $185 i32) (param $186 i32) (param $187 i32) (param $188 i32) (param $189 i32) (param $190 i32) (param $191 i32) (param $192 i32) (param $193 i32) (param $194 i32) (param $195 i32) (param $196 i32) (param $197 i32) (param $198 i32) (param $199 i32) (param $200 i32) (param $201 i32) (param $202 i32) (param $203 i32) (param $204 i32) (param $205 i32) (param $206 i32) (param $207 i32) (param $208 i32) (param $209 i32) (param $210 i32) (param $211 i32) (param $212 i32) (param $213 i32) (param $214 i32) (param $215 i32) (param $216 i32) (param $217 i32) (param $218 i32) (param $219 i32) (param $220 i32) (param $221 i32) (param $222 i32) (param $223 i32) (param $224 i32) (param $225 i32) (param $226 i32) (param $227 i32) (param $228 i32) (param $229 i32) (param $230 i32) (param $231 i32) (param $232 i32) (param $233 i32) (param $234 i32) (param $235 i32) (param $236 i32) (param $237 i32) (param $238 i32) (param $239 i32) (param $240 i32) (param $241 i32) (param $242 i32) (param $243 i32) (param $244 i32) (param $245 i32) (param $246 i32) (param $247 i32) (param $248 i32) (param $249 i32) (param $250 i32) (param $251 i32) (param $252 i32) (param $253 i32) (param $254 i32) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $254) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $253) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $252) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $251) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $250) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $249) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $248) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $247) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $246) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $245) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $244) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $243) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $242) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $241) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $240) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $239) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $238) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $237) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $236) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $235) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $234) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $233) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $232) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $231) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $230) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $229) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $228) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $227) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $226) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $225) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $224) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $223) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $222) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $221) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $220) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $219) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $218) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $217) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $216) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $215) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $214) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $213) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $212) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $211) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $210) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $209) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $208) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $207) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $206) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $205) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $204) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $203) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $202) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $201) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $200) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $199) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $198) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $197) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $196) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $195) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $194) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $193) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $192) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $191) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $190) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $189) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $188) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $187) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $186) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $185) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $184) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $183) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $182) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $181) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $180) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $179) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $178) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $177) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $176) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $175) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $174) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $173) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $172) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $171) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $170) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $169) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $168) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $167) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $166) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $165) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $164) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $163) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $162) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $161) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $160) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $159) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $158) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $157) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $156) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $155) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $154) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $153) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $152) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $151) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $150) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $149) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $148) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $147) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $146) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $145) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $144) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $143) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $142) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $141) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $140) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $139) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $138) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $137) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $136) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $135) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $134) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $133) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $132) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $131) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $130) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $129) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $128) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $127) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $126) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $125) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $124) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $123) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $122) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $121) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $120) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $119) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $118) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $117) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $116) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $115) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $114) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $113) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $112) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $111) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $110) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $109) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $108) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $107) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $106) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $105) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $104) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $103) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $102) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $101) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $100) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $99) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $98) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $97) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $96) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $95) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $94) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $93) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $92) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $91) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $90) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $89) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $88) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $87) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $86) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $85) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $84) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $83) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $82) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $81) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $80) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $79) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $78) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $77) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $76) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $75) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $74) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $73) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $72) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $71) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $70) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $69) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $68) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $67) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $66) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $65) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $64) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $63) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $62) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $61) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $60) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $59) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $58) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $57) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $56) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $55) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $54) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $53) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $52) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $51) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $50) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $49) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $48) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $47) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $46) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $45) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $44) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $43) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $42) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $41) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $40) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $39) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $38) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $37) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $36) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $35) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $34) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $33) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $32) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $31) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $30) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $29) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $28) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $27) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $26) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $25) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $24) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $23) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $22) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $21) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $20) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $19) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $18) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $17) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $16) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $15) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $14) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $13) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $12) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $11) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $10) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $9) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $8) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $7) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $6) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $5) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $4) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $3) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) From 7c6df8251b69aa5d5d511fbccf66b65118528274 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 3 Mar 2025 12:20:20 -0800 Subject: [PATCH 329/622] Add a custom descriptors feature (#7339) Although the proposal is called "Custom RTTs," the more precise term for its main feature is "custom descriptors." To decrease long-term confusion at the expense of some possible short-term confusion, name the corresponding new feature "custom descriptors." This feature will guard the use of both descriptor/describes clauses and exact reference types. --- src/tools/tool-options.h | 2 ++ src/wasm-binary.h | 1 + src/wasm-features.h | 16 +++++++++++++--- src/wasm/wasm-binary.cpp | 2 ++ src/wasm/wasm.cpp | 9 +++++---- test/binaryen.js/kitchen-sink.js.txt | 2 +- test/example/c-api-kitchen-sink.txt | 2 +- test/lit/help/wasm-as.test | 6 ++++++ test/lit/help/wasm-ctor-eval.test | 6 ++++++ test/lit/help/wasm-dis.test | 6 ++++++ test/lit/help/wasm-emscripten-finalize.test | 6 ++++++ test/lit/help/wasm-merge.test | 6 ++++++ test/lit/help/wasm-metadce.test | 6 ++++++ test/lit/help/wasm-opt.test | 6 ++++++ test/lit/help/wasm-reduce.test | 6 ++++++ test/lit/help/wasm-split.test | 6 ++++++ test/lit/help/wasm2js.test | 6 ++++++ ...res_roundtrip_print-features_all-features.txt | 1 + 18 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index bff71bb4138..3c42b6b1f13 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -106,6 +106,8 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::StackSwitching, "stack switching") .addFeature(FeatureSet::SharedEverything, "shared-everything threads") .addFeature(FeatureSet::FP16, "float 16 operations") + .addFeature(FeatureSet::CustomDescriptors, + "custom descriptors (RTTs) and exact references") .add("--enable-typed-function-references", "", "Deprecated compatibility flag", diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 9765afd0cbe..2915db46864 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -398,6 +398,7 @@ extern const char* SharedEverythingFeature; extern const char* FP16Feature; extern const char* BulkMemoryOptFeature; extern const char* CallIndirectOverlongFeature; +extern const char* CustomDescriptorsFeature; enum Subsection { NameModule = 0, diff --git a/src/wasm-features.h b/src/wasm-features.h index 7ada02e9979..a7c3ce0c4f5 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -54,11 +54,12 @@ struct FeatureSet { // that we can automatically generate tool flags that set it, but otherwise // it does nothing. Binaryen always accepts LEB call-indirect encodings. CallIndirectOverlong = 1 << 20, + CustomDescriptors = 1 << 21, MVP = None, // Keep in sync with llvm default features: // https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153 Default = SignExt | MutableGlobals, - All = (1 << 21) - 1, + All = (1 << 22) - 1, }; static std::string toString(Feature f) { @@ -105,9 +106,14 @@ struct FeatureSet { return "bulk-memory-opt"; case CallIndirectOverlong: return "call-indirect-overlong"; - default: - WASM_UNREACHABLE("unexpected feature"); + case CustomDescriptors: + return "custom-descriptors"; + case MVP: + case Default: + case All: + break; } + WASM_UNREACHABLE("unexpected feature"); } std::string toString() const { @@ -159,6 +165,9 @@ struct FeatureSet { assert(has || !hasBulkMemory()); return has; } + bool hasCustomDescriptors() const { + return (features & CustomDescriptors) != 0; + } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -184,6 +193,7 @@ struct FeatureSet { void setSharedEverything(bool v = true) { set(SharedEverything, v); } void setFP16(bool v = true) { set(FP16, v); } void setBulkMemoryOpt(bool v = true) { set(BulkMemoryOpt, v); } + void setCustomDescriptors(bool v = true) { set(CustomDescriptors, v); } void setMVP() { features = MVP; } void setAll() { features = All; } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f9d9225bbc7..7099df8f2fe 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1358,6 +1358,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::BulkMemoryOptFeature; case FeatureSet::CallIndirectOverlong: return BinaryConsts::CustomSections::CallIndirectOverlongFeature; + case FeatureSet::CustomDescriptors: + return BinaryConsts::CustomSections::CustomDescriptorsFeature; case FeatureSet::None: case FeatureSet::Default: case FeatureSet::All: diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index c8a82311c30..d023109ce7c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -28,8 +28,8 @@ Name RETURN_FLOW("*return:)*"); Name RETURN_CALL_FLOW("*return-call:)*"); Name NONCONSTANT_FLOW("*nonconstant:)*"); -namespace BinaryConsts { -namespace CustomSections { +namespace BinaryConsts::CustomSections { + const char* Name = "name"; const char* SourceMapUrl = "sourceMappingURL"; const char* Dylink = "dylink"; @@ -59,8 +59,9 @@ const char* SharedEverythingFeature = "shared-everything"; const char* FP16Feature = "fp16"; const char* BulkMemoryOptFeature = "bulk-memory-opt"; const char* CallIndirectOverlongFeature = "call-indirect-overlong"; -} // namespace CustomSections -} // namespace BinaryConsts +const char* CustomDescriptorsFeature = "custom-descriptors"; + +} // namespace BinaryConsts::CustomSections Name STACK_POINTER("__stack_pointer"); Name MODULE("module"); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 16e19a6a0c9..711972ad48b 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -33,7 +33,7 @@ Features.RelaxedSIMD: 4096 Features.ExtendedConst: 8192 Features.Strings: 16384 Features.MultiMemory: 32768 -Features.All: 2097151 +Features.All: 4194303 InvalidId: 0 BlockId: 1 IfId: 2 diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 2e319129e13..0f9ee08a0f7 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -47,7 +47,7 @@ BinaryenFeatureMemory64: 2048 BinaryenFeatureRelaxedSIMD: 4096 BinaryenFeatureExtendedConst: 8192 BinaryenFeatureStrings: 16384 -BinaryenFeatureAll: 2097151 +BinaryenFeatureAll: 4194303 (f32.neg (f32.const -33.61199951171875) ) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 63f4f03adf8..7514841a7a6 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -128,6 +128,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index 2960ddb9ae0..bed94623224 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -135,6 +135,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index 1efe7ffd82b..a10b41a4f3c 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -121,6 +121,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index 1ed447b16b1..4cb9ef940a0 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -163,6 +163,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index 0e3b5f8b166..0c51047952b 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -151,6 +151,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 5d01bc62c43..1a7c495fabd 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -775,6 +775,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) +;; CHECK-NEXT: and exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors +;; CHECK-NEXT: (RTTs) and exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index cc80f52c037..1a6be04783d 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -787,6 +787,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) +;; CHECK-NEXT: and exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors +;; CHECK-NEXT: (RTTs) and exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 9cd47473841..1a04f2f5878 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -160,6 +160,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 1e4267dc26f..095c66047ac 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -260,6 +260,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and +;; CHECK-NEXT: exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 6e9c7732a3a..6db49301db1 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -738,6 +738,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-descriptors Enable custom descriptors (RTTs) +;; CHECK-NEXT: and exact references +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors +;; CHECK-NEXT: (RTTs) and exact references +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt index 2cd2573f10b..34976434e9d 100644 --- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt +++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt @@ -19,6 +19,7 @@ --enable-fp16 --enable-bulk-memory-opt --enable-call-indirect-overlong +--enable-custom-descriptors (module (type $0 (func (result v128 externref))) (func $foo (type $0) (result v128 externref) From 4596b984b91c7d0c17c83b5518f96e7bec741f9b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Mar 2025 12:57:46 -0800 Subject: [PATCH 330/622] [Parser] Error properly on br_on* going to a target without a value (#7338) Internally we subtract 1 from the number of values while processing such things, but if we start with 0 we'd overflow and hit a confusing error later. Fixes the last part of #7337 --- src/wasm/wasm-ir-builder.cpp | 3 +++ test/lit/parse-bad-gc-br_on-novalue.wast | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 test/lit/parse-bad-gc-br_on-novalue.wast diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 1835949b63e..1ea7b86afdd 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1974,6 +1974,9 @@ Result<> IRBuilder::makeBrOn(Index label, BrOnOp op, Type in, Type out) { case BrOnCast: case BrOnCastFail: // Modeled as sending one value. + if (extraArity == 0) { + return Err{"br_on target does not expect a value"}; + } extraArity -= 1; break; } diff --git a/test/lit/parse-bad-gc-br_on-novalue.wast b/test/lit/parse-bad-gc-br_on-novalue.wast new file mode 100644 index 00000000000..788c486464c --- /dev/null +++ b/test/lit/parse-bad-gc-br_on-novalue.wast @@ -0,0 +1,17 @@ +;; RUN: not wasm-opt %s -all 2>&1 | filecheck %s + +;; Check we error properly when a block has no value, but a br_on with a value +;; targets it. + +(module + ;; CHECK: 10:7: error: br_on target does not expect a value + (func $f + (block $foo + (br_on_cast $foo (ref eq) (ref eq) + (ref.i31 + (i32.const 0) + ) + ) + ) + ) +) From 6b5327c75c4e4229145694bda6487108e2fbcc6f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Mar 2025 14:26:55 -0800 Subject: [PATCH 331/622] [NFC] Avoid huge output in validator messages (#7334) Nested expressions that all fail to validate can lead to quadratic output and OOMs. Instead, put a limit on the size we print, and after it only emit truncated error messages of fixed size. Also make some of the types more precise in this code, which makes it easier to read. Fixes #7333 --- src/wasm/wasm-validator.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 3f67906f105..157226994c1 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -42,16 +42,28 @@ template::type>::value>::type* = nullptr> inline std::ostream& -printModuleComponent(T curr, std::ostream& stream, Module& wasm) { +printModuleComponent(T curr, std::ostringstream& stream, Module& wasm) { stream << curr << std::endl; return stream; } // Extra overload for Expressions, to print their contents. -inline std::ostream& -printModuleComponent(Expression* curr, std::ostream& stream, Module& wasm) { +inline std::ostream& printModuleComponent(Expression* curr, + std::ostringstream& stream, + Module& wasm) { if (curr) { - stream << ModuleExpression(wasm, curr) << '\n'; + // Print the full expression if we can, but avoid doing so if the output is + // already very large. This avoids quadratic output in some cases (e.g. if + // we have many nested expressions in each other, all of which fail to + // validate). + const std::ostringstream::pos_type MAX_OUTPUT = 16 * 1024; + if (stream.tellp() < MAX_OUTPUT) { + stream << ModuleExpression(wasm, curr) << '\n'; + } else { + // Print something, at least. + stream << "[not printing " << getExpressionName(curr) + << " because output is already very large]\n"; + } } return stream; } @@ -98,7 +110,7 @@ struct ValidationInfo { return printModuleComponent(curr, ret, wasm); } - std::ostream& printFailureHeader(Function* func) { + std::ostringstream& printFailureHeader(Function* func) { auto& stream = getStream(func); if (quiet) { return stream; From 93703106a2f4d68c61359b0fa10f784d9feef8a8 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 3 Mar 2025 14:33:31 -0800 Subject: [PATCH 332/622] Parsing and emitting of exact types (#7342) Implement text and binary parsing of exact references types, including parsing of reference type shorthands with the exact prefix. Also implement binary writing of exact types. When writing exact types when custom descriptors are not enabled, generalize the types to their inexact versions. This is very similar to how we generalize function types when GC is not enabled, for instance. --- scripts/test/fuzzing.py | 4 +- src/parser/contexts.h | 18 +++++-- src/parser/parsers.h | 73 ++++++++++++++++++---------- src/wasm-binary.h | 2 + src/wasm/wasm-binary.cpp | 38 ++++++++++++++- test/lit/basic/exact-references.wast | 53 ++++++++++++++++++++ 6 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 test/lit/basic/exact-references.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 270a9cf6b80..32b4f44f078 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -100,7 +100,9 @@ 'stack_switching_suspend.wast', 'stack_switching_resume.wast', 'stack_switching_resume_throw.wast', - 'stack_switching_switch.wast' + 'stack_switching_switch.wast', + # TODO: fuzzer support for exact references + 'exact-references.wast', ] diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 55601b174c5..3b9cb21c3e0 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -127,7 +127,9 @@ struct NullTypeParserCtx { TypeT makeF64() { return Ok{}; } TypeT makeV128() { return Ok{}; } - TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; } + TypeT makeRefType(HeapTypeT, Nullability, Exactness) { return Ok{}; } + + HeapTypeT getHeapTypeFromRefType(TypeT) { return Ok{}; } TupleElemListT makeTupleElemList() { return Ok{}; } void appendTupleElem(TupleElemListT&, TypeT) {} @@ -257,10 +259,13 @@ template struct TypeParserCtx { TypeT makeF64() { return Type::f64; } TypeT makeV128() { return Type::v128; } - TypeT makeRefType(HeapTypeT ht, Nullability nullability) { - return Type(ht, nullability); + TypeT + makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { + return Type(ht, nullability, exactness); } + HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } + std::vector makeTupleElemList() { return {}; } void appendTupleElem(std::vector& elems, Type elem) { elems.push_back(elem); @@ -1115,10 +1120,13 @@ struct ParseTypeDefsCtx : TypeParserCtx { : TypeParserCtx(typeIndices), in(in), builder(builder), names(builder.size()) {} - TypeT makeRefType(HeapTypeT ht, Nullability nullability) { - return builder.getTempRefType(ht, nullability); + TypeT + makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { + return builder.getTempRefType(ht, nullability, exactness); } + HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } + TypeT makeTupleType(const std::vector types) { return builder.getTempTupleType(types); } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 4ba88fd8bf2..b54de9979c6 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -30,6 +30,8 @@ using namespace std::string_view_literals; template Result absheaptype(Ctx&, Shareability); template Result heaptype(Ctx&); +template +MaybeResult maybeReftypeAbbrev(Ctx&); template MaybeResult maybeReftype(Ctx&); template Result reftype(Ctx&); template MaybeResult tupletype(Ctx&); @@ -456,68 +458,85 @@ template Result heaptype(Ctx& ctx) { // | 'i31ref' => i31ref // | 'structref' => structref // | 'arrayref' => arrayref -// | '(' ref null? t:heaptype ')' => ref null? t -template MaybeResult maybeReftype(Ctx& ctx) { +// | ... +template +MaybeResult maybeReftypeAbbrev(Ctx& ctx) { if (ctx.in.takeKeyword("funcref"sv)) { - return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("externref"sv)) { - return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("anyref"sv)) { - return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("eqref"sv)) { - return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("i31ref"sv)) { - return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable); + return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("structref"sv)) { - return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("arrayref"sv)) { - return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("exnref"sv)) { - return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("stringref"sv)) { - return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("contref"sv)) { - return ctx.makeRefType(ctx.makeContType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullref"sv)) { - return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullexternref"sv)) { - return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullfuncref"sv)) { - return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullexnref"sv)) { - return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullcontref"sv)) { - return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact); } + return {}; +} - if (!ctx.in.takeSExprStart("ref"sv)) { - return {}; +// reftype ::= ... +// | '(' 'exact' (ref null ht):shorthand ')' => ref null exact ht +// | '(' ref null? exact? ht:heaptype ')' => ref null? t +template MaybeResult maybeReftype(Ctx& ctx) { + if (ctx.in.takeSExprStart("exact"sv)) { + auto rt = maybeReftypeAbbrev(ctx); + CHECK_ERR(rt); + if (!rt) { + return ctx.in.err("expected reftype shorthand"); + } + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); + } + return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact); } - auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; - - auto type = heaptype(ctx); - CHECK_ERR(type); - - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of reftype"); + if (ctx.in.takeSExprStart("ref"sv)) { + auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; + auto exactness = ctx.in.takeKeyword("exact"sv) ? Exact : Inexact; + auto type = heaptype(ctx); + CHECK_ERR(type); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); + } + return ctx.makeRefType(*type, nullability, exactness); } - return ctx.makeRefType(*type, nullability); + return maybeReftypeAbbrev(ctx); } template Result reftype(Ctx& ctx) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 2915db46864..bf5d9708583 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -327,6 +327,7 @@ enum EncodedType { eqref = -0x13, // 0x6d nonnullable = -0x1c, // 0x64 nullable = -0x1d, // 0x63 + exact = -0x1e, // 0x62 contref = -0x18, // 0x68 nullcontref = -0x0b, // 0x75 // exception handling @@ -1497,6 +1498,7 @@ class WasmBinaryReader { Type getType(); // Get a type given the initial S32LEB has already been read, and is provided. Type getType(int code); + Type getTypeNoExact(int code); HeapType getHeapType(); HeapType getIndexedHeapType(); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 7099df8f2fe..f7abd4908f3 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1551,6 +1551,12 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) { void WasmBinaryWriter::writeType(Type type) { if (type.isRef()) { + // Exact references are introduced by the custom descriptors feature, but + // can be used internally even when it is not enabled. In that case, we have + // to generalize the types to be inexact before writing them. + if (!wasm->features.hasCustomDescriptors()) { + type = Type(type.getHeapType(), type.getNullability(), Inexact); + } // The only reference types allowed without GC are funcref, externref, and // exnref. We internally use more refined versions of those types, but we // cannot emit those without GC. @@ -1567,6 +1573,12 @@ void WasmBinaryWriter::writeType(Type type) { type = Type(type.getHeapType().getTop(), Nullable); } } + // If the type is exact, emit the exact prefix and continue on without + // considering exactness. + if (type.isExact()) { + o << S32LEB(BinaryConsts::EncodedType::exact); + type = Type(type.getHeapType(), type.getNullability(), Inexact); + } auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { switch (heapType.getBasic(Unshared)) { @@ -2155,7 +2167,7 @@ Signature WasmBinaryReader::getBlockType() { return Signature(Type::none, getType(code)); } -Type WasmBinaryReader::getType(int code) { +Type WasmBinaryReader::getTypeNoExact(int code) { Type type; if (getBasicType(code, type)) { return type; @@ -2171,6 +2183,17 @@ Type WasmBinaryReader::getType(int code) { WASM_UNREACHABLE("unexpected type"); } +Type WasmBinaryReader::getType(int code) { + if (code == BinaryConsts::EncodedType::exact) { + auto type = getTypeNoExact(getS32LEB()); + if (!type.isRef()) { + throwError("invalid exact prefix on non-reference type"); + } + return Type(type.getHeapType(), type.getNullability(), Exact); + } + return getTypeNoExact(code); +} + Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { @@ -2331,7 +2354,7 @@ void WasmBinaryReader::readTypes() { } return builder.getTempHeapType(size_t(htCode)); }; - auto makeType = [&](int32_t typeCode) { + auto makeTypeNoExact = [&](int32_t typeCode) { Type type; if (getBasicType(typeCode, type)) { return type; @@ -2356,6 +2379,17 @@ void WasmBinaryReader::readTypes() { } WASM_UNREACHABLE("unexpected type"); }; + auto makeType = [&](int32_t typeCode) { + if (typeCode == BinaryConsts::EncodedType::exact) { + auto type = makeTypeNoExact(getS32LEB()); + if (!type.isRef()) { + throwError("unexpected exact prefix on non-reference type"); + } + return builder.getTempRefType( + type.getHeapType(), type.getNullability(), Exact); + } + return makeTypeNoExact(typeCode); + }; auto readType = [&]() { return makeType(getS32LEB()); }; auto readSignatureDef = [&]() { diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast new file mode 100644 index 00000000000..73130afbd33 --- /dev/null +++ b/test/lit/basic/exact-references.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +;; Also check that if we emit a binary without custom descriptors enabled, the +;; types are generalized to be inexact. + +;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o - | wasm-opt -all -S -o - \ +;; RUN: | filecheck %s --check-prefix=NO-EXACT + +(module + ;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; CHECK-BIN: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; NO-EXACT: (type $foo (struct (field anyref) (field (ref any)) (field (ref null $foo)) (field (ref $foo)))) + (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) + + + ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) + ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) + ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) + (import "" "g1" (global $g1 (exact anyref))) + + ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact any))) + ;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact any))) + ;; NO-EXACT: (import "" "g2" (global $g2 (ref any))) + (import "" "g2" (global $g2 (ref exact any))) + + ;; CHECK-TEXT: (import "" "g3" (global $g3 (ref null exact $foo))) + ;; CHECK-BIN: (import "" "g3" (global $g3 (ref null exact $foo))) + ;; NO-EXACT: (import "" "g3" (global $g3 (ref null $foo))) + (import "" "g3" (global $g3 (ref null exact $foo))) + + ;; CHECK-TEXT: (import "" "g4" (global $g4 (ref exact $foo))) + ;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo))) + ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) + (import "" "g4" (global $g4 (ref exact $foo))) +) +;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) + +;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) + +;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any))) + +;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0))) + +;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0))) From dc055a8ebae55730864f87692c60da23c431fc80 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 5 Mar 2025 09:27:06 -0800 Subject: [PATCH 333/622] GUFA: Fix a nondeterminism bug by pre-filtering (#7331) The repeated "merge new content in, and filter based on the location it arrives to" operation is non-commutative, and it turns out there was a corner case we missed. Filtering before and after is enough to make us return the same result with the ordering swapped. This does make GUFA 5% slower, unfortunately. But this does result in better code in some cases aside from fixing the nondeterminism. Also add clearer comments about the problem here. We likely need to just make the order of operations here deterministic (though a downside of that is that the fuzzer wouldn't find bugs like this, and it would be slower). --- src/ir/possible-contents.cpp | 62 +++++++++++++++++--------- test/lit/passes/gufa-cast-all.wast | 70 ++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 21 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index ecf5f513c2b..24c72715ed2 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2413,23 +2413,35 @@ bool Flower::updateContents(LocationIndex locationIndex, auto location = getLocation(locationIndex); // Handle special cases: Some locations can only contain certain contents, so - // filter accordingly. In principle we need to filter both before and after - // combining with existing content; filtering afterwards is obviously - // necessary as combining two things will create something larger than both, - // and our representation has limitations (e.g. two different ref types will - // result in a cone, potentially a very large one). Filtering beforehand is - // necessary for the a more subtle reason: consider a location that contains - // an i8 which is sent a 0 and then 0x100. If we filter only after, then we'd - // combine 0 and 0x100 first and get "unknown integer"; only by filtering - // 0x100 to 0 beforehand (since 0x100 & 0xff => 0) will we combine 0 and 0 and - // not change anything, which is correct. + // filter accordingly. For example, if anyref arrives to a non-nullable + // location, we know it must be (ref any). As a result, each time we update + // the contents at a location we are both merging in the new contents, and + // filtering based on what we know of the location. // - // For efficiency reasons we aim to only filter once, depending on the type of - // filtering. Most can be filtered a single time afterwards, while for data - // locations, where the issue is packed integer fields, it's necessary to do - // it before as we've mentioned, and also sufficient (see details in - // filterDataContents). + // The operation of merging in new content and also filtering is *not* + // commutative. Set intersection and union of course is, but the shapes we + // work with here are limited, e.g. we have cones which include all children + // up to a fixed depth (and not specific children or each with a different + // depth). For example, if we start e.g. with a ref.func literal, and a + // ref.null arrives, then merging results in a cone that allows null, as that + // is the best shape we have that includes both. If the location is non- + // nullable then the cone becomes non-nullable, so we ended up with something + // worse than the original ref.func literal. In contrast, if we filtered the + // new contents first, the null would vanish (as no null is possible in the + // non-nullable location), so that order ends up better. + // + // For those reasons we filter the new contents arriving and also the merged + // contents afterwards, to try to get the best results. This also avoids some + // nondeterminism hazards with different orders. TODO: This does not avoid + // them all, in principle, due to lack of commutativity. Using a deterministic + // order (like abstract interpretation) would fix that. if (auto* dataLoc = std::get_if(&location)) { + // Filtering data contents is especially important to do before, and not + // necessary afterwards. For example, imagine a location that contains an + // i8 which is sent a 0 and then 0x100. If we filter only after, then we'd + // combine 0 and 0x100 first and get "unknown integer"; only by filtering + // 0x100 to 0 beforehand (since 0x100 & 0xff => 0) will we combine 0 and 0 + // and not change anything, which is best. filterDataContents(newContents, *dataLoc); #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 std::cout << " pre-filtered data contents:\n"; @@ -2438,12 +2450,9 @@ bool Flower::updateContents(LocationIndex locationIndex, #endif } else if (auto* exprLoc = std::get_if(&location)) { if (exprLoc->expr->is() || exprLoc->expr->is()) { - // Packed data reads must be filtered before the combine() operation, as - // we must only combine the filtered contents (e.g. if 0xff arrives which - // as a signed read is truly 0xffffffff then we cannot first combine the - // existing 0xffffffff with the new 0xff, as they are different, and the - // result will no longer be a constant). There is no need to filter atomic - // RMW operations here because they always do unsigned reads. + // As mentioned above, data locations can have packed reads, which require + // filtering before. Note that there is no need to filter atomic RMW + // operations here because they always do unsigned reads. filterPackedDataReads(newContents, *exprLoc); #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 std::cout << " pre-filtered packed read contents:\n"; @@ -2451,8 +2460,19 @@ bool Flower::updateContents(LocationIndex locationIndex, std::cout << '\n'; #endif } + + // Generic filtering. We do this both before and after. + // + // The outcome of this filtering does not affect whether it is worth sending + // more later (we compute that at the end), so use a temp out var for that. + bool worthSendingMoreTemp = true; + filterExpressionContents(newContents, *exprLoc, worthSendingMoreTemp); + } else if (auto* globalLoc = std::get_if(&location)) { + // Generic filtering. We do this both before and after. + filterGlobalContents(newContents, *globalLoc); } + // After filtering newContents, combine it onto the existing contents. contents.combine(newContents); if (contents.isNone()) { diff --git a/test/lit/passes/gufa-cast-all.wast b/test/lit/passes/gufa-cast-all.wast index e6b64851a4d..92ba242fe34 100644 --- a/test/lit/passes/gufa-cast-all.wast +++ b/test/lit/passes/gufa-cast-all.wast @@ -284,3 +284,73 @@ ) ) +;; Test pre-filtering. +(module + ;; CHECK: (type $A (func)) + (type $A (func)) + + ;; CHECK: (import "a" "b" (global $global i32)) + (import "a" "b" (global $global i32)) + + ;; CHECK: (elem declare func $test) + + ;; CHECK: (func $test (type $A) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_on_non_null $block + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (type $A) + ;; This block is declared as having type $A. Two values appear to reach it: + ;; one from a br that sends a ref.func, and one from a br_on_non_null which + ;; sends a null with the type (ref nofunc) (in practice that branch is not + ;; taken, of course, but GUFA does see all branches; later optimizations + ;; would optimize the branch away). + ;; + ;; We see the ref.func first, so the block $block begins with that content. + ;; Then we see the null arrive. Immediately combining the null with a + ;; ref.func would give a cone - the best shape we have that can allow both a + ;; null and a ref.func. If we later filter the result to the block, which is + ;; non-nullable, the cone becomes non-nullable too - but it is a cone now, + ;; and not the original ref.func, preventing us from applying the constant + ;; value of the ref.func in the output. Early filtering of the arriving + ;; content fixes this: the null is immediately filtered into nothing, since + ;; it is null and the location can only contain non-nullable contents. As a + ;; result, we can optimize the block (and the br_if) to return a ref.func. + (drop + (block $block (result (ref $A)) + (drop + (br_if $block + (ref.func $test) + (global.get $global) + ) + ) + (br_on_non_null $block + (ref.null nofunc) + ) + (unreachable) + ) + ) + ) +) + From bc47696bdccd01ee79e697562f18abdd0980b673 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 5 Mar 2025 11:29:04 -0800 Subject: [PATCH 334/622] Validate that allocations produce non-nullable references (#7346) For `struct.new`, `array.new`, `ref.func`, `ref.i31`, `cont.new`, and `cont.bind`, check in the validator that the result type is a non-nullable reference. This ensures that we have the most precise possible type information for these instructions. The generic stale type checker does not check this because finalizing these expressions does not change their types. --- src/wasm/wasm-validator.cpp | 73 +++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 157226994c1..46dea371411 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2355,6 +2355,11 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) { shouldBeTrue(!getFunction() || getModule()->features.hasReferenceTypes(), curr, "ref.func requires reference-types [--enable-reference-types]"); + if (!shouldBeTrue(curr->type.isNonNullable(), + curr, + "ref.func should have a non-nullable reference type")) { + return; + } if (!info.validateGlobally) { return; } @@ -2372,7 +2377,6 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) { // API (for now those users create the type with funcref). This also needs to // be fixed in LegalizeJSInterface and FuncCastEmulation and other places that // update function types. - // TODO: check for non-nullability } void FunctionValidator::visitRefEq(RefEq* curr) { @@ -2843,16 +2847,33 @@ void FunctionValidator::visitCallRef(CallRef* curr) { void FunctionValidator::visitRefI31(RefI31* curr) { shouldBeTrue( getModule()->features.hasGC(), curr, "ref.i31 requires gc [--enable-gc]"); - if (curr->type.isRef() && curr->type.getHeapType().isShared()) { + shouldBeSubType(curr->value->type, + Type::i32, + curr->value, + "ref.i31's argument should be i32"); + + if (curr->type == Type::unreachable) { + return; + } + + if (!shouldBeTrue(curr->type.isNonNullable(), + curr, + "ref.i31 should have a non-nullable reference type")) { + return; + } + auto heapType = curr->type.getHeapType(); + if (!shouldBeTrue(heapType.isBasic() && + heapType.getBasic(Unshared) == HeapType::i31, + curr, + "ref.i31 should have an i31 reference type")) { + return; + } + if (heapType.isShared()) { shouldBeTrue( getModule()->features.hasSharedEverything(), curr, "ref.i31_shared requires shared-everything [--enable-shared-everything]"); } - shouldBeSubType(curr->value->type, - Type::i32, - curr->value, - "ref.i31's argument should be i32"); } void FunctionValidator::visitI31Get(I31Get* curr) { @@ -2967,6 +2988,11 @@ void FunctionValidator::visitStructNew(StructNew* curr) { if (curr->type == Type::unreachable) { return; } + if (!shouldBeTrue(curr->type.isNonNullable(), + curr, + "struct.new should have a non-nullable reference type")) { + return; + } auto heapType = curr->type.getHeapType(); if (!shouldBeTrue( heapType.isStruct(), curr, "struct.new heap type must be struct")) { @@ -3200,6 +3226,11 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) { if (curr->type == Type::unreachable) { return; } + if (!shouldBeTrue(curr->type.isNonNullable(), + curr, + "array.new should have a non-nullable reference type")) { + return; + } auto heapType = curr->type.getHeapType(); if (!shouldBeTrue( heapType.isArray(), curr, "array.new heap type must be array")) { @@ -3630,12 +3661,20 @@ void FunctionValidator::visitContNew(ContNew* curr) { curr, "cont.new requires stack-switching [--enable-stack-switching]"); - shouldBeTrue( - (curr->type.isContinuation() && - curr->type.getHeapType().getContinuation().type.isSignature()) || - curr->type == Type::unreachable, - curr, - "cont.new must be annotated with a continuation type"); + if (curr->type == Type::unreachable) { + return; + } + + if (!shouldBeTrue(curr->type.isNonNullable(), + curr, + "cont.new should have a non-nullable reference type")) { + return; + } + + shouldBeTrue(curr->type.isContinuation() && + curr->type.getHeapType().getContinuation().type.isSignature(), + curr, + "cont.new must be annotated with a continuation type"); } void FunctionValidator::visitContBind(ContBind* curr) { @@ -3657,6 +3696,16 @@ void FunctionValidator::visitContBind(ContBind* curr) { curr->type == Type::unreachable, curr, "the second type annotation on cont.bind must be a continuation type"); + + if (curr->type == Type::unreachable) { + return; + } + + if (!shouldBeTrue(curr->type.isNonNullable(), + curr, + "cont.bind should have a non-nullable reference type")) { + return; + } } void FunctionValidator::visitSuspend(Suspend* curr) { From d81f5bb4ec434febd710d34715d0f1cfc93b35ab Mon Sep 17 00:00:00 2001 From: Cheng Shao Date: Fri, 7 Mar 2025 19:45:54 +0100 Subject: [PATCH 335/622] MergeSimilarFunctions: Do a return_call when possible (#7350) This patch makes MergeSimilarFunctions do a return_call when the module has tail-call enabled. This is not merely an optimization, but is crucial for correctness when optimizing wasm modules produced by GHC that relies on tail-call to do control flow transfers. Previously, -Oz would break tail-call enabled modules by making the control stack grow where it shouldn't. --- src/passes/MergeSimilarFunctions.cpp | 17 ++++++++++++----- .../merge-similar-functions_all-features.wast | 4 ++-- .../passes/merge-similar-functions_types.wast | 8 ++++---- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/passes/MergeSimilarFunctions.cpp b/src/passes/MergeSimilarFunctions.cpp index 9059e041168..54dcd4034f0 100644 --- a/src/passes/MergeSimilarFunctions.cpp +++ b/src/passes/MergeSimilarFunctions.cpp @@ -165,7 +165,8 @@ struct EquivalentClass { Function* target, Function* shared, const std::vector& params, - const std::vector& extraArgs); + const std::vector& extraArgs, + bool isReturn); bool deriveParams(Module* module, std::vector& params, @@ -480,7 +481,12 @@ void EquivalentClass::merge(Module* module, for (auto& param : params) { extraArgs.push_back(param.lowerToExpression(builder, module, i)); } - replaceWithThunk(builder, func, sharedFn, params, extraArgs); + replaceWithThunk(builder, + func, + sharedFn, + params, + extraArgs, + module->features.hasTailCall()); } return; } @@ -617,7 +623,8 @@ EquivalentClass::replaceWithThunk(Builder& builder, Function* target, Function* shared, const std::vector& params, - const std::vector& extraArgs) { + const std::vector& extraArgs, + bool isReturn) { std::vector callOperands; Type targetParams = target->getParams(); for (Index i = 0; i < targetParams.size(); i++) { @@ -628,8 +635,8 @@ EquivalentClass::replaceWithThunk(Builder& builder, callOperands.push_back(value); } - // TODO: make a return_call when possible? - auto ret = builder.makeCall(shared->name, callOperands, target->getResults()); + auto ret = builder.makeCall( + shared->name, callOperands, target->getResults(), isReturn); target->vars.clear(); target->body = ret; return target; diff --git a/test/lit/passes/merge-similar-functions_all-features.wast b/test/lit/passes/merge-similar-functions_all-features.wast index 74abd8c52d4..8ba20a6ded3 100644 --- a/test/lit/passes/merge-similar-functions_all-features.wast +++ b/test/lit/passes/merge-similar-functions_all-features.wast @@ -103,7 +103,7 @@ ;; CHECK: (elem declare func $return_a $return_b) ;; CHECK: (func $return_call_a (type $0) (result i32) - ;; CHECK-NEXT: (call $byn$mgfn-shared$return_call_a + ;; CHECK-NEXT: (return_call $byn$mgfn-shared$return_call_a ;; CHECK-NEXT: (ref.func $return_a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -115,7 +115,7 @@ ) ;; CHECK: (func $return_call_b (type $0) (result i32) - ;; CHECK-NEXT: (call $byn$mgfn-shared$return_call_a + ;; CHECK-NEXT: (return_call $byn$mgfn-shared$return_call_a ;; CHECK-NEXT: (ref.func $return_b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/merge-similar-functions_types.wast b/test/lit/passes/merge-similar-functions_types.wast index 980f10cd9c1..aaf36d34595 100644 --- a/test/lit/passes/merge-similar-functions_types.wast +++ b/test/lit/passes/merge-similar-functions_types.wast @@ -17,7 +17,7 @@ ;; CHECK: (elem declare func $2 $3) ;; CHECK: (func $0 (type $type$0) - ;; CHECK-NEXT: (call $byn$mgfn-shared$0 + ;; CHECK-NEXT: (return_call $byn$mgfn-shared$0 ;; CHECK-NEXT: (ref.func $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -41,7 +41,7 @@ (nop) ) ;; CHECK: (func $1 (type $type$0) - ;; CHECK-NEXT: (call $byn$mgfn-shared$0 + ;; CHECK-NEXT: (return_call $byn$mgfn-shared$0 ;; CHECK-NEXT: (ref.func $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -126,7 +126,7 @@ ;; CHECK: (elem declare func $2 $3) ;; CHECK: (func $0 (type $type$0) - ;; CHECK-NEXT: (call $byn$mgfn-shared$0 + ;; CHECK-NEXT: (return_call $byn$mgfn-shared$0 ;; CHECK-NEXT: (ref.func $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -150,7 +150,7 @@ (nop) ) ;; CHECK: (func $1 (type $type$0) - ;; CHECK-NEXT: (call $byn$mgfn-shared$0 + ;; CHECK-NEXT: (return_call $byn$mgfn-shared$0 ;; CHECK-NEXT: (ref.func $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From bd59ac636ab0036df7a3a23eb433437f5fa3a3cd Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Mar 2025 11:18:54 -0800 Subject: [PATCH 336/622] Binary encoding and decoding of exact casts (#7347) The binary format for casts needs to be extended to support exact references. In particular, add new opcodes for `ref.test` and `ref.cast` that take reference type immediates instead of heap type immediates. Use two more bits in the flags immediate of `br_on_cast` and `br_on_cast_fail` to encode the source and destination exactness. --- src/wasm-binary.h | 9 + src/wasm/wasm-binary.cpp | 18 +- src/wasm/wasm-stack.cpp | 52 +++- test/lit/basic/exact-references.wast | 377 ++++++++++++++++++++++++++- 4 files changed, 442 insertions(+), 14 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index bf5d9708583..77e13326999 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -304,6 +304,13 @@ enum SegmentFlag { UsesExpressions = 1 << 2 }; +enum BrOnCastFlag { + InputNullable = 1 << 0, + OutputNullable = 1 << 1, + InputExact = 1 << 2, + OutputExact = 1 << 3, +}; + enum EncodedType { // value types i32 = -0x1, // 0x7f @@ -1126,6 +1133,8 @@ enum ASTNodes { I31GetS = 0x1d, I31GetU = 0x1e, RefI31Shared = 0x1f, + RefTestRT = 0x20, + RefCastRT = 0x21, // Shared GC Opcodes diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f7abd4908f3..a5886ede07f 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4251,16 +4251,30 @@ Result<> WasmBinaryReader::readInst() { return builder.makeRefTest(Type(getHeapType(), NonNullable)); case BinaryConsts::RefTestNull: return builder.makeRefTest(Type(getHeapType(), Nullable)); + case BinaryConsts::RefTestRT: + return builder.makeRefTest(getType()); case BinaryConsts::RefCast: return builder.makeRefCast(Type(getHeapType(), NonNullable)); case BinaryConsts::RefCastNull: return builder.makeRefCast(Type(getHeapType(), Nullable)); + case BinaryConsts::RefCastRT: + return builder.makeRefCast(getType()); case BinaryConsts::BrOnCast: case BinaryConsts::BrOnCastFail: { auto flags = getInt8(); auto label = getU32LEB(); - auto in = Type(getHeapType(), (flags & 1) ? Nullable : NonNullable); - auto cast = Type(getHeapType(), (flags & 2) ? Nullable : NonNullable); + auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) + ? Nullable + : NonNullable; + auto dstNull = (flags & BinaryConsts::BrOnCastFlag::OutputNullable) + ? Nullable + : NonNullable; + auto srcExact = + (flags & BinaryConsts::BrOnCastFlag::InputExact) ? Exact : Inexact; + auto dstExact = + (flags & BinaryConsts::BrOnCastFlag::OutputExact) ? Exact : Inexact; + auto in = Type(getHeapType(), srcNull, srcExact); + auto cast = Type(getHeapType(), dstNull, dstExact); auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; return builder.makeBrOn(label, kind, in, cast); } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 060b01b04ee..506b11a085e 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2260,22 +2260,38 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { void BinaryInstWriter::visitRefTest(RefTest* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->castType.isNullable()) { - o << U32LEB(BinaryConsts::RefTestNull); + if (curr->castType.isExact() && + parent.getModule()->features.hasCustomDescriptors()) { + // Fall back to the general form with a reftype immediate. + o << U32LEB(BinaryConsts::RefTestRT); + parent.writeType(curr->castType); } else { - o << U32LEB(BinaryConsts::RefTest); + // Use the special-case form with heap type immediate. + if (curr->castType.isNullable()) { + o << U32LEB(BinaryConsts::RefTestNull); + } else { + o << U32LEB(BinaryConsts::RefTest); + } + parent.writeHeapType(curr->castType.getHeapType()); } - parent.writeHeapType(curr->castType.getHeapType()); } void BinaryInstWriter::visitRefCast(RefCast* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->type.isNullable()) { - o << U32LEB(BinaryConsts::RefCastNull); + if (curr->type.isExact() && + parent.getModule()->features.hasCustomDescriptors()) { + // Fall back to the general form with a reftype immediate. + o << U32LEB(BinaryConsts::RefCastRT); + parent.writeType(curr->type); } else { - o << U32LEB(BinaryConsts::RefCast); + // Use the special-case form with heap type immediate. + if (curr->type.isNullable()) { + o << U32LEB(BinaryConsts::RefCastNull); + } else { + o << U32LEB(BinaryConsts::RefCast); + } + parent.writeHeapType(curr->type.getHeapType()); } - parent.writeHeapType(curr->type.getHeapType()); } void BinaryInstWriter::visitBrOn(BrOn* curr) { @@ -2298,8 +2314,24 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { } assert(curr->ref->type.isRef()); assert(Type::isSubType(curr->castType, curr->ref->type)); - uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | - (curr->castType.isNullable() ? 2 : 0); + uint8_t flags = 0; + if (curr->ref->type.isNullable()) { + flags |= BinaryConsts::BrOnCastFlag::InputNullable; + } + if (curr->castType.isNullable()) { + flags |= BinaryConsts::BrOnCastFlag::OutputNullable; + } + if (parent.getModule()->features.hasCustomDescriptors()) { + // If custom descriptors (and therefore exact references) are not + // enabled, then these flags wouldn't be recognized, and we will be + // generalizing all exact references to be non-exact anyway. + if (curr->ref->type.isExact()) { + flags |= BinaryConsts::BrOnCastFlag::InputExact; + } + if (curr->castType.isExact()) { + flags |= BinaryConsts::BrOnCastFlag::OutputExact; + } + } o << flags; o << U32LEB(getBreakIndex(curr->name)); parent.writeHeapType(curr->ref->type.getHeapType()); diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index 73130afbd33..6aec1ca4d89 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -12,8 +12,8 @@ ;; Also check that if we emit a binary without custom descriptors enabled, the ;; types are generalized to be inexact. -;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o - | wasm-opt -all -S -o - \ -;; RUN: | filecheck %s --check-prefix=NO-EXACT +;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o %t.noexact.wasm +;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s --check-prefix=NO-EXACT (module ;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) @@ -22,8 +22,26 @@ (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) + ;; CHECK-TEXT: (type $1 (func (param anyref) (result anyref))) + + ;; CHECK-TEXT: (type $2 (func (param (exact i31ref)))) + + ;; CHECK-TEXT: (type $3 (func (param (exact eqref)))) + ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) + ;; CHECK-BIN: (type $1 (func (param anyref) (result anyref))) + + ;; CHECK-BIN: (type $2 (func (param (exact i31ref)))) + + ;; CHECK-BIN: (type $3 (func (param (exact eqref)))) + ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) + ;; NO-EXACT: (type $1 (func (param anyref) (result anyref))) + + ;; NO-EXACT: (type $2 (func (param i31ref))) + + ;; NO-EXACT: (type $3 (func (param eqref))) + ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) (import "" "g1" (global $g1 (exact anyref))) @@ -41,9 +59,291 @@ ;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo))) ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) (import "" "g4" (global $g4 (ref exact $foo))) + + ;; CHECK-TEXT: (func $ref-test (type $2) (param $0 (exact i31ref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.test (ref exact i31) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.test (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-test (type $2) (param $0 (exact i31ref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (ref exact i31) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $ref-test (type $2) (param $0 i31ref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test (ref i31) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $ref-test (param (ref null exact i31)) + (drop + (ref.test (ref exact i31) + (local.get 0) + ) + ) + (drop + (ref.test (ref null exact i31) + (local.get 0) + ) + ) + ) + + ;; CHECK-TEXT: (func $ref-cast (type $3) (param $0 (exact eqref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact none) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast (type $3) (param $0 (exact eqref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (ref exact none) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $ref-cast (type $3) (param $0 eqref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast (ref none) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $ref-cast (param (ref null exact eq)) + (drop + (ref.cast (ref exact eq) + (local.get 0) + ) + ) + (drop + (ref.cast (ref null exact eq) + (local.get 0) + ) + ) + (drop + (ref.cast (ref exact i31) + (local.get 0) + ) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $br-on-cast (param anyref) (result anyref) + (drop + (br_on_cast 0 anyref (ref null exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast 0 anyref (ref exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast 0 anyref (ref null exact i31) + (local.get 0) + ) + ) + (local.get 0) + ) + + ;; CHECK-TEXT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $br-on-cast-fail (param anyref) (result anyref) + (drop + (br_on_cast_fail 0 anyref (ref null exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast_fail 0 anyref (ref exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast_fail 0 anyref (ref null exact i31) + (local.get 0) + ) + ) + (local.get 0) + ) ) ;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) +;; CHECK-BIN-NODEBUG: (type $1 (func (param anyref) (result anyref))) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param (exact i31ref)))) + +;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact eqref)))) + ;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) ;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any))) @@ -51,3 +351,76 @@ ;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0))) ;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0))) + +;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (exact i31ref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact i31) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 (exact eqref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact none) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $1) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $3 (type $1) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) From 84023b05b0e860a06543b3d57c81f9dd09253983 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Mar 2025 13:49:51 -0800 Subject: [PATCH 337/622] Do not fuzz on V8 with custom descriptors enabled (#7351) V8 does not yet support custom descriptors, so update all fuzz handlers that use it to avoid running when the feature is enabled. Since running V8 is important, though, disable the feature most of the time, just like we do for shared-everything. --- scripts/bundle_clusterfuzz.py | 1 + scripts/clusterfuzz/run.py | 1 + scripts/fuzz_opt.py | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/bundle_clusterfuzz.py b/scripts/bundle_clusterfuzz.py index 4220c3c51c2..65714340a5e 100755 --- a/scripts/bundle_clusterfuzz.py +++ b/scripts/bundle_clusterfuzz.py @@ -106,6 +106,7 @@ '-all', '--disable-shared-everything', '--disable-fp16', + '--disable-custom-descriptors', ] with tarfile.open(output_file, "w:gz") as tar: diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index ccde7435955..e3b59234e7f 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -87,6 +87,7 @@ '-all', '--disable-shared-everything', '--disable-fp16', + '--disable-custom-descriptors', ] diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 09ddec2e1f5..e12dc809bc5 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -151,8 +151,10 @@ def randomize_feature_opts(): # The shared-everything feature is new and we want to fuzz it, but it # also currently disables fuzzing V8, so disable it most of the time. + # Same with custom descriptors. if random.random() < 0.9: FEATURE_OPTS.append('--disable-shared-everything') + FEATURE_OPTS.append('--disable-custom-descriptors') print('randomized feature opts:', '\n ' + '\n '.join(FEATURE_OPTS)) @@ -813,8 +815,8 @@ def run(self, wasm, extra_d8_flags=[]): def can_run(self, wasm): # V8 does not support shared memories when running with # shared-everything enabled, so do not fuzz shared-everything - # for now. - return all_disallowed(['shared-everything']) + # for now. It also does not yet support custom descriptors. + return all_disallowed(['shared-everything', 'custom-descriptors']) def can_compare_to_self(self): # With nans, VM differences can confuse us, so only very simple VMs @@ -864,7 +866,7 @@ def can_run(self, wasm): if random.random() < 0.5: return False # wasm2c doesn't support most features - return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc']) + return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'custom-descriptors']) def run(self, wasm): run([in_bin('wasm-opt'), wasm, '--emit-wasm2c-wrapper=main.c'] + FEATURE_OPTS) @@ -1165,7 +1167,7 @@ def can_run_on_wasm(self, wasm): # implement wasm suspending using JS async/await. if JSPI: return False - return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'multimemory', 'memory64']) + return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'multimemory', 'memory64', 'custom-descriptors']) # given a wasm, find all the exports of particular kinds (for example, kinds @@ -1571,7 +1573,7 @@ def can_run_on_wasm(self, wasm): return False # see D8.can_run - return all_disallowed(['shared-everything']) + return all_disallowed(['shared-everything', 'custom-descriptors']) # Check that the text format round-trips without error. @@ -1752,7 +1754,11 @@ def can_run_on_wasm(self, wasm): # mode. We also cannot run shared-everything code in d8 yet. We also # cannot compare if there are NaNs (as optimizations can lead to # different outputs). - return not CLOSED_WORLD and all_disallowed(['shared-everything']) and not NANS + if CLOSED_WORLD: + return False + if NANS: + return False + return all_disallowed(['shared-everything', 'custom-descriptors']) # Test --fuzz-preserve-imports-exports, which never modifies imports or exports. From 9ae16af990793c86e4c78463700ac6a73749e30c Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 7 Mar 2025 15:08:07 -0800 Subject: [PATCH 338/622] Gate LTO in the Emscripten build behind the BYN_ENABLE_LTO CMake flag (#7352) Make it default to ON when using emscripten --- CMakeLists.txt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index caa840beeb6..01332f33791 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ endif() # more useful error reports from users. option(BYN_ENABLE_ASSERTIONS "Enable assertions" ON) -option(BYN_ENABLE_LTO "Build with LTO" Off) +option(BYN_ENABLE_LTO "Build with LTO" ${EMSCRIPTEN}) # Turn this off to avoid the dependency on gtest. option(BUILD_TESTS "Build GTest-based tests" ON) @@ -341,8 +341,10 @@ if(EMSCRIPTEN) # On Node.js, make the tools immediately usable. add_link_flag("-sNODERAWFS") endif() + if (BYN_ENABLE_LTO) # in opt builds, LTO helps so much (>20%) it's worth slow compile times add_nondebug_compile_flag("-flto") + endif() if(EMSCRIPTEN_ENABLE_WASM64) add_compile_flag("-sMEMORY64 -Wno-experimental") add_link_flag("-sMEMORY64") @@ -523,7 +525,9 @@ if(EMSCRIPTEN) target_link_libraries(binaryen_wasm PRIVATE optimized "--closure=1") # TODO: Fix closure warnings! (#5062) target_link_libraries(binaryen_wasm PRIVATE optimized "-Wno-error=closure") - target_link_libraries(binaryen_wasm PRIVATE optimized "-flto") + if (BYN_ENABLE_LTO) + target_link_libraries(binaryen_wasm PRIVATE optimized "-flto") + endif() target_link_libraries(binaryen_wasm PRIVATE debug "--profiling") # Avoid catching exit as that can confuse error reporting in Node, # see https://github.com/emscripten-core/emscripten/issues/17228 @@ -575,7 +579,9 @@ if(EMSCRIPTEN) endif() # TODO: Fix closure warnings! (#5062) target_link_libraries(binaryen_js PRIVATE optimized "-Wno-error=closure") - target_link_libraries(binaryen_js PRIVATE optimized "-flto") + if(BYN_ENABLE_LTO) + target_link_libraries(binaryen_js PRIVATE optimized "-flto") + endif() target_link_libraries(binaryen_js PRIVATE debug "--profiling") target_link_libraries(binaryen_js PRIVATE debug "-sASSERTIONS") # Avoid catching exit as that can confuse error reporting in Node, From a4966d76a39b3cef22fbbf81919475a184916249 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Mar 2025 16:03:44 -0800 Subject: [PATCH 339/622] [NFC] Document that PossibleContents is not a distributive lattice (#7349) As proven in https://github.com/WebAssembly/binaryen/pull/7331#discussion_r1979927844 --- src/ir/possible-contents.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 2f28dcf3c0a..42336e1e6ee 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -61,6 +61,25 @@ namespace wasm { // not track what they might be, so we must assume the worst // in the calling code. // +// This is a lattice, but it is not a distributive lattice +// (https://en.wikipedia.org/wiki/Lattice_(order)#Distributivity): +// +// Cone(ref func) ^ [(ref.func $foo) u (ref.null func)] = +// Cone(ref func) ^ Cone(ref null func) = ;; best shape that can +// ;; hold both a ref.func +// ;; and a null +// Cone(ref func) +// +// while +// +// [Cone(ref func) ^ (ref.func $foo)] u [Cone(ref func) ^ (ref.null func)] = +// (ref.func $foo) u none = +// (ref.func $foo) +// +// "Fixing" this would require us to support a shape that includes both a +// ref.func and a null, and does not "forget" the ref.func as Cone does. It is +// not clear if that would be worth the complexity and overhead. +// class PossibleContents { struct None : public std::monostate {}; From f9b7876ac1acd74924f5abed28e881676c8764ac Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 10 Mar 2025 17:32:46 -0700 Subject: [PATCH 340/622] Update finalization for exact references (#7353) Update the finalization of all instructions whose types (or sent types) depend on their operand reference types to handle exact references correctly. Specifically, update `ref.as_non_null`, `br_on_null`, `br_on_non_null`, `br_on_cast`, and `br_on_cast_fail`. Also add TODOs on all instructions that allocate new heap objects to remind us to make their types exact in the future. --- src/wasm/wasm.cpp | 43 +++- test/lit/basic/exact-references.wast | 296 ++++++++++++++++++++++++--- 2 files changed, 304 insertions(+), 35 deletions(-) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index d023109ce7c..fa12dd49cf5 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -800,6 +800,7 @@ void MemoryGrow::finalize() { void RefNull::finalize(HeapType heapType) { assert(heapType.isBottom()); + // TODO: Make this exact. type = Type(heapType, Nullable); } @@ -922,6 +923,7 @@ static void populateTryTableSentTypes(TryTable* curr, Module* wasm) { // wasm spec defines when GC is enabled (=== non-nullable types are allowed). // If GC is not enabled then we emit a nullable type in the binary format in // WasmBinaryWriter::writeType. + // TODO: Make this exact. Type exnref = Type(HeapType::exn, NonNullable); for (Index i = 0; i < curr->catchTags.size(); i++) { auto tagName = curr->catchTags[i]; @@ -976,6 +978,7 @@ void RefI31::finalize() { if (value->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. assert(type.isRef() && type.getHeapType().isMaybeShared(HeapType::i31)); } } @@ -1011,10 +1014,12 @@ void CallRef::finalize() { // unreachable instead (and similar in other GC accessors), although this // would currently cause the parser to admit more invalid modules. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } else if (type.isTuple()) { Tuple elems; for (auto t : type) { + // TODO: Make this exact. elems.push_back( t.isRef() ? Type(t.getHeapType().getBottom(), NonNullable) : t); } @@ -1071,7 +1076,8 @@ void BrOn::finalize() { switch (op) { case BrOnNull: // If we do not branch, we flow out the existing value as non-null. - type = Type(ref->type.getHeapType(), NonNullable); + type = + Type(ref->type.getHeapType(), NonNullable, ref->type.getExactness()); break; case BrOnNonNull: // If we do not branch, we flow out nothing (the spec could also have had @@ -1081,7 +1087,8 @@ void BrOn::finalize() { case BrOnCast: if (castType.isNullable()) { // Nulls take the branch, so the result is non-nullable. - type = Type(ref->type.getHeapType(), NonNullable); + type = + Type(ref->type.getHeapType(), NonNullable, ref->type.getExactness()); } else { // Nulls do not take the branch, so the result is non-nullable only if // the input is. @@ -1092,7 +1099,9 @@ void BrOn::finalize() { if (castType.isNullable()) { // Nulls do not take the branch, so the result is non-nullable only if // the input is. - type = Type(castType.getHeapType(), ref->type.getNullability()); + type = Type(castType.getHeapType(), + ref->type.getNullability(), + castType.getExactness()); } else { // Nulls take the branch, so the result is non-nullable. type = castType; @@ -1115,11 +1124,14 @@ Type BrOn::getSentType() { return Type::unreachable; } // BrOnNonNull sends the non-nullable type on the branch. - return Type(ref->type.getHeapType(), NonNullable); + return Type( + ref->type.getHeapType(), NonNullable, ref->type.getExactness()); case BrOnCast: // The same as the result type of br_on_cast_fail. if (castType.isNullable()) { - return Type(castType.getHeapType(), ref->type.getNullability()); + return Type(castType.getHeapType(), + ref->type.getNullability(), + castType.getExactness()); } else { return castType; } @@ -1129,7 +1141,8 @@ Type BrOn::getSentType() { return Type::unreachable; } if (castType.isNullable()) { - return Type(ref->type.getHeapType(), NonNullable); + return Type( + ref->type.getHeapType(), NonNullable, ref->type.getExactness()); } else { return ref->type; } @@ -1150,6 +1163,7 @@ void StructGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1225,6 +1239,7 @@ void ArrayGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1302,15 +1317,17 @@ void RefAs::finalize() { auto valHeapType = value->type.getHeapType(); switch (op) { case RefAsNonNull: - type = Type(valHeapType, NonNullable); + type = Type(valHeapType, NonNullable, value->type.getExactness()); break; case AnyConvertExtern: type = Type(HeapTypes::any.getBasic(valHeapType.getShared()), - value->type.getNullability()); + value->type.getNullability(), + Inexact); break; case ExternConvertAny: type = Type(HeapTypes::ext.getBasic(valHeapType.getShared()), - value->type.getNullability()); + value->type.getNullability(), + Inexact); break; default: WASM_UNREACHABLE("invalid ref.as_*"); @@ -1323,11 +1340,15 @@ void StringNew::finalize() { (end && end->type == Type::unreachable)) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } -void StringConst::finalize() { type = Type(HeapType::string, NonNullable); } +void StringConst::finalize() { + // TODO: Make this exact. + type = Type(HeapType::string, NonNullable); +} void StringMeasure::finalize() { if (ref->type == Type::unreachable) { @@ -1350,6 +1371,7 @@ void StringConcat::finalize() { if (left->type == Type::unreachable || right->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } @@ -1375,6 +1397,7 @@ void StringSliceWTF::finalize() { end->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index 6aec1ca4d89..e6c1599ea48 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -22,25 +22,41 @@ (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) - ;; CHECK-TEXT: (type $1 (func (param anyref) (result anyref))) + ;; CHECK-TEXT: (type $1 (func (param (exact anyref)) (result (ref exact any)))) - ;; CHECK-TEXT: (type $2 (func (param (exact i31ref)))) + ;; CHECK-TEXT: (type $2 (func (param anyref) (result anyref))) - ;; CHECK-TEXT: (type $3 (func (param (exact eqref)))) + ;; CHECK-TEXT: (type $3 (func (param (exact i31ref)))) + + ;; CHECK-TEXT: (type $4 (func (param (exact eqref)))) + + ;; CHECK-TEXT: (type $5 (func (param (exact anyref)))) + + ;; CHECK-TEXT: (type $6 (func (param (exact anyref)) (result (exact anyref)))) ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) - ;; CHECK-BIN: (type $1 (func (param anyref) (result anyref))) + ;; CHECK-BIN: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + + ;; CHECK-BIN: (type $2 (func (param anyref) (result anyref))) + + ;; CHECK-BIN: (type $3 (func (param (exact i31ref)))) - ;; CHECK-BIN: (type $2 (func (param (exact i31ref)))) + ;; CHECK-BIN: (type $4 (func (param (exact eqref)))) - ;; CHECK-BIN: (type $3 (func (param (exact eqref)))) + ;; CHECK-BIN: (type $5 (func (param (exact anyref)))) + + ;; CHECK-BIN: (type $6 (func (param (exact anyref)) (result (exact anyref)))) ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) ;; NO-EXACT: (type $1 (func (param anyref) (result anyref))) - ;; NO-EXACT: (type $2 (func (param i31ref))) + ;; NO-EXACT: (type $2 (func (param anyref) (result (ref any)))) + + ;; NO-EXACT: (type $3 (func (param i31ref))) - ;; NO-EXACT: (type $3 (func (param eqref))) + ;; NO-EXACT: (type $4 (func (param eqref))) + + ;; NO-EXACT: (type $5 (func (param anyref))) ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) (import "" "g1" (global $g1 (exact anyref))) @@ -60,7 +76,7 @@ ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) (import "" "g4" (global $g4 (ref exact $foo))) - ;; CHECK-TEXT: (func $ref-test (type $2) (param $0 (exact i31ref)) + ;; CHECK-TEXT: (func $ref-test (type $3) (param $0 (exact i31ref)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (ref.test (ref exact i31) ;; CHECK-TEXT-NEXT: (local.get $0) @@ -72,7 +88,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-test (type $2) (param $0 (exact i31ref)) + ;; CHECK-BIN: (func $ref-test (type $3) (param $0 (exact i31ref)) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.test (ref exact i31) ;; CHECK-BIN-NEXT: (local.get $0) @@ -84,7 +100,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-test (type $2) (param $0 i31ref) + ;; NO-EXACT: (func $ref-test (type $3) (param $0 i31ref) ;; NO-EXACT-NEXT: (drop ;; NO-EXACT-NEXT: (ref.test (ref i31) ;; NO-EXACT-NEXT: (local.get $0) @@ -109,7 +125,7 @@ ) ) - ;; CHECK-TEXT: (func $ref-cast (type $3) (param $0 (exact eqref)) + ;; CHECK-TEXT: (func $ref-cast (type $4) (param $0 (exact eqref)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (ref.cast (ref exact eq) ;; CHECK-TEXT-NEXT: (local.get $0) @@ -126,7 +142,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast (type $3) (param $0 (exact eqref)) + ;; CHECK-BIN: (func $ref-cast (type $4) (param $0 (exact eqref)) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.cast (ref exact eq) ;; CHECK-BIN-NEXT: (local.get $0) @@ -143,7 +159,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-cast (type $3) (param $0 eqref) + ;; NO-EXACT: (func $ref-cast (type $4) (param $0 eqref) ;; NO-EXACT-NEXT: (drop ;; NO-EXACT-NEXT: (ref.cast (ref eq) ;; NO-EXACT-NEXT: (local.get $0) @@ -178,7 +194,7 @@ ) ) - ;; CHECK-TEXT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; CHECK-TEXT: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) ;; CHECK-TEXT-NEXT: (block $label (result anyref) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact eqref) @@ -198,7 +214,7 @@ ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; CHECK-BIN: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) ;; CHECK-BIN-NEXT: (block $block (result anyref) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact eqref) @@ -257,7 +273,7 @@ (local.get 0) ) - ;; CHECK-TEXT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; CHECK-TEXT: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) ;; CHECK-TEXT-NEXT: (block $label (result anyref) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact eqref) @@ -277,7 +293,7 @@ ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; CHECK-BIN: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) ;; CHECK-BIN-NEXT: (block $block (result anyref) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact eqref) @@ -335,14 +351,197 @@ ) (local.get 0) ) + + ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (ref.as_non_null + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (ref.as_non_null + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-ref-as-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (ref.as_non_null + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-ref-as-non-null (param (ref null exact any)) (result (ref exact any)) + (ref.as_non_null + (local.get 0) + ) + ) + + ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-TEXT-NEXT: (block $label + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_null $label + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_null $block + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-null (type $5) (param $0 anyref) + ;; NO-EXACT-NEXT: (block $block + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_null $block + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-null (param (ref null exact any)) + (drop + (block (result (ref exact any)) + (br_on_null 1 + (local.get 0) + ) + ) + ) + ) + + ;; CHECK-TEXT: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_non_null $label + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN-NEXT: (br_on_non_null $block + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT-NEXT: (br_on_non_null $block + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-non-null (param (ref null exact any)) (result (ref exact any)) + (br_on_non_null 0 + (local.get 0) + ) + (unreachable) + ) + + ;; CHECK-TEXT: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (block $label (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) + ;; CHECK-BIN-NEXT: (block $block (result (exact anyref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref anyref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-cast (param (ref null exact any)) (result (ref null exact any)) + (drop + (block (result (ref exact any)) + (br_on_cast 1 (ref null exact any) (ref null exact any) + (local.get 0) + ) + ) + ) + (unreachable) + ) + + ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-cast-fail (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref anyref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-cast-fail (param (ref null exact any)) (result (ref exact any)) + (drop + (block (result (ref null exact any)) + (br_on_cast_fail 1 (ref null exact any) (ref null exact any) + (local.get 0) + ) + ) + ) + (unreachable) + ) ) ;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) -;; CHECK-BIN-NODEBUG: (type $1 (func (param anyref) (result anyref))) +;; CHECK-BIN-NODEBUG: (type $1 (func (param (exact anyref)) (result (ref exact any)))) -;; CHECK-BIN-NODEBUG: (type $2 (func (param (exact i31ref)))) +;; CHECK-BIN-NODEBUG: (type $2 (func (param anyref) (result anyref))) -;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact eqref)))) +;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact i31ref)))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param (exact eqref)))) + +;; CHECK-BIN-NODEBUG: (type $5 (func (param (exact anyref)))) + +;; CHECK-BIN-NODEBUG: (type $6 (func (param (exact anyref)) (result (exact anyref)))) ;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) @@ -352,7 +551,7 @@ ;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0))) -;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (exact i31ref)) +;; CHECK-BIN-NODEBUG: (func $0 (type $3) (param $0 (exact i31ref)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact i31) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) @@ -365,7 +564,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 (exact eqref)) +;; CHECK-BIN-NODEBUG: (func $1 (type $4) (param $0 (exact eqref)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact eq) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) @@ -383,7 +582,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $2 (type $1) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG: (func $2 (type $2) (param $0 anyref) (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact eqref) @@ -404,7 +603,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $3 (type $1) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG: (func $3 (type $2) (param $0 anyref) (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact eqref) @@ -424,3 +623,50 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $4 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (ref.as_non_null +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_null $block +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $6 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_non_null $block +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $7 (type $6) (param $0 (exact anyref)) (result (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $8 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) From 894c7704608580485fcdff545e92501a5db77571 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 10 Mar 2025 21:39:48 -0700 Subject: [PATCH 341/622] Fix assertion in OptimizeInstructions for exact refs (#7354) When optimizing a cast to an exact reference to a bottom type, OptimizeInstructions previously triggered an assertion that expected the cast type to be inexact. Fix the assertion and surrounding code to be more robust to the presence of exact reference types and add a test. --- scripts/test/fuzzing.py | 1 + src/passes/OptimizeInstructions.cpp | 14 ++++++----- .../passes/optimize-instructions-exact.wast | 24 +++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 test/lit/passes/optimize-instructions-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 32b4f44f078..a9f450e5055 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -103,6 +103,7 @@ 'stack_switching_switch.wast', # TODO: fuzzer support for exact references 'exact-references.wast', + 'optimize-instructions-exact.wast', ] diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index c8c192685a2..0987ed845d6 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2346,18 +2346,20 @@ struct OptimizeInstructions } [[fallthrough]]; case GCTypeUtils::SuccessOnlyIfNull: { - auto nullType = Type(curr->type.getHeapType().getBottom(), Nullable); // The cast either returns null or traps. In trapsNeverHappen mode // we know the result, since by assumption it will not trap. if (getPassOptions().trapsNeverHappen) { - replaceCurrent(builder.makeBlock( - {builder.makeDrop(curr->ref), builder.makeRefNull(nullType)}, - curr->type)); + replaceCurrent( + builder.makeBlock({builder.makeDrop(curr->ref), + builder.makeRefNull(curr->type.getHeapType())}, + curr->type)); return; } // Otherwise, we should have already refined the cast type to cast - // directly to null. - assert(curr->type == nullType); + // directly to null. We do not further refine the cast type to exact + // null because the extra precision is not useful and doing so would + // increase the size of the instruction encoding. + assert(curr->type.isNull()); break; } case GCTypeUtils::Unreachable: diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast new file mode 100644 index 00000000000..ae35380c9a1 --- /dev/null +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -0,0 +1,24 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that optimizations on casts involving exact reference types work +;; correctly. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (func $cast-to-exact-none (type $0) (param $0 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-exact-none (param anyref) + (drop + ;; This will not be changed, but should not trigger an assertion. + (ref.cast (exact nullref) + (local.get 0) + ) + ) + ) +) From 7d2f80dd674019d13c03a9136de9ecc32a762dec Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 10 Mar 2025 21:40:40 -0700 Subject: [PATCH 342/622] Update LocalSubtyping for exact references (#7355) After calculating the best possible type for a local, LocalSubtyping then checks to see whether the local can be non-nullable based on whether all of its gets are dominated by sets. If it cannot be non-nullable, the new type is adjusted to be nullable. This adjustment did not previously preserve exactness, causing an assertion that the optimization improves the type to fail. Fix the adjustment and add a test. --- scripts/test/fuzzing.py | 1 + src/passes/LocalSubtyping.cpp | 3 +- test/lit/passes/local-subtyping-exact.wast | 39 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/local-subtyping-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index a9f450e5055..0f4a1befa74 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -104,6 +104,7 @@ # TODO: fuzzer support for exact references 'exact-references.wast', 'optimize-instructions-exact.wast', + 'local-subtyping-exact.wast', ] diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp index 7b30e35387f..38f93b19435 100644 --- a/src/passes/LocalSubtyping.cpp +++ b/src/passes/LocalSubtyping.cpp @@ -152,7 +152,8 @@ struct LocalSubtyping : public WalkerPass> { // Remove non-nullability if we disallow that in locals. if (newType.isNonNullable()) { if (cannotBeNonNullable.count(i)) { - newType = Type(newType.getHeapType(), Nullable); + newType = + Type(newType.getHeapType(), Nullable, newType.getExactness()); } } else if (!newType.isDefaultable()) { // Aside from the case we just handled of allowed non-nullability, we diff --git a/test/lit/passes/local-subtyping-exact.wast b/test/lit/passes/local-subtyping-exact.wast new file mode 100644 index 00000000000..725c7d499fb --- /dev/null +++ b/test/lit/passes/local-subtyping-exact.wast @@ -0,0 +1,39 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that LocalSubtyping handles exact references properly when it +;; determines that a local that would otherwise be non-nullable must be nullable +;; because of control flow dominance constraints. + +;; RUN: wasm-opt %s -all --local-subtyping -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 (exact nullref)) (result anyref) + ;; CHECK-NEXT: (local $1 (exact nullref)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $test (param (exact nullref)) (result anyref) + (local (exact nullref)) + (if + (i32.const 0) + (then + (local.set 1 + ;; This would let the local be (ref exact none) if it dominated the get. + (ref.as_non_null + (local.get 0) + ) + ) + ) + ) + (local.get 1) + ) +) From f0a8b6f985694321e0153495a11f60b76867b5b2 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 10 Mar 2025 21:41:27 -0700 Subject: [PATCH 343/622] Fix TypeUpdating for exact references in heap types (#7356) Update the locations in wasm-type.h and type-updating.cpp responsible for propagating exactness from references in heap types from the old types to the new types and add a test. Leave updating other parts of type-updating to later PRs with further tests that will exercise them. --- scripts/test/fuzzing.py | 1 + src/ir/type-updating.cpp | 4 +++- src/wasm-type.h | 3 ++- test/lit/passes/remove-unused-types-exact.wast | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/remove-unused-types-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 0f4a1befa74..0bbfbcd9717 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -105,6 +105,7 @@ 'exact-references.wast', 'optimize-instructions-exact.wast', 'local-subtyping-exact.wast', + 'remove-unused-types-exact.wast', ] diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index b90d8eb8790..cba761465b3 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -205,7 +205,9 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { Type getNew(Type type) { if (type.isRef()) { - return Type(getNew(type.getHeapType()), type.getNullability()); + return Type(getNew(type.getHeapType()), + type.getNullability(), + type.getExactness()); } if (type.isTuple()) { auto tuple = type.getTuple(); diff --git a/src/wasm-type.h b/src/wasm-type.h index f09e1e440fe..a33a2bed907 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -709,7 +709,8 @@ struct TypeBuilder { return t; } assert(t.isRef()); - return getTempRefType(map(t.getHeapType()), t.getNullability()); + return getTempRefType( + map(t.getHeapType()), t.getNullability(), t.getExactness()); }; auto copyType = [&](Type t) -> Type { if (t.isTuple()) { diff --git a/test/lit/passes/remove-unused-types-exact.wast b/test/lit/passes/remove-unused-types-exact.wast new file mode 100644 index 00000000000..6cb9c1ddd74 --- /dev/null +++ b/test/lit/passes/remove-unused-types-exact.wast @@ -0,0 +1,16 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --closed-world --remove-unused-types -S -o - | filecheck %s + +;; Test that a simple type rewrite handles exact references in heap type +;; definitions correctly. In particular, the function should continue returning +;; an exact nullref and the call expression should have the same type. + +(module + ;; CHECK: (func $return-exact (type $0) (result (exact nullref)) + ;; CHECK-NEXT: (call $return-exact) + ;; CHECK-NEXT: ) + (func $return-exact (result (exact nullref)) + (call $return-exact) + ) +) From 0472ba2cf66908a64591f7374e49ce7bc10eadca Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 10 Mar 2025 21:42:17 -0700 Subject: [PATCH 344/622] Handle exact references when fixing non-defaultable locals (#7357) Some passes leave non-defaultable locals in an invalid state where not all the gets of the local are structurally dominated by sets. To fix this, the pass runner automatically calls `TypeUpdating::handleNonDefaultableLocals` to find the problematic locals and make them nullable. This function did not previously preserve exactness in the updated local types. Fix it and add a test. --- scripts/test/fuzzing.py | 1 + src/ir/type-updating.cpp | 2 +- test/lit/passes/coalesce-locals-exact.wast | 47 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/coalesce-locals-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 0bbfbcd9717..fa60ba07af6 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -106,6 +106,7 @@ 'optimize-instructions-exact.wast', 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', + 'coalesce-locals-exact.wast', ] diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index cba761465b3..3a4b32bd1cb 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -467,7 +467,7 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) { Type getValidLocalType(Type type, FeatureSet features) { assert(type.isConcrete()); if (type.isNonNullable()) { - return Type(type.getHeapType(), Nullable); + return Type(type.getHeapType(), Nullable, type.getExactness()); } if (type.isTuple()) { std::vector elems(type.size()); diff --git a/test/lit/passes/coalesce-locals-exact.wast b/test/lit/passes/coalesce-locals-exact.wast new file mode 100644 index 00000000000..1568d3634cf --- /dev/null +++ b/test/lit/passes/coalesce-locals-exact.wast @@ -0,0 +1,47 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that TypeUpdating::handleNonDefaultableLocals handles locals with exact +;; reference types correctly, and in particular that it preserves the exactness +;; of the types. + +;; RUN: wasm-opt %s -all --coalesce-locals -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 (exact i31ref)) (result (ref exact i31)) + ;; CHECK-NEXT: (local $1 (exact i31ref)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param (exact i31ref)) (result (ref exact i31)) + (local $l (ref exact i31)) + ;; This dead set will be optimized out. + (local.set $l + (ref.as_non_null + (local.get 0) + ) + ) + (block $l + ;; This remaining set does not structurally dominate the get. + (local.set $l + (ref.as_non_null + (local.get 0) + ) + ) + ) + ;; This will have to be fixed up and the local made nullable. + (local.get $l) + ) +) From 626e212f9ae4314948f7e6b210102463e41d10f9 Mon Sep 17 00:00:00 2001 From: Oscar Spencer Date: Tue, 11 Mar 2025 13:21:09 -0500 Subject: [PATCH 345/622] [C API] Add all features (#7362) Also reorders existing ones to match wasm-features.h. Fixes #7361 --- src/binaryen-c.cpp | 27 +++++++++++++++++++++------ src/binaryen-c.h | 9 +++++++-- src/js/binaryen.js-post.js | 9 +++++++-- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 8eb73b4c259..5917b8b6051 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -427,21 +427,21 @@ BinaryenFeatures BinaryenFeatureMVP(void) { BinaryenFeatures BinaryenFeatureAtomics(void) { return static_cast(FeatureSet::Atomics); } -BinaryenFeatures BinaryenFeatureBulkMemory(void) { - return static_cast(FeatureSet::BulkMemory); -} BinaryenFeatures BinaryenFeatureMutableGlobals(void) { return static_cast(FeatureSet::MutableGlobals); } BinaryenFeatures BinaryenFeatureNontrappingFPToInt(void) { return static_cast(FeatureSet::TruncSat); } -BinaryenFeatures BinaryenFeatureSignExt(void) { - return static_cast(FeatureSet::SignExt); -} BinaryenFeatures BinaryenFeatureSIMD128(void) { return static_cast(FeatureSet::SIMD); } +BinaryenFeatures BinaryenFeatureBulkMemory(void) { + return static_cast(FeatureSet::BulkMemory); +} +BinaryenFeatures BinaryenFeatureSignExt(void) { + return static_cast(FeatureSet::SignExt); +} BinaryenFeatures BinaryenFeatureExceptionHandling(void) { return static_cast(FeatureSet::ExceptionHandling); } @@ -472,6 +472,21 @@ BinaryenFeatures BinaryenFeatureStrings(void) { BinaryenFeatures BinaryenFeatureMultiMemory(void) { return static_cast(FeatureSet::MultiMemory); } +BinaryenFeatures BinaryenFeatureStackSwitching(void) { + return static_cast(FeatureSet::StackSwitching); +} +BinaryenFeatures BinaryenFeatureSharedEverything(void) { + return static_cast(FeatureSet::SharedEverything); +} +BinaryenFeatures BinaryenFeatureFP16(void) { + return static_cast(FeatureSet::FP16); +} +BinaryenFeatures BinaryenFeatureBulkMemoryOpt(void) { + return static_cast(FeatureSet::BulkMemoryOpt); +} +BinaryenFeatures BinaryenFeatureCallIndirectOverlong(void) { + return static_cast(FeatureSet::CallIndirectOverlong); +} BinaryenFeatures BinaryenFeatureAll(void) { return static_cast(FeatureSet::All); } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 8616571db59..5feca3ba2e3 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -210,11 +210,11 @@ typedef uint32_t BinaryenFeatures; BINARYEN_API BinaryenFeatures BinaryenFeatureMVP(void); BINARYEN_API BinaryenFeatures BinaryenFeatureAtomics(void); -BINARYEN_API BinaryenFeatures BinaryenFeatureBulkMemory(void); BINARYEN_API BinaryenFeatures BinaryenFeatureMutableGlobals(void); BINARYEN_API BinaryenFeatures BinaryenFeatureNontrappingFPToInt(void); -BINARYEN_API BinaryenFeatures BinaryenFeatureSignExt(void); BINARYEN_API BinaryenFeatures BinaryenFeatureSIMD128(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureBulkMemory(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureSignExt(void); BINARYEN_API BinaryenFeatures BinaryenFeatureExceptionHandling(void); BINARYEN_API BinaryenFeatures BinaryenFeatureTailCall(void); BINARYEN_API BinaryenFeatures BinaryenFeatureReferenceTypes(void); @@ -225,6 +225,11 @@ BINARYEN_API BinaryenFeatures BinaryenFeatureRelaxedSIMD(void); BINARYEN_API BinaryenFeatures BinaryenFeatureExtendedConst(void); BINARYEN_API BinaryenFeatures BinaryenFeatureStrings(void); BINARYEN_API BinaryenFeatures BinaryenFeatureMultiMemory(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureStackSwitching(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureSharedEverything(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureFP16(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureBulkMemoryOpt(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureCallIndirectOverlong(void); BINARYEN_API BinaryenFeatures BinaryenFeatureAll(void); // Modules diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index c8d8e31ba58..6e8d7a958d2 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -145,11 +145,11 @@ function initializeConstants() { Module['Features'] = {}; [ 'MVP', 'Atomics', - 'BulkMemory', 'MutableGlobals', 'NontrappingFPToInt', - 'SignExt', 'SIMD128', + 'BulkMemory', + 'SignExt', 'ExceptionHandling', 'TailCall', 'ReferenceTypes', @@ -160,6 +160,11 @@ function initializeConstants() { 'ExtendedConst', 'Strings', 'MultiMemory', + 'StackSwitching', + 'SharedEverything', + 'FP16', + 'BulkMemoryOpt', + 'CallIndirectOverlong', 'All' ].forEach(name => { Module['Features'][name] = Module['_BinaryenFeature' + name](); From 3f59a7d7964336b2d0d612a49806f099d8c92274 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 11 Mar 2025 11:34:34 -0700 Subject: [PATCH 346/622] [NFC] Add type.with(...) API to update reference types (#7359) There are many places where we have to copy a reference type with some modification, for example to make it refer to a different heap type or to make it non-nullable or nullable. Previously the only way to do this was with the `Type` constructor, retrieving and passing in the unmodified fields of the old type explicitly. With the addition of exact types, all of these sites have to be updated to additionally propagate the old type's exactness. To simplify these call sites and make them more robust against future additions to the structure of reference types, introduce new APIs to update just a single part of a reference type at a time. --- src/ir/type-updating.cpp | 6 ++---- src/passes/LocalSubtyping.cpp | 3 +-- src/wasm-type.h | 11 +++++++++++ src/wasm/wasm.cpp | 22 +++++++--------------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 3a4b32bd1cb..6dd91e0960e 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -205,9 +205,7 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { Type getNew(Type type) { if (type.isRef()) { - return Type(getNew(type.getHeapType()), - type.getNullability(), - type.getExactness()); + return type.with(getNew(type.getHeapType())); } if (type.isTuple()) { auto tuple = type.getTuple(); @@ -467,7 +465,7 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) { Type getValidLocalType(Type type, FeatureSet features) { assert(type.isConcrete()); if (type.isNonNullable()) { - return Type(type.getHeapType(), Nullable, type.getExactness()); + return type.with(Nullable); } if (type.isTuple()) { std::vector elems(type.size()); diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp index 38f93b19435..a26637684b7 100644 --- a/src/passes/LocalSubtyping.cpp +++ b/src/passes/LocalSubtyping.cpp @@ -152,8 +152,7 @@ struct LocalSubtyping : public WalkerPass> { // Remove non-nullability if we disallow that in locals. if (newType.isNonNullable()) { if (cannotBeNonNullable.count(i)) { - newType = - Type(newType.getHeapType(), Nullable, newType.getExactness()); + newType = newType.with(Nullable); } } else if (!newType.isDefaultable()) { // Aside from the case we just handled of allowed non-nullability, we diff --git a/src/wasm-type.h b/src/wasm-type.h index a33a2bed907..62177af2c08 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -398,6 +398,17 @@ class Type { return isExact() ? Exact : Inexact; } + // Return a new reference type with some part updated to the specified value. + Type with(HeapType heapType) { + return Type(heapType, getNullability(), getExactness()); + } + Type with(Nullability nullability) { + return Type(getHeapType(), nullability, getExactness()); + } + Type with(Exactness exactness) { + return Type(getHeapType(), getNullability(), exactness); + } + private: template bool hasPredicate() { for (const auto& type : *this) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index fa12dd49cf5..ca9fcc93328 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1076,8 +1076,7 @@ void BrOn::finalize() { switch (op) { case BrOnNull: // If we do not branch, we flow out the existing value as non-null. - type = - Type(ref->type.getHeapType(), NonNullable, ref->type.getExactness()); + type = ref->type.with(NonNullable); break; case BrOnNonNull: // If we do not branch, we flow out nothing (the spec could also have had @@ -1087,8 +1086,7 @@ void BrOn::finalize() { case BrOnCast: if (castType.isNullable()) { // Nulls take the branch, so the result is non-nullable. - type = - Type(ref->type.getHeapType(), NonNullable, ref->type.getExactness()); + type = ref->type.with(NonNullable); } else { // Nulls do not take the branch, so the result is non-nullable only if // the input is. @@ -1099,9 +1097,7 @@ void BrOn::finalize() { if (castType.isNullable()) { // Nulls do not take the branch, so the result is non-nullable only if // the input is. - type = Type(castType.getHeapType(), - ref->type.getNullability(), - castType.getExactness()); + type = castType.with(ref->type.getNullability()); } else { // Nulls take the branch, so the result is non-nullable. type = castType; @@ -1124,14 +1120,11 @@ Type BrOn::getSentType() { return Type::unreachable; } // BrOnNonNull sends the non-nullable type on the branch. - return Type( - ref->type.getHeapType(), NonNullable, ref->type.getExactness()); + return ref->type.with(NonNullable); case BrOnCast: // The same as the result type of br_on_cast_fail. if (castType.isNullable()) { - return Type(castType.getHeapType(), - ref->type.getNullability(), - castType.getExactness()); + return castType.with(ref->type.getNullability()); } else { return castType; } @@ -1141,8 +1134,7 @@ Type BrOn::getSentType() { return Type::unreachable; } if (castType.isNullable()) { - return Type( - ref->type.getHeapType(), NonNullable, ref->type.getExactness()); + return ref->type.with(NonNullable); } else { return ref->type; } @@ -1317,7 +1309,7 @@ void RefAs::finalize() { auto valHeapType = value->type.getHeapType(); switch (op) { case RefAsNonNull: - type = Type(valHeapType, NonNullable, value->type.getExactness()); + type = value->type.with(NonNullable); break; case AnyConvertExtern: type = Type(HeapTypes::any.getBasic(valHeapType.getShared()), From 2bddedc624b73452eacb59edc75595b3563f56e8 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 11 Mar 2025 11:35:05 -0700 Subject: [PATCH 347/622] Update RemoveUnusedBrs for exact references (#7360) When optimizing branching casts, RemoveUnusedBrs previously assumed that if a source reference type was not a subtype of another target reference type, but the source's heap type was a subtype of the target's heap type, then it could use ref.as_non_null to convert from the source to the target. This is no longer true now that we have exact types because the types may differ in their exactness rather than in their nullness. Update the check guarding the use of ref.as_non_null to specifically check that a non-nullable version of the source type is a subtype of the destination type and add a test. --- scripts/test/fuzzing.py | 1 + src/passes/RemoveUnusedBrs.cpp | 4 +- test/lit/passes/remove-unused-brs-exact.wast | 40 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/remove-unused-brs-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index fa60ba07af6..09604a37821 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -107,6 +107,7 @@ 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', 'coalesce-locals-exact.wast', + 'remove-unused-brs-exact.wast', ] diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 47a1f1c6553..72dea0405e1 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -859,8 +859,8 @@ struct RemoveUnusedBrs : public WalkerPass> { if (Type::isSubType(expr->type, type)) { return expr; } - if (HeapType::isSubType(expr->type.getHeapType(), - type.getHeapType())) { + if (type.isNonNullable() && expr->type.isNullable() && + Type::isSubType(expr->type.with(NonNullable), type)) { return builder.makeRefAs(RefAsNonNull, expr); } return builder.makeRefCast(expr, type); diff --git a/test/lit/passes/remove-unused-brs-exact.wast b/test/lit/passes/remove-unused-brs-exact.wast new file mode 100644 index 00000000000..3bebd07de02 --- /dev/null +++ b/test/lit/passes/remove-unused-brs-exact.wast @@ -0,0 +1,40 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --remove-unused-brs -S -o - | filecheck %s + +;; Check that we optimize the cast correctly when the fallthrough has exact +;; type. In particular, we should not insert a ref.as_non_null, which would +;; trap. + +(module + ;; CHECK: (func $br_on_cast_fail (type $0) (param $0 (exact nullref)) + ;; CHECK-NEXT: (local $1 nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail (param (ref null exact none)) + (local $1 nullref) + (drop + (block $block (result (ref none)) + (drop + (br_on_cast_fail $block nullref nullref + (local.tee $1 + (local.get 0) + ) + ) + ) + (return) + ) + ) + ) +) From b29abbe0955c4ab5a245581842ac4445eb728646 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 11 Mar 2025 16:13:02 -0700 Subject: [PATCH 348/622] Fuzzer: Do not error on hashMemory() already existing (#7364) --- src/tools/fuzzing/fuzzing.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index e529706f786..611830be312 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1107,8 +1107,9 @@ void TranslateToFuzzReader::addHashMemorySupport() { } contents.push_back(builder.makeLocalGet(0, Type::i32)); auto* body = builder.makeBlock(contents); + auto name = Names::getValidFunctionName(wasm, "hashMemory"); auto* hasher = wasm.addFunction(builder.makeFunction( - "hashMemory", Signature(Type::none, Type::i32), {Type::i32}, body)); + name, Signature(Type::none, Type::i32), {Type::i32}, body)); if (!preserveImportsAndExports) { wasm.addExport( From 8b47ebf8ad8609f7b2f511f268e6b9302979816f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 11 Mar 2025 17:05:15 -0700 Subject: [PATCH 349/622] Fix optimization of casts to exact null types (#7365) The logic for optimizing ref.casts that are known to succeed did not account for possible casts to exact null types, leading to it producing invalid IR. Fix it and add a test. --- src/passes/OptimizeInstructions.cpp | 21 +++++++++---- .../passes/optimize-instructions-exact.wast | 30 ++++++++++++------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 0987ed845d6..90dcf5c5d64 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2277,10 +2277,14 @@ struct OptimizeInstructions // emit a null check. bool needsNullCheck = ref->type.getNullability() == Nullable && curr->type.getNullability() == NonNullable; + // Same with exactness. + bool needsExactCast = ref->type.getExactness() == Inexact && + curr->type.getExactness() == Exact; // If the best value to propagate is the argument to the cast, we can // simply remove the cast (or downgrade it to a null check if - // necessary). - if (ref == curr->ref) { + // necessary). This does not work if we need a cast to prove + // exactness. + if (ref == curr->ref && !needsExactCast) { if (needsNullCheck) { replaceCurrent(builder.makeRefAs(RefAsNonNull, curr->ref)); } else { @@ -2289,17 +2293,22 @@ struct OptimizeInstructions return; } // Otherwise we can't just remove the cast and replace it with `ref` - // because the intermediate expressions might have had side effects. - // We can replace the cast with a drop followed by a direct return of - // the value, though. + // because the intermediate expressions might have had side effects or + // we need to check exactness. We can replace the cast with a drop + // followed by a direct return of the value, though. if (ref->type.isNull()) { + // TODO: Remove this once we type ref.null as exact. + if (needsExactCast) { + return; + } + // We can materialize the resulting null value directly. // // The type must be nullable for us to do that, which it normally // would be, aside from the interesting corner case of // uninhabitable types: // - // (ref.cast func + // (ref.cast (ref func) // (block (result (ref nofunc)) // (unreachable) // ) diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast index ae35380c9a1..6e03c9a4977 100644 --- a/test/lit/passes/optimize-instructions-exact.wast +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -6,19 +6,27 @@ ;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s (module - ;; CHECK: (func $cast-to-exact-none (type $0) (param $0 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK: (func $cast-any-to-exact-none (type $0) (param $0 anyref) (result (exact nullref)) + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast-to-exact-none (param anyref) - (drop - ;; This will not be changed, but should not trigger an assertion. - (ref.cast (exact nullref) - (local.get 0) - ) + (func $cast-any-to-exact-none (param anyref) (result (exact nullref)) + ;; This will not be changed, but should not trigger an assertion. + (ref.cast (exact nullref) + (local.get 0) + ) + ) + ;; CHECK: (func $cast-null-to-exact-none (type $1) (result (exact nullref)) + ;; CHECK-NEXT: (local $0 nullref) + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-null-to-exact-none (result (exact nullref)) + (local nullref) + (ref.cast (exact nullref) + (local.get 0) ) ) ) From a1758cf1f5d08d2cd3c69163f9a9ec2648129e41 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 12 Mar 2025 12:42:08 -0700 Subject: [PATCH 350/622] [Parsing] Emit better errors when binary parser encounters a bad code (#7367) Noticed in #7363 --- src/wasm/wasm-binary.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index a5886ede07f..efacec1a1ac 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3537,7 +3537,7 @@ Result<> WasmBinaryReader::readInst() { return builder.makeStructCmpxchg(type, field, order); } } - return Err{"unknown atomic operation"}; + return Err{"unknown atomic operation " + std::to_string(op)}; } case BinaryConsts::MiscPrefix: { auto op = getU32LEB(); @@ -3597,7 +3597,7 @@ Result<> WasmBinaryReader::readInst() { return builder.makeStore(2, offset, align, Type::f32, mem); } } - return Err{"unknown misc operation"}; + return Err{"unknown misc operation: " + std::to_string(op)}; } case BinaryConsts::SIMDPrefix: { auto op = getU32LEB(); @@ -4234,7 +4234,7 @@ Result<> WasmBinaryReader::readInst() { Store64LaneVec128, offset, align, getLaneIndex(2), mem); } } - return Err{"unknown SIMD operation"}; + return Err{"unknown SIMD operation " + std::to_string(op)}; } case BinaryConsts::GCPrefix: { auto op = getU32LEB(); @@ -4377,10 +4377,10 @@ Result<> WasmBinaryReader::readInst() { case BinaryConsts::ExternConvertAny: return builder.makeRefAs(ExternConvertAny); } - return Err{"unknown GC operation"}; + return Err{"unknown GC operation " + std::to_string(op)}; } } - return Err{"unknown operation"}; + return Err{"unknown operation " + std::to_string(code)}; } void WasmBinaryReader::readExports() { From b4bdcc33115b31758c56b83bb9de4642c411a042 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 12 Mar 2025 16:47:44 -0700 Subject: [PATCH 351/622] [Wasm GC] Fix GUFA on trampled params in TrapsNeverHappens mode (#7368) GUFA in TNH mode will infer that this will trap: (func $foo (param $x) (ref.cast $T (local.get $x)) ) (func $bar (call $foo (X)) ) If X's type will cause a trap when cast to T, then we infer that the call will trap, and optimize. However, we were missing a check for the param being trampled, (func $foo (param $x) (local.set $x (Y)) ;; !!!!!!!!!! (ref.cast $T (local.get $x)) ) Then if Y can be cast, we should not actually trap. Fix this by just tracking which params are written to. We only look in the first basic block anyhow, and traverse it in order, so that is enough. Fixes #7366 --- src/ir/possible-contents.cpp | 22 ++++++++++- test/lit/passes/gufa-tnh.wast | 73 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 24c72715ed2..efb82b2fb15 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1545,6 +1545,22 @@ void TNHOracle::scan(Function* func, self->inEntryBlock = false; } + // We note params that are written to, as local changes prevent us from + // inferences: + // + // (func $foo (param $x) + // (local.set $x ..) + // (ref.cast (local.get $x)) ;; this is no longer casting the actual + // ;; parameter + // + std::unordered_set writtenParams; + + void visitLocalSet(LocalSet* curr) { + if (getFunction()->isParam(curr->index)) { + writtenParams.insert(curr->index); + } + } + void visitCall(Call* curr) { info.calls.push_back(curr); } void visitCallRef(CallRef* curr) { @@ -1570,13 +1586,15 @@ void TNHOracle::scan(Function* func, auto* fallthrough = Properties::getFallthrough(expr, options, wasm); if (auto* get = fallthrough->dynCast()) { - // To optimize, this needs to be a param, and of a useful type. + // To optimize, this needs to be an unmodified param, and of a useful + // type. // // Note that if we see more than one cast we keep the first one. This is // not important in optimized code, as the most refined cast would be // the only one to exist there, so it's ok to keep things simple here. if (getFunction()->isParam(get->index) && type != get->type && - info.castParams.count(get->index) == 0) { + info.castParams.count(get->index) == 0 && + !writtenParams.count(get->index)) { info.castParams[get->index] = type; } } diff --git a/test/lit/passes/gufa-tnh.wast b/test/lit/passes/gufa-tnh.wast index 31183da3777..1713dac6c56 100644 --- a/test/lit/passes/gufa-tnh.wast +++ b/test/lit/passes/gufa-tnh.wast @@ -2063,3 +2063,76 @@ ) ) ) + +;; Test writes to locals that interfere with inferences about casts. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $top (sub (struct))) + (type $top (sub (struct))) + ;; CHECK: (type $bot (sub $top (struct))) + (type $bot (sub $top (struct))) + ) + + ;; CHECK: (type $2 (func (param (ref $top)))) + + ;; CHECK: (type $3 (func (param (ref extern)))) + + ;; CHECK: (export "$invokeMain" (func $invokeMain)) + + ;; CHECK: (func $main-set (type $2) (param $0 (ref $top)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (struct.new_default $bot) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $bot) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $main-set (param (ref $top)) + ;; We receive a top as input, but write a bot to it, trampling the ignored + ;; parameter. Thanks to the trampling, the cast below will succeed, and so we + ;; should not make anything unreachable in the caller - nothing traps here. + (local.set 0 + (struct.new $bot) + ) + (drop + (ref.cast (ref $bot) + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $main-noset (type $2) (param $0 (ref $top)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $main-noset (param (ref $top)) + ;; As above, but without the local.set. Here we will trap, so the caller can + ;; optimize to unreachable. + (drop + (ref.cast (ref $bot) + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $invokeMain (type $3) (param $0 (ref extern)) + ;; CHECK-NEXT: (call $main-set + ;; CHECK-NEXT: (struct.new_default $top) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $main-noset + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $invokeMain (export "$invokeMain") (param (ref extern)) + (call $main-set + (struct.new $top) + ) + (call $main-noset + (struct.new $top) + ) + ) +) From 83d8edbe8ddf2f2b99dce5c816531c02d2ed8908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 13 Mar 2025 18:57:12 +0000 Subject: [PATCH 352/622] Prepare for type exports (#7335) Change the type of the field `value` in class `Export` to `std::variant`, since a type export references a heap type and not a named component. --- src/binaryen-c.cpp | 34 ++++------------- src/ir/export-utils.cpp | 3 +- src/ir/export-utils.h | 2 +- src/ir/module-splitting.cpp | 18 +++++---- src/ir/module-utils.cpp | 8 ++-- src/ir/possible-contents.cpp | 8 ++-- src/passes/Asyncify.cpp | 6 ++- src/passes/DeadArgumentElimination.cpp | 2 +- src/passes/Directize.cpp | 2 +- src/passes/EncloseWorld.cpp | 5 ++- src/passes/GenerateDynCalls.cpp | 5 +-- src/passes/Inlining.cpp | 2 +- src/passes/JSPI.cpp | 5 ++- src/passes/LegalizeJSInterface.cpp | 15 +++++--- src/passes/Metrics.cpp | 23 +++++++----- src/passes/MultiMemoryLowering.cpp | 4 +- src/passes/OnceReduction.cpp | 2 +- src/passes/PostEmscripten.cpp | 11 ++++-- src/passes/Print.cpp | 3 +- src/passes/PrintCallGraph.cpp | 2 +- src/passes/RemoveUnusedModuleElements.cpp | 16 ++++---- src/passes/ReorderFunctions.cpp | 4 +- src/passes/SafeHeap.cpp | 5 ++- src/passes/SimplifyGlobals.cpp | 2 +- src/passes/opt-utils.h | 2 +- src/shell-interface.h | 4 +- src/tools/execution-results.h | 6 +-- src/tools/fuzzing/fuzzing.cpp | 8 +--- src/tools/spec-wrapper.h | 4 +- src/tools/wasm-ctor-eval.cpp | 16 ++++---- src/tools/wasm-merge.cpp | 16 +++++--- src/tools/wasm-metadce.cpp | 18 +++++---- src/tools/wasm-reduce.cpp | 2 +- src/tools/wasm2c-wrapper.h | 2 +- src/tools/wasm2js.cpp | 8 +++- src/wasm-builder.h | 8 +--- src/wasm-interpreter.h | 16 ++++---- src/wasm.h | 16 ++++++-- src/wasm/wasm-binary.cpp | 45 +++++++++++------------ src/wasm/wasm-emscripten.cpp | 16 +------- src/wasm/wasm-validator.cpp | 10 +++-- src/wasm2js.h | 18 ++++----- 42 files changed, 208 insertions(+), 194 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 5917b8b6051..b32b7d6ff22 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4908,50 +4908,35 @@ WASM_DEPRECATED BinaryenExportRef BinaryenAddExport(BinaryenModuleRef module, BinaryenExportRef BinaryenAddFunctionExport(BinaryenModuleRef module, const char* internalName, const char* externalName) { - auto* ret = new Export(); - ret->value = internalName; - ret->name = externalName; - ret->kind = ExternalKind::Function; + auto* ret = new Export(externalName, ExternalKind::Function, internalName); ((Module*)module)->addExport(ret); return ret; } BinaryenExportRef BinaryenAddTableExport(BinaryenModuleRef module, const char* internalName, const char* externalName) { - auto* ret = new Export(); - ret->value = internalName; - ret->name = externalName; - ret->kind = ExternalKind::Table; + auto* ret = new Export(externalName, ExternalKind::Table, internalName); ((Module*)module)->addExport(ret); return ret; } BinaryenExportRef BinaryenAddMemoryExport(BinaryenModuleRef module, const char* internalName, const char* externalName) { - auto* ret = new Export(); - ret->value = internalName; - ret->name = externalName; - ret->kind = ExternalKind::Memory; + auto* ret = new Export(externalName, ExternalKind::Memory, internalName); ((Module*)module)->addExport(ret); return ret; } BinaryenExportRef BinaryenAddGlobalExport(BinaryenModuleRef module, const char* internalName, const char* externalName) { - auto* ret = new Export(); - ret->value = internalName; - ret->name = externalName; - ret->kind = ExternalKind::Global; + auto* ret = new Export(externalName, ExternalKind::Global, internalName); ((Module*)module)->addExport(ret); return ret; } BinaryenExportRef BinaryenAddTagExport(BinaryenModuleRef module, const char* internalName, const char* externalName) { - auto* ret = new Export(); - ret->value = internalName; - ret->name = externalName; - ret->kind = ExternalKind::Tag; + auto* ret = new Export(externalName, ExternalKind::Tag, internalName); ((Module*)module)->addExport(ret); return ret; } @@ -5102,11 +5087,8 @@ void BinaryenSetMemory(BinaryenModuleRef module, memory->shared = shared; memory->addressType = memory64 ? Type::i64 : Type::i32; if (exportName) { - auto memoryExport = std::make_unique(); - memoryExport->name = exportName; - memoryExport->value = memory->name; - memoryExport->kind = ExternalKind::Memory; - ((Module*)module)->addExport(memoryExport.release()); + ((Module*)module) + ->addExport(new Export(exportName, ExternalKind::Memory, memory->name)); } ((Module*)module)->removeDataSegments([&](DataSegment* curr) { return true; @@ -5940,7 +5922,7 @@ const char* BinaryenExportGetName(BinaryenExportRef export_) { return ((Export*)export_)->name.str.data(); } const char* BinaryenExportGetValue(BinaryenExportRef export_) { - return ((Export*)export_)->value.str.data(); + return ((Export*)export_)->getInternalName()->str.data(); } // diff --git a/src/ir/export-utils.cpp b/src/ir/export-utils.cpp index 0b19b720429..8ee5b4a8eb0 100644 --- a/src/ir/export-utils.cpp +++ b/src/ir/export-utils.cpp @@ -28,7 +28,8 @@ std::vector getExportedByKind(Module& wasm, G getModuleItem) { std::vector ret; for (auto& ex : wasm.exports) { if (ex->kind == K) { - ret.push_back(getModuleItem(ex->value)); + // The kind is either ExternalKind::Function or ExternalKind::Global + ret.push_back(getModuleItem(*ex->getInternalName())); } } return ret; diff --git a/src/ir/export-utils.h b/src/ir/export-utils.h index ce6db7663f2..5b869ee066f 100644 --- a/src/ir/export-utils.h +++ b/src/ir/export-utils.h @@ -27,7 +27,7 @@ std::vector getExportedGlobals(Module& wasm); inline bool isExported(const Module& module, const Function& func) { for (auto& exportFunc : module.exports) { if (exportFunc->kind == ExternalKind::Function && - exportFunc->value == func.name) { + *exportFunc->getInternalName() == func.name) { return true; } } diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index bcf0b344201..485afa110a4 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -355,9 +355,9 @@ void ModuleSplitter::setupJSPI() { // Support the first version of JSPI, where the JSPI pass added the load // secondary module export. // TODO: remove this when the new JSPI API is only supported. - if (primary.getExportOrNull(LOAD_SECONDARY_MODULE)) { - internalLoadSecondaryModule = - primary.getExport(LOAD_SECONDARY_MODULE)->value; + if (auto* loadSecondary = primary.getExportOrNull(LOAD_SECONDARY_MODULE); + loadSecondary && loadSecondary->kind == ExternalKind::Function) { + internalLoadSecondaryModule = *loadSecondary->getInternalName(); // Remove the exported LOAD_SECONDARY_MODULE function since it's only needed // internally. primary.removeExport(LOAD_SECONDARY_MODULE); @@ -471,7 +471,7 @@ ModuleSplitter::initExportedPrimaryFuncs(const Module& primary) { std::map functionExportNames; for (auto& ex : primary.exports) { if (ex->kind == ExternalKind::Function) { - functionExportNames[ex->value] = ex->name; + functionExportNames[*ex->getInternalName()] = ex->name; } } return functionExportNames; @@ -527,10 +527,10 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() { Builder builder(primary); for (auto& ex : primary.exports) { if (ex->kind != ExternalKind::Function || - !secondaryFuncs.count(ex->value)) { + !secondaryFuncs.count(*ex->getInternalName())) { continue; } - Name secondaryFunc = ex->value; + Name secondaryFunc = *ex->getInternalName(); if (primary.getFunctionOrNull(secondaryFunc)) { // We've already created a thunk for this function continue; @@ -823,7 +823,9 @@ void ModuleSplitter::shareImportableItems() { std::unordered_map, Name> exports; for (auto& ex : primary.exports) { if (ex->kind != ExternalKind::Function) { - exports[std::make_pair(ex->kind, ex->value)] = ex->name; + if (auto* name = ex->getInternalName()) { + exports[std::make_pair(ex->kind, *name)] = ex->name; + } } } @@ -843,7 +845,7 @@ void ModuleSplitter::shareImportableItems() { ? minified.getName() : genericExportName); Name exportName = Names::getValidExportName(primary, baseName); - primary.addExport(new Export{exportName, primaryItem.name, kind}); + primary.addExport(new Export(exportName, kind, primaryItem.name)); secondaryItem.base = exportName; } }; diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 328aee64d22..7f2dfcc089c 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -632,12 +632,12 @@ void classifyTypeVisibility(Module& wasm, for (auto& ex : wasm.exports) { switch (ex->kind) { case ExternalKind::Function: { - auto* func = wasm.getFunction(ex->value); + auto* func = wasm.getFunction(*ex->getInternalName()); notePublic(func->type); continue; } case ExternalKind::Table: { - auto* table = wasm.getTable(ex->value); + auto* table = wasm.getTable(*ex->getInternalName()); assert(table->type.isRef()); notePublic(table->type.getHeapType()); continue; @@ -646,14 +646,14 @@ void classifyTypeVisibility(Module& wasm, // Never a reference type. continue; case ExternalKind::Global: { - auto* global = wasm.getGlobal(ex->value); + auto* global = wasm.getGlobal(*ex->getInternalName()); if (global->type.isRef()) { notePublic(global->type.getHeapType()); } continue; } case ExternalKind::Tag: - notePublic(wasm.getTag(ex->value)->type); + notePublic(wasm.getTag(*ex->getInternalName())->type); continue; case ExternalKind::Invalid: break; diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index efb82b2fb15..c422dc3d5be 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2183,7 +2183,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) for (auto& ex : wasm.exports) { if (ex->kind == ExternalKind::Table) { - shared.publicTables.insert(ex->value); + shared.publicTables.insert(*ex->getInternalName()); } } @@ -2295,7 +2295,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) // Exports can be modified from the outside. for (auto& ex : wasm.exports) { if (ex->kind == ExternalKind::Function) { - calledFromOutside(ex->value); + calledFromOutside(*ex->getInternalName()); } else if (ex->kind == ExternalKind::Table) { // If any table is exported, assume any function in any table (including // other tables) can be called from the outside. @@ -2319,7 +2319,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) } else if (ex->kind == ExternalKind::Global) { // Exported mutable globals are roots, since the outside may write any // value to them. - auto name = ex->value; + auto name = *ex->getInternalName(); auto* global = wasm.getGlobal(name); if (global->mutable_) { roots[GlobalLocation{name}] = PossibleContents::fromType(global->type); @@ -2337,7 +2337,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) } for (auto& ex : wasm.exports) { if (ex->kind == ExternalKind::Tag) { - publicTags.insert(ex->value); + publicTags.insert(*ex->getInternalName()); } } for (auto tag : publicTags) { diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index 994142e482f..3ea9ca6b40c 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -1668,7 +1668,7 @@ struct Asyncify : public Pass { for (auto& theExport : module->exports) { if (theExport->kind == ExternalKind::Memory && theExport->name == asyncifyMemoryValue) { - asyncifyMemory = theExport->value; + asyncifyMemory = *theExport->getInternalName(); break; } } @@ -1901,7 +1901,9 @@ struct ModAsyncify void doWalkFunction(Function* func) { // Find the asyncify state name. auto* unwind = this->getModule()->getExport(ASYNCIFY_STOP_UNWIND); - auto* unwindFunc = this->getModule()->getFunction(unwind->value); + auto* unwindFunc = this->getModule()->getFunction( + ((unwind->kind == ExternalKind::Function)) ? *unwind->getInternalName() + : Name()); FindAll sets(unwindFunc->body); assert(sets.list.size() == 1); asyncifyStateName = sets.list[0]->name; diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index 8d801e87e9b..93e6ba44b26 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -260,7 +260,7 @@ struct DAE : public Pass { // Exports are considered unseen calls. for (auto& curr : module->exports) { if (curr->kind == ExternalKind::Function) { - hasUnseenCalls.insert(curr->value); + hasUnseenCalls.insert(*curr->getInternalName()); } } diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp index 93b3d04e747..4b0cb6ae78b 100644 --- a/src/passes/Directize.cpp +++ b/src/passes/Directize.cpp @@ -223,7 +223,7 @@ struct Directize : public Pass { for (auto& ex : module->exports) { if (ex->kind == ExternalKind::Table) { - tables[ex->value].mayBeModified = true; + tables[*ex->getInternalName()].mayBeModified = true; } } diff --git a/src/passes/EncloseWorld.cpp b/src/passes/EncloseWorld.cpp index 5c6b70546a8..34ceb51bfc2 100644 --- a/src/passes/EncloseWorld.cpp +++ b/src/passes/EncloseWorld.cpp @@ -52,11 +52,12 @@ struct EncloseWorld : public Pass { std::vector> newExports; for (auto& ex : module->exports) { if (ex->kind == ExternalKind::Function) { - auto* func = module->getFunction(ex->value); + auto* name = ex->getInternalName(); + auto* func = module->getFunction(*name); // If this opens up types, replace it with an enclosed stub. if (opensTypes(func)) { auto stubName = makeStubStubForExport(func, module); - ex->value = stubName; + *name = stubName; } } } diff --git a/src/passes/GenerateDynCalls.cpp b/src/passes/GenerateDynCalls.cpp index 8d2f3fa24eb..c13656d4837 100644 --- a/src/passes/GenerateDynCalls.cpp +++ b/src/passes/GenerateDynCalls.cpp @@ -110,10 +110,7 @@ static void exportFunction(Module& wasm, Name name, bool must_export) { if (wasm.getExportOrNull(name)) { return; // Already exported } - auto exp = new Export; - exp->name = exp->value = name; - exp->kind = ExternalKind::Function; - wasm.addExport(exp); + wasm.addExport(new Export(name, ExternalKind::Function, name)); } void GenerateDynCalls::generateDynCallThunk(HeapType funcType) { diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 998e355c91f..876c186d471 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -1271,7 +1271,7 @@ struct Inlining : public Pass { } for (auto& ex : module->exports) { if (ex->kind == ExternalKind::Function) { - infos[ex->value].usedGlobally = true; + infos[*ex->getInternalName()].usedGlobally = true; } } if (module->start.is()) { diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp index d5fed1faf63..2ff8aa6ca47 100644 --- a/src/passes/JSPI.cpp +++ b/src/passes/JSPI.cpp @@ -126,7 +126,8 @@ struct JSPI : public Pass { for (auto& ex : module->exports) { if (ex->kind == ExternalKind::Function && canChangeState(ex->name.toString(), listedExports)) { - auto* func = module->getFunction(ex->value); + auto* name = ex->getInternalName(); + auto* func = module->getFunction(*name); Name wrapperName; auto iter = wrappedExports.find(func->name); if (iter == wrappedExports.end()) { @@ -135,7 +136,7 @@ struct JSPI : public Pass { } else { wrapperName = iter->second; } - ex->value = wrapperName; + *name = wrapperName; } } diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 453b649c0bc..427b8dfac0e 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -71,11 +71,12 @@ struct LegalizeJSInterface : public Pass { for (auto& ex : module->exports) { if (ex->kind == ExternalKind::Function) { // if it's an import, ignore it - auto* func = module->getFunction(ex->value); + auto* name = ex->getInternalName(); + auto* func = module->getFunction(*name); if (isIllegal(func)) { // Provide a legal function for the export. auto legalName = makeLegalStub(func, module); - ex->value = legalName; + *name = legalName; if (exportOriginals) { // Also export the original function, before legalization. This is // not normally useful for JS, except in cases like dynamic linking @@ -193,7 +194,9 @@ struct LegalizeJSInterface : public Pass { if (!setTempRet0) { if (exportedHelpers) { auto* ex = module->getExport(SET_TEMP_RET_EXPORT); - setTempRet0 = module->getFunction(ex->value); + setTempRet0 = module->getFunction((ex->kind == ExternalKind::Function) + ? *ex->getInternalName() + : Name()); } else { setTempRet0 = getFunctionOrImport( module, SET_TEMP_RET_IMPORT, Type::i32, Type::none); @@ -206,7 +209,9 @@ struct LegalizeJSInterface : public Pass { if (!getTempRet0) { if (exportedHelpers) { auto* ex = module->getExport(GET_TEMP_RET_EXPORT); - getTempRet0 = module->getFunction(ex->value); + getTempRet0 = module->getFunction((ex->kind == ExternalKind::Function) + ? *ex->getInternalName() + : Name()); } else { getTempRet0 = getFunctionOrImport( module, GET_TEMP_RET_IMPORT, Type::none, Type::i32); @@ -361,7 +366,7 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { std::unordered_map exportedFunctions; for (auto& exp : module->exports) { if (exp->kind == ExternalKind::Function) { - exportedFunctions[exp->value] = exp->name; + exportedFunctions[*exp->getInternalName()] = exp->name; } } diff --git a/src/passes/Metrics.cpp b/src/passes/Metrics.cpp index 186d7e6956f..92c44b6871a 100644 --- a/src/passes/Metrics.cpp +++ b/src/passes/Metrics.cpp @@ -133,16 +133,19 @@ struct Metrics baseline = sizeAfterGlobalCleanup(&test); } for (auto& exp : module->exports) { - // create a test module where we remove the export and then see how much - // can be removed thanks to that - Module test; - ModuleUtils::copyModule(*module, test); - test.removeExport(exp->name); - counts.clear(); - counts["[removable-bytes-without-it]"] = - baseline - sizeAfterGlobalCleanup(&test); - printCounts(std::string("export: ") + exp->name.toString() + " (" + - exp->value.toString() + ')'); + // Removing a type export will not remove any code + if (auto* name = exp->getInternalName()) { + // create a test module where we remove the export and then see how + // much can be removed thanks to that + Module test; + ModuleUtils::copyModule(*module, test); + test.removeExport(exp->name); + counts.clear(); + counts["[removable-bytes-without-it]"] = + baseline - sizeAfterGlobalCleanup(&test); + printCounts(std::string("export: ") + exp->name.toString() + " (" + + name->toString() + ')'); + } } // check how much size depends on the start method if (!module->start.isNull()) { diff --git a/src/passes/MultiMemoryLowering.cpp b/src/passes/MultiMemoryLowering.cpp index c22477b79c5..9397f6cad44 100644 --- a/src/passes/MultiMemoryLowering.cpp +++ b/src/passes/MultiMemoryLowering.cpp @@ -473,7 +473,7 @@ struct MultiMemoryLowering : public Pass { // Ensuring only the first memory is an exported memory for (auto& exp : wasm->exports) { if (exp->kind == ExternalKind::Memory && - exp->value == getFirstMemory().name) { + *exp->getInternalName() == getFirstMemory().name) { isExported = true; } else if (exp->kind == ExternalKind::Memory) { Fatal() << "MultiMemoryLowering: only the first memory can be exported"; @@ -706,7 +706,7 @@ struct MultiMemoryLowering : public Pass { // We checked in prepCombinedMemory that any memory exports are for // the first memory, so setting the exports to the combinedMemory means // calling JS will not have to worry about offsets - exp->value = combinedMemory; + *exp->getInternalName() = combinedMemory; } } } diff --git a/src/passes/OnceReduction.cpp b/src/passes/OnceReduction.cpp index 4542a01dfb0..c760dbaed9d 100644 --- a/src/passes/OnceReduction.cpp +++ b/src/passes/OnceReduction.cpp @@ -372,7 +372,7 @@ struct OnceReduction : public Pass { // An exported global cannot be "once" since the outside may read and // write to it in ways we are unaware. // TODO: See comment above on mutability. - optInfo.onceGlobals[ex->value] = false; + optInfo.onceGlobals[*ex->getInternalName()] = false; } } diff --git a/src/passes/PostEmscripten.cpp b/src/passes/PostEmscripten.cpp index 20f26e211b6..72630663591 100644 --- a/src/passes/PostEmscripten.cpp +++ b/src/passes/PostEmscripten.cpp @@ -134,7 +134,9 @@ static void removeSegment(Module& wasm, Name segment) { } static Address getExportedAddress(Module& wasm, Export* export_) { - Global* g = wasm.getGlobal(export_->value); + Global* g = wasm.getGlobal((export_->kind == ExternalKind::Global) + ? *export_->getInternalName() + : Name()); auto* addrConst = g->init->dynCast(); return addrConst->value.getUnsigned(); } @@ -238,11 +240,12 @@ struct PostEmscripten : public Pass { auto sideModule = hasArgument("post-emscripten-side-module"); EmJsWalker walker(sideModule); walker.walkModule(&module); - for (const Export& exp : walker.toRemove) { + for (Export& exp : walker.toRemove) { if (exp.kind == ExternalKind::Function) { - module.removeFunction(exp.value); + module.removeFunction(*exp.getInternalName()); } else { - module.removeGlobal(exp.value); + assert(exp.kind == ExternalKind::Global); + module.removeGlobal(*exp.getInternalName()); } module.removeExport(exp.name); } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 71878c973b3..90eecef2eef 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3087,7 +3087,8 @@ void PrintSExpression::visitExport(Export* curr) { WASM_UNREACHABLE("invalid ExternalKind"); } o << ' '; - curr->value.print(o) << "))"; + // TODO: specific case for type exports + curr->getInternalName()->print(o) << "))"; } void PrintSExpression::emitImportHeader(Importable* curr) { diff --git a/src/passes/PrintCallGraph.cpp b/src/passes/PrintCallGraph.cpp index 6022c8703d7..6a4b1d50048 100644 --- a/src/passes/PrintCallGraph.cpp +++ b/src/passes/PrintCallGraph.cpp @@ -65,7 +65,7 @@ struct PrintCallGraph : public Pass { // Exports for (auto& curr : module->exports) { if (curr->kind == ExternalKind::Function) { - Function* func = module->getFunction(curr->value); + Function* func = module->getFunction(*curr->getInternalName()); o << " \"" << func->name << "\" [style=\"filled\", fillcolor=\"gray\"];\n"; } diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index b3874b8dce3..4677ac3b1b9 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -621,15 +621,16 @@ struct RemoveUnusedModuleElements : public Pass { // Exports are roots. for (auto& curr : module->exports) { if (curr->kind == ExternalKind::Function) { - roots.emplace_back(ModuleElementKind::Function, curr->value); + roots.emplace_back(ModuleElementKind::Function, + *curr->getInternalName()); } else if (curr->kind == ExternalKind::Global) { - roots.emplace_back(ModuleElementKind::Global, curr->value); + roots.emplace_back(ModuleElementKind::Global, *curr->getInternalName()); } else if (curr->kind == ExternalKind::Tag) { - roots.emplace_back(ModuleElementKind::Tag, curr->value); + roots.emplace_back(ModuleElementKind::Tag, *curr->getInternalName()); } else if (curr->kind == ExternalKind::Table) { - roots.emplace_back(ModuleElementKind::Table, curr->value); + roots.emplace_back(ModuleElementKind::Table, *curr->getInternalName()); } else if (curr->kind == ExternalKind::Memory) { - roots.emplace_back(ModuleElementKind::Memory, curr->value); + roots.emplace_back(ModuleElementKind::Memory, *curr->getInternalName()); } } @@ -785,7 +786,8 @@ struct RemoveUnusedModuleElements : public Pass { continue; } - auto* func = module->getFunction(exp->value); + auto* name = exp->getInternalName(); + auto* func = module->getFunction(*name); if (!func->body) { continue; } @@ -812,7 +814,7 @@ struct RemoveUnusedModuleElements : public Pass { } } if (ok) { - exp->value = calledFunc->name; + *name = calledFunc->name; } } } diff --git a/src/passes/ReorderFunctions.cpp b/src/passes/ReorderFunctions.cpp index cf3c8bb1f0f..aa8cbf4582e 100644 --- a/src/passes/ReorderFunctions.cpp +++ b/src/passes/ReorderFunctions.cpp @@ -76,7 +76,9 @@ struct ReorderFunctions : public Pass { counts[module->start]++; } for (auto& curr : module->exports) { - counts[curr->value]++; + if (curr->kind == ExternalKind::Function) { + counts[*curr->getInternalName()]++; + } } ElementUtils::iterAllElementFunctionNames( module, [&](Name& name) { counts[name]++; }); diff --git a/src/passes/SafeHeap.cpp b/src/passes/SafeHeap.cpp index 195900696e3..52332149050 100644 --- a/src/passes/SafeHeap.cpp +++ b/src/passes/SafeHeap.cpp @@ -159,8 +159,9 @@ struct SafeHeap : public Pass { auto addressType = module->memories[0]->addressType; if (auto* existing = info.getImportedFunction(ENV, GET_SBRK_PTR)) { getSbrkPtr = existing->name; - } else if (auto* existing = module->getExportOrNull(GET_SBRK_PTR)) { - getSbrkPtr = existing->value; + } else if (auto* existing = module->getExportOrNull(GET_SBRK_PTR); + existing && existing->kind == ExternalKind::Function) { + getSbrkPtr = *existing->getInternalName(); } else if (auto* existing = info.getImportedFunction(ENV, SBRK)) { sbrk = existing->name; } else { diff --git a/src/passes/SimplifyGlobals.cpp b/src/passes/SimplifyGlobals.cpp index ccfee5e6530..294e70fd7a4 100644 --- a/src/passes/SimplifyGlobals.cpp +++ b/src/passes/SimplifyGlobals.cpp @@ -515,7 +515,7 @@ struct SimplifyGlobals : public Pass { } for (auto& ex : module->exports) { if (ex->kind == ExternalKind::Global) { - map[ex->value].exported = true; + map[*ex->getInternalName()].exported = true; } } diff --git a/src/passes/opt-utils.h b/src/passes/opt-utils.h index 69f055fd37e..2e25a6aa489 100644 --- a/src/passes/opt-utils.h +++ b/src/passes/opt-utils.h @@ -104,7 +104,7 @@ inline void replaceFunctions(PassRunner* runner, // replace in exports for (auto& exp : module.exports) { if (exp->kind == ExternalKind::Function) { - maybeReplace(exp->value); + maybeReplace(*exp->getInternalName()); } } } diff --git a/src/shell-interface.h b/src/shell-interface.h index 35f5772cf6e..3a8b6d23314 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -125,11 +125,11 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) { auto inst = getImportInstance(import); auto* exportedGlobal = inst->wasm.getExportOrNull(import->base); - if (!exportedGlobal) { + if (!exportedGlobal || exportedGlobal->kind != ExternalKind::Global) { Fatal() << "importGlobals: unknown import: " << import->module.str << "." << import->name.str; } - globals[import->name] = inst->globals[exportedGlobal->value]; + globals[import->name] = inst->globals[*exportedGlobal->getInternalName()]; }); } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 508ea816312..f0b31cdf7db 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -59,7 +59,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { : loggings(loggings), wasm(wasm) { for (auto& exp : wasm.exports) { if (exp->kind == ExternalKind::Table && exp->name == "table") { - exportedTable = exp->value; + exportedTable = *exp->getInternalName(); break; } } @@ -205,7 +205,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { // No callable export. throwJSException(); } - return callFunctionAsJS(exp->value); + return callFunctionAsJS(*exp->getInternalName()); } Literals callRefAsJS(Literal ref) { @@ -279,7 +279,7 @@ struct ExecutionResults { continue; } std::cout << "[fuzz-exec] calling " << exp->name << "\n"; - auto* func = wasm.getFunction(exp->value); + auto* func = wasm.getFunction(*exp->getInternalName()); FunctionResult ret = run(func, wasm, instance); results[exp->name] = ret; if (auto* values = std::get_if(&ret)) { diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 611830be312..bc3bc9fc938 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1412,7 +1412,7 @@ void TranslateToFuzzReader::processFunctions() { for (Index i = 0; i < numInitialExports; i++) { auto& exp = wasm.exports[i]; if (exp->kind == ExternalKind::Function && upTo(RESOLUTION) < chance) { - auto* func = wasm.getFunction(exp->value); + auto* func = wasm.getFunction(*exp->getInternalName()); if (!func->imported()) { auto* call = builder.makeCall(pick(noParamsOrResultFuncs), {}, Type::none); @@ -1483,11 +1483,7 @@ Function* TranslateToFuzzReader::addFunction() { }); if (validExportParams && (numAddedFunctions == 0 || oneIn(2)) && !wasm.getExportOrNull(func->name) && !preserveImportsAndExports) { - auto* export_ = new Export; - export_->name = func->name; - export_->value = func->name; - export_->kind = ExternalKind::Function; - wasm.addExport(export_); + wasm.addExport(new Export(func->name, ExternalKind::Function, func->name)); } // add some to an elem segment while (oneIn(3) && !random.finished()) { diff --git a/src/tools/spec-wrapper.h b/src/tools/spec-wrapper.h index 5c0b8cfc8a8..e9cb123c978 100644 --- a/src/tools/spec-wrapper.h +++ b/src/tools/spec-wrapper.h @@ -27,10 +27,10 @@ namespace wasm { inline std::string generateSpecWrapper(Module& wasm) { std::string ret; for (auto& exp : wasm.exports) { - auto* func = wasm.getFunctionOrNull(exp->value); - if (!func) { + if (exp->kind != ExternalKind::Function) { continue; // something exported other than a function } + auto* func = wasm.getFunctionOrNull(*exp->getInternalName()); ret += std::string("(invoke \"") + exp->name.toString() + "\" "; for (const auto& param : func->getParams()) { // zeros in arguments TODO more? diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index a940d928412..b7e1d438a8c 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -210,12 +210,12 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { if (it != linkedInstances.end()) { auto* inst = it->second.get(); auto* globalExport = inst->wasm.getExportOrNull(global->base); - if (!globalExport) { + if (!globalExport || globalExport->kind != ExternalKind::Global) { throw FailToEvalException(std::string("importGlobals: ") + global->module.toString() + "." + global->base.toString()); } - globals[global->name] = inst->globals[globalExport->value]; + globals[global->name] = inst->globals[*globalExport->getInternalName()]; } else { throw FailToEvalException(std::string("importGlobals: ") + global->module.toString() + "." + @@ -1182,7 +1182,7 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, (localExprs.size() && func->getParams() != Type::none))) { auto originalFuncType = wasm.getFunction(funcName)->type; auto copyName = Names::getValidFunctionName(wasm, funcName); - wasm.getExport(exportName)->value = copyName; + *wasm.getExport(exportName)->getInternalName() = copyName; if (func->imported()) { // We must have return-called this imported function. Generate a new @@ -1295,10 +1295,10 @@ void evalCtors(Module& wasm, std::cout << "trying to eval " << ctor << '\n'; } Export* ex = wasm.getExportOrNull(ctor); - if (!ex) { + if (!ex || ex->kind != ExternalKind::Function) { Fatal() << "export not found: " << ctor; } - auto funcName = ex->value; + auto funcName = *ex->getInternalName(); auto outcome = evalCtor(instance, interface, funcName, ctor); if (!outcome) { if (!quiet) { @@ -1319,7 +1319,9 @@ void evalCtors(Module& wasm, } else { // We are keeping around the export, which should now refer to an // empty function since calling the export should do nothing. - auto* func = wasm.getFunction(exp->value); + auto* func = wasm.getFunction((exp->kind == ExternalKind::Function) + ? *exp->getInternalName() + : Name()); auto copyName = Names::getValidFunctionName(wasm, func->name); auto* copyFunc = ModuleUtils::copyFunction(func, wasm, copyName); if (func->getResults() == Type::none) { @@ -1327,7 +1329,7 @@ void evalCtors(Module& wasm, } else { copyFunc->body = interface.getSerialization(*outcome); } - wasm.getExport(exp->name)->value = copyName; + *wasm.getExport(exp->name)->getInternalName() = copyName; } } } catch (FailToEvalException& fail) { diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index 166c875aecd..cc35b072b49 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -220,7 +220,10 @@ void updateNames(Module& wasm, KindNameUpdates& kindNameUpdates) { // the module scope. void mapModuleFields(Module& wasm) { for (auto& curr : wasm.exports) { - mapName(ModuleItemKind(curr->kind), curr->value); + // skip type exports + if (auto* name = curr->getInternalName()) { + mapName(ModuleItemKind(curr->kind), *name); + } } for (auto& curr : wasm.elementSegments) { mapName(ModuleItemKind::Table, curr->table); @@ -440,10 +443,13 @@ void fuseImportsAndExports() { KindModuleExportMaps kindModuleExportMaps; for (auto& ex : merged.exports) { - assert(exportModuleMap.count(ex.get())); - ExportInfo& exportInfo = exportModuleMap[ex.get()]; - kindModuleExportMaps[ex->kind][exportInfo.moduleName][exportInfo.baseName] = - ex->value; + // skip type exports + if (auto* name = ex->getInternalName()) { + assert(exportModuleMap.count(ex.get())); + ExportInfo& exportInfo = exportModuleMap[ex.get()]; + kindModuleExportMaps[ex->kind][exportInfo.moduleName] + [exportInfo.baseName] = *name; + } } // Find all the imports and see which have corresponding exports, which means diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index 41dcf6ad4c5..b79507f8019 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -123,14 +123,18 @@ struct MetaDCEGraph { nodes[dceName] = DCENode(dceName); }); for (auto& exp : wasm.exports) { - if (exportToDCENode.find(exp->name) == exportToDCENode.end()) { - auto dceName = getName("export", exp->name.toString()); - exportToDCENode[exp->name] = dceName; - nodes[dceName] = DCENode(dceName); + // skip type exports + // TODO: shall we keep track of type dependencies? + if (auto* name = exp->getInternalName()) { + if (exportToDCENode.find(exp->name) == exportToDCENode.end()) { + auto dceName = getName("export", exp->name.toString()); + exportToDCENode[exp->name] = dceName; + nodes[dceName] = DCENode(dceName); + } + // we can also link the export to the thing being exported + auto& node = nodes[exportToDCENode[exp->name]]; + node.reaches.push_back(getDCEName(ModuleItemKind(exp->kind), *name)); } - // we can also link the export to the thing being exported - auto& node = nodes[exportToDCENode[exp->name]]; - node.reaches.push_back(getDCEName(ModuleItemKind(exp->kind), exp->value)); } // Add initializer dependencies // if we provide a parent DCE name, that is who can reach what we see diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 6b89103a3ac..6aac43653a5 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -1121,7 +1121,7 @@ struct Reducer } } void visitExport(Export* curr) { - if (names.count(curr->value)) { + if (auto* name = curr->getInternalName(); name && names.count(*name)) { exportsToRemove.push_back(curr->name); } } diff --git a/src/tools/wasm2c-wrapper.h b/src/tools/wasm2c-wrapper.h index 53343f2d16c..38c924b47aa 100644 --- a/src/tools/wasm2c-wrapper.h +++ b/src/tools/wasm2c-wrapper.h @@ -157,7 +157,7 @@ int main(int argc, char** argv) { ret += " case " + std::to_string(functionExportIndex++) + ":\n"; - auto* func = wasm.getFunction(exp->value); + auto* func = wasm.getFunction(*exp->getInternalName()); ret += std::string(" puts(\"[fuzz-exec] calling ") + exp->name.toString() + "\");\n"; diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp index 2c48e5be064..e0f61a21766 100644 --- a/src/tools/wasm2js.cpp +++ b/src/tools/wasm2js.cpp @@ -591,8 +591,12 @@ Expression* AssertionEmitter::translateInvoke(InvokeAction& invoke, for (auto& arg : invoke.args) { args.push_back(builder.makeConstantExpression(arg)); } - Type type = - wasm.getFunction(wasm.getExport(invoke.name)->value)->getResults(); + Export* exp = wasm.getExport(invoke.name); + Type type = wasm + .getFunction((exp->kind == ExternalKind::Function) + ? *exp->getInternalName() + : Name()) + ->getResults(); return builder.makeCall(invoke.name, args, type); } diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 0e28f3d5a66..8344355b64a 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -142,12 +142,8 @@ class Builder { } static std::unique_ptr - makeExport(Name name, Name value, ExternalKind kind) { - auto export_ = std::make_unique(); - export_->name = name; - export_->value = value; - export_->kind = kind; - return export_; + makeExport(Name name, std::variant value, ExternalKind kind) { + return std::make_unique(name, kind, value); } enum Mutability { Mutable, Immutable }; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2a019513d2a..0b55dd00882 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2910,10 +2910,10 @@ class ModuleRunnerBase : public ExpressionRunner { // call an exported function Literals callExport(Name name, const Literals& arguments) { Export* export_ = wasm.getExportOrNull(name); - if (!export_) { + if (!export_ || export_->kind != ExternalKind::Function) { externalInterface->trap("callExport not found"); } - return callFunction(export_->value, arguments); + return callFunction(*export_->getInternalName(), arguments); } Literals callExport(Name name) { return callExport(name, Literals()); } @@ -2921,10 +2921,10 @@ class ModuleRunnerBase : public ExpressionRunner { // get an exported global Literals getExport(Name name) { Export* export_ = wasm.getExportOrNull(name); - if (!export_) { + if (!export_ || export_->kind != ExternalKind::Global) { externalInterface->trap("getExport external not found"); } - Name internalName = export_->value; + Name internalName = *export_->getInternalName(); auto iter = globals.find(internalName); if (iter == globals.end()) { externalInterface->trap("getExport internal not found"); @@ -2966,7 +2966,8 @@ class ModuleRunnerBase : public ExpressionRunner { if (table->imported()) { auto& importedInstance = linkedInstances.at(table->module); auto* tableExport = importedInstance->wasm.getExport(table->base); - return importedInstance->getTableInstanceInfo(tableExport->value); + return importedInstance->getTableInstanceInfo( + *tableExport->getInternalName()); } return TableInstanceInfo{self(), name}; @@ -3021,7 +3022,8 @@ class ModuleRunnerBase : public ExpressionRunner { if (memory->imported()) { auto& importedInstance = linkedInstances.at(memory->module); auto* memoryExport = importedInstance->wasm.getExport(memory->base); - return importedInstance->getMemoryInstanceInfo(memoryExport->value); + return importedInstance->getMemoryInstanceInfo( + *memoryExport->getInternalName()); } return MemoryInstanceInfo{self(), name}; @@ -3161,7 +3163,7 @@ class ModuleRunnerBase : public ExpressionRunner { while (global->imported()) { inst = inst->linkedInstances.at(global->module).get(); Export* globalExport = inst->wasm.getExport(global->base); - global = inst->wasm.getGlobal(globalExport->value); + global = inst->wasm.getGlobal(*globalExport->getInternalName()); } return inst->globals[global->name]; diff --git a/src/wasm.h b/src/wasm.h index 7458a3d14e3..e3f53379a9d 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2255,11 +2255,21 @@ enum class ModuleItemKind { class Export { public: - // exported name - note that this is the key, as the internal name is - // non-unique (can have multiple exports for an internal, also over kinds) + // exported name - note that this is the key, as the internal name, + // or the exported type, is non-unique (can have multiple exports for an + // internal, also over kinds) Name name; - Name value; // internal name ExternalKind kind; + +private: + std::variant value; // internal name or exported type + +public: + Export(Name name, ExternalKind kind, std::variant value) + : name(name), kind(kind), value(value) { + assert(std::get_if(&value)); + } + Name* getInternalName() { return std::get_if(&value); } }; class ElementSegment : public Named { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index efacec1a1ac..de6a5867b93 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -603,19 +603,19 @@ void WasmBinaryWriter::writeExports() { o << U32LEB(int32_t(curr->kind)); switch (curr->kind) { case ExternalKind::Function: - o << U32LEB(getFunctionIndex(curr->value)); + o << U32LEB(getFunctionIndex(*curr->getInternalName())); break; case ExternalKind::Table: - o << U32LEB(getTableIndex(curr->value)); + o << U32LEB(getTableIndex(*curr->getInternalName())); break; case ExternalKind::Memory: - o << U32LEB(getMemoryIndex(curr->value)); + o << U32LEB(getMemoryIndex(*curr->getInternalName())); break; case ExternalKind::Global: - o << U32LEB(getGlobalIndex(curr->value)); + o << U32LEB(getGlobalIndex(*curr->getInternalName())); break; case ExternalKind::Tag: - o << U32LEB(getTagIndex(curr->value)); + o << U32LEB(getTagIndex(*curr->getInternalName())); break; default: WASM_UNREACHABLE("unexpected extern kind"); @@ -4387,34 +4387,33 @@ void WasmBinaryReader::readExports() { size_t num = getU32LEB(); std::unordered_set names; for (size_t i = 0; i < num; i++) { - auto curr = std::make_unique(); - curr->name = getInlineString(); - if (!names.emplace(curr->name).second) { + Name name = getInlineString(); + if (!names.emplace(name).second) { throwError("duplicate export name"); } - curr->kind = (ExternalKind)getU32LEB(); - auto* ex = wasm.addExport(std::move(curr)); + ExternalKind kind = (ExternalKind)getU32LEB(); + std::variant value; auto index = getU32LEB(); - switch (ex->kind) { + switch (kind) { case ExternalKind::Function: - ex->value = getFunctionName(index); - continue; + value = getFunctionName(index); + break; case ExternalKind::Table: - ex->value = getTableName(index); - continue; + value = getTableName(index); + break; case ExternalKind::Memory: - ex->value = getMemoryName(index); - continue; + value = getMemoryName(index); + break; case ExternalKind::Global: - ex->value = getGlobalName(index); - continue; + value = getGlobalName(index); + break; case ExternalKind::Tag: - ex->value = getTagName(index); - continue; - case ExternalKind::Invalid: + value = getTagName(index); break; + case ExternalKind::Invalid: + throwError("invalid export kind"); } - throwError("invalid export kind"); + wasm.addExport(new Export(name, kind, value)); } } diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp index 86efb67988d..4c450bdb2d1 100644 --- a/src/wasm/wasm-emscripten.cpp +++ b/src/wasm/wasm-emscripten.cpp @@ -35,20 +35,8 @@ namespace wasm { void addExportedFunction(Module& wasm, Function* function) { wasm.addFunction(function); - auto export_ = new Export; - export_->name = export_->value = function->name; - export_->kind = ExternalKind::Function; - wasm.addExport(export_); -} - -// TODO(sbc): There should probably be a better way to do this. -bool isExported(Module& wasm, Name name) { - for (auto& ex : wasm.exports) { - if (ex->value == name) { - return true; - } - } - return false; + wasm.addExport( + new Export(function->name, ExternalKind::Function, function->name)); } Global* getStackPointerGlobal(Module& wasm) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 46dea371411..840abfe1a38 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3990,7 +3990,7 @@ static void validateExports(Module& module, ValidationInfo& info) { for (auto& curr : module.exports) { if (curr->kind == ExternalKind::Function) { if (info.validateWeb) { - Function* f = module.getFunction(curr->value); + Function* f = module.getFunction(*curr->getInternalName()); for (const auto& param : f->getParams()) { info.shouldBeUnequal( param, @@ -4006,7 +4006,7 @@ static void validateExports(Module& module, ValidationInfo& info) { } } } else if (curr->kind == ExternalKind::Global) { - if (Global* g = module.getGlobalOrNull(curr->value)) { + if (Global* g = module.getGlobalOrNull(*curr->getInternalName())) { if (!module.features.hasMutableGlobals()) { info.shouldBeFalse(g->mutable_, g->name, @@ -4020,24 +4020,28 @@ static void validateExports(Module& module, ValidationInfo& info) { } std::unordered_set exportNames; for (auto& exp : module.exports) { - Name name = exp->value; if (exp->kind == ExternalKind::Function) { + Name name = *exp->getInternalName(); info.shouldBeTrue(module.getFunctionOrNull(name), name, "module function exports must be found"); } else if (exp->kind == ExternalKind::Global) { + Name name = *exp->getInternalName(); info.shouldBeTrue(module.getGlobalOrNull(name), name, "module global exports must be found"); } else if (exp->kind == ExternalKind::Table) { + Name name = *exp->getInternalName(); info.shouldBeTrue(module.getTableOrNull(name), name, "module table exports must be found"); } else if (exp->kind == ExternalKind::Memory) { + Name name = *exp->getInternalName(); info.shouldBeTrue(module.getMemoryOrNull(name), name, "module memory exports must be found"); } else if (exp->kind == ExternalKind::Tag) { + Name name = *exp->getInternalName(); info.shouldBeTrue( module.getTagOrNull(name), name, "module tag exports must be found"); } else { diff --git a/src/wasm2js.h b/src/wasm2js.h index 9a4416068c5..f59180a8237 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -88,7 +88,8 @@ bool isTableExported(Module& wasm) { return false; } for (auto& ex : wasm.exports) { - if (ex->kind == ExternalKind::Table && ex->value == wasm.tables[0]->name) { + if (ex->kind == ExternalKind::Table && + *ex->getInternalName() == wasm.tables[0]->name) { return true; } } @@ -342,7 +343,7 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { // Scan the wasm for important things. for (auto& exp : wasm->exports) { if (exp->kind == ExternalKind::Function) { - functionsCallableFromOutside.insert(exp->value); + functionsCallableFromOutside.insert(*exp->getInternalName()); } } ElementUtils::iterAllElementFunctionNames( @@ -534,11 +535,8 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { {}, builder.makeReturn(builder.makeGlobalGet( INT64_TO_32_HIGH_BITS, Type::i32)))))); - auto e = new Export(); - e->name = WASM_FETCH_HIGH_BITS; - e->value = WASM_FETCH_HIGH_BITS; - e->kind = ExternalKind::Function; - wasm->addExport(e); + wasm->addExport(new Export( + WASM_FETCH_HIGH_BITS, ExternalKind::Function, WASM_FETCH_HIGH_BITS)); } if (flags.emscripten) { asmFunc[3]->push_back(ValueBuilder::makeName("// EMSCRIPTEN_END_FUNCS\n")); @@ -765,7 +763,8 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) { ValueBuilder::appendToObjectWithQuotes( exports, fromName(export_->name, NameScope::Export), - ValueBuilder::makeName(fromName(export_->value, NameScope::Top))); + ValueBuilder::makeName( + fromName(*export_->getInternalName(), NameScope::Top))); break; } case ExternalKind::Memory: { @@ -807,7 +806,8 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) { case ExternalKind::Global: { Ref object = ValueBuilder::makeObject(); - IString identName = fromName(export_->value, NameScope::Top); + IString identName = + fromName(*export_->getInternalName(), NameScope::Top); // getter { From 425d7c88aa7db984ddb063ae33e0b54f7ec32543 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 13 Mar 2025 15:37:06 -0700 Subject: [PATCH 353/622] Type `ref.null` as exact (#7371) `RefNull` expression now have type `(ref exact null bot)`, allowing them to be used wherever a nullable exact reference is expected. Update the fuzzer and fix a few bugs with exactness propagation this uncovers. --- scripts/test/fuzzing.py | 1 + src/ir/manipulation.h | 5 +- src/ir/type-updating.cpp | 1 + src/ir/type-updating.h | 1 + src/literal.h | 2 +- src/passes/OptimizeInstructions.cpp | 5 - src/tools/fuzzing.h | 6 + src/tools/fuzzing/fuzzing.cpp | 75 ++++++-- src/wasm-builder.h | 10 +- src/wasm/literal.cpp | 4 +- src/wasm/wasm-validator.cpp | 4 + src/wasm/wasm.cpp | 3 +- test/lit/basic/reference-types.wast | 16 +- .../lit/ctor-eval/materialize-null-local.wast | 26 +++ test/lit/exec/exact.wast | 21 +++ test/lit/heap-types.wast | 4 +- test/lit/passes/cfp.wast | 4 +- test/lit/passes/code-pushing-gc.wast | 4 +- test/lit/passes/dae-gc-refine-params.wast | 2 +- test/lit/passes/dae-gc.wast | 2 +- test/lit/passes/flatten_all-features.wast | 6 +- test/lit/passes/global-refining.wast | 18 +- test/lit/passes/gufa-refs.wast | 52 ++--- test/lit/passes/gufa-vs-cfp.wast | 4 +- test/lit/passes/heap2local-rmw.wast | 38 ++-- test/lit/passes/heap2local.wast | 178 +++++++++--------- test/lit/passes/local-subtyping-nn.wast | 4 +- test/lit/passes/local-subtyping.wast | 2 +- test/lit/passes/merge-blocks.wast | 2 +- test/lit/passes/monomorphize-context.wast | 4 +- .../passes/optimize-instructions-exact.wast | 3 +- .../passes/optimize-instructions-gc-tnh.wast | 4 +- test/lit/passes/optimize-instructions-gc.wast | 4 +- test/lit/passes/precompute-gc.wast | 2 +- test/lit/passes/remove-unused-brs-gc.wast | 22 +-- test/lit/passes/signature-refining_gto.wat | 4 +- test/lit/passes/ssa.wast | 4 +- .../passes/type-refining-isorecursive.wast | 6 +- test/lit/passes/type-refining-rmw.wast | 4 +- test/lit/passes/type-refining.wast | 14 +- test/passes/precompute_all-features.txt | 2 +- 41 files changed, 336 insertions(+), 237 deletions(-) create mode 100644 test/lit/ctor-eval/materialize-null-local.wast create mode 100644 test/lit/exec/exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 09604a37821..abb7da98002 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -108,6 +108,7 @@ 'remove-unused-types-exact.wast', 'coalesce-locals-exact.wast', 'remove-unused-brs-exact.wast', + 'exact.wast', ] diff --git a/src/ir/manipulation.h b/src/ir/manipulation.h index e7816af9fce..c766fb8719e 100644 --- a/src/ir/manipulation.h +++ b/src/ir/manipulation.h @@ -40,10 +40,9 @@ template inline Nop* nop(InputType* target) { } template -inline RefNull* refNull(InputType* target, Type type) { - assert(type.isNullable() && type.getHeapType().isBottom()); +inline RefNull* refNull(InputType* target, HeapType type) { auto* ret = convert(target); - ret->finalize(type); + ret->finalize(Type(type.getBottom(), Nullable, Exact)); return ret; } diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 6dd91e0960e..65886bd2bd9 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -342,6 +342,7 @@ Type GlobalTypeRewriter::getTempType(Type type) { if (type.isRef()) { auto heapType = type.getHeapType(); if (auto it = typeIndices.find(heapType); it != typeIndices.end()) { + // TODO: Handle exactness. return typeBuilder.getTempRefType(typeBuilder[it->second], type.getNullability()); } diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h index fe1cd2806aa..e4e2c536992 100644 --- a/src/ir/type-updating.h +++ b/src/ir/type-updating.h @@ -497,6 +497,7 @@ class TypeMapper : public GlobalTypeRewriter { auto heapType = type.getHeapType(); auto iter = mapping.find(heapType); if (iter != mapping.end()) { + // TODO: Handle exactness. return getTempType(Type(iter->second, type.getNullability())); } return getTempType(type); diff --git a/src/literal.h b/src/literal.h index 50666083eb4..91bbacbfa3c 100644 --- a/src/literal.h +++ b/src/literal.h @@ -243,7 +243,7 @@ class Literal { } } static Literal makeNull(HeapType type) { - return Literal(Type(type.getBottom(), Nullable)); + return Literal(Type(type.getBottom(), Nullable, Exact)); } static Literal makeFunc(Name func, HeapType type) { return Literal(func, type); diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 90dcf5c5d64..916a493bca6 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2297,11 +2297,6 @@ struct OptimizeInstructions // we need to check exactness. We can replace the cast with a drop // followed by a direct return of the value, though. if (ref->type.isNull()) { - // TODO: Remove this once we type ref.null as exact. - if (needsExactCast) { - return; - } - // We can materialize the resulting null value directly. // // The type must be nullable for us to do that, which it normally diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 383e5af70c1..bb43fa39fa2 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -360,6 +360,10 @@ class TranslateToFuzzReader { // instruction for EH is supposed to exist only at the beginning of a 'catch' // block, so it shouldn't be moved around or deleted freely. bool canBeArbitrarilyReplaced(Expression* curr) { + // TODO: Remove this once we better support exact references. + if (curr->type.isExact()) { + return false; + } return curr->type.isDefaultable() && !EHUtils::containsValidDanglingPop(curr); } @@ -521,7 +525,9 @@ class TranslateToFuzzReader { Type getLoggableType(); bool isLoggableType(Type type); Nullability getNullability(); + Exactness getExactness(); Nullability getSubType(Nullability nullability); + Exactness getSubType(Exactness exactness); HeapType getSubType(HeapType type); Type getSubType(Type type); Nullability getSuperType(Nullability nullability); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index bc3bc9fc938..2f8910db8cf 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1566,20 +1566,22 @@ void TranslateToFuzzReader::recombine(Function* func) { } std::vector ret; - auto heapType = type.getHeapType(); - auto nullability = type.getNullability(); + ret.push_back(type); - if (nullability == NonNullable) { - ret = getRelevantTypes(Type(heapType, Nullable)); + if (type.isNonNullable()) { + auto nullable = getRelevantTypes(type.with(Nullable)); + ret.insert(ret.end(), nullable.begin(), nullable.end()); + } + if (type.isExact()) { + auto inexact = getRelevantTypes(type.with(Inexact)); + ret.insert(ret.end(), inexact.begin(), inexact.end()); + // Do not consider exact references to supertypes. + return ret; } - while (1) { - ret.push_back(Type(heapType, nullability)); - auto super = heapType.getSuperType(); - if (!super) { - break; - } - heapType = *super; + for (auto heapType = type.getHeapType().getSuperType(); heapType; + heapType = heapType->getSuperType()) { + ret.push_back(type.with(*heapType)); } return ret; @@ -4902,9 +4904,17 @@ static auto makeArrayBoundsCheck(Expression* ref, Function* func, Builder& builder, Expression* length = nullptr) { - auto tempRef = builder.addVar(func, ref->type); + // The reference might be a RefNull, in which case its type is exact. But we + // want to avoid creating exact-typed locals until we support them more widely + // in the fuzzer, so adjust the type. TODO: remove this once exact references + // are better supported. + Type refType = ref->type; + if (refType.isExact()) { + refType = refType.with(Inexact); + } + auto tempRef = builder.addVar(func, refType); auto tempIndex = builder.addVar(func, index->type); - auto* teeRef = builder.makeLocalTee(tempRef, ref, ref->type); + auto* teeRef = builder.makeLocalTee(tempRef, ref, refType); auto* teeIndex = builder.makeLocalTee(tempIndex, index, index->type); auto* getSize = builder.makeArrayLen(teeRef); @@ -4931,7 +4941,7 @@ static auto makeArrayBoundsCheck(Expression* ref, // An additional use of the length, if it was provided. Expression* getLength = nullptr; } result = {builder.makeBinary(LtUInt32, effectiveIndex, getSize), - builder.makeLocalGet(tempRef, ref->type), + builder.makeLocalGet(tempRef, refType), builder.makeLocalGet(tempIndex, index->type), getLength}; return result; @@ -5320,6 +5330,23 @@ Nullability TranslateToFuzzReader::getNullability() { return Nullable; } +Exactness TranslateToFuzzReader::getExactness() { + // Without GC, the only heap types are func and extern, neither of which is + // exactly inhabitable. To avoid introducing uninhabitable types, only + // generate exact references when GC is enabled. We don't need custom + // descriptors to be enabled even though that is the feature that introduces + // exact references because the binary writer can always generalize the exact + // reference types away. + // + // if (wasm.features.hasGC() && oneIn(8)) { + // return Exact; + // } + // + // However, we cannot yet handle creating exact references in general, so for + // now we always generate inexact references when given the choice. TODO. + return Inexact; +} + Nullability TranslateToFuzzReader::getSubType(Nullability nullability) { if (nullability == NonNullable) { return NonNullable; @@ -5327,6 +5354,13 @@ Nullability TranslateToFuzzReader::getSubType(Nullability nullability) { return getNullability(); } +Exactness TranslateToFuzzReader::getSubType(Exactness exactness) { + if (exactness == Exact) { + return Exact; + } + return getExactness(); +} + HeapType TranslateToFuzzReader::getSubType(HeapType type) { if (oneIn(3)) { return type; @@ -5418,9 +5452,18 @@ Type TranslateToFuzzReader::getSubType(Type type) { if (!funcContext && heapType.isMaybeShared(HeapType::exn)) { return type; } - heapType = getSubType(heapType); + if (type.isExact()) { + // The only other possible heap type is bottom, but we don't want to + // generate too many bottom types. + if (!heapType.isBottom() && oneIn(20)) { + heapType = heapType.getBottom(); + } + } else { + heapType = getSubType(heapType); + } auto nullability = getSubType(type.getNullability()); - auto subType = Type(heapType, nullability); + auto exactness = getSubType(type.getExactness()); + auto subType = Type(heapType, nullability, exactness); // We don't want to emit lots of uninhabitable types like (ref none), so // avoid them with high probability. Specifically, if the original type was // inhabitable then return that; avoid adding more uninhabitability. diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 8344355b64a..13c124783a6 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -670,11 +670,11 @@ class Builder { } RefNull* makeRefNull(HeapType type) { auto* ret = wasm.allocator.alloc(); - ret->finalize(Type(type.getBottom(), Nullable)); + ret->finalize(Type(type.getBottom(), Nullable, Exact)); return ret; } RefNull* makeRefNull(Type type) { - assert(type.isNullable() && type.isNull()); + assert(type.isNullable() && type.isNull() && type.isExact()); auto* ret = wasm.allocator.alloc(); ret->finalize(type); return ret; @@ -1270,7 +1270,7 @@ class Builder { return makeConst(value); } if (value.isNull()) { - return makeRefNull(type); + return makeRefNull(type.getHeapType()); } if (type.isFunction()) { return makeRefFunc(value.getFunc(), type.getHeapType()); @@ -1435,8 +1435,8 @@ class Builder { return maybeWrap(makeConstantExpression(Literal::makeZeros(curr->type))); } if (curr->type.isNullable()) { - return maybeWrap(ExpressionManipulator::refNull( - curr, Type(curr->type.getHeapType().getBottom(), Nullable))); + return maybeWrap( + ExpressionManipulator::refNull(curr, curr->type.getHeapType())); } if (curr->type.isRef() && curr->type.getHeapType().isMaybeShared(HeapType::i31)) { diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 05027ee6bd6..d1fe5cfcc03 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -72,7 +72,9 @@ Literal::Literal(const uint8_t init[16]) : type(Type::v128) { } Literal::Literal(std::shared_ptr gcData, HeapType type) - : gcData(gcData), type(type, gcData ? NonNullable : Nullable) { + : gcData(gcData), + type(type, gcData ? NonNullable : Nullable, gcData ? Inexact : Exact) { + // TODO: Use exact types for more than just nulls. // The type must be a proper type for GC data: either a struct, array, or // string; or an externalized version of the same; or a null. assert((isData() && gcData) || diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 840abfe1a38..5676ce55b58 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2288,6 +2288,10 @@ void FunctionValidator::visitRefNull(RefNull* curr) { curr->type.isNullable(), curr, "ref.null types must be nullable")) { return; } + if (!shouldBeTrue( + curr->type.isExact(), curr, "ref.null types must be exact")) { + return; + } shouldBeTrue( curr->type.isNull(), curr, "ref.null must have a bottom heap type"); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ca9fcc93328..81b2fc43516 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -800,8 +800,7 @@ void MemoryGrow::finalize() { void RefNull::finalize(HeapType heapType) { assert(heapType.isBottom()); - // TODO: Make this exact. - type = Type(heapType, Nullable); + type = Type(heapType, Nullable, Exact); } void RefNull::finalize(Type type_) { type = type_; } diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 82b1f8da4e3..7c4f2a23e76 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -961,7 +961,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block2 (result eqref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block2 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -987,7 +987,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block5 (result funcref) - ;; CHECK-BIN-NEXT: (ref.cast nullfuncref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullfuncref) ;; CHECK-BIN-NEXT: (br_if $block5 ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1023,7 +1023,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block9 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block9 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1043,7 +1043,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block11 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block11 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -2298,7 +2298,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block2 (result eqref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block2 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2324,7 +2324,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block5 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullfuncref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullfuncref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block5 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2360,7 +2360,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block9 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block9 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2380,7 +2380,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block11 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block11 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) diff --git a/test/lit/ctor-eval/materialize-null-local.wast b/test/lit/ctor-eval/materialize-null-local.wast new file mode 100644 index 00000000000..1f880f6816b --- /dev/null +++ b/test/lit/ctor-eval/materialize-null-local.wast @@ -0,0 +1,26 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-ctor-eval %s -all --ctors=test --kept-exports=test --ignore-external-input -S -o - \ +;; RUN: | filecheck %s + +;; Check that materializing a non-data null local, which at time of writing uses +;; Builder::makeConstantExpression, does not trigger an assertion failure. + +(module + (func $test (export "test") (param $0 externref) + (local $3 anyref) + (local.set $3 + (any.convert_extern + (local.get $0) + ) + ) + ) +) +;; CHECK: (type $0 (func (param externref))) + +;; CHECK: (export "test" (func $test_1)) + +;; CHECK: (func $test_1 (type $0) (param $0 externref) +;; CHECK-NEXT: (local $3 anyref) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) diff --git a/test/lit/exec/exact.wast b/test/lit/exec/exact.wast new file mode 100644 index 00000000000..2667e0e4dce --- /dev/null +++ b/test/lit/exec/exact.wast @@ -0,0 +1,21 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +(module + ;; CHECK: [fuzz-exec] calling convert-null-extern + ;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null + (func $convert-null-extern (export "convert-null-extern") (result (exact nullref)) + (local externref) + ;; The value produced by this cast must be exact to avoid triggering an + ;; assertion. + (ref.cast (exact nullref) + (any.convert_extern + (local.get 0) + ) + ) + ) +) +;; CHECK: [fuzz-exec] calling convert-null-extern +;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null +;; CHECK-NEXT: [fuzz-exec] comparing convert-null-extern diff --git a/test/lit/heap-types.wast b/test/lit/heap-types.wast index 4613f4894fd..baf77196611 100644 --- a/test/lit/heap-types.wast +++ b/test/lit/heap-types.wast @@ -12,7 +12,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.test (ref none) + ;; CHECK-NEXT: (ref.test (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -29,7 +29,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 0bc4372ec35..ab9b7f8a745 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -1034,7 +1034,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) @@ -1233,7 +1233,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) diff --git a/test/lit/passes/code-pushing-gc.wast b/test/lit/passes/code-pushing-gc.wast index 1aac5c55cf7..fb9ff12f7da 100644 --- a/test/lit/passes/code-pushing-gc.wast +++ b/test/lit/passes/code-pushing-gc.wast @@ -7,7 +7,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $out (result (ref func)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) + ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -48,7 +48,7 @@ ;; CHECK-NEXT: (ref.func $br_on_no) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) + ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index 8cefbe88184..a93f3e7347f 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -262,7 +262,7 @@ ) ;; This function is called in ways that allow us to make the first parameter ;; non-nullable. - ;; CHECK: (func $various-params-null (type $13) (param $x (ref none)) (param $y (ref null $"{i32}")) + ;; CHECK: (func $various-params-null (type $13) (param $x (ref exact none)) (param $y (ref null $"{i32}")) ;; CHECK-NEXT: (local $temp i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 1f39567d146..015e515c02b 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -110,7 +110,7 @@ ) ;; CHECK: (func $bar (type $2) (param $0 i31ref) - ;; CHECK-NEXT: (local $1 nullref) + ;; CHECK-NEXT: (local $1 (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/flatten_all-features.wast b/test/lit/passes/flatten_all-features.wast index b601b8a1295..6c117d0a907 100644 --- a/test/lit/passes/flatten_all-features.wast +++ b/test/lit/passes/flatten_all-features.wast @@ -3581,8 +3581,8 @@ ;; CHECK: (func $subtype (type $7) (result anyref) ;; CHECK-NEXT: (local $0 eqref) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 nullref) - ;; CHECK-NEXT: (local $3 nullref) + ;; CHECK-NEXT: (local $2 (exact nullref)) + ;; CHECK-NEXT: (local $3 (exact nullref)) ;; CHECK-NEXT: (local $4 eqref) ;; CHECK-NEXT: (local $5 eqref) ;; CHECK-NEXT: (local $6 eqref) @@ -3680,7 +3680,7 @@ ;; CHECK: (type $0 (func (result funcref))) ;; CHECK: (func $0 (type $0) (result funcref) - ;; CHECK-NEXT: (local $0 (ref nofunc)) + ;; CHECK-NEXT: (local $0 (ref exact nofunc)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index 927dfd1f26c..7be5a9e4e2a 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -11,8 +11,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) @@ -32,8 +32,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) @@ -202,16 +202,16 @@ ;; (ref null func) to nullfuncref only when not exported, and if exported, then ;; only when immutable in open world. (module - ;; CHECK: (global $mut (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $mut (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) (global $mut (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm nullfuncref (ref.null nofunc)) - ;; CLOSD: (global $imm nullfuncref (ref.null nofunc)) + ;; CHECK: (global $imm (exact nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $imm (exact nullfuncref) (ref.null nofunc)) (global $imm (ref null func) (ref.null nofunc)) ;; CHECK: (global $mut-exp (mut funcref) (ref.null nofunc)) ;; CLOSD: (global $mut-exp (mut funcref) (ref.null nofunc)) (global $mut-exp (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm-exp nullfuncref (ref.null nofunc)) + ;; CHECK: (global $imm-exp (exact nullfuncref) (ref.null nofunc)) ;; CLOSD: (global $imm-exp funcref (ref.null nofunc)) (global $imm-exp (ref null func) (ref.null nofunc)) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 6d48d651a5f..6142550638d 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -955,7 +955,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 1 ;; CHECK-NEXT: (local.get $child) @@ -970,7 +970,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) @@ -1072,9 +1072,9 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result nullref) + ;; CHECK-NEXT: (block $block (result (exact nullref)) ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1085,9 +1085,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block0 (result nullref) + ;; CHECK-NEXT: (block $block0 (result (exact nullref)) ;; CHECK-NEXT: (br $block0 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1098,13 +1098,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block1 (result nullref) + ;; CHECK-NEXT: (block $block1 (result (exact nullref)) ;; CHECK-NEXT: (br $block1 - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1237,7 +1237,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) @@ -1577,7 +1577,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $null ;; CHECK-NEXT: (array.new_default $null @@ -1775,10 +1775,10 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $x - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $storage - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $pass-through ;; CHECK-NEXT: (ref.null none) @@ -1928,7 +1928,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2153,7 +2153,7 @@ ;; CHECK-NEXT: (pop (tuple anyref anyref)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2213,7 +2213,7 @@ ;; CHECK: (func $func (type $1) (result (ref $"{}")) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (ref none)) + ;; CHECK-NEXT: (block $block (result (ref exact none)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2539,10 +2539,10 @@ ;; CHECK: (func $test-nulls (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -2554,9 +2554,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (select (result i31ref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.i31 @@ -3256,7 +3256,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -3793,7 +3793,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $chars ;; CHECK-NEXT: (local.get $chars) @@ -6056,7 +6056,7 @@ ) (module - ;; CHECK: (type $0 (func (result i64 nullref i32))) + ;; CHECK: (type $0 (func (result i64 (exact nullref) i32))) ;; CHECK: (type $array (sub (array (mut i8)))) (type $array (sub (array (mut i8)))) @@ -6096,7 +6096,7 @@ ;; CHECK: (func $loop-tuple-br_on (type $2) ;; CHECK-NEXT: (tuple.drop 3 - ;; CHECK-NEXT: (loop $loop (type $0) (result i64 nullref i32) + ;; CHECK-NEXT: (loop $loop (type $0) (result i64 (exact nullref) i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index bd731d89150..eb26820b74d 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -1087,7 +1087,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct3 2 ;; CHECK-NEXT: (local.get $ref) @@ -1329,7 +1329,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $create3) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local-rmw.wast b/test/lit/passes/heap2local-rmw.wast index 33fef1cb506..f0b397dcc99 100644 --- a/test/lit/passes/heap2local-rmw.wast +++ b/test/lit/passes/heap2local-rmw.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -107,7 +107,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -140,7 +140,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -173,7 +173,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -206,7 +206,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -239,7 +239,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -270,7 +270,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -312,7 +312,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -345,7 +345,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -378,7 +378,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -411,7 +411,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -444,7 +444,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -477,7 +477,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -508,7 +508,7 @@ ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -550,7 +550,7 @@ ;; CHECK-NEXT: (local $2 (ref null $struct)) ;; CHECK-NEXT: (local $3 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -581,7 +581,7 @@ ;; CHECK-NEXT: (local $4 (ref null $struct)) ;; CHECK-NEXT: (local $5 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -623,7 +623,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -657,7 +657,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 619a33a23c5..8b0384671c3 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -102,7 +102,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -135,7 +135,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -162,7 +162,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -193,7 +193,7 @@ ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) @@ -259,7 +259,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -318,7 +318,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -388,7 +388,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) @@ -418,7 +418,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -456,7 +456,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -494,7 +494,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -559,7 +559,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -673,7 +673,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -713,7 +713,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -771,7 +771,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -821,7 +821,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -901,7 +901,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -927,7 +927,7 @@ ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -975,7 +975,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (local $2 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new_default $struct.recursive) ;; CHECK-NEXT: ) @@ -1070,7 +1070,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) @@ -1104,7 +1104,7 @@ ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (loop $outer ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -1271,7 +1271,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1287,7 +1287,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1303,7 +1303,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1343,7 +1343,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1362,7 +1362,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1411,7 +1411,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1422,7 +1422,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1553,7 +1553,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1749,7 +1749,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1800,7 +1800,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1851,7 +1851,7 @@ ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $block - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) @@ -1905,7 +1905,7 @@ ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1938,7 +1938,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1977,7 +1977,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2061,7 +2061,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2096,7 +2096,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2138,7 +2138,7 @@ ;; CHECK-NEXT: (local.get $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2174,7 +2174,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2216,7 +2216,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2253,7 +2253,7 @@ ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2287,7 +2287,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2334,7 +2334,7 @@ ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (local $7 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2351,7 +2351,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2396,7 +2396,7 @@ ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2450,7 +2450,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2472,7 +2472,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2494,7 +2494,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) @@ -2560,7 +2560,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2582,7 +2582,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2648,7 +2648,7 @@ ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2697,7 +2697,7 @@ ;; CHECK-NEXT: (local $2 (ref $A)) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2736,7 +2736,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2776,7 +2776,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2819,9 +2819,9 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2870,7 +2870,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2907,7 +2907,7 @@ ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -3005,7 +3005,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3151,7 +3151,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3197,7 +3197,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) @@ -3327,11 +3327,11 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3391,7 +3391,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -3500,7 +3500,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3512,7 +3512,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3533,7 +3533,7 @@ ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $24 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) @@ -3706,7 +3706,7 @@ ;; CHECK: (func $array.nested.refinalize.get (type $3) (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3726,7 +3726,7 @@ ;; CHECK: (func $array.nested.refinalize.set (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3756,7 +3756,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3849,7 +3849,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3867,7 +3867,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3916,7 +3916,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3983,7 +3983,7 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4030,7 +4030,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4070,7 +4070,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -4173,7 +4173,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4253,7 +4253,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4319,7 +4319,7 @@ ;; CHECK: (func $array.cast.struct (type $0) (result (ref struct)) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4341,7 +4341,7 @@ ;; CHECK: (func $array.cast.struct.null (type $3) (result structref) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4380,7 +4380,7 @@ ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (local $array (ref array)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4405,7 +4405,7 @@ ;; CHECK-NEXT: (local.tee $struct ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4442,7 +4442,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result structref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -4513,7 +4513,7 @@ ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) @@ -4565,7 +4565,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4620,7 +4620,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (block (result (ref null exact (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4666,7 +4666,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (block (result (ref null exact (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/local-subtyping-nn.wast b/test/lit/passes/local-subtyping-nn.wast index 3754230d82f..f2237156f01 100644 --- a/test/lit/passes/local-subtyping-nn.wast +++ b/test/lit/passes/local-subtyping-nn.wast @@ -8,7 +8,7 @@ (import "out" "i32" (func $i32 (result i32))) ;; CHECK: (func $non-nullable (type $1) - ;; CHECK-NEXT: (local $x (ref none)) + ;; CHECK-NEXT: (local $x (ref exact none)) ;; CHECK-NEXT: (local $y (ref $0)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.as_non_null @@ -41,7 +41,7 @@ ) ;; CHECK: (func $uses-default (type $2) (param $i i32) - ;; CHECK-NEXT: (local $x nullref) + ;; CHECK-NEXT: (local $x (exact nullref)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $i) ;; CHECK-NEXT: (then diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 795a0a01cd1..777df93524f 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -273,7 +273,7 @@ ) ;; CHECK: (func $multiple-iterations-refinalize-call-ref-bottom (type $0) - ;; CHECK-NEXT: (local $f nullfuncref) + ;; CHECK-NEXT: (local $f (exact nullfuncref)) ;; CHECK-NEXT: (local $x (ref none)) ;; CHECK-NEXT: (local.set $f ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 86181f7a488..7a517ccbc27 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -20,7 +20,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $label$1 (result i31ref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label$1 nullref (ref none) + ;; CHECK-NEXT: (br_on_cast $label$1 (exact nullref) (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/monomorphize-context.wast b/test/lit/passes/monomorphize-context.wast index 7f9cb2f566e..92b483a7ae1 100644 --- a/test/lit/passes/monomorphize-context.wast +++ b/test/lit/passes/monomorphize-context.wast @@ -277,7 +277,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast nullref +;; ALWAYS-NEXT: (ref.cast (exact nullref) ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -555,7 +555,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast nullref +;; ALWAYS-NEXT: (ref.cast (exact nullref) ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast index 6e03c9a4977..61769b78f34 100644 --- a/test/lit/passes/optimize-instructions-exact.wast +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -19,9 +19,10 @@ ) ;; CHECK: (func $cast-null-to-exact-none (type $1) (result (exact nullref)) ;; CHECK-NEXT: (local $0 nullref) - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) (func $cast-null-to-exact-none (result (exact nullref)) (local nullref) diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index c930f2fc19a..3893e048e14 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -509,7 +509,7 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: (block ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result nullref) + ;; TNH-NEXT: (block (result (exact nullref)) ;; TNH-NEXT: (drop ;; TNH-NEXT: (call $get-i32) ;; TNH-NEXT: ) @@ -532,7 +532,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (block ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result nullref) + ;; NO_TNH-NEXT: (block (result (exact nullref)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (call $get-i32) ;; NO_TNH-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 4b88507ffc7..86804d7ddbd 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -1570,7 +1570,7 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $a ;; CHECK-NEXT: (ref.null none) @@ -1590,7 +1590,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.cast nullref diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast index fc4cc7c2ee9..94adaa53fbb 100644 --- a/test/lit/passes/precompute-gc.wast +++ b/test/lit/passes/precompute-gc.wast @@ -28,7 +28,7 @@ ;; CHECK: (func $test-fallthrough (type $func-return-i32) (result i32) ;; CHECK-NEXT: (local $x funcref) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (block (result nullfuncref) + ;; CHECK-NEXT: (block (result (exact nullfuncref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $test-fallthrough) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 2ee6171cbcb..c26fe3a95ba 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -63,7 +63,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -119,7 +119,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.cast (ref null $struct) ;; CHECK-NEXT: (local.tee $any @@ -438,7 +438,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $nullable-struct2) ;; CHECK-NEXT: ) @@ -507,7 +507,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (local.get $nullable-struct2) @@ -653,7 +653,7 @@ ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (ref.test (ref none) + ;; CHECK-NEXT: (ref.test (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -663,13 +663,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result nullref) + ;; CHECK-NEXT: (if (result (exact nullref)) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -681,7 +681,7 @@ ;; CHECK-NEXT: (then ;; CHECK-NEXT: (block $something (result (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $something ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -697,8 +697,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result nullref) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (select (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (block $nothing ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -805,7 +805,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result nullref) + ;; CHECK-NEXT: (select (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat index c69eeb24455..1eedcd4f67c 100644 --- a/test/lit/passes/signature-refining_gto.wat +++ b/test/lit/passes/signature-refining_gto.wat @@ -9,11 +9,11 @@ ;; CHECK-NOT: (type $A (type $A (struct (field (mut (ref null $A))))) - ;; CHECK: (type $0 (func (param (ref none)))) + ;; CHECK: (type $0 (func (param (ref exact none)))) ;; CHECK: (type $1 (func (param funcref i32))) - ;; CHECK: (func $struct.get (type $0) (param $0 (ref none)) + ;; CHECK: (func $struct.get (type $0) (param $0 (ref exact none)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast index 0d696f75a0d..a0b18b0777d 100644 --- a/test/lit/passes/ssa.wast +++ b/test/lit/passes/ssa.wast @@ -36,9 +36,9 @@ ;; CHECK: (func $refine-to-null (type $3) (result (ref $A)) ;; CHECK-NEXT: (local $0 (ref null $A)) - ;; CHECK-NEXT: (block $label (result (ref none)) + ;; CHECK-NEXT: (block $label (result (ref exact none)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label nullref (ref none) + ;; CHECK-NEXT: (br_on_cast $label (exact nullref) (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/type-refining-isorecursive.wast b/test/lit/passes/type-refining-isorecursive.wast index 537f99e668e..025b8cec9f5 100644 --- a/test/lit/passes/type-refining-isorecursive.wast +++ b/test/lit/passes/type-refining-isorecursive.wast @@ -5,11 +5,11 @@ ;; The types should be refined to a set of three mutually recursive types. ;; CHECK: (rec - ;; CHECK-NEXT: (type $2 (sub (struct (field nullexternref) (field (ref $0))))) + ;; CHECK-NEXT: (type $2 (sub (struct (field (exact nullexternref)) (field (ref $0))))) - ;; CHECK: (type $1 (sub (struct (field nullfuncref) (field (ref $2))))) + ;; CHECK: (type $1 (sub (struct (field (exact nullfuncref)) (field (ref $2))))) - ;; CHECK: (type $0 (sub (struct (field nullref) (field (ref $1))))) + ;; CHECK: (type $0 (sub (struct (field (exact nullref)) (field (ref $1))))) (type $0 (sub (struct nullref anyref))) (type $1 (sub (struct nullfuncref anyref))) (type $2 (sub (struct nullexternref anyref))) diff --git a/test/lit/passes/type-refining-rmw.wast b/test/lit/passes/type-refining-rmw.wast index a4bb0a85cae..7739fceb0f4 100644 --- a/test/lit/passes/type-refining-rmw.wast +++ b/test/lit/passes/type-refining-rmw.wast @@ -6,7 +6,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared any))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) @@ -75,7 +75,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared eq))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 622e7422011..02357e8b6c0 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1101,7 +1101,7 @@ ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1180,7 +1180,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut nullref)))) + ;; CHECK-NEXT: (type $A (struct (field (mut (exact nullref))))) (type $A (struct (field (mut anyref)))) ;; CHECK: (type $B (struct (field (mut nullref)))) (type $B (struct (field (mut (ref null $A))))) @@ -1234,7 +1234,7 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut (ref noextern))))) + ;; CHECK-NEXT: (type $A (struct (field (mut (ref exact noextern))))) (type $A (struct (field (mut externref)))) ;; CHECK: (type $1 (func)) @@ -1252,7 +1252,7 @@ ;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref) ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (ref.cast (ref exact noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1304,7 +1304,7 @@ ;; CHECK: (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (ref.cast (ref exact noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1581,7 +1581,7 @@ (type $never (sub (struct (field i32)))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $optimizable (struct (field (mut nullfuncref)))) + ;; CHECK-NEXT: (type $optimizable (struct (field (mut (exact nullfuncref))))) (type $optimizable (struct (field (mut (ref null func))))) ;; CHECK: (type $2 (func)) @@ -1604,7 +1604,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (block (result (ref exact none)) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index 189adadcec1..1372035920f 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -286,7 +286,7 @@ ) ) (drop - (block $l2 (result nullexternref) + (block $l2 (result (exact nullexternref)) (drop (block $l3 (global.set $global-mut From 0402784e331a0dc7096689ebf5930807d960b729 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 13 Mar 2025 18:59:18 -0700 Subject: [PATCH 354/622] Support describes and descriptor clauses in the type system (#7369) Update HeapTypeInfo, the rec group hashing and equality comparison utilities, the type canonicalization algorithm, and the TypeBuilder interface to support `describes` and `descriptor` clauses on heap type definitions. Parsing, the binary format, and validation for these clauses is left to future PRs. --- src/wasm-type.h | 22 ++++++++ src/wasm/wasm-type.cpp | 105 +++++++++++++++++++++++++++++++++++- test/gtest/type-builder.cpp | 51 ++++++++++++++++++ 3 files changed, 177 insertions(+), 1 deletion(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 62177af2c08..5bcf750db89 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -192,6 +192,10 @@ class HeapType { // as well, just like |getDeclaredSuperType|. std::optional getSuperType() const; + // Get this type's descriptor or described types if they exist. + std::optional getDescriptorType() const; + std::optional getDescribedType() const; + // Return the depth of this heap type in the nominal type hierarchy, i.e. the // number of supertypes in its supertype chain. size_t getDepth() const; @@ -712,6 +716,12 @@ struct TypeBuilder { if (auto super = type.getDeclaredSuperType()) { setSubType(i, map(*super)); } + if (auto desc = type.getDescriptorType()) { + setDescriptor(i, map(*desc)); + } + if (auto desc = type.getDescribedType()) { + setDescribed(i, map(*desc)); + } setOpen(i, type.isOpen()); setShared(i, type.getShared()); @@ -782,6 +792,10 @@ struct TypeBuilder { // the given HeapType. void setSubType(size_t i, std::optional super); + // Set the descriptor or described type for the type at index `i`. + void setDescriptor(size_t i, std::optional desc); + void setDescribed(size_t i, std::optional desc); + // Create a new recursion group covering slots [i, i + length). Groups must // not overlap or go out of bounds. void createRecGroup(size_t i, size_t length); @@ -858,6 +872,14 @@ struct TypeBuilder { builder.setSubType(index, other); return *this; } + Entry& descriptor(std::optional other) { + builder.setDescriptor(index, other); + return *this; + } + Entry& describes(std::optional other) { + builder.setDescribed(index, other); + return *this; + } Entry& setOpen(bool open = true) { builder.setOpen(index, open); return *this; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index d35d1537c47..b7cacd3e69a 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -52,6 +52,10 @@ struct HeapTypeInfo { Shareability share = Unshared; // The supertype of this HeapType, if it exists. HeapTypeInfo* supertype = nullptr; + // The descriptor of this HeapType, if it exists. + HeapTypeInfo* descriptor = nullptr; + // The HeapType described by this one, if it exists. + HeapTypeInfo* described = nullptr; // The recursion group of this type or null if the recursion group is trivial // (i.e. contains only this type). RecGroupInfo* recGroup = nullptr; @@ -976,6 +980,26 @@ std::optional HeapType::getSuperType() const { WASM_UNREACHABLE("unexpected kind"); } +std::optional HeapType::getDescriptorType() const { + if (isBasic()) { + return std::nullopt; + } + if (auto* desc = getHeapTypeInfo(*this)->descriptor) { + return HeapType(uintptr_t(desc)); + } + return std::nullopt; +} + +std::optional HeapType::getDescribedType() const { + if (isBasic()) { + return std::nullopt; + } + if (auto* desc = getHeapTypeInfo(*this)->described) { + return HeapType(uintptr_t(desc)); + } + return std::nullopt; +} + size_t HeapType::getDepth() const { size_t depth = 0; std::optional super; @@ -1149,6 +1173,12 @@ std::vector HeapType::getReferencedHeapTypes() const { if (auto super = getDeclaredSuperType()) { types.push_back(*super); } + if (auto desc = getDescriptorType()) { + types.push_back(*desc); + } + if (auto desc = getDescribedType()) { + types.push_back(*desc); + } return types; } @@ -1280,6 +1310,10 @@ FeatureSet HeapType::getFeatures() const { feats |= FeatureSet::ReferenceTypes | FeatureSet::GC; } + if (heapType.getDescriptorType() || heapType.getDescribedType()) { + feats |= FeatureSet::CustomDescriptors; + } + if (heapType.isStruct() || heapType.isArray()) { feats |= FeatureSet::ReferenceTypes | FeatureSet::GC; } else if (heapType.isSignature()) { @@ -1765,6 +1799,16 @@ std::ostream& TypePrinter::print(HeapType type) { if (type.isShared()) { os << "(shared "; } + if (auto desc = type.getDescribedType()) { + os << "(describes "; + printHeapTypeName(*desc); + os << ' '; + } + if (auto desc = type.getDescriptorType()) { + os << "(descriptor "; + printHeapTypeName(*desc); + os << ' '; + } switch (type.getKind()) { case HeapTypeKind::Func: print(type.getSignature()); @@ -1781,6 +1825,12 @@ std::ostream& TypePrinter::print(HeapType type) { case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } + if (type.getDescriptorType()) { + os << ')'; + } + if (type.getDescribedType()) { + os << ')'; + } if (type.isShared()) { os << ')'; } @@ -1927,9 +1977,18 @@ size_t RecGroupHasher::hash(HeapType type) const { size_t RecGroupHasher::hash(const HeapTypeInfo& info) const { size_t digest = wasm::hash(bool(info.supertype)); + wasm::rehash(digest, !!info.supertype); if (info.supertype) { hash_combine(digest, hash(HeapType(uintptr_t(info.supertype)))); } + wasm::rehash(digest, !!info.descriptor); + if (info.descriptor) { + hash_combine(digest, hash(HeapType(uintptr_t(info.descriptor)))); + } + wasm::rehash(digest, !!info.described); + if (info.described) { + hash_combine(digest, hash(HeapType(uintptr_t(info.described)))); + } wasm::rehash(digest, info.isOpen); wasm::rehash(digest, info.share); wasm::rehash(digest, info.kind); @@ -2051,7 +2110,7 @@ bool RecGroupEquator::eq(HeapType a, HeapType b) const { } bool RecGroupEquator::eq(const HeapTypeInfo& a, const HeapTypeInfo& b) const { - if (bool(a.supertype) != bool(b.supertype)) { + if (!!a.supertype != !!b.supertype) { return false; } if (a.supertype) { @@ -2061,6 +2120,26 @@ bool RecGroupEquator::eq(const HeapTypeInfo& a, const HeapTypeInfo& b) const { return false; } } + if (!!a.descriptor != !!b.descriptor) { + return false; + } + if (a.descriptor) { + HeapType descA(uintptr_t(a.descriptor)); + HeapType descB(uintptr_t(b.descriptor)); + if (!eq(descA, descB)) { + return false; + } + } + if (!!a.described != !!b.described) { + return false; + } + if (a.described) { + HeapType descA(uintptr_t(a.described)); + HeapType descB(uintptr_t(b.described)); + if (!eq(descA, descB)) { + return false; + } + } if (a.isOpen != b.isOpen) { return false; } @@ -2227,6 +2306,16 @@ void TypeBuilder::setSubType(size_t i, std::optional super) { HeapTypeInfo* sub = impl->entries[i].info.get(); sub->supertype = super ? getHeapTypeInfo(*super) : nullptr; } +void TypeBuilder::setDescriptor(size_t i, std::optional desc) { + assert(i < size() && "index out of bounds"); + HeapTypeInfo* info = impl->entries[i].info.get(); + info->descriptor = desc ? getHeapTypeInfo(*desc) : nullptr; +} +void TypeBuilder::setDescribed(size_t i, std::optional desc) { + assert(i < size() && "index out of bounds"); + HeapTypeInfo* info = impl->entries[i].info.get(); + info->described = desc ? getHeapTypeInfo(*desc) : nullptr; +} void TypeBuilder::createRecGroup(size_t index, size_t length) { assert(index <= size() && index + length <= size() && "group out of bounds"); @@ -2382,6 +2471,20 @@ void updateReferencedHeapTypes( info->supertype = getHeapTypeInfo(it->second); } } + + // Update the descriptor and described types. + if (info->descriptor) { + HeapType desc(uintptr_t(info->descriptor)); + if (auto it = canonicalized.find(desc); it != canonicalized.end()) { + info->descriptor = getHeapTypeInfo(it->second); + } + } + if (info->described) { + HeapType desc(uintptr_t(info->described)); + if (auto it = canonicalized.find(desc); it != canonicalized.end()) { + info->described = getHeapTypeInfo(it->second); + } + } } TypeBuilder::BuildResult diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index feb3b69c652..39d388064ee 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -504,6 +504,57 @@ TEST_F(TypeTest, CanonicalizeSupertypes) { EXPECT_NE(built[4], built[5]); } +TEST_F(TypeTest, CanonicalizeDescriptors) { + constexpr int numGroups = 3; + constexpr int groupSize = 4; + TypeBuilder builder(numGroups * groupSize); + + for (int i = 0; i < numGroups; ++i) { + builder.createRecGroup(i * groupSize, groupSize); + } + for (int i = 0; i < numGroups * groupSize; ++i) { + builder[i] = Struct(); + } + + // A B A' B' + builder[0].descriptor(builder[1]); + builder[1].describes(builder[0]); + builder[2].descriptor(builder[3]); + builder[3].describes(builder[2]); + + // A' A B B' + builder[4].descriptor(builder[7]); + builder[5].descriptor(builder[6]); + builder[6].describes(builder[5]); + builder[7].describes(builder[4]); + + auto translate = [&](HeapType t) -> HeapType { + for (int i = 0; i < groupSize; ++i) { + if (t == builder[i]) { + return builder[2 * groupSize + i]; + } + } + WASM_UNREACHABLE("unexpected type"); + }; + + // A B A' B' again + builder[8].copy(builder[0], translate); + builder[9].copy(builder[1], translate); + builder[10].copy(builder[2], translate); + builder[11].copy(builder[3], translate); + + auto result = builder.build(); + ASSERT_TRUE(result); + auto built = *result; + + EXPECT_EQ(built[0], built[8]); + EXPECT_EQ(built[1], built[9]); + EXPECT_EQ(built[2], built[10]); + EXPECT_EQ(built[3], built[11]); + + EXPECT_NE(built[0].getRecGroup(), built[4].getRecGroup()); +} + TEST_F(TypeTest, CanonicalizeFinal) { // Types are different if their finality flag is different. TypeBuilder builder(2); From db9d68fac59779ec7c9a42f894cd63e660c7998b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 13 Mar 2025 20:15:40 -0700 Subject: [PATCH 355/622] Fix clang-format on CI (#7374) Make it print the diff again. Previously, when there was a formatting problem, the script would exit the first time it ran `git clang-format` without printing the results. Update the script to ignore errors on that first run to ensure it makes it to the part where the results are printed. --- scripts/clang-format-diff.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/clang-format-diff.sh b/scripts/clang-format-diff.sh index d0046e8cd55..b58caca40b1 100755 --- a/scripts/clang-format-diff.sh +++ b/scripts/clang-format-diff.sh @@ -15,7 +15,7 @@ LLVM_VERSION=${LLVM_VERSION:=17} MERGE_BASE=$(git merge-base $BRANCH HEAD) FORMAT_ARGS="--binary=clang-format-${LLVM_VERSION} ${MERGE_BASE}" -FORMAT_MSG=$(git clang-format ${FORMAT_ARGS} -q --diff) +FORMAT_MSG=$(git clang-format ${FORMAT_ARGS} -q --diff || true) if [ -n "$FORMAT_MSG" -a "$FORMAT_MSG" != "no modified files to format" ] then echo "Please run git clang-format with clang-format-${LLVM_VERSION} before committing, or apply this diff:" From 9b161a9bf93c26feb05c727a838444c42398ba78 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Thu, 13 Mar 2025 21:20:02 -0700 Subject: [PATCH 356/622] [Interpreter] Instance (#7343) Adds an instance struct to encapsulate the runtime representation of a module, as seen in the [spec](https://webassembly.github.io/spec/core/exec/runtime.html#module-instances). Also adds an instantiate method, which runs the initialization expressions of globals in the module. --- src/interpreter/interpreter.cpp | 51 ++++++++-- src/interpreter/interpreter.h | 8 +- src/interpreter/store.h | 17 +++- test/gtest/interpreter.cpp | 159 +++++++++++++++++++++----------- 4 files changed, 169 insertions(+), 66 deletions(-) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index fea5a67ba0f..71ed2d031c4 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -47,11 +47,14 @@ struct ExpressionInterpreter : OverriddenVisitor { ExpressionInterpreter(Interpreter& parent) : parent(parent) {} WasmStore& store() { return InterpreterImpl::getStore(parent); } + Frame& frame() { return store().callStack.back(); } + Instance& instance() { return frame().instance; } + void push(Literal val) { store().push(val); } Literal pop() { return store().pop(); } Flow visitNop(Nop* curr) { WASM_UNREACHABLE("TODO"); } - Flow visitBlock(Block* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitBlock(Block* curr) { return {}; } Flow visitIf(If* curr) { WASM_UNREACHABLE("TODO"); } Flow visitLoop(Loop* curr) { WASM_UNREACHABLE("TODO"); } Flow visitBreak(Break* curr) { WASM_UNREACHABLE("TODO"); } @@ -60,8 +63,14 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitCallIndirect(CallIndirect* curr) { WASM_UNREACHABLE("TODO"); } Flow visitLocalGet(LocalGet* curr) { WASM_UNREACHABLE("TODO"); } Flow visitLocalSet(LocalSet* curr) { WASM_UNREACHABLE("TODO"); } - Flow visitGlobalGet(GlobalGet* curr) { WASM_UNREACHABLE("TODO"); } - Flow visitGlobalSet(GlobalSet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitGlobalGet(GlobalGet* curr) { + push(instance().globalValues[curr->name]); + return {}; + } + Flow visitGlobalSet(GlobalSet* curr) { + instance().globalValues[curr->name] = pop(); + return {}; + } Flow visitLoad(Load* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStore(Store* curr) { WASM_UNREACHABLE("TODO"); } Flow visitAtomicRMW(AtomicRMW* curr) { WASM_UNREACHABLE("TODO"); } @@ -272,13 +281,33 @@ struct ExpressionInterpreter : OverriddenVisitor { } // anonymous namespace -std::vector Interpreter::run(Expression* root) { - // Create a fresh store and execution frame, then run the expression to - // completion. - store = WasmStore(); - store.callStack.emplace_back(); - store.callStack.back().exprs = ExpressionIterator(root); +Result<> Interpreter::addInstance(std::shared_ptr wasm) { + return instantiate(store.instances.emplace_back(wasm)); +} + +Result<> Interpreter::instantiate(Instance& instance) { + for (auto& global : instance.wasm->globals) { + store.callStack.emplace_back(instance, ExpressionIterator(global->init)); + auto results = run(); + assert(results.size() == 1); + instance.globalValues[global->name] = results[0]; + } + return Ok{}; +} + +// This is a temporary convenience while stil using gTests to validate this +// interpreter. Once spec tests can run, this shall be deleted. +std::vector Interpreter::runTest(Expression* root) { + static std::shared_ptr dummyModule = std::make_shared(); + if (store.instances.empty()) { + auto result = addInstance(dummyModule); + } + store.callStack.emplace_back(store.instances.back(), + ExpressionIterator(root)); + return run(); +} +std::vector Interpreter::run() { ExpressionInterpreter interpreter(*this); while (auto& it = store.callStack.back().exprs) { if (auto flow = interpreter.visit(*it)) { @@ -288,7 +317,9 @@ std::vector Interpreter::run(Expression* root) { } } - return store.callStack.back().valueStack; + auto valueStack = store.callStack.back().valueStack; + store.callStack.pop_back(); + return valueStack; } } // namespace wasm diff --git a/src/interpreter/interpreter.h b/src/interpreter/interpreter.h index 688a989d3f5..210cc990ae8 100644 --- a/src/interpreter/interpreter.h +++ b/src/interpreter/interpreter.h @@ -18,6 +18,7 @@ #define interpreter_interpreter_h #include "store.h" +#include "support/result.h" namespace wasm { @@ -25,11 +26,16 @@ class Interpreter { public: // TODO: Methods to instantiate modules. // TODO: Methods to run exported functions. - std::vector run(Expression* root); + + Result<> addInstance(std::shared_ptr wasm); + std::vector runTest(Expression* root); + std::vector run(); private: interpreter::WasmStore store; friend class InterpreterImpl; + + Result<> instantiate(interpreter::Instance& instance); }; } // namespace wasm diff --git a/src/interpreter/store.h b/src/interpreter/store.h index 03b3ff3ef07..e2a1eb398d4 100644 --- a/src/interpreter/store.h +++ b/src/interpreter/store.h @@ -22,18 +22,28 @@ #include "expression-iterator.h" #include "literal.h" +#include "support/result.h" namespace wasm::interpreter { +// Contains the runtime representation of an instance of a Wasm module. +struct Instance { + std::shared_ptr wasm; + std::unordered_map globalValues; + + Instance(std::shared_ptr wasm) : wasm(std::move(wasm)){}; +}; + // A frame of execution for a function call. struct Frame { - // TODO: Reference to current instance to find referenced functions, globals, - // tables, memories, etc. - + Instance& instance; std::vector locals; std::vector valueStack; ExpressionIterator exprs; + Frame(Instance& instance, ExpressionIterator&& exprs) + : instance(instance), exprs(std::move(exprs)){}; + // TODO: Map loops to ExpressionIterators so we can branch backwards. // Helpers to push and pop the current value stack. @@ -55,6 +65,7 @@ struct WasmStore { // TODO: Storage for memories, tables, globals, heap objects, etc. // TODO: Map instances and import names to other instances to find imports. std::vector callStack; + std::vector instances; Frame& getFrame() { assert(!callStack.empty()); diff --git a/test/gtest/interpreter.cpp b/test/gtest/interpreter.cpp index 6bb3b9cc302..ca1beb6c564 100644 --- a/test/gtest/interpreter.cpp +++ b/test/gtest/interpreter.cpp @@ -38,7 +38,7 @@ TEST(InterpreterTest, AddI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(3))}; EXPECT_EQ(results, expected); @@ -55,7 +55,7 @@ TEST(InterpreterTest, SubI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(-1))}; EXPECT_EQ(results, expected); @@ -72,7 +72,7 @@ TEST(InterpreterTest, MulI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(2))}; EXPECT_EQ(results, expected); @@ -89,7 +89,7 @@ TEST(InterpreterTest, EqI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -106,7 +106,7 @@ TEST(InterpreterTest, AndI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -123,7 +123,7 @@ TEST(InterpreterTest, OrI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(6))}; EXPECT_EQ(results, expected); @@ -140,7 +140,7 @@ TEST(InterpreterTest, XorI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(2))}; EXPECT_EQ(results, expected); @@ -157,7 +157,7 @@ TEST(InterpreterTest, ShlI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(48))}; EXPECT_EQ(results, expected); @@ -174,7 +174,7 @@ TEST(InterpreterTest, RotLI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(16))}; EXPECT_EQ(results, expected); @@ -191,7 +191,7 @@ TEST(InterpreterTest, RotRI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(1073741824))}; EXPECT_EQ(results, expected); @@ -210,7 +210,7 @@ TEST(InterpreterTest, LtUI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(1))}; EXPECT_EQ(results, expected); @@ -227,7 +227,7 @@ TEST(InterpreterTest, GtUI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -244,7 +244,7 @@ TEST(InterpreterTest, ShrUI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -263,7 +263,7 @@ TEST(InterpreterTest, LtSI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(int32_t(0))}; EXPECT_EQ(results, expected); @@ -280,7 +280,7 @@ TEST(InterpreterTest, GtSI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(int32_t(1))}; EXPECT_EQ(results, expected); @@ -297,7 +297,7 @@ TEST(InterpreterTest, ShrSI32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -316,7 +316,7 @@ TEST(InterpreterTest, AddI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(3))}; EXPECT_EQ(results, expected); @@ -333,7 +333,7 @@ TEST(InterpreterTest, SubI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(-1))}; EXPECT_EQ(results, expected); @@ -350,7 +350,7 @@ TEST(InterpreterTest, MulI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(2))}; EXPECT_EQ(results, expected); @@ -367,7 +367,7 @@ TEST(InterpreterTest, EqI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -384,7 +384,7 @@ TEST(InterpreterTest, AndI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(0))}; EXPECT_EQ(results, expected); @@ -401,7 +401,7 @@ TEST(InterpreterTest, OrI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(3))}; EXPECT_EQ(results, expected); @@ -418,7 +418,7 @@ TEST(InterpreterTest, XorI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(3))}; EXPECT_EQ(results, expected); @@ -435,7 +435,7 @@ TEST(InterpreterTest, ShlI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(4))}; EXPECT_EQ(results, expected); @@ -452,7 +452,7 @@ TEST(InterpreterTest, RotLI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(4))}; EXPECT_EQ(results, expected); @@ -469,7 +469,7 @@ TEST(InterpreterTest, RotRI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(4611686018427387904))}; EXPECT_EQ(results, expected); @@ -488,7 +488,7 @@ TEST(InterpreterTest, LtUI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(1))}; EXPECT_EQ(results, expected); @@ -505,7 +505,7 @@ TEST(InterpreterTest, GtUI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -522,7 +522,7 @@ TEST(InterpreterTest, ShrUI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(0))}; EXPECT_EQ(results, expected); @@ -541,7 +541,7 @@ TEST(InterpreterTest, LtSI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(int32_t(0))}; EXPECT_EQ(results, expected); @@ -558,7 +558,7 @@ TEST(InterpreterTest, GtSI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(int32_t(1))}; EXPECT_EQ(results, expected); @@ -575,7 +575,7 @@ TEST(InterpreterTest, ShrSI64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint64_t(0))}; EXPECT_EQ(results, expected); @@ -594,7 +594,7 @@ TEST(InterpreterTest, AddF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(1.0))}; EXPECT_EQ(results, expected); @@ -611,7 +611,7 @@ TEST(InterpreterTest, SubF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(-1.0))}; EXPECT_EQ(results, expected); @@ -628,7 +628,7 @@ TEST(InterpreterTest, MulF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(3.0))}; EXPECT_EQ(results, expected); @@ -645,7 +645,7 @@ TEST(InterpreterTest, DivF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(2.5))}; EXPECT_EQ(results, expected); @@ -662,7 +662,7 @@ TEST(InterpreterTest, EqF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -678,7 +678,7 @@ TEST(InterpreterTest, SqrtF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(2.2360679775))}; EXPECT_EQ(results, expected); @@ -694,7 +694,7 @@ TEST(InterpreterTest, CeilF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(2.0))}; EXPECT_EQ(results, expected); @@ -710,7 +710,7 @@ TEST(InterpreterTest, FloorF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(1.0))}; EXPECT_EQ(results, expected); @@ -726,7 +726,7 @@ TEST(InterpreterTest, TruncF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(2.0))}; EXPECT_EQ(results, expected); @@ -742,7 +742,7 @@ TEST(InterpreterTest, NearF32) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(float(2.0))}; EXPECT_EQ(results, expected); @@ -761,7 +761,7 @@ TEST(InterpreterTest, AddF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(1.0))}; EXPECT_EQ(results, expected); @@ -778,7 +778,7 @@ TEST(InterpreterTest, SubF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(-1.0))}; EXPECT_EQ(results, expected); @@ -795,7 +795,7 @@ TEST(InterpreterTest, MulF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(3.0))}; EXPECT_EQ(results, expected); @@ -812,7 +812,7 @@ TEST(InterpreterTest, DivF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(2.5))}; EXPECT_EQ(results, expected); @@ -829,7 +829,7 @@ TEST(InterpreterTest, EqF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(uint32_t(0))}; EXPECT_EQ(results, expected); @@ -845,7 +845,7 @@ TEST(InterpreterTest, SqrtF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(2.23606797749979))}; EXPECT_EQ(results, expected); @@ -861,7 +861,7 @@ TEST(InterpreterTest, CeilF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(2.0))}; EXPECT_EQ(results, expected); @@ -877,7 +877,7 @@ TEST(InterpreterTest, FloorF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(1.0))}; EXPECT_EQ(results, expected); @@ -893,7 +893,7 @@ TEST(InterpreterTest, TruncF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(2.0))}; EXPECT_EQ(results, expected); @@ -909,8 +909,63 @@ TEST(InterpreterTest, NearF64) { auto expr = builder.build(); ASSERT_FALSE(expr.getErr()); - auto results = Interpreter{}.run(*expr); + auto results = Interpreter{}.runTest(*expr); std::vector expected{Literal(double(2.0))}; EXPECT_EQ(results, expected); } + +TEST(InterpreterTest, GlobalI32) { + auto wasm = std::make_shared(); + Builder builder(*wasm); + IRBuilder irBuilder(*wasm); + wasm->addGlobal( + builder.makeGlobal("global", + Type::i32, + builder.makeConst(Literal::makeZero(Type::i32)), + Builder::Mutable)); + + ASSERT_FALSE( + irBuilder.makeBlock(Name{}, Signature(Type::none, Type::i32)).getErr()); + ASSERT_FALSE(irBuilder.makeConst(Literal(int32_t(5))).getErr()); + ASSERT_FALSE(irBuilder.makeGlobalSet("global").getErr()); + ASSERT_FALSE(irBuilder.makeGlobalGet("global").getErr()); + ASSERT_FALSE(irBuilder.visitEnd().getErr()); + + auto expr = irBuilder.build(); + ASSERT_FALSE(expr.getErr()); + + Interpreter interpreter; + auto result = interpreter.addInstance(wasm); + auto results = interpreter.runTest(*expr); + std::vector expected{Literal(int32_t(5))}; + + EXPECT_EQ(results, expected); +} + +TEST(InterpreterTest, GlobalInitI32) { + auto wasm = std::make_shared(); + Builder builder(*wasm); + IRBuilder irBuilder(*wasm); + wasm->addGlobal(builder.makeGlobal( + "global", + Type::i32, + builder.makeBinary( + AddInt32, builder.makeConst(int32_t(2)), builder.makeConst(int32_t(3))), + Builder::Mutable)); + + ASSERT_FALSE( + irBuilder.makeBlock(Name{}, Signature(Type::none, Type::i32)).getErr()); + ASSERT_FALSE(irBuilder.makeGlobalGet("global").getErr()); + ASSERT_FALSE(irBuilder.visitEnd().getErr()); + + auto expr = irBuilder.build(); + ASSERT_FALSE(expr.getErr()); + + Interpreter interpreter; + auto result = interpreter.addInstance(wasm); + auto results = interpreter.runTest(*expr); + std::vector expected{Literal(int32_t(5))}; + + EXPECT_EQ(results, expected); +} From f3d100e0f017d391035ce1c627edf2b47a516dcd Mon Sep 17 00:00:00 2001 From: Congcong Cai Date: Wed, 19 Mar 2025 04:31:37 +0800 Subject: [PATCH 357/622] Fix compile error when enable WASM_INTERPRETER_DEBUG [NFC] (#7375) --- src/wasm-interpreter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 0b55dd00882..479d246b2d6 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3824,7 +3824,7 @@ class ModuleRunnerBase : public ExpressionRunner { if (ptrFlow.breaking()) { return ptrFlow; } - NOTE_EVAL1(flow); + NOTE_EVAL1(ptrFlow); Flow vecFlow = self()->visit(curr->vec); if (vecFlow.breaking()) { return vecFlow; From d79639a355d0b3098d212164106ed0a9162f34c6 Mon Sep 17 00:00:00 2001 From: Daniel Lehmann Date: Wed, 19 Mar 2025 17:25:54 +0100 Subject: [PATCH 358/622] Use mimalloc allocator for Linux static build (#7378) The new CMake flag MIMALLOC_STATIC controls this. mimalloc fixes perf problems, especially on heavily multi-threaded workloads (many functions, high core count machines) on the musl allocator, see #5561. --- .github/workflows/ci.yml | 2 +- .github/workflows/create_release.yml | 2 +- .gitmodules | 3 +++ CMakeLists.txt | 29 +++++++++++++++++++++------- third_party/CMakeLists.txt | 15 ++++++++++++++ third_party/mimalloc | 1 + 6 files changed, 43 insertions(+), 9 deletions(-) create mode 160000 third_party/mimalloc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c98dfb349e..4c4a4f27248 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -250,7 +250,7 @@ jobs: - name: cmake run: | - ./alpine.sh cmake . -G Ninja -DCMAKE_INSTALL_PREFIX=out/install -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIB=ON -DCMAKE_INSTALL_PREFIX=install + ./alpine.sh cmake . -G Ninja -DCMAKE_INSTALL_PREFIX=out/install -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIB=ON -DMIMALLOC_STATIC=ON -DCMAKE_INSTALL_PREFIX=install - name: build run: | diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index a71ad6da9d2..f6b8f7802ad 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -140,7 +140,7 @@ jobs: - name: cmake run: | - ./alpine.sh cmake . -G Ninja -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIB=ON -DCMAKE_INSTALL_PREFIX=install + ./alpine.sh cmake . -G Ninja -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIB=ON -DMIMALLOC_STATIC=ON -DCMAKE_INSTALL_PREFIX=install - name: build run: | diff --git a/.gitmodules b/.gitmodules index 990b797a0e0..a674125e389 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "third_party/fuzztest"] path = third_party/fuzztest url = https://github.com/google/fuzztest +[submodule "third_party/mimalloc"] + path = third_party/mimalloc + url = https://github.com/microsoft/mimalloc.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 01332f33791..b48ae745dd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,14 +49,21 @@ endif() option(BUILD_STATIC_LIB "Build as a static library" OFF) if(MSVC) - # We don't have dllexport declarations set up for windows yet. + # We don't have dllexport declarations set up for Windows yet. set(BUILD_STATIC_LIB ON) endif() -# Turn this off to install only tools and not static/dynamic libs +# Advised to turn on when statically linking against musl libc (e.g., in the +# Alpine Linux build we use for producing official Linux binaries), because +# musl libc's allocator has very bad performance on heavily multi-threaded +# workloads / high core count machines. +# See https://github.com/WebAssembly/binaryen/issues/5561. +option(MIMALLOC_STATIC "Build with statically linked mimalloc allocator" OFF) + +# Turn this off to install only tools and not static/dynamic libs. option(INSTALL_LIBS "Install libraries" ON) -# Turn this on to build only the subset of tools needed for emscripten +# Turn this on to build only the subset of tools needed for Emscripten. option(BUILD_EMSCRIPTEN_TOOLS_ONLY "Build only tools needed by emscripten" OFF) # Turn this on to build binaryen.js as ES5, with additional compatibility configuration for js_of_ocaml. @@ -345,11 +352,11 @@ if(EMSCRIPTEN) # in opt builds, LTO helps so much (>20%) it's worth slow compile times add_nondebug_compile_flag("-flto") endif() - if(EMSCRIPTEN_ENABLE_WASM64) - add_compile_flag("-sMEMORY64 -Wno-experimental") - add_link_flag("-sMEMORY64") - endif() + if(EMSCRIPTEN_ENABLE_WASM64) + add_compile_flag("-sMEMORY64 -Wno-experimental") + add_link_flag("-sMEMORY64") endif() +endif() # clang doesn't print colored diagnostics when invoked from Ninja if(UNIX AND CMAKE_GENERATOR STREQUAL "Ninja") @@ -455,6 +462,14 @@ if(BUILD_LLVM_DWARF) target_link_libraries(binaryen llvm_dwarf) endif() +if(MIMALLOC_STATIC) + if(NOT(LINUX AND BUILD_STATIC_LIB) OR EMSCRIPTEN) + message(FATAL_ERROR "Statically linking mimalloc is only supported when building as a native, statically linked library on Linux.") + endif() + message(STATUS "Building with statically linked mimalloc allocator.") + target_link_libraries(binaryen mimalloc-static) +endif() + add_subdirectory(src/ir) add_subdirectory(src/asmjs) add_subdirectory(src/cfg) diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index fde5276d7da..9bcd9838acf 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -15,3 +15,18 @@ endif() if(BUILD_LLVM_DWARF) add_subdirectory(llvm-project) endif() + +if(MIMALLOC_STATIC) + # Using a C++ compiler avoids warnings about -fno-rtti with a C compiler. + set(MI_USE_CXX ON) + # We only need the static library, nothing else. + set(MI_BUILD_STATIC ON) + set(MI_BUILD_SHARED OFF) + set(MI_BUILD_OBJECT OFF) + set(MI_BUILD_TESTS OFF) + # Do not show debug and warning messages of the allocator by default. + # (They can still be enabled via MIMALLOC_VERBOSE=1 wasm-opt ...) + add_compile_definitions(MI_DEBUG=0) + + add_subdirectory(mimalloc) +endif() diff --git a/third_party/mimalloc b/third_party/mimalloc new file mode 160000 index 00000000000..e1123000597 --- /dev/null +++ b/third_party/mimalloc @@ -0,0 +1 @@ +Subproject commit e1123000597b1abe38164e09d5713652e1e82e59 From 2a84e5a108e61e7db326a226e2728c8b93a0b28f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Mar 2025 09:51:28 -0700 Subject: [PATCH 359/622] [Docs] Add slides that explain GUFA ("Just Flow Stuff") (#7379) Simplified slides from a talk I gave, perhaps useful as docs. --- media/just_flow_stuff.pdf | Bin 0 -> 221244 bytes src/ir/possible-contents.h | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 media/just_flow_stuff.pdf diff --git a/media/just_flow_stuff.pdf b/media/just_flow_stuff.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dfcc539e591d70b271b89d211c759d526f7a9789 GIT binary patch literal 221244 zcmeFZ1yo#1w=UWQZy>lsfZ*=#Zow_M2O16T?(Tu$?(QT=@F2n6HMncg+hp(k|NET( z+&j)a=Z-txd*d~uy4I>St7?_>oZtH9T#ze@NzgMhumQ=5H#SEnfvi9#pskSwkdF_@ zsOt976v!xQ=xAtVYX)SLH?%f&c)1`T0Ay4&G&6l?Xk=>jyM$5R$=U&^4X*oAPX)** z2L5&gGQWuG{+3s8ag_QrO?*JBfIROiq+L*lb%J#S3MeR)u9c}G_R8P+U zNq{Z*&kSG%Fr^0H7s%1d^mn-^xb!2y4L}UeKY+i50Y(4^fFr;jU_?ik>&I#P=2Vz3-nKt-3 z2w)9v#R{B%04Gy`3Ap?NSlS+(n*v<_?8gS&gB7^M0bmY3vIk4rfbU$F`ftNz|JyMC znBa^U4I|WMKlbF|mW^lTp;x#t|&&0Oa^XQrXnO z*2&%&JiDBK6bRec*n%%}|8XX1Z|n5&ua!}?H?(p1_|lQF8@Q#HwK8=E8Jj9gioD$6 zKiA@=?Z0+{1jx$K)E>wvVP)uODrRbIYXZL8ccwOGj^;qF-<^Q#IXHq>%o^yLex#*q ztJ8$q=DTt`mn)PNQD3tZNWo2J_C6aK$wb?3i6VN7%Q`*8U~&$wGM+G8i)NW7~yU z`c-q>rXq)PRdEJP+P6y=Z0>TiFz7;X+NfvD7<42qko1BCEysf{ltmLarD$} zj{hY3RG{Zd_{M0p$9;10*x|vg>Yf$m#Oe81;IO@X^3aL#=BvV4f5#dN z5@4#R3aXK4Oy#~q5E!7GW4u0)Kwnf5K-o9 z$dhyx3{D><-d@{hiA^j2IA~gzZFy>@k0xC$JVU@!y%Re--Y$;6YW-nuATI$~-LsV; zq%U_9R|A3R*ZQ#pveZh^vgL_Bdjz2~qy902uUAx`Q_}N@lmV?dXXp*wAxv*#P^c2=;(8D;mzWlV zdjVsLB|oWch2k9tBCEu;oz81dyoNW5Id(z*o?Y}T)+W>b;!yjsUlQTptrVC$RLY`G zY4*GWj%*vcT?l-znm%@uxL4;Ly44JeND;4^v{zR_WRpPD%h=9hIz!+5aU&u5Fz$KP zz`EL%|NRH<-JI*yuT5Usjxu$DFRt;%l6Wmj3h?hMi!S1I*ghsR4=PaNi1>;7!xQJ^ zior)ZR!ltHJAXHQbsG3md@}%U#jZtI>q$6hr?Dyi3Oc# zzB+G)Rd3z2IKb7$J%@NIm2b{&%Owvp1u33?4A0Gk<7Tihgd{ul9V{jDx4U36MI?6V z_J^=-ZyBcV$UsAs`Hfp|V z0g}MC9yX8BwNopdFg*-k>VSOEvBq5_MTtJ8 zzA6!PoMr29vhP#!$9LC`h?5Xq1b-=)q% zDfoVS9ctW{q-%$->~yQ7@2yH;$Nrn-qv5&I`Co|F72%)OBPh(6={YKQ?$8%A8dj|> z^(pd5@G0pcsX%@~tn4wITp zs*-P>Qy{qyoM-3S&mS|7RCArg6|}U z1vJT@B`xC#$fkGOs*5jdi=}xjc#CDASwl&g?$JZIlY2PoUA(l**hE ztCuQnqjTv-6)nW7ZobbERnGHdrWeg2t@yk<8g@>}nOkj+ySLVG;8wSLSpQr|_5D?D zijDXrd2eD!_y$WY-1Hoc)VW&G26_srgK4j3_q$((yVN)Jxr|aC-$**w_glV#x@<)5 z*&Qvq(c=B|ZS&CBG*lU_(Xre*s^7pYyrIiEjAE#dq=>8%oTJx$Z-5#mG4DMY1WiNr zDmC;c2c6BOjn>9kh;>c@>SC@eWux%U8|ofnsD^i=e$7^epdN-ro4e`=_fMS>SlGBx zA8gB)z0LMr=BJX0g{6?NB2flFJB44H^r&~RfR||*IEr>8=b!40Fj8k5lz??>xYeq$@ z*Q{@JB%66HW0z@l-IQy-^Oi3nS6wF`y8ucJ>GSzVqe%%~mY~8{Jtn2rmJGN}$}OR} z4WR9cUFvrY_?OHT=%nJvA$L79h}TG==$M%7j;qnOV?Td=OG7)|@b=1#XYJ)8_Y-2? zvYTNIKv_T*5cu_TT0epN)=vJF_IwT&bRyK}TaH&JJ-*k~PuFw4UXU`5L9o@htJzU{ zHZtFelLG@|HbXbXS?|^c3YF@N&c!hxV}`C;-u}$*IG8d{d5WwRdVICc#|Uzbb-{hGjJ3CglAx>KV+ExhToX~jxtr8j2wTXd1>q4 zNK+aNAniZezQ8%4qrDRt&1ry4z+jGrlM~1&WePGgcLZ{=zaUL3TYHs{hQ?rtf8s<& z5s;&UqN%;8t@TG+n-|E+1;&PdVni`f)qjT&ng2kg|AdIobI)r46d4I=2>`?^0008~ z2Y6lthyq}spkbh)U|^tOU}0h4fG9v9JUkHNH8LU!HUe5Qt8NhlNM=zx{ds20(=as6k*tLc9gM zLWO`tg?R1(5P|O#8shf{@aG5O6(sn6Vd3C`2w;H*6u>J8NXS=EkkHUjP+(~<@O1zb zDl{4ivoH+0k|FF{I}8@TxEwfAk(y3SKhuHnp?hfb@%l4^?x52oS2-No|&DSUs&JR+}i%Rv%9x{c7Abrb$xUD z>+WS-5CF(Oh6VoqV_^SoT&Uo2y@G;*go1q;7sM+UaDqgIf+k^xK@(PjHMB#2%i;%z zArhBU(+N+?s(gxRWIqnXB4b-8KYJP4?~(oI2Il`?8reSv_Rn!G01zP|z>5co3J?U` z6^2t~zY0GSoUhuTXxp6J7`>JH5UyXnEP3t2KrR=0|0EAfY=uKfnUJ72F9eODBm|AQ zk;!};Rt55@Hw)$?2eHZKtZ38fh!`nPu>Q*8I5aV~Sc8mi`Qf1pz)%wZ50Yjk#ccg5 z;;xH(Uz2^~7q8NjcnPSRZ-Cd*WWIoXIRI!}PiGa!syu)E!x>DBOCIRUWa+$ulO1fT zsMoBk=!qIPLF6VWSCveAYvbhM_Sg32@P!#nMzTpRG2wmbH6UQ5<%}c3dV^Q@m6yys zds@nH^?dT1FWJQ*NDY!KVGcq^-_KLD#C7n_+J2TJ6CEO_I%?yU z_Bk>qq5?G$)Z0k(0sbI>^X1Ff)ew%16>6sH?-7QYbK0hGXJ)XuK;aBO5iSX)?*LS} zHGH~nnjN!vyU|6yM12hlYTf7i+5q^8WmA%V+MNT*jSB^76aMW~Fua`4Fk7-@uS@Fm z2&uQV8*TAj5i)x8F!Ck;fwwP^D))C1Fn$bozukFir|-CK@Z#2Ie(L4-ba)2nW3Aq6 zwWouZnJOFkvjFKcU|`*q^QkT88L%*Yx$HS`M)ahB(AjZyg#0v9Rk#bt4X65-RF(D& zu%vBqia~Kj@8me{WTzb`+eOy-(u?~H=+Ce6IAb}8DquQoO|^3Ww7swp&fDG8E`s8) z_rtTStbbDw-Fpg)BVlxXk54#PJC8P>6@d*Htdz9enm)*bI>Ju{xx#08>eHRcbQT=5 zfuvalm1-poTUEoDl+VZ}0nkYe6>&5eVW0@R-aO zL?27DZpN!Ts!(rLteuDgT8Y-PUTg0=qby*w&Qwt}#xK5pechzHhW)`FqEATC-tAS} zm-=`m8Cuh#<=LOdX4u0^S-L)Jo=GtTw>PB>wrs3cd)~7qKsJ4tmS)__qOAMv#Z(>Tmu+^_xS-V)C0bRoP?{90*^;bl5L1AV# z3J-P0+dD)Na=&KVR1;r*dv1)qg1_ii;hq5pn~KkXad^E3J=rsbCt1!e!Mocj!AlX1 z3PlKU3`N**OBd4h)&6Cc4Pv7)PxCCZ7U3JLUvLP-gb_C40$)=u!mq!aF)r*+NZe|} z6j{mW>}e%pOH=JU;HtV?a1V7b7xc^oy;60b}3^2-U z=Y9rMMc6)8=*w#i`_w4t6FdWEdz-*JuITq}gZ!xw`V2UC)k6ZynfriuFsD9?r{psr zq3hK4(L?kZ&``Gb1wd8FF)DH<^B~tYWAh+VwI1Avs=X)uhNB%uGn!wRd&I3Dpmsvw zs^+O?3NTRJBvwiv^(^X;h`YZ;*p^pcm}MKoIL~l+VwH_M0?$7 ze1nAw#X)&+&WA(s$L=%PAG-7^iJ^2jdW=`A2kxfYl zw$F#K!_Si$aR#N|uEGKv!ORfhagx>t-2-{YZ>1nHZgpG-v&rx3((q8M9GHEs2< z_OMHSyl|>~rm9pyGyU7*CvH!vGs)E`G7neq>^e(FuRb`V>OBKC=~d13P-m`;?5=64 zD`BTmsvpH&M-Pn)@D+j=f(zbeu_{gx1Pk_rmgYSJ-Y^Qic?2*2?=3Le0T8f_%EF$1 zx}9PY$aGv?qur%BWekaX1{gj~Jqb)|Z^bCr%I?8a{=3^w^eG4tjEE_>p6;*FxaQ$tI-_oU8ESiNo;Cyw#7xI*` z=htPE;4fB8M}1iEF7|=_bnuv9A&5+Erz3b|=V{&kdP%E<{N%*?C`nSCq;(i#^-b*`FdmjeJJ*wB22}LEdwW_f2Ss^!k%y~GX-yzd^Yec1tIobeqKAR znAB$>Y$x|kl=;7YYFP2T&|B>iwqF55 zFi-&7(mfF>qA}udX3Ggp$~oZHB&(}XAxZ!4{6QxCr}I-s8NtmreQb+%@@YOZGZznr zg1kNVkhr2%`BJht`+0R5fR&-VT~^wjUYnlisrv+D$P?nJZ3uKt*87>E<)t7i96z}e zQV**0$)z~GfQ@GW)~+}5r1(u}16=u?P9J#slW+C1JsXCfkCEYU#rqL-7lDo7NPa7kj z0T);^#>@5x2bdkUMmsQ#eDtSP!sAyEWfoBm8$>8?w3VmzN8%Ljf!?Z+jWCiG3bY4B zKh9W-xI6s4NzeMKigkp`7Uhav)i53twA&l8G+3VjE;pLIoln=WCEOb=`U`p= z5;m&3zDfwT+`HQIn*2a{O1iXmMLtp}9I$p-i@_?zY8-DM!+r<5B;jb#zepX^qoQyV z>y?VGw>7a`=yBz_cN#1abDeve@G5J$m63$7mMrEO5YB!nrrb|QO($Q4UTo^b4fRGC zcA^zwjWELE8GtQ7=GsM+CZD=@Xk9?$tRwI~Uo!ce&0&u}V6)qw?cr=Inb&6b<{ zV%v?|=(UuN6Ir~wM)}x_BKfUM!aQvn1Tb%d)>gw%gRz{Ip8+5C%ky}xx4vr#eEa%D zZn=m$Bc>sN7|DV|;LmSSSXZcdpry8wR`M+wOGBw%MeGq*s`D9ebzRB3KE=}?Oa?aT zqMF)i2fR{4Aopq!by+)Kl9XsHRVau|+>esP;ed&=s_qWp?TQDeRVlyVp>a@|f>jz< zQC{j7j@enID6Z z8^gWexPPQNKH2QOOp0+E#|-;%hC>DbnpjWd+7^l=alm)d)XCi7s^-bY;%}$3f%R3d zhsds}9oU!R%IO_kKHi}Hvb<4bjTebIj(q)S(I0ACPspXU>>7R`+kiTd=4QJU#nCS4 z+aVewZ>d+;P=PExYZy&vD_cF48`bnyPdk6GXbJ`vCU_A|8rSDE>z4N^cog{=ur^5a zL=$tX@Id$s5H$;V2ArCLOEKR)1A<)Es_xJqz&ODFyWnlA;JWTD?Ni`0;I+?ME%FnE zFXNs7&FOWG)3r6J?nKqG^QZF)ThgRco^)zT%$$<Ng!%9OrvEfu!NmPfx&q8W{GXvK{=G-*{|$7-d%*brPgne=VgA2D zSNu)?{aqLSXOCNmFg>MDLjteJCm%#B7d7=#dfH`!KP))-I|oDuf7w4<#HZChS+MMZ z_ppZUVhmX$F^%Mc{XuXcgzt0u^@+e|Y})J@;Jx*N>A-4j^>p(oxJ>Z=hVmJJu3Pj# zddKvC$icEmZZ0Hwtm*Z<7j0@`zW*_*vMpe_+S0fMkxJ!PJwPVe^uS{^_{bb1- z-C^R)uXq)iDY?9b7^6^0w5buS5FvnP>F!A*w=a5KCeq>dA!bLnsD^7XOMak1 z{nj$~fVJAPFSOe!=GT6RC6U#pv)}>-#NxO(20(*;Gs^k=+unjiU`=CN+4pg6wulyD zaokTXxVUincFUh7-S;i(<`&xX2=BFfY>mTmqz9w+BUNhyc4bN)paE_!J^HYPA!|ss zx{YTr?R?@z3(~Hs3HvPDTjKQfJ@|ugbA4mJ4x0K1?7Eg4vq9L}XG|>ta*Z*EXPWPI z+L{)3lr9Dj138w~XkGz$!$xtM?lmm~q;B~=tg0HVA`gP2PZM&^ZyxHeGvBx^r z32rskGuRVMNVr#t9K%AI`dMMtqGy1%oS%qSd`Sn!yf(#txRcu6gV4 zQPd3IVYta6FOO-L%)#u=xi0pTJowJ98HJw#dE)NVZVVC607~8s#(V5+@RNP6FTcfK zyFXs?{dlGXFdY7au`g^%LHRQPWl!du9aA3vHycCjC1 zfuFl^7`AMoR(-nI*OHv0@o(aCQ*U1*M%3HRc_1gEP9=QntDh7ge?aR9(41~aK4T1M z?UfUTs+AcdMM3n_Pa>U`hHaHcxhrFOca+56d;)`zZ67VIY>tSskx|~=C^%rf-@L&MfVZ52%2%QumKC9h;UG=-L{-z^25G!$Ogm8_OY2Y&*s<#}g=584Z^1ykE&YIf6Zn4y{d>mQI~Ysh?^BWvhCNhQJ`! z@`op8rOuvDePz|!BXIwGvpdYR-NPfIgaK>CRNQEP0?Tj+nv`qE1JOgGf+A!6fY40; z6I`B26DPIrSW;jB;P{wlD%K@e>peRWw+fvrZGdnh#E z2vXp)I_X^WT&2SG;7sb|`p=BqZ_>@vRf$%FT3me_f+ux^9+du$YNtB^m%6xsXMk{B zI1K4m8sGjiZow#kwTAE0z{z9AGXVZ3*r6|>yb*hhWA_md}Rn zvtEPwE{6y6zaN9m$q|^B9c$SndcbG`wQX8)&(86r2tM(`=_1mj)(MT z0N#-&&{NOx7nsQ6!9!`g5OjEq`*}DYvvI*a;xO4@r+?a3FL;N|XADN%pTNedtl^X~ zlAh1BXw=2nuyme`#m&?SK?Rbz&%R|}AQ58XMZYSE+xuWSZLVhpx06oCUCTnJg=$Zz z0>KlhK07kFyQS;%sDSKiCjK%e@ubct$ky+t6z>p%x+^<{3SHlNiazIEE*r&aiQzCS3nu7AS8f_>H}h<6b)U)8gK6?Ymw`);}cpPyA} z;zM)v)5^J8uvE(MAp)(z;|!!gk8{~GfZ&PJHK0?gJs|H^QdCYuc}i4jh|HFFt{mH< z6vbY8Umli#;B3Y9lN_Y<1o;_bIGy8d>L4mx!VsF&S**Z*4K5`gq7zpy!1G|^(N2K+ zX(AJKqJn-$msG}%mn-t&$$EyQ#+hWO0Of5oy!CNI zveV>RqCvq|6qk!di`=T(gt73OeL(p6Pg-8-V;dbwD5?pmo!nwv%5%&OqFV99s0uAtN!U6I zKu?cPx8{q6hpf-_WqMFpk03&MjF$~#sIV=cPo23QQNcLH!m+McLV;2Be+?t~syb`E zFxkh$)?vd-RQ-&k(fU-p^j$E{;{A%Md1(J~;;e)Ci<1r#pg0d$Gr0zHdjw{I57hRl zkE7wOiesZ2w29|$qbcZ-g4*p#LStDJ)IAY1eSwF&f4EQna-;n3=7Rs>BioB#_jlMP z6F1jC+}D4(xmj6R+5Rg0OYkK+R015l_&XT!<$wjq{AWPu%NZ+>`6d1o93J{|#s*}5 zi3j~19|{hQbOd{>ZC)yZ{m6{(6^txQjsIxpw@aHH_}kwNuKN3w1Ni%S@{hXWu8xu_ zjxVn6m;QyVKAIac0e`#M!Q#J9nSsCk>3?=*Z)>b#>InA9GlGL-!QrN+u8w~;EAnS9 z`e!cvM|A67p8LPN`7ho4x8gd%p`O2|;h%w>EPn=t{u$ZH^5;DL*FrmgH~+G5zt`h` z5!=b~$Kw3cbMKn|Lvz_yg%g8wKaCTlFsTQV2p`p0+;n|*v%-Cf8OjHk6s#{{`{?8P$4_e*5OIBh)oXPvpA*!MPhANn-k>Yj4?Q0T&}#@L28 z>z~fzKful6SSq=BR-krDUeQ`l_D|bONh!4Bp>pQ`pmieFplI3iN0DBTu!%3XZy?K~ zQjD0gm0EBmLIW{X<{$=%+YKTEthC_Ugee6u)uQV^>6FN5VLu>!gIxCBU!TAK-sHT} z@s+}cuJ2MW=8Ht4wbOPIv+#vpl-CKS&&X_wF|1Wg1(Olwp;dIKSuts)xUl!uYd)Mx}#AaK>7)=rywUvZ;RMg~Q6?PK^7-9>Paj`^0HC>x`Qf ziRfqMh`P0s02GpSX=PrEggYc@ck&~uZ2E~;p)%D?;gO$VN&>3neKERCL$j_gt1YjM zqO7aGzN?0{a$?fHVAe>A1wwpEQtU(17iH(<%srvknH9~%B$0#PIpCr!iV~BR+5W!E zRK^x1MitqFE@s30;|&83Cwe)E4mE&{oJP&ZQO1-N7|)z0Ydr7{#c7!4eJyL~pFwMu)ZO`Yw0082i3c%oyUnOw=4cJ2X>6lsXCE~p=2Q3T6{9@^9zN)>>6V_33I-Ab0|6(o%TISji$At{{2td_s8%7IQxV2uw6*|X6Myy280ZT2*?;z8g4$jQM<$% zP;z~LD?cHo81h&gLoA_zoAjeP5T$_1tm`~I5Rk_*@gbk?n2cVizXcmoCQ-YI4sz;) z@OaWV%_)MoKrg4|cK+~u4WBCe2N`HXR%I;xayGZtp|)z49vQRx0~R_L^opWp>CdS@ zc(=GN?AfhtNR%p#RN8pbhnx^w@U*{_f7P6DOu;HE+H&2XQHe5HP-H6|va5XjjO+62 z6z@=X$v9pH@4IuRzCEaI2E++d6}h0s&_kF?5{IZKWbxLJ`2h4*iJRPWYyM3pxdjN@AOABV|=Ofa^b<38m8^IZ?k2k|DZOUeT-NJy?f$Pmh{i(^d9KBp45O__z*=d-I zH|gAfT$t89)LgpY#e-4W8FBUKF4`@u(!f1_s&yIx$jqNqQXY6 z)*>FV^dPB~Ne|cY>6{i3BQ@H+buK%}lo;B3n20d3y#kfZ?m97L*VWRnq3ey&@hss_68D@SzbXgde?lqo*M-tx3yAV^7wQVdf7R8DrRK9FWs1!4$C{;!)M>29-hWI-*vLuW9di=`&nTmwK!8bJ|8T^C!W!Ujcoa*mO3* z)mbIG4!)QQs%g|xppLWeK)=Ub*zjL@(BCTeMK?3Eaxnd6KYy$Hm%_iriT$<+|4`!p zbEC=$WckC4{x+)rB@_E%asO`_QI@}lt^N0m=)b%j!SY8S8slF^^uP6f1k2wE>%W5E z{-YNpSpGeq{PKpx4;zAhR2yv}|4VYFSCq+fpCC!GaEs^F9<#I&2PiP0!>J_L3~)aR zbz?;qSkJSZ1KqxFG24%7w55N}TqbEq*B#^ug*+bdYbiZ>Eca~@fvkP>NBR`LNmw6E&*#f5 zb)QG$q=NhB^#jeRto^F%D^UX4{EC6wn9fgh8~8*A`da6PvY+d4YYuA4?2m4hx3!U( zNUoKNQ6-Z1UI~7%K_4* z*2^*SWRvK!vwzj=KHVwQ_2+F_jV0X4uADOoMpaC5qu`UWPoZrI@^sE!mV4+Z{=VS8 z5MfEoG@(;%5&xSn1;>!C#c#@$O1Ib^?QPlyZXvL>gvoi;G^(bt{OX|x-}GSg##}K{eUKXb zR^BEG1FNNfhn|5t-)s8yDzzB*%FHF@`+a^>!_~Bm#!87LT-n#c90P`Lq#t1Qlx0cX zR==IK7J+a|1`Aswo#X{jMnZKq)`kj~_z1>>2^bCBvRm&a8^5#|uyspWC6|`l4GHQ` z&IdL2$sY(>ZDtB&aBE zdX+#>w*`llHNU_||J^D?cKlQxF0R2?X7-@r;P45MbW-Lrbut}tK_^G0nkbS$nVMlYTnJ)09`y--Vi$Nv9B9z%0{>HjQ}Xphq~ zla-9h7Ez5%oSrhtv;B)<)LSFb)4QazuJ70@pE~7e`RM#{im6=%F}bp^0x8b53&qw7m2CoYKHexjds#i25#^GziC!6fLDrbC1=qX(NDxz~4 z3Ev<3pu^F18eQW1vdgB-@DtriZRdlOLV}GlBvsa$!aYCDH^_>qY|dq<@w07*M}b9c z)r0xk<-I!xOYEg2n%C&a%$o&lC9wyNU8^vaoXGgmbCn%l8+y2G!sRWO=se;!Q zG!r_4H9BswkxGfDqN|E%ld+LBW5%Jgw;4N*Fo@Ia{4D*+YNlE|K=btgG3rMuZd2|d zZW+44OA@m_&pJtY=1?7`e3qpKfV%xP%zS${fpI(@5=Bvq!3Uc;ND&Vh31W34cnbHN zzIV)@F^Ge^38r}bL{P-BHsa-8BTa<)@EDjFi>OIX)|q#v3n3i?g@G1v^|~f)6`DCj z!pIpMqDP(Ld2w$Jr#Y(4v*i;fZ7Rx`7bfzb{iRPp6RYN$1ovrAhYn>Of$-=ByXbP6 zgkI+hrB&f-MAq%DxU5Er1!P|h_*AHp>GecXtv=cD)kqoO#L3sb!ISk@lW^P1boV{? zXkwsPRqU{;VjO*+d@d&QgZvjLb2ha_B$NdKa8gHIeRjU~c8+`Xi(70m7AfaR?930| za;J;e_)C1%F3>h3QCYB(s_z^@Tf3d6mId2{OPEoxbpryNih`kivk9&CT6Ru+@b;t% z3JkN!Fx@yj`BxXOEtgxn-5ai^VDl5!?^tFn%br@*GZI?h+!tsZYN|tMw-A(Py{@#c z8r=^bkmfh&+bX%-U9M2q{uXLh^no^KvvRi0@=ZHCIxbU=C{(eo!R;P$RGOo|* zOMrya7f4BPf`z`O!z)JLzO$dRMCjnb%1jtdCmk@eb0&pp66T>5|KP;_HPS3J{@YWI zfH)6tNygl+ySFmJ2udfBrUz%-l+A-SVea-l6rE3<_)juTqT17RX^Po2ZsXHhy3dk! z(*f2x;yb7?o6X^g_Bj1&uu&VOkl#n=KXKNe*6&-`{FHq!@m}~sv?7SOyoyFWy4{a z^P)^WkB+2s9|oJ40{F{uFR``B*^o65$&ZbIylvd2v$k6s+n-s@c|1zcctUIlw>>9S zzQUuh#uE0ZcC%BuRiI0oN$yw?Ma{pjjzjF=&Kk0fZ8PMCajmB1SrnrREeSUYpxc2m z|5nS2D_oBP5ka0nI0jhHh;R~}le|x(ZcY>$q7Q<82Q6F{zvq6%%S4{041}51!)ix? z-`l2|qX;D)fDMrNiB=KD#8G7KUp{SE+WIc5nSJmu#*sw&y@m>-Rs>G+;{A6!pws4? zpu49n9`>d4D;$yHuoU1x1d2O1kA%ADNYd9AtkC_n3y z-qcoY*rz>DFN4qcpCD-QPOtcDBN`JmwJKqQL*2#97Yi)N^vSju3{FK%oiSj?^AeC` z3fd1OX)5xm^P%pc<_BWQW-`iGV(VQ8rm%0CH5S^D9MQwdVy@Wl%9UmnhhfX1Jlf-$ zeH?u=<#(CHy=#Cj^nI8#bmGl=4 z3Osi&wrTxEtFy|_*yNJG)Lx>`#@^yf$>nGKPDyevxLkMrK6{bOtDX05Hg>bp{XE|$ zu5Xsqi7SFpMd*C!^ow*m{;U)C6S4*0-tm8cEC1qUSeV&3|A8z2Vl4iSEB}*Z&A-K! z{~&ArEy{dBj{kG&=Km(HWc~YV75^<<$@(W1^G|Yy^-m7#Ph1J+y?(Pd|21;vck}5)=Mfa4TKVn7#DC`G#BU( zr+aHr`h@Tpaldq?*J`=S1KMbVd=o#t?d;RU%bNM?N95607XrM_?iyxZC6USdkvBg5 z;-pZnoAruEgcq}pDz1y06Xup;_qX*OLOB&cN)-VY)Tfhk*avoMw|-8a)b`~^(bHs~ z#>Sr6`((8VmtPT1-pSG2mAB?{q^lR4u0vDpszE$$bZ* zK)wq_H9AvKq!Cu-A2sjFqU@lsOE_U0@-i8+Fsh z?zi|?m)tMhiCQ==hquJ#qp}x^`=l${HWD-sDMx=9w-2_PEgmuua?WFUd>ffKz~&Jg z;(%W@60C!@)EVC_J9PHgw7nePIaKnPe7x>k-7DlhB*_RFeJc3+riO9mRz;wtw@N|& zLvhlW)QQ*l!Zf>p8>ZM(%yTrl^mfpxH5qDRFGlJ$3Ku*Dvgxa+Q1Vz;Ql*1De^6LK z2vouwSfVH8n`*CXlcb6|*>}}tR^;vnCS9haLdq7H>{DU+$$g*M(&jyaqaWoz1gK^Q zI@KXcma&cD`qmdG30885lPj8=y;f=&z_>Row|$K(Cy0iMkZ8VjsL&vOm=aDrWj6+M zMN-J(NPYL**R#WYHAPfMWYY%ctq zfl_HD#lBt+`bgq06sj{}ugR-=L)hY(;WgB5sB!QXX)5!Zg$BVOoi{>$CAqcvUQSNq z(YUd&=rbl9T{Jm-ICq??XD*7|F#mnOuRi2n;XnNgUaXJYFz@26WMOp?pUfC;mM*VYp12`g}M4DPU;9Jot&`D zu|!ePwkyBt_i#O*QKiv;3Uz5418XNo&LZQzO|24WQaY-`gK{9H;aa%ISlUM^8VXQe zsYu2$$*GNCJ}4`o*uEzDjuU^0%Z8HW5NPihWS9}+mnaohKtdNyCS{n2_fB}oHJ%J2 zBUz77v1g*y6jk_$5<`?wI)EtY7tYMo#H+-4C1 zLST7On6l(p{>ZR55;tS($~%=*sj0L%mny$4cYLps{u%j$*M)45InzI-Qy6ue3Q6j< z!?a$LXPW!`Ou6?cCj0^2jZ>ZRxL9q<`mGS2LUAz|2w`zjj$k(OIX1%Z*BZfud!4AW zIm*antf$XxBGXB_Me%~O;!KRgrZd~p-Gi#y~-7}UxYU8 z(ZAZt7Hy9qD*Ne+65Ik&gPZhB^M_CUL2~{u)RQEKA$YdbG@80!dXE+F+g5C7vrKm; z4)SPc;&@8(d2H1*ca&+9XDG^FN!{qdzk6RVPb-GRC!Etmy$mR(Kb=L6~UY3aJwBVlZm&UmwycnV?tC zw3-ipg;&RbWS*g>nRW~te+?3sm6~-OzN?>z-`e)lQev`NDI!h0+JpPx#pB-w$%_e( zA*ZQ_#2a;5gub5YPC*{%{-Ko8NDQCGY$ZfZS6lZS(}>F8X1+T>A0~aM>L4QZ6B6JI zwIx0%*ZG>rIib*#Q))Yzl!h|!kwkbHJjXWMY+6Cq(#hLuu!U}>`MGT0F9v;`ct*?M zOx-X$>8jqQY$bs%V4PAlU@~e0T3LFOl6R7n!sXLta=2pIs>*MQe_jmwA$K@_Kc{)9 z6#eOjne$R4L2*>s_EFO_2z1sr^K{W}IQ-&32f97B%?agSxBj%{Vf&^{~rC@|O=lR1&~q(q4yry>c0wlsU_NVY`H zC&#$mEaSclW^&(QL;3oyh@HI${L&N8$7xcY)nj_6nWJ;dgMJ6=CdmXi1qvxy2OA|@ zDURE<9NRyz(Eo$b&;K!2jrCtx zHP(O2sE|49@X4tNIWSUo0)y*lHnlt$4p9r$#0t*N_uMNG!&Exs^}L@WyDOuQ)CrzD za$m9hfgU$ew+ie|O`A{nNs*vali=O@J>{5Y$W6LHaY>vb?{5C?YLuZWcbcZ^QyMw7+q&yt^x8|QT&d|o8fk{&!_JK%yN(A-l^<1 z&L(f)&#-x;_$G2;*@h8?9J)rf)DQs~Wg{+{{Vmx+%)y23JG@<@-KdOoaH;h$@#DO+ zu|H`0IdLvIld($rPH#Td5UUYaC|k+b(^b{cHL>Ze{X`|BSCNB5`Z`Q0<%kbg8%TyB zjDE&0$s}P5OffSIlBo}#!MK)06XLWe(-9rAk7o*rE*N#D60%&bMJ}4~H!2OlycLf_ z25FbKDOb21j+VjD7vwo2(GOwMCiWLin5hS=?vrT829C?$ezS+ZWYn~l!lRd2xmb^cDIF}2F|B*iBC!l+O zwdm6iC~f9<{#X1Iud11EV=F~IQe#bClR$lY{2)AHheooI`1WxcScBug$Gm9v4$?+h zHzsWo#m;OcKo~CBtQamx9NYTjT|7*7`bk@_q4`pPOa`%K0!f}f&Yb~ne29;HO8khi zTEF;cctB?!TR!b;G9!AKAqMg&f?AQ!!hR$3stCVCrsQjKpoLZZTNVrR5t z%hB(*%ab01l=vd^sEEz)UN9(|h1j-*+zWf_E!x^6Xgm5|+sQ}E60;6-FK~;QfWbl6Rb({uzCC7zQ@jZdDKN{M|2>V}lb!+{<2Z_#ecW){ggcU5!N1 z{!~%#kaM;hZlQ&zB;D@D(0$yIY3Kb@77EqeI(|N5H(-TnDf_SL{(;*98CCY z)KXB&#Ds*4zF z5WB?u@W8GNUZcUQJ?5k89N~uY`=GBkC{6@34fo64N9mJu5YrgOe2lQFTi@)5RhNce z7Xu0BAk@%@*cc~(Kla8-tJ8_JFykk4)P7PEYGDQvYMGYv)W5AJ(87!c7k-A|trF{vYJMWmFwlwlx~uCAho0ySux)yStO% z?iM7tLvVL@x8MXPxI1|im8$OQ>f58ot^U#9eg8OzF*ZlobIm#TTx&-*cw=sa`8OSp ztf+A`Y}?G=5F1q1+hUFekcI}Hm!WEr>0bLm?Wxl?(j|Lz0axkR0l3qqbWTH&EG6sE zNe6gzjl#}CncPsYqL#)%`oYd0cep%WZr-Rb+MJ_z;&Lxm$yftzm(gZu?DkDt5!=?t zP@nrbTP*A8d&i2E72Z%+eQvM~y`F8S?#;iU<}dTs=GP;P7m%=za3zl7p`BZJ7RGRt_88Bdq#@X)Rayisuna53;z>J z{JTBK$n<-b>y66&BPIT8_TYaHC1(1M8G=lIG6b3aXb3X>B}4FS`G1EJGyT~RELT~T zMWTndUFi*OM?<`hWX&}YIJgbAP2FPqhM=aUhS!b;1MirX^~3)`mouZSt)=Y{|0<7t zX}rGU)3|NHj3otHVU;x#&=|~BChY<9PK4w8GFGjvuzPl@uvKE^dA1SzM>CGt6*g=z zLJro@AGBuD6?{X?t6rQBKIfq`m$+t_OsVXqHbEpqt zRxySSLKX@!(T7ISSTTxd8t_>0x0CaFyMr|7Sx#a%LttvmXtwUKTi9P6!F4Uv>N;df zE1XK3GwtX7nNnivD+8Z`Cr-LQ!=?w*c$S+5aeqs!)P67#qC6wT!z+*LUlGa5Q6Tjq zB7&09FeJn}Rk#b_YF|B-q>HpHOY`>>2ko;}AlRYDd&mRZhSgUBJO)8n6@1uAMtHm{ z4_buZBc)sNf9FRx#2DeoW_?kq?xwh>4n#i7emtX`-T;0JsQo@;K@@c^h4Yb)qi+ho z*}0!zkrl(5yMBQz~Dm>gf<$OlsK?@kM>( zDo0xYK5`1&QV}tVjY&|U!3XlB4?Nw3R)UlU%0s!5pasq`=E&|bXDaaB07OdUgq$R{ z<^3-NS;CtQq_?6UZ~Na*wg)F9$vN){W{(r8JBr2O`Aee54OAxi@xOEh#(sz}U$j}_mfjv=N8-x;e0}H~#NAfqU*KB<(4csKa z^Y@3_m{Q#ktbl1#8x6XHr#d5d(|YQ5vL0TfL8;9!amJz6VF<|_yz&!w^?~odK}|-} zA^tP&z3rJ_HW%ybxTBv=z}t5H2iSYt(Z6rP|Je!nug}*o{l}aDraw6WuLnzS2OmGr zL74uM6Y#eD@1OX;Z~|WI)z6gh?-NMNRV8gPS>Z{l`QTd7K$g}VAj0Y);8RaUZDEW? z>a@RB?+nVX3QpnC0}_|Vzk7zT{^88);93V)Zj@FH?%7nc&DHS@+O|k9z5l#5-OT3f zX^XopJKdhf?>gwPHs?o_@cH>?3d{5z^T!_jg&*EtX?@SB?d~F^Xx#JZ?oaAH{?XqVt!E|@9f(kMJm%jwL-8gw^rl)hVN zZcEu@e-?AS{qc0Bu3%VY`cMEBNw#dU`x;T5F-LzquzUb=m!TPwWFQ1-@{o`jME_)H z!QV*4Ysl4~Mkui+Ou&f!m|6xFd@DhO!;D=fXj2iL}VG`UMg!t-q(-@eG=QiLS1iCgi zj~Ji(6PK>PddtFw`3?~Uase7-M!>Haj*@_T#sYFne2dm4IY93iJ99~2)75tYdRMB9+M$vK_N11bZ6CZ9u?kYo{R=6naU^&7ci5dQ*mI;AE5(a&@`H_A!s#RL-$)}oi|T977S;~jCE?m@B!X!)ahW>(~G<>UGN{Oh1{QUiF9}0_g4^L z69=Kli^WE~sTW+M5KQA^)%mO~%u%BQ=s*e~gQ)bx18w^0-3%54hMYz)(vVLD>;?)IdQKq+M15k=#+NfT{$+eQEbCsmvM&{i0&*K1i>8B|v zxcL|S|D|0ch zb72XZ)ywOU{_d^{8I06*N@#NFXXITT%rH-HnoFRffseNIA`)+s(V)dpta$jmxoEN= z5{|^ej;K{S#Y#P$gAb9bOlCfkLA`X%2cMpJXc!}Mbq!Q%?{nJ{6#r3uMdjV<2OPS9 z^p+q7DUMocpACn}TGW~cCgIJf$tdl;0_=7#Tb>?aS!uP68GC?p%=!SMlUm} zjZ7tat-0+^+MbOWI@7KPW+8@>Oi(sFzqEiZ!hj(`NL&k z!aMEFutogYC03?a;eh_ih&G*^VqiEf4TMO5R`{DA4b-Ln(qB_F&RL5C> zcZT-%+HeJG(ul!_UM^ACYs;`nVazbJX3QkYhM|GxFeMqvwu!%cUXrrS#lbCv8;VeR6nMfcwg0yn8g&AyRedeZ&%Ovuo`C$WLy%#q8l@;`A zs95@-H!}9Zs+S~YW9G4CmrqTnw_-NZlBDz#EGP~;SJw`Ki9|0FLwRZ?j*YluIcN#o zvpe})*o0#JG$Tmc2*{%-6HT>{{ z1ZX}*-`9Y(Akdq+)u_(V$#;b@n4KA{6KqCHuD;kXQt2HK9sbYj0To9@x^t*!fju%g zxlhd7XgurnAJtZDZ@y?(u@P*!e&_;Nq7HFrsSyP2XQ?4xK+0+1j@eo|K5s|dokhhQ zTs6LG67ILTkdE2_HFG_W(}u4|GfNsFHMW!Pg_GBY_jK5~8&ZJHY2HY1C`L(P*n8MI zsWwY_hK9+9e!!%QW;$HZK=G-N{T%D_kxe>1M|NfDkk=|dGO;j30m#-AmiHjA#B^yPU(E20@|e!mB*rxnDanfwng$2Mq~^aVDo$r0Dc|-B z&-q^DoI3m&>)&wV7eJw>V|bhR`peI681>5!e;2BHgR$R{=ikM8Hb~}Q*!n;A!@WhU zeve=M_c$udf1il|OA#yPpF-kq5i90jN&auq{nyrrzYwu{TmJWN<6qT*Ulq*XN36#Q@!d5CuCNpPVgsc~al{CN#} zGUzj8B95O0UbVNpM$56}mnc2SEK-wPC8 z2Lz&*o%03Fz+3&{u+b`*O%px3*`A3kB+SFB!6U+dF3#Knycm2ZsF#_ z&7F|6hpB@(*RnFOLnXdN!)j`i_gJSOz6EgunWUTmFlz>>2ZGdrs*rf}IG)#{O_q{N zvgW-Gs`;kCh()|pH^w2BBA}0D`)$HXXdz<%UXT}rq^PJ1LyH{HMgSGuxehs)e$a&^ zmR;IO200|10b;pRn0yMHOhBFL(3sx2l0-d?nymZ4Xi^4&=(}D>DugCA^Gg(&+RhbH z?PVUit-v;L@Wkr0{Ac^csNkSnIdO_PSO+c#Di+_IF3vdg-=GoC_jX%Z7Q)B&%*044hMauZRps0U zmz1jgg5HEo0(C4Mh(fEjZ?dwy8Po`oZ3{UjKpQnw=Hy*DJ*<*U8C+2VX>`WHSmcSP zja<5i)PdfTpYKJ*=sp~v$r%>?RciDE=mZ431GH_u&c7~ZzB@JkD{5?&6YS?7&Vx&& zsthRZsIj(=q-=y5B(JAA$HccK+7-`hOINS$?6>zv0_o z0%De*^!sl>%<{AI_@~Fi^0Pqw@3O9M%m4O5|5Xna%dfou?}4~ZP5l>-r@$f$&g$a} zz_Ps|G7m5ipapPt0BL?LpN{1X95hu(}s z%<&k{A!A~;RZr&u@bH_7hNrl232=1n%I-Y$EkmiCUFwB#57O7PXwOH4dCc!xXH@{) z6YkL+{xOnk_a~b@_osdxFO&xicivvwz0c_#OFOc5(N!qQofGQsfr@p}lWBBEJJA!NV+u&+b$JR7N75k__Pp!Vr_E+X zlPgtz?JZiqm<_`8%$rW&i$i^j1xQK zX-&~t-|?1UuMW=4xTqY}yOcR6&tTVe_z~H=6Be>mG9fHsdp?USGXEqSq<&Cy04!p_ z8FS-fDm>@o39J}BA`z{g@%lwZyz`iA3nrC!6L5G}3a1-26N|Kbwt*R&?(ZgxTqE{w zQ!W#{1J~Yrj>?=}0BEF69p-u`nE1wPq(bd7`)^$x@sIKwv;e+Gg5oVaUwT*G6ah7? zoT`^uX~)tMV@fJ$92nJY|A9a|UiUC_5li}WZhjz7SH#dZ%taUK&_~iUD6DJaSm^wi zW2G1ig-Ji1m}w=7ENH3`MD?K2d_8ulu3EnoqV@D{!UP!)V;cS(7m4B4PT@8KFXLW@ zwm8GV6MPyRL+2RWo5yx`sZUOs^U_lu@?sKVWF)i>=nyj$d*sT5_U4NdDJ0)X4h+O3 z4ywKcvf8qkT^34xO>iKgh%3rFvr*~4DmqYPAh`&;c)tKnaHdyZ^VTm;~fZ{!aVXY*9#s&KlVv!m8pMAsIz~XQfT9TG1U6QT&H!$`! z%b06bp>I-hrmu~sd7Nx!FH^B+6{)RiLa3LW^=mCIvJ_b++SytOIC`z4 zN_hO!5=tC=ytse(cBLlYG9xAs&WDd?S?7hUHIa{!Gu(a!EyXh`&CEdfV$3e4Z&_ZR zPEd{oE)C)?b*_R4!3rlS2=)$BE6UdK(@W5lh<;EEtU$sXed*Kme8-jAkKwKSh?MER zXhmwh1howGf(1J-?P;WCp<1gh;n~7Hk`)p~T4YYh0#39qDCYXa3PN%MrnO|zM3$v= z=;E2p!Jrj4;nyY%j)~>DwBPe!XNr-n4COsZ6Q~+c&-F=e#v53hB1g{vk1<9|Y6J^q zt($?igr&z1)NP!55m%I|=0eu-hY9mBAznKHVLE0&k(}*zlAJkP5^I{r>M|vsuRfl+9#_TqN@`)~Y3pJHBnPf-@9KwCy>s(EbMmp8(2bWIn&V!I%E zzhBV4pnWO%WMaTOIkIssi^iC)UT;pX}JZ2@dVdOH4gI7Y*?xxEW) zLTnqekux0GI*wsX!+wR?SQt6-qQ)fsyGBeB0iP9 zKQ`0+eh*i}qyge+D>SzB7> z3z6=DI=C(H(at4+UVY719=t)+_K926fq4yyrlK}FqZ@&qzDlbyZsd`DL+DRyVDPxupe^XLTs&U>hkYS->e^K4GI=twk_=tT;n*bnuh z9Kkw-CUtrGoBGSRLnGtzqchJjr#lrJ&xmALsc+-iH zdbG&Fi$K;ap_8GvAZWIWBQQyU#LRT&lWBzm_3U^Cd`fO1kYZY$Zqv04Q3lb^Oh<~LEPo26(DsOhLgh$|6gJ(j?Aka}_9 zb>bAq$W>9%B4$HB^qP)Lk&->0{q94LxhF+zYV9j(gF@_ke!<`q8)J`mmZl1&Jf(X% z3f~V$XRsb05|2GGD%RSIrE;~uHoz~NtY3zzFv_D53z5aHoy|!mAmdJ7f ztIh6(5|{f%)aO$huSe{2O%!U_GAyzJd#H!AmnR($i7`u!PH>`$62?3B5hOfqt1`|{ zNZuX_!fK1a7e%sLQgFUN+OFjkoKyUnA25KdpcRXm5q_Jb>)nAk2r2;q$Q6-q=?Kl zRmWa{`$Jwl!-^Zi{5PMOd+U286vJZ&R1_mh&?}c-f-G&5P+>?NOWiTxx)oh!5J`6m zH{@|o()@TP9WiTg_jhSVQDnsnp9~aYkZw- zOU9g$EcIB_{-9l>;vAe%o4OYSV#pC^*n=@mAG_K@_Do=D9v`PJ(ji9|df5*u2stk| zGD+q_s3{Ow;Rt>L(xTgts_iDkr7X1aL-iO1odc8-JXqS5cMr>Ll^XD^ig2F$Cw$;H z#Z1rqdQkt954_c?f8+!Ie~`?q|1rtT`X|ZE`bWvk`j;g0+w%VoA7K5nWIon-ZIEDw z*NL@-QV0b0&C>kp7ZS|TsnaNdL zFm_t;#3ge%4V~9;B7Aj$8h!cuVd;sVb?4r8k@u>GA+@o1mxKG}9XHN&bnd)lh{4E2 z;Z|e~ezlI&vLWlkOAfb{F8|Qs%?<0!y3Uiq@?Hn3=0gYTglgPNj<-wcYS&?knI*yt z(bx1@uO5Y$KAGs61T6-~@1R!d`+$uVzWKX4>MrS>2#SCN^!EWnU7YFuU(r#}0D=J>j9@uO$$7CZTI^d;~ z-c8?dE}ZgO^V(`%8an2xTjH>4y8UQZx5Y^;b$(j4hCOQnb1Gl7Ztr>I_Of}$X;p7u zH^W8Np*=8XLTh-8>vf6d3U@Z!Q`f=McrM)Xqx#v!m)+lRIOyxJK&fY_ZMJ=}d2fPR z0(n$uq%WfadNte>y34dln)9&y2)+h1MdV=&=n8YqArYXiwaEJ$LVgu$8QSdW&V9;e!AQ2;MU?Vt8kx}>2 ztr#aPmIGuuw-4dkZP$cM=)vrMGhtEhkJ(F|BIIm`^K>y1dpKNQAY9k1i@g`WD?DUB zADAxl1U?c7e-neMaAB?Be&BI)vTagE%Z;}3!UWt`5R8%$n0bFee{Y~ZYV^%(A*j39 z8WFmte}E1T5!i4-JOVS50)XSIQRnOJCr}aliUCz_TS82OoDL+#T(agnZf`kH;>hvbXwZJ46JE6T!YY(e=36J7XuI4r~=t=g3a6T)2G8h^UuxO8l}(O z#*<$ExJa~%A++M>q84k36oD_r4i{&cA~&SY5@5G< zyWsX~cg^`Ic|56+nZ7z6P+l!<{Q!!lO%qfv4Cwy?KAzWjMZ{80e^^r_C~w}0fS?yg zq`}P8%|At?%8zkMMF=~z5|42~heoeMk&wQH1=AJmCy)QNxTeiv#*;C)XdPqD3fW__ZoLUinOT_0I+{ujqAAm&E2RQx4HFHIhp5VqKen?Z5u^zp=IwBj66IMXwY@nw1$eFuIFb1I z4H{x-(hd}za&<^E2-T|iDXn~owQ^PA$kds(AuLb`zq=Y4RX78vB^K3*$bE6FB!d#xi_JZ^C|NK@ne=QC-G>`gVY|Vr%mO(Ox@)`A8m~O4w;~Q0>@O?p?7}_X;Rj zXdQSmvPjOgMsdgkHY{JPxFdgEAHUxV6m}SH1KT@dBRKS1Hj%ugZM060%b~>W0H0(O5Y*-uY0S90iMZ3su+9pc z*}DMu13XZgaR_L<54JhAxHAr<_Ju@=vl%R&!Jnpt z94y1{_j4K4Pgpc5VfbAuu&TSvIc&bL&2d;%BCI|MJyyCyi=2F8HZ(c5jo>e=#ks&? zg0nBYRpHF=>&c;R=Z{Dd@rdsUE@eFy4Rxx}JFd>=);-QM`Un8c% zL&%}C_9V)z56rMPvBBFm_@qm9F%mxRU5YL+A%QZo9xPkSLUJBb^@>1TG|NS%m7hVeMpS_gh*XB|p(f2rg2nax%PP}*u zcYb&X-r958&v7HD-S+HJ;hj9XfCOHAn8bx<>Dzz`FMzy(x}k0*rlti0pS3*)cnpa= z>()LEa4OF!&t0=@vA%Jrh3AhAeiLKrj|Fx88pHUZ*E^Vd6X(RwY?j(FXjVhWf3#S5KO3i!mxM#q=++F3%GWW3C@Lk%wXY!bdRNjq!@IBd7 zUKZbzspoXpGQbBW92^3|)O%91?twqf%&+tNL;Ry(VKY6Zo zJe%Ixl1W|E$jf%$7;~S!bHgsvVI#hEN_wpO!(^E@fj&Co5>|K2dscnpW!bkv6B5%j znY=qG`rN1Dz6q!N@;O8+kQx(Zu%f{bdM{_zLcyv{oMw+n^Hz2(^%*>FK{~Uh1NI;86FXg*Nd4pJ?SRp@ufoh-W<;3z=QCn z3|hPtMz%S8?jz9i;iW^TuP-_W(3GPi9H*ntZKEhJDFFqo0vdenQ@U$RK(rT|NI>vd zk25`hKH84tAQZHU=2TT%amt1l1^e8X$_v1S)@fU+g03}XGb!;_8{e*{98Yk~b<04X zn!@MNMqXNE;|bqQ@78*P_qJ@tm@amj>alq9j2LCL{@?xUrb$?;W@Sg57L>gcazo9; zjlkwyEA$0z1<9GZBQ`T7jJQ7@%SfzvYZ#nNNE|z9d5J#y<9_p=etO|p$z5OMA^4g455-Xi0-i)`8J}epHJISD= zL=#_+GopjrX=nji_3bRdsa%NI7f%gYry~7eeZO6ksiZ2FQQD-4tdn55$IR#cuU#YW z88psDo?xr{yZ}G4tU-Mv(=?0M?#w>)^{SH3%kr5tu}6W5s+<-sE2Y~jL(`I)vWN6Y zAkX7T0XGe}Vj{q_QimERxeRgUq?4fI2n^NO1-BSH|72~K>O{tgJEZsi4$}eWDj$mr zB75|MnGW_UInj9XMNjsQgaUp~91Efd3u#`?Q5Y6bNCMdF)nuk+6v>1RYsnTaT3z7Q zB|Fi2s^;d+ZgR$b1_2`SEdDd9H%H~qKxNOZ#0faXC0tYlhR|{oo3Sd?w@K|SNj~n^ zgP_G-LX)g~v|?3eK({h;a!uF%1K|+wI?5)Lf)wxv5@0bLjtC=*4!tkjKV2|yE{=zm z6mARJ2c+oDA#JRoR@rp3W#wGR)aVKw88@bmad;CKv1!n(apR?_fMr*?U3_q+o>uhZ zLQZmk%~N8;@5Bg_TzSgkW8wV;LT_tl*FIa zcpi}57lxOASScR;A-m*U_zV97#FErVdgm8~fdE*JQa7ndyx~Z&GSBcefbe!ME7bn| z9M&(wAc8U=>@DEVh}#AZ!qq85-FF_Pe6PKBz+H01c=K@J-88vlP(g*Zi5l`&43j^= z5yC}R)k55*NikgHk8--F=lQWNqPr}h`eL*vg$QH_JA{ClgV5FUfsjLDv_N1ize=eE z=mIkfsp^Y~WPc4Oj`?7V3Bq13#!6+t$1-OCR+h%Zz~?_LvP4J+ZnwpGwwC(C&=RNd zu{S%ORh}7`fFMY(3ZydaBN5f$yd2mtWv(F6CKe+k+N>EuUoP=^JLvm{PHcs3jKF^Q z0<^iI2takeyrAkerH}H=0R+9^0}7N)2xe`siX#f7O3@gS?CMHG46EKD$Ji(`m(i{z zXAJs!QN%5lmO#1^zNaZnNSzw^q-bmMN#hF&lR%osIpNZr2gZ~{CQvdY#!k~YwukNc z51??|RKd39Uld11puu#wnyTTvafVJ^5mQ;cN~ZwiR7uu*&@d8v5c5{SZ_)J@H_|Zf zYEqN+mI9%RduEfwa-AjcN3&m}rdrvEHAYXoai?l-h{TBzOq97&k!l5m#Sp@I$Fq(# zSeQe)K}FlhNs*A{Mg#E-Iw;fSNf#$2*{Dr`S?9V%D@&P~a^eZVXb>4B9508i*G64u zN5Po361s>L$IEX4r#9Evx7wrBnb?*#CL?m8@oGndxz4Ji9X`0kfi4LTv(oR?9TAxy zS5w=JFTo>7ok(EL@$%GSZ_G#Q#JVn^nQ9wh#Hw#};b>#s8wqvZ?4*wcV!9+>EdBt2 zS=i5#3(p~h!b$sBCk-mhj!Gc6^5AVLVX2*?_`r z;p`LH>AFWLV*2I9Nqi|H;USVMVP(L)m#aj9$m=FE1JShT)!zjo^O!zTgYhQIA`}*L zo|y!b6X1cFnKczCshR+vcg%#u3TD`loMf1*Ni#>j-Y?X!=YA?qP?S%pRl6*iYG9db zX}Ha3xE&F!;cik3qHIVzsWT;-&2wnE|9myY>Eu61Wk{f^?>7@1XM&E4T_!o6MTTYR zPf0{LX98KkqL&%oJue(X*S#!%fna(y^F{SniNa}7AAn`+cS5hUD8m>D=`Vm%?q@;( z7Nx)vcMZ{ODlKd_D?d=^UA#Z7x%ugl)5%O?#pTHT{UJpJqfvLCm0}Z6ksz$Y&N_x# z<|4nq4#X1P2Z^RaG?L0?026N{zr5y^T&RGfY~0kmB3UHQ>K&V6N*YuH-;P;Dss_n; z8R;y-j0lf{mx4k^_V12^$P850jQTvD0l5sY*!27X)1A3L8nay`g(*?pf|>>ELI9R# zInX-4%+I}xg2ZNz7$h4Q+spN|T(`Ewos7J7?dQhu*kd+5+;F%qw=ZMmShTl%irZVD zX=1kV78>&%^W1uPpjAbGlKLlf|2I>Wftl_1L)JIa`j2$~e>7EppC0qaNqTRO*df_| zCglHcdBT5B1oZVy|A&%CY(GuYzexeMpV|MP$s@L(?PveHx;k)eIl2^|c$ zd?dB}>AwBPY=`ee@oQh%P5qIXjol9)i3xJk^l-hlZyas(oc)sLE)t2%;}=)=dR#HD zePx_L9E>3ghD*7oc&~9xHCPhwSg=^W1j}rF>t()GR@O` zzHG;8xg9Jbt?sO7tBbZ0oELy1;MYZ(ak!aElI%34r(;AE3T;FsZ|B017SJ3JWM2@X zMtKhT=JK~v4yDg($YrxZtw3>Kor|^JVy|$bI^~~l9W~@AY~_xBb8-qm0KYj*_;QLh zxnSh`$tq5JnwYy<46<4{bsu>f$^>$HAYM8mZNGsYHS=n)kV$924XQlFQ$%G5L*t{X zW@G(XYqTfOdhPT=oSmH!mY+Ud8|J16r`E;9QA=T0 z^=U}Jm}fB7J_?lXJd7c%gN(HzZsO;TTw3O3dSYD4+*Bh1epTI0wzO`P!`q@Yz7 zWG=0S=3(Ynwe!78PJRc-`1$=f!$jqU zaN|an7cf){Gq`4wv=J2Of$2t^0zonC3&O+somkXG9{(u~=hqZ2ZHd z3WT}EcI^W^tfKWs`QFvQbdz4yNoFDn)dMhk=|`Ut1n0G||4o27A~ zOR$!);!sCJK#ib%ZPzGDQ&88o!uK`T3Fm;M#e%HQwsuszn;@0FnpQa4TxyIc2^=MS z$D)f=tfd?td)k3#;j)PdYt_Kc1!O!p;mqmo##o+5T${Ymk`*M*!8X%JogH^N-yU+z8<>IF z_GfT@+seP}dG^;SQ$G*9-caNpu=x#We#eG?!;1NT1e<>`SAKh2e<>Bp_S2O4J8b^d zF!?Q>|7vypg?Rqk^1r>%f0YVl|J4opdu%RO)3jY?K{GbV0%`=b#sOpMTM8~mP5Aaa z-2iH@qQ{Tj5;bG(E;dB~0a|Few9D)C!y`Dsc;x*tQ3SDAj@WmvhD+}pCmE0}rW4wd zpk2@fIMb_ihyEt;dCV^}5hJ%LNVKL(>RVK@(b1-?ahQdMhElYvy_e+NS($j-={D^c zzC7L&2SW>alHOkIub5c(W#Ij3A5K zEa3}@Srewht}RN$%@2PUS#X;t26Syj(Bb{MpBfepm{=jmW|@CC$9i7!*7_T`No7ZS zMbuyCp(&^pth>KE-jUf(PLYeyv*qp*015trsgjNBlP5d)O_2fbF;7q@zGH}Gkrf!E$3QNMYC*`pMg?@vc|GxNVW_ChZPugG8d3AFhP5M~E*)dZhy4`$msvJJ zn#cs9?V=J30@X&{V%I<~ZFDTB*VcgPo9Wb7f>!w|KYv{9Kq*c=3kWSRWzxYe1fdAE z>o?7V))P>-=#Lv9rtk%=%VCnVTC-B{a&FxXskzZHwx%$u_Y)Rr#xkLAFhpjZH)*Y9 z&?V|0iP3-3(6`E+cB2IU)I~j(qsHQ9H*Z46+8J#~gaEl=&o!n|`XyLFupW}Duy3Oz z`3Hd%*qRx-7G_PWU*FMi-=#_-6^cgoMk|Y#@&L|;hY${-I_rp5m%R!BN^LzrDU`u8 zi#1f!oaEAG8OX#p9VFFvXMjcXk1Q7KoavgKQG^aoErBXf&1T$K2&hb#tZI<7{sPAJ z6u`Wumy%QqoO#iGk2Lp)$2jac40cwM?^YB&|Jia5K&3f$LrFd@gqDk~N@_ zO{+s+F&%7|mPHS-(euyeTX9eHh;9V0mWPnu(KIO0R?SKj=oedREZxJ}gYiXYQUZ}V zPSRN_-E?hs0R}6LNfI3f7A29JUZm_y!TAl%!HWxDO!$JmFnybnxv+|Uin^g`<&?l# zm$`i`X6}x72>@Ou2i1b8B$Y3S-V-I)u)lh7y3+Az4%+l}`%$Pek8Fd=r2L`yr%d6s z034!6abmhmH5qu(3R}C>q$z{##yeo&5Oz1uo9dTfIcf~&ocr}loh3LN|k>O6B_!1>S2~QT&a!D~j%%}6oxMGdj9Rwq>kAteoxi78)fNFQOkBc@k>B~B{$)Cg%wEE9QuStMCo}7)wLyNU5;F*$`E5HjDxrWP6OlwKM&JF ztD#nt2ub8Zj70=9N~F@FPgJgl$i(=#k?R(DotH|=d_#pTE!X72CgV#Nmf=Wo*j=jf z6*890B7~o#$!&we8;_N``M+Xd92=;{=kXcLa`On*nKWi0_X|O5@YzbjO_5)X&!E)5 zW9K}%UUTrlWS!=Lhf`mh_@OC$d4(~Hn;^gv(_R(Jdb;aN_?!d6O8i`{$yta1Gi)(R zdYhwIo@a~PNb%j5??L{)`Nd>@olUhxb`ol7-J~q3RyT4E#=IkBd7KFU%zQa<@(bC5 zcA*pazOr^E?qE3AKm-nQ}*(8YHp&f&^y5&@9(=%zRt=K$?Gr6MfKYhMS_c@foif)W@ z8?7slE%GMeS;AVBo)--thIhTIhFgGjusH<)OqQ_xCpF?1MESKwyvD+Qs}a8)a{gV7 zcmurOG3x(}P+R!UqwBRrn?*IFM>j*CCqtB0SD%$jy}7STglnQL{|x1jiv7jXmBb76xSg4W-x|G8#0l>XA%uP zx_MJe-&_LSUo|DsGEh>hNoUM??l@O>W#qSAxUvcgD9q2{_eC|5@aPn9_j?5P>DKLK z+T5VYP^3_7#}S9cI5~xkmE?DhXBkJpY_nx#j@B>$AC5bCFUvo}ClZcmeQZ@y#Y;`h zH#76h76zskK&}p$m!W}5z6<|Is@IQXiFnPGY{Q{>h?MV9LiX^*q~JK<{a(|;lr3#U z3be{cnYzWMUOa;`Q#IMd#X}6CQF&qAz}Bay?OIi=K~=69W?uGqBza^9cY3dbq`flb z&lcj9he2SxW{@|&vQlis#b(9zb1sqOsPPU}kgrpd7|DE1ELr3i!GkkCu0Wge`ciZs z&{fZC3iyxVn3?n~;r4M)AtF}M-t@kI;iiZ0S<5tg=1AsEZ}2bxzSo=gBzuUF9bS%Y zgcQd$1OqX&CE-O991Pm$4d7bfwttsEcy-&Wy7}G%4$}3+J&v6N(`*Akr=jvT*$r}= z&<^K|x?LUJiC}4qTm8~1Ey~^t4-+T+#OK6`nrYdvJIktNabZ}Q#Sdf^-xpUcV8iMW z@kw;iR8$4k!XkJgCayoD_1L<*T%@mBJ_p5mpEuT;Oii0(s}Bsix$9OGF_Ouo^2KI1 z0uhrG@h^_y4wYLJ zb~8jr%aDk21zAa|_F@o2#2uR#n#3PSiM=BwCRriM8-182;-!<+qOPZo;<3Pc7)ZcG zsDVy$v`|%9T3q69V#U=Bzb;s_HN-pKqhcd4C;b`4f8FxGfCKw$-sUHY|B5%iJo)!9 z{y$Rv*Gqz5Cp-L#VZS);ueSu#zxEfuVcH+ADE=p2{eL0(f7LucE8Abbp_rcTXNvi6 z9uPgM)I$R_W~!^z9;4^Yg~-USZfUnb}PvHg+~(_tO}~!UWC~+@PpYv{kG&1F@#-!4Y{I z?;-!3yOQmvg!!+~SHM}9BYK_*$P)B~0rBW76GO1~G{xMaZ3@1bSXtLFC$~clMaQ(C z?=ZX-y-vUJkOl|uH;dbBJbF9Ho=x-UMESFZ&oXsYWu>!M7{AwYdQUKCIljP;*or$2 zW&%K}!FY#)9@}A`5$CzBeSve2j!GKrIz-K&5oXY;W0YR}J$FXHOZ{9yZ;0YRSSOS` zAZn;_lqpo0xGuqczh=07>{Cvj{u+3|{jRjU&c_M2N%tdpijJI!LC!|Bdw8A}VH?5S z`8h3*mG`SxJGBx(;SA zJXaYq6gRqXD~zMO6iZO<&es%#R<>s8n=sKQiJpQk(wX<`O?z!{*F%R89anzWOzl|A zu1?&UycxS!NDB|X{_e@Ej7aBm;J&Z#NXpc+<1BIt;J9&AP>&I6)K~idad*~1apqaS z2Liz@5Zv9Nao6DP4#C~sgS)%C26skZ=tDNEt7@9Fr5Oerg)GuDbNf0L2(F`3$GbNnr zt4l^X#nczSbJX|?2F6T+zBlL4w_TBG+#xvzglZa{5vl{TPt-8HJ!yx6)J8lTy%KMS zs<=MB5dDm?l{VQdZ9ylJDq!kD%}$O4XCna(9<92%`3BEdB~*ci6XnD*hB_LXr}Whp zwg$Xhb8Dh0N6{xPyV@&xH>vi>z#zdVK8)aX@ST=uI&MKj?vAfY81tHV(&Kjsn+|6$ z)!@)QhBq6^+XC9jO~GZ>FsM0&h}LVR(d+g~_xEHfW8t8u^gES}J%X2f<8ynE#}r8Y zOx-gYt1eozthKBcLR#fEN!Sx#G1nybcd|>g)LV?q?V6S_vt~QOkh<)9L{{?EV!|kC z5XEv{y{;}_nS12bC!oE(zmIJ7E+*vfVK%)Urn|1 zj!+NB`K=E&BSY`C|L1sDe(@d!PGc+P9Yzv~Ag#H_w3e1+xG#K&1`@1T;x(?w52o=d zQs@VnX%*2~S|TYZgw1y4?9@C<<#hURE-H=8Fv(q>OrXipt-zkc3+GRhdZXj32hLUN z0aoL%np;G$cRgmJ32ZycJ@J@fA6}Pe5I&+vG_hr>5~|*}@xh-Y@2{?)jrMNBmLk&L zNatca!XtkeS^Sw}`mV15fah7;AD-!ZMfi7~=~t8b!v_AV5yt<`ZT-ku{rBXh0Cc~1 z!hfqe0eF7;e~3B&&yM@sL*HE!;5R4!Z@Q*$clhzP{8ilXJZk;RjeqZ&o_8`^PSGJV zzgg={O}YYVSp@+G8FMu6TZNmpM1Q}7QEiyo3kG8GX&RQ<8Qd8NCuxjyWTtMnAb&dK zX)SGRo5QPZLrer`A=OkjujlJ!+P7KU;&UYoc0fySHeK5J+a!p$jLu(PKz&wpGEDRpe?qpRnT&? zfj)9Q&MsmqC(tkjl3i%T8_AO^cpVe&P*Pam%c4HmH>l`#87QwlYYj|yRR?KhsQj@ZE&Fhb&9xLVhp`~o>~;LQ;uJNA zhHS5=xPjwhc}EAN-ROjQcgH!$>-{2%UoVaALO|v6Abc2{0Zxqyz?V4NVNtgNE9lD1 z?Kb`bqzc`-JtlK`i;F3B2Ky5W`0Z5ua(-Bzm!*9-fZxvKFAx6S0De2TKTg{JYykgi zRoe620DvFUaQ`JdO29LD>OWMDeRd;%Hh8~E;_n4n0N}U0%fD;zzGuI_RYU&L;QcE4 ze{b;e6&Ed$00?y$S|{S4w0ZeKK+BI?jxfnY8zi%u%#jpseRwVWC;6dpJB;i&7bUBA zNh}goP8|*z7`DT>wx3PiP)m+pzQEmK%2*S!T*-<&DYZuxcdu|&(^hXwxA+3LI$W)N zaCkbpHfHy2i1ZCC@0R=1!@=w)b3W<>f`I&r^tYX_`TSH)!RSC6M zjR%Nsn|Sp?p>NdRHv;%?i1M636YD+tC$<{gKir*fMLw?_koJ^~7wW!J%Ij@<>!M|y z>9t)k2fSa!oLY_YNH`r>q}{r81HUwo8IT>1*t_7GcGeBf=>-GMEP`Hw6(>)LRWwbe zO{`70y<)mU;$^SfCbn-iSGPqxP|y_Kj@h zOdy^KL<#+{{y`k93hp|0JNDbUuF)}7GmgOeC0^2ah~TUC-eCYx^8pec_KN~K8_=)D z{kx8>u)wGIh+-`mwVM1%SvV^f_*jRrQ|RM3Cl=x-Z30k=*6ggOL9P9BIOCi_J6#`e z5TwbX=+;1S(`56mG5E|X{NzXX?_M%G3X)UWhlsbWjf#^QHjhsL;S-_K$B09R>Zu*X z#-aEuxGafK_d4tFhon`o6ukJ_B?Q;|W}2H--s=obTM_5hV{#4cftit=IN$I9eG85Z z!6Wo<{ z6^^P(BxWwg`l2A05QV-Zn}bqP5*VhnZhIu8#T%+C6f3#Z^1lA*2~E&7#Rl=PK9vjO zZ9f8f|6ceisp~h{pYo@LY+QKyi=+WWkFo+{3xrLn@EJ zb-7*RKeKD!H5|)7)y!3XXe* zb_WvFDb63=g(@h@XvPj*%D5(qAB)I?=9uR0$%*i-(Vo<}I9l{Mj*6y8u23zP_gxJi z3?FbZaA*72U_>4d25-Gwx9Yp_``%xIWK?k~gB4L+-K(0RrOk}`++VV;FFK~t{Y1!K zc#OZ{$9=DJ(>GC(*8ae-E0>A8WN5>aV|6qI8NpD>cFtP1+97aChTP|f5;Wg71y{>7Ir zwr)-}H<*3tYEZUg)yyO+LjV@yX{RIC%Xo18L@b;bJEW zcnojzpsC1=T)Pw7(?1Sz3|NBof|WOv8?YS0*6?@izJC?k|N5y5S$>Re5zOHIDe`s&vYb=yFY66`$YeMbB}Os_UEaeJ>yl=j(5 zI>$3Yq>b!#&8j1*P2Oum$TKpPu+yuOaeLWbB#Z&1hs_X8OU*8VnJTpDBA45n3T43q zI^4bSM*L(nw+O%V){$K7Smy5g#i6>Q!49xq2-{?bxP&NzwASw139*KXRj_ra9d-l4pg7R_d(C#bv#hAq6Tw@MZ z$Ky0Y)vUolhujHwM);YJTOb)IZhn~I49QV%ma@K?DzJQ}hM6hM0E5i4IBAcE{*T9v+X zhWUtIV8`$}(^tX0Kxqfh{}A-4YwAKi;2in<_`E)eFVM$FJp;aR7DAsiKVrEMImJ{l z09|*e1jS@N3TC_BUgJSaps6|34}Mt80%^`!<4abkR$hU3<^Hl`N!@_z;$?d%NSiFKE#u$)feFw2!X77LbEw1FPLa zeeE#zOY-q~1b>+4W^kI_k5^kg*gSG9Y+*R(96P9OpofajGo!>***tl)r?$$6iVvBb z<1ldPS;Gq5aiEqwh*3GcGGUt-Bu;oK-$a+a9gj7D3EC9DXjQme!A;?E<7-EL)H0;D zRI!mLIp0&aqUMj|bfB^?xK zfn02fDjFe_s|X||XY*lFzrk9*>V_=0DWGakUcm_tg+2v=7CEUm4!TsWq^C*%UE{if zujKi7l!b6Ofu;PXDI1YhMS`fORQP_Mfl|Ufl`C5HB((_*fT}nKywu$T z%xYY&R$_i6Z3}7lkj0-zH`iOG~VE@uUYJQIpSQ&m`_5k0``JasD|2Gi=!+$P9 zVEA){@Qu;^ydwX5gz$~y{rvsEX*9px;fJsKt4zl?iugaLcH5r2&oKp&an_)M@#PdC zad_9l1JNdSKhs7EMjRH(X-lN6po(KE_pW3~AKfr6dZhxRL(ZtuP zJ*jt#B2m8EC9KS&{n)?TLo1pv@$?{wcuVYY3Cs2IdB4Z+$As1hn@%Qc&>i&_ESe&| zhIF%Nz9q=3N0bzKuhBC=hQ5KkYs&wPK9ze-5(lX1m%51tCT-Jio4~98weUzzQVQ*9 zyb^7`d^vHoJQaB8WMxIfgmQeLJz^Q&{~LbF)8Ot=+*PPyAaetl4gZC$1ja_I=v6;H zo8YXEH9yY!Ep)C?(m0-WY&zpUd%$N| zqM1{VG+%a?rEClF9qI5XAD`O*w1xZOsdgv|K6#(hnJpli4Ayp1>KIvY2;Rc?zMd<4 z4loi_m1T>~N}=(%YZ%2#7rwq|VfV7g+?$_pkS59%Us~mycVT zTYB+%9VZB)Fbg-9bf?FSrEYtB?v>`*r+$ToNtnKL6N~8_*P&I=3T<*Ufw|=P+a&il zo}{Ra%iWJ4_Mg}eh)S1>@>{dzJ-BH>dkBJqnjxC8WM-`hfoBn@eD?N? z#UmbSUJJezYi4&+{{e3)4LNL8F#WO}U*LC0JaVQO-! z8Yj?v*eM!Nuh3Gv^;qPAg{0cZC#?{zATCUMG#iW{GdB~d2C^Ar1y?DPO|x!+Fe*6} z4wfEln|yNXdUG!-(W(b- z)6Pm6J^nn&z(T0tr1B!|qtXU+x86c&eDm6!w>oi(I;|4cE4kghpw=-kWT(TF@!W9nkSzj%V-|*}W~wy1@LX^)JTn(h&xG?C zz!=`SpV=P`zZ&j~1aV1+dhx!>M^!73Js-Y=Z5n7)88@G95#qv6MhXL4X?vS$oxc&o zQyaw;5l;c;^Yiaw3KL9Gk8gHrUMzjpW!vD`_1c94bNwZVaQXmCVXKNFgP;=@Wf{ti z=S5rI+!#eUTIS__;08d&J^)ugV*Kcx1=VE+aUjRVQQ3w#hiLs5g)-~{Aa`1ived}h zT=uH;Etcvk*4`bo25|Cz1#S=CBdhW11!-N(ZcZ!Np7;-6diJ&PripQX;LDc~Tch5D)UPRwqY zhRY8ZAc>yMM7XbS6p3S|)F?cZ(VB#c3rG05}!%U_|ZMp8P zdmw@{sT&H7D0$Z);;lL{#5k=b?bO__v^>VzYVWR03-_Q~-9s3(%<@G+mqz7aiam5R zJl#ifMP9z{hWdhCPi;0t$zY`=@w{2TI7LC~F6Eca`tT*}{c{Mq0Sk86w!#TJ_Qqfb zt)*I+MDOEI2GN4mpm*J2!arJARY2GGP~%{EwcTWovvbR9lxu}|ABI6JEErc7)zNaV zgTZxlpu?j5gzLd_N!lId_-I)9u_8(&&S8=1oXXde#ua;m(;~}tgwnO zh?tVbxJl38OxFpoB5_hQS8jkRvXiym<^eCP;x0=3L_ZLo5~G+R@4M=zXlBZIGv`|Z z7sG8l6|{hLz_<9mq}7@pBVU%B90aUJR4LuERsVMXG#+p6M2bm3u^vbT@WI zuihvvnO=(RJg=iLjmz63V(UN}jeOOHvRla`@FFv2=3(hxYI73J9pUUrA!ZWteUR~H zX9sjf59_for?*|kP%0Me6;5Z*tpeJY<32YCS|`(fu~#8pi>vS!59dx%GqjjCnA1PA z2j4X;(?1r81AfMe27LQB{@<8`|DwG1e~u3g_%k3H@CQOP;O`-#f4#$x7y7SM>rWuj zJt{+%m@LTo?@JqZQS*#Lk*mT|R_~%UeY<6`Q6pZ@(_EtC4HjLft%1ER{2MTJ_qede z`?*(cjxAsL<>|Sf)Gjy~TQ0mO>x1(dL!p(IxLxsb<`PPF<~R$cJ^UfmbF-Y)^W3D( zt;FJmZ9L9^e_}@6G*h);-HfZ?WGcpYUbM2wGcVdj)Sey5WZgp~=hllsG?%JGz8!9* zG%-gReog)#z-VpI#*D>W$c$L&i;kHJ+)-jCl&9oYlR}|s3#RkIq^#Y%kcSN>57F5H zrw#41FqnR>YVrDkq=Q&&djAbJws#lYLf`@v&%;FndS@UOoOwAI$5CAL*ptfk)smSK zD5@KMIJnJQ?ppxLD0vyJ5J96WG%ANJOj-u0q<{&P{@r+x|jk|+g zO^v`o4H^P4==kgt*U}S<6`JwFN-p~Nu+T!Q(X!U4-pfRRBJapu`vmlRld!VQ@L93$ z{g+UP#-gDEqr&70!T_1Fu8%=uNqn;01Wut_Mx{EFMRYtZ`>UYGS42L<=0oFlwo4xY zS~XBcC9b>OoGgmK7(iUA#|$C88<^F6%uw5wNzZ9%n4<+O-B0K|k_cWhq749dVZn{& z%$*_SwDt(KO^NqA{RpK9v{dxYQitguhF}oo==Ex%^kF;Zh_t(;&79AZ9JHgCCzOnH ze1d882_3gH>5&u}s|XI4q1@us zPSU`TdPM*|AFpAyAzgc)Xb{WG3|M`Wl-YIZJbU7$5!jLAstyMUwVB)kHA~V77F(0u z#%ccn)S{6V-jnh^QSnRW3l6GVZ5KK#Q!AQ#jg#BsHduiz#mfaR%$Q-l|dk3Fy&>*Zh{ zJvfbfba9EVxijS58F~maaLm~mxpM&S;B~cF&3i+p#Iv>)96@PPbC(B>MTYZ_4`MK@ zY^L@7NG=!gXDxnXYh?-h5Q6w0#L1K7(<>X45ip6G&a){=U8y_@lR?v3*fhY|^2Jfg z(X3kfb#34$HRpizTf`$yT-?+8E1Nx&nY&pDd~>jQF}irAD)Dol$iDPoN6ALZQY?A0 z)9S-D7o2ldKlCJ`arv1{WXyRv+I*^+Qn_YrS^?VtH|Cg2b<{a+5S+4A!B2ob$T**B zK8@Ele*%^wV*&e#H~2x&er{I#u}At>d-g|l_^(2=f9SoRltBMNEB-e*kN=!H{4-`7 z@CVK|;O~L90sk^-8}Jj<_AkvH&+1U~le9Ud;se+lLEn#>$0Pf~G8mtkizN3*+Wf#l zO)2A0)V@M+r+|-!9TLH1Cq~8Bw5-pw8Ep-dG|&gf=baX4sBF@CwI?*l^@tHC!%L}N zB`tRa7nqr}KKB!>ukQ*`CaS50ZGH}Duh@`T0@_fr{DAHC;Wlq$6MjpM$0<=<@Iwr0 zg+9CM{voMQ!)AWw5HugO=!?}>I0SXM~t3v*@~A=l4Yco9Fm6(RkJR{P&V4f+gB}o;-;Yx z>N(UAbhp&Wad+iA!8+oeaXlHI$uoKfULqOw8bAkI)ajqb^K;9T^aU9*^_ z5+!xBD5e4)0?i?B$1x8-do(9kcZS*v;C&*4ja%?$79jhCf;}AVEXf{(l|abJHeK!iD%LUwe3(#&FLXvUV&@^`lc_3k8Ur-&ai= zsFz&5c`&Xkbwi&Che_w03O*;Ub$&UrR>h!#*yW;=Vp~VTgFA|RgqM8fyTt432Zn|j zRDpQc7~Iu_`}U#-eYQRD(pVv0U*`4WJOAC2ePD5sobnps59~8KnF2J%qsQEDCATS0 zt=Ga@6UD-z68%-c@0A(PQb+~S%jj>^s*L5dtFPYXyU$i0DcuNDjx~b32%9h#RFX`x zLz?!nqUu?6!7ZdvG{nEl7muD}tT$~($wg_*7@KZsm62>3%DdL#*vcs-XptX{Z(Xb` zvy<)LFP&uEo=kA)U#;prtPM^QI0|Lf#<+^`^zHz4y8-nLNw?I$K1p|PM@~~RIRJU+ ze}cW;S$4(n0^;Zn9{!n$`!0A{o{L`J}C;2++^uS7Qx5C=71U9#*!lZ^x*7{^#vE5?f9m5`K7 z@{1vO0YQ4cc``|0M~yS~j>cROs_5);XY0B;M3M%m6>J~5;5 zkE3XQn?K+rp|xIM@7h&Yvovu3_0x$@-X#fB`PXj2+qMnOM--v*o34rq&$fHfv(V*g zTHoWAP>Kuj$319IKRpn&kara|^Z^^~bTnS>V{oFc$$|iO4IV>k*mH{WmfUaOMB6}0 zMo+aH4VZ>7PC76h&@=lbwY(gIb+Zg7wqu8Zz^bQr^9{A1A>u^OZJgm}!xhL}@wQsF zw?1lJhj;RFaTEB=&N=16L=&#Rh>loWH*q2rc{eKV+1oI;eV~1XZB})60J&(?ci@Lz zN#hDPZ*s(?|m&EEXN#4OcGi?p9j zR?UwVDL-u89kyBMs~o@;gVa24A1;hsWZb0M5yc=SmR#l~9vceA6@-3TH7vr}J1&9u zsl~vr`wrMr;IPNkys|H`kcl6qPRH`fiYt z<_vf$&+Rl0DhX$L*+hv{C0~iM5H}s=|) zZn}(qA$a9!HU(NQD5CodEx0aaww0FT5T$)9i7u1^9a^H`u3T>FHW^~DcTH}J5zeUo z3G@Za2}|KaFnTYtJ1+Qh33mMpgk-|@rkZ7B2ZbQ(fG?b|%!o2L&-^1ghG8acb4Bae z82M)cMUaw0diPAmZy^Iksm!~LI{VgS%PCvo4`5Ncor^)ZpoonLLn|RkbqT+#6+ljrjPCT7C@jPD8{B-p&o# zF^9Nvyj7W94?2GI-JPdsIzek)2(#(e;wqHHG9lt0oydB2FU2orESIiMnI&KmBLj2N}6?))j*F&B#!%(O>%yp+GdANCD|AB# zH!GRROq|Oo>X(9LMw99&twPu62|qlep|Y)aR-h)EyVm;h%Pv|!IXNIUsO2f>q#T6N zG$jfMmO!|GH)d+h1I9(DYk3jZ%s-QQ^0zdZfFKi>F_W&NCR z{aq{KcPRDuLL<}P>(2NmTJ~SIBL2p{{(UQAzT7v}jXhv>?(-E4R4#1C@v7YulnH-a z#3Tf>Py+{{C_GACWQgZvak$(DyHF!0>XWOXl;igBX!2_@KD1fBb+l@89?exKQG|ic z=Y(wfT-eR?D!8Kb%ZSO#W@uzps~q`tA%qX&r6(z(O3q(fAM5W$oLy2)O|C~awdNC@ zPmzsUndM?3TGR6hy3hP=cDFHc1nG_V1WrEZDv25Vi5?_hgM@6bal&@*Bi zOtJ(LrW2dZ=mHa9Ey%UZoAmP)RD^1} zp3-`n;(#31VOn(ThG3hN>MXf1>B+n3Ri*1KchkXhS*EkjE~Suk3a1ReHtjpDCSBCV ziRV{$rLj3|o2{K#YL7_L_AbNF6V<#BtIbdJics=2Ntrf)iCp758Yi=JI3j zB~tG$^ySZtz;|8B^t?vlhYk2XNcHdesf`-FM>9q3T^^~Ve7d~Tn$(j&(Z7gxN)JWjc z1}!|DE*G-Pi;vIE#ZrBKMnVo@3W{YcAS>s zl83j(D#@JYRn_1Dw#|}~V!jaPTKJ@%`yOyjlbRx50o1^9v4bst?Y1iIpU z0_UFW@ya{sf zm)^H16fmlN@y6l`!sa1tCBg)U<>UU6(|TwnfF=A4DdDu)5O zUc+!)B5BHGZ-=$g=6KoqxSZrc%C6)_@q@F;C1oRnycE9t@!tHXGQ3wX&XRUJP2L)8 zoOsH33^607y{aYznRLPYGITMq!g>_gt7({Cd74d)cwPsQOtE{^*s+OeH0Tgp+#AfI z(ejU{x-q>gSbcTQYf`TZCmBs*n-?ue$-kN%WOA27V3~<*W0f)BzdL;7#Dr$nujt%9 z!&G?)xy6w9)bw#Uz(*v8i%3)ol>!0GiGbd04R$l6w+27?ARoWUYkP1;vQEFs@`AW@ zqw1N6G$Jro)-w8;SR9iuiLn~E-k|Bn5ChG=>;#`vB=hkKE3&6uh zC8?nFF0tppcn;M3^@qb0nkO!F{&wv-mgE)TYGp*4wt`I#1hvvMV zGH6f;MyN$uoNXi^*~xlKgxT^*FC@*8DZ^(y!k|FKtR3AKB?Z#<-2eK$*9XQYt^9+t z7MMg$+?47T(@)|37+~lhFO|Qj?`@mKY2*nf@p#mSV3LcU`y8mJZPM{WBp!_86f_03 zoHmb-p|`zdM(10jH?0VE7S$5B;kBsCxF&AGpiX`zn3mK*#?bFTFNUN^c7A8FW)l&g zKuN9asq<+8cAwz@@=U{?QEhgETiLCqA5~Kjlw(o^im9}!Blxw4BiN$aeqPt<%uAdg zy!eiC1R)CRvb4tWRT=(i+Ql_Iq)$}Q;xZg%fV4X{W#zB&?{cA9T2fjIlnUi+v`Svo zqvMy?%w-ehdDt^`!xB~3pkX}sp04#YO~<{D*PS;gTI{x^II?4k^>9bh+MPR_pK0!O zq*s_#Xe3Fpvs6<*nw*e&wIH@ny2*pO)^#~l)yYQi`XHRh!ZwXZ?*rdUdNeTzV^}}vYAmXV z0Pjdy4$47KE2O;{&%RGPd5iCZ5!XOxrIL|T!Cs6Ci4~%hB8{dxt$;#~_^0JN?nSqqLpnS_(wf;lT-xW=J|2n3Mi0@`RGt?JXYNmig-cea z`_D7*ef+a=Dy(%mqpn}y?41}v@ublYQ5QF+F4kYF-5PXQv3`VHm%Cd+o9zUZPBWu$ z)=ha6t1#32mSRzG=yS3YsX6)ER_MKW_(v#B3>xAteuRSX8U}B1a~`-=cK1kJCr__u zjIx+O>gv9!sq?bhE6OffXmP+(IwI7m{vRA|@dBa&Vg8@N}Fad-$sbb)JK% z2pMx)uA@3T2S^7lvY~!PFtSJaXoCsAe2X*dr;lWlmplF11kVqJ9Uz$#tlD9(nBG0j zi}UiP08F_=3fH?yBRf($UCem=8sU07tAl(mm!a{BFBYX)Xgf<3`cmyF2X4;Q7-;qs zP2$V=TcC5P?189#InwH)rtTekY3GKm*;E$wN4!Z7MTsRaDC?VG( z(b1I24!v>Q8w&)@8MP-Sf(LdZ&2mQ>cZ1Wc}p7UWzO`Z45i`(!r0eJjaBdMO8fMoFz8T zWG0C*0NL*#_YE2~?5Fn6Y}(Dm72aY21jfwax@(T)35EE$?q_ModE;rDa$}kND#y8= z`?%U~9EV6Cl`Q=^rSDKe1qvzS%Ic6}6F_De1Brx&W@hxKX(`82#) zo>wKlgDh^!3{7J$Na}BVD^XA&$_NkPJ!&Y;)!b&wmX|?9$Hw6rEPsQ zU^RyV^%0?LliV-v44-7MH<$_|uO9{iey%6K_y5441hvJ(Wd5;-f}Xg@)P)qmR>UsM zv7;_bC*>2_0zm-Q3|3*8KV?g5d3O1Vl!D@5lJ1aUr=Y@RH=!TaBv$V8wFX;s4z+H- z&WO!|T4Qy~F2-H}bRAtQeZgFHvNfV2*C_;kAY)(A^R=?nb6W+Pt%_Y*g7s^pp=G+N zw2m7ThUFC__j8{$Wlx6*3zQ(g%)6u-QlU!v;riI(6l_NEh(ktVLni@pQWD8%v z;FTI$i((H4TQ|KV-L@^xq*e&chD#P9%dHDlAbBe)5yF*lFBgy4gEv;Qyx9Y8V-Q>l z>V3)LK5Vu2Wrh{}HU{#2HAJVO&|<@tI=Y8FBFek#PX0<22FKOMb%z!-hsWgD?vBVW z%%CZzuN+HLgm^J?O0zlPv#K?b`sFXm3N;!)-xHg^21M;Go>9k0%J4D1=5-UUr;LT= z^2y(4_}~a0f!)aMy}a%Hl~zYD3(}|zhzZH{nsYhG`H)Ue0>O~j-D=~;E10{)NM)Hr zB0xf|Cg&RV%o!f07$2^n`2H=oknm1T0G3V%P1Yi|9cuRl=9cYKlN;`t1M*egk$0Y4#5RZ*dWwbqJ7x8bzbsBIA z0D;#ZoiyRRPVwzjH*6 z?3Zkmp8fNB1O_%{8aigC=dmk#Mg|%N2Bzm!6eAN8&2zui^I9%?W=0ye=f_x~G@)qu zt!(uTY@dJTmmYk&UmoH6U3QF5&)&q!^7%GUw2E?~zkVSzwzs#oW2dDxGO;&y(52C{ zvY<7yHL*4`u%y*BvDC43dA_i>H87wxaxm1P(b2WDx7E?Jr#7*)H?XyS#_#C-@;k|Y zS$XpOsrln+esgvIYl-kXQSeP9{MhsGUzDKu&#|Kce}+c`{y>ig{5^d1uXp(IHvCnm z*iQh`WlEaT$N=Or^ECnO@or&Qz^b<()q~}>?0Tn9>dFpPD1>Y-ur3O6_fyu>!qn*V z2R-x<(8&1Jh7vN~2ixW9EuAq|)B5i$DU$W%43wH*&k*heFw0|M5u!nxT zil+q$t4e}h#E9o%T;<&^IFt1WuSk%4WnUL2-qz)07DuTepO8cCc(bwH0} zG(J-DL`RVu*CdQ|9bi~hZ$FBkTMf^_7Z=e2{DG!*@x^WL91Z~@CtO00v1s%x^>Eit z0E9;FJ3$02AOA1djUbn zdKauY`B-H~-#&mHX3VM6B(1{%HxE8$eFiT8Wq3Kqcs1O4(ci}WcwLv1?J5}&zL$>VVLeUD;9O`Jvwd^gj9O0go%>!wXG{0-I} z;|-t4_e7^OEx>B*-7#`BGHn%vl1U-w#q5=tua7^$yIxpEL?nk@Gx#S>Ep;e1&`Mcz z-t@RXo-Fe`%%Vd!Zg1QPm=+y;v^yh69vRrXi#;B0*g8Tkv^qkcq&$*>FA$zu-`(G> zaOTS7PCt^kKD{AdT4(agY){cz`?{8~3gY3fp0UJ0J2HTDzByo10w~xFt~RG|zRqB~ zk?m{k)EAcR6*h7i<&z+J?-{%|e!fEgjvPtg0j3Jz&C-!aRweOKXxnH%AD;uMCNxRX za$GmYfq69f&LFtIvjLLVFy%!+9@=NBMj|kA1`e2bEDmsJz89#*Bw@RBoN@H?F)7oq zB8ncffNK5~ud9+b;Do9?Mlj+LYiR^oppJYM1?~clT|}6T_VSFSi|?V)s!9gR%Y8fa zG92>n;UC>~`|ph}rj~~rXv=Qk9z*G1g%>#QZYRpbc_0$G-Oh`IW=2&G=zIj;A&q*| zhMJkv5mtD))A&FXWLDoI)?aj}qrd3C=)>TTz(pUm0r_%&HGWLMY*X!b+;66fh!}+> zvBaPFBJPE^bJ7bV(XTju_}kQSNHCN{A_4fP?+!mQL2C~|(1@{|)ocI{$62JP<~#Q$ zv&=(Iru3Ra)TgZaL~N&r`Ugi4j=rL~h`~<+5(V1!g`2HnkN*Os%lVm-&tW$!*4W{- zff1jYH5?j}_(a7>E?rF(6}`)U~vDL<3j0zl3dIZXkWg?z1C06f-Ak;!( zHe$Wa1d@8tUma>2CQ8(rpHsc4o{Eb{tnxsUiCd8&JrBEW%I%*-6#Z1C3z>|_M2hI_ z<5xIqaLQ@cORO(P<%_0>IjNJQ3)2=7K+JOYqG(i~CC8Z8x<=mNCEad`z*QhSdWg#> zPD9NvC3=~XU5*}0u<9?*87ncXAknkh=vi>aEz|FH*l&_#P>=|s1pEwqL^Z4*7pf!- z6>8$XysnaP8x^)f9E!>x7toSdn=GOpHHKbze~PJxKWSs~IUUxH6@)KWU)LV#>;q}Wq44PzJ(2& zab&wHU$4wyRCcHGawNG9(OzIk0;ld%J9ar21KsC*Fuk7s?e1~9s{-`+q>uN*db1J| z3#QFWOfL{4Z(uFXfiBydeD&5jbLy)6X;k;8vmK6N(OkEg=btY~Oy1V0C(`jAn=@?8 zl;MV`NA|b&H;EiSJ{fRq_;@%TdUTrhC7dxrTX!%*x29^ekh8Yr8N|O-O`V``OGJ(m zoo%;V3bvy$^9dwL_*9kx0-;%E_-=T?+_lV-YmPq9T@{K~Zzw0GS`cs`W4-WcXwQg= z6Ghu2MaP%ie^vDB9d+cA%#A(?rQzvT#{ zar0wezc-<67@n;^vgUlDZd|--E87b!x>04rTn%O|O!?*4_TIg~e zn;;0tS)C6d!K`tp{&(XzU_6bqUu`WavY>s@$kOo)K3GmR*Aa+$KnA-fgQtYNGl3UL zpD)(~IB&(@VvBT=va=-h7H0*~jG2?rHi;{Z>5G)@>|#^*x0$`nip z$!a@kWgjlc;}J){<<_V45M$)#aC!w+ycn3R_qm(jn$JWVUlBccw2C>JNX#{nsv7W^ z9X!3wZPx2V&I?c6c2%91<}{z64V0gjJLRtJ&77&}J6;Evk3YrsMa@I_B1q07g`Uz= z{y5Ha)dUZ_!8~^`(=zy}-ghoQVbhXx>&xwZ?#T^c5yGZ-GGZn^9F#V?VnL+QGWvQk zvZ7yr0y5B^^~=YE2-rN-YSq+qDOWl`;Hi-So<^_f$e65c$@%&O9&PL@s3+O_#F$vQ+dhRQqD+Q2gPwty25s2+!piRY3AZCV)~TwFue_3*|{Jj2exof9*O))xa=A73or*Jzx(H`_Ih(M%BIbyU_FUAScF z@u!%cfmSQ@7S6`-#Tn@&uGirMeBF)>Gy_&-vFflZbQO95n2DTA>lW^>K{aF zBo-5bB{_>W!kWV%m`%UXTaEgmNJ3dHP#F>Wc{~v~V}v(Fs1SIkNM9G-H#6$F=9j(< z>mw2CIx#MCE!(#YDsGnCem<9oW){=Rlq6lEi`XZ;lt8zD*^HX|YTI+3X%meV5eJRs zOWqK9=*xuFmKL_x1b_Ba?3(B48)dY2PTR|q@n}3v>-Ac5>pJtp0n@Gg<+-8*e8qME zGy5v`+y>E}8SbEV+J=h207)Bhxo(SC`lej8x)uWe3^;nYxwTD9_o(3_pPnygVH`B$ z@@y1^M0XP-M`il!;}P7AozjRIH@Q(0C_rJjuhEzCGHZ>8V(t$&cUE5NJ;2wco=b4B z(b%n05o~UCDefO`MIW-tVQie8vqZ`p={0aIVH*PPx)PnS;SvMIyA<8q1D(7^zO3%A zLPkWw#nFz>Hf}C?yXGi4O^1K7EOC(-LNw4)I2rU-d!W$E(5X)6?-ev_9racuA@mGh z8(&|U^CpiCyhYd5AtS31=-!Swl+S>~0-GL}L zj3_{5*yyTJk1Bcf(_G88%0Z? z5=bX**Dh@lWnBw1%p9sEh_ow`f#y1x`CuEGbm=UHDB-lq3$gM z>e`a6QQV#2?(Po3-QC?Cg1Zykf+o02a1Rc_gS!QHcMJA*lB3<{_Uk^kf86f}=VY_@ zUaQueld4%`)U4q&+lLZ9E1piKv&SjS2)yC{9+6q6d#9v~2WfL%4_G>fvCi)4V@U_5vV@`mY$tkt33Uzr=19<3AwWmz zjV)AaIWhWkuC3C9djgx=fR>b+Sf6W-@I;_+24L;(hU7|#zfwH}moARl;|fOtRLwf- zn(fxB=m?SZGKVSF5n++T?Bu9VR85;Tf{(Ch-cmwA*)`@Gon86cOGm1X@u9)d1ALX>xLH2y24Kip& zXQ5aqi`VJaXcuEfSLym9u1y%u#7ylb2^mhpDhOg&1}ze@)Nl@~wmrO{ozWn1>0o1{ zQ83KKY^%JQ-i9Q&q-f^2SI+M2zBAnUCWSSMMU0*T5#E?&WUmKv=eFH^WJ|KR(6$T< zif6boZv=1J-3iod<7*zv_buzU=@{-tN$0Okat(Q-Bcy0$uGT2rdr7t-I-jm(9S#wa zFxW7*m!I?-gyrVVF-}m)d1m7&R68+bK6aLt)JF!^%1)|D#E62Cmr-0Q;zV#D@rH2mNZkGoGptLX~h#^7{(ak4}3e9oX537p8C6^>7^6 z9T+|tR?=?j26rxDg7Agz`n}D5wb?4Z@bL|$+U_uZL=o-G7LKI&F0X?g@m=|H{^r@+5YOTK7?A@ zhgdTDgEsgjUS<&O9&b&9rd>W_k;gmL@Vq6jd&E~VUSlVptacB$jZTZeGqwb6LunO_?zaaA>zNbR z3#i-2&k)&J?hw`eQ!oBvZ}*!FGBe{J0LtG)l>Z1&{#i}^|LMKme-Du4{H2G-A4aeh+-|Bj*P`D1Y4)t z*Gf-dM5ohpe%R0FH>X-pXJcj39#0qDVtE~^tE=jvcf2fhCnS0+`>#ZwGJT$*^KkR1 zQs*nf(yCkv(3=vscl4o%y7k0JHf=G$a77!$x@~SdF?1W}J;h(Y&2O1Hb_T$bNR>C~Mau~%@GJT!)T865I*y-8SBf|L z54z4h0jQ*Dczl}b*-8cXvtl0rm9$pugUD-=UnvpMdPM2T+3m^!HYN`F25lq^X5Sjo z_4YL|Ues~D3u=u5(Fgy9d}wTyX|Yf~2VOc9%R6=u37NM3xL2+Fcyi%-%3~11qTPL+ zLgSt82hhgYaEJvelceNq@JcHBDp7SLXZ)Iqxq$(7$EYD>!a1?P3SceSbNP?Kw5gJE zYYMVVhBJDq0+DF4=%#qA__ee&+pA~0#-az}WLeL-?LK)kZfE-bmYm~96PKZl^lixW zyq+(`Vp_M~VM#%Mr}WqQ=yMu|Vrss3I$#d(ePuSp=O;Z&b#VlmTgZaT_?gp414EbELT9`tB7TC9juSE}TzA$!wI7UbkxG7V9NSz7hepmwt8B0K`n}8H zLE6!QixE~2xyu+>8gVeOvQYl2z144%yH2$71!-36@;UiFR4@gfG z`PE-(aub$dqtgerOb3+qh!~2WXCq86+d8D7ODt(!Q+^$M=QGy={k+Ly%+m#UyBV}#La;rmQfb9_GXBwdahH}63UB-Owt>bI3Qnq79l zN`7OT=Aj2bCaDky*6V^HHuK(deKAYw!4BOnB{LF4EAvhSN5Kpd-WgpdYzC4=62P4W zdvB<2`{EnhzwTPJ=~Q&6=k6Q5&}5icdEl1Vre33PcRw1 zHuE_jhacD^sv-cJtOd1iXK^tFO4P=7yu>~Bg>of*0n8a@Gs^JHB1%ER+n)w>c~@zHe67hpO8G>EVpBodXlMo5MF}BTx@` z2nKgNvP;eePT-Zy8j3Z}l0EW$dx-_9IiYq31Q)8SKB!LN!qi#FDs&p++Z?HP4h|&D zN4(l@_yJBrGaHQX#B7!4wEh4m?XjP?Vk!iAmdcwZ6%xUg@0Opy%9C_uV03Y8 zEKC^WHn{txbpboAS3nn^qdz(Ceyt6MV==jmBJqjn=AD`bqRPp6_HvenJfC3&-7!V} z`crUyk9}u&paNNN_4<3)p+c^@;ViiEcyD~#zETo&)W$%Xn=axNI&eNwm@;G_TSqPs zJECV@)otCpk!7PKw+XqAOzF~2cw0}b4_y4`I)HGjShU?P-Bdw*$X{)vG)Au(QFFec zEqgwhHlSu2?leZ*&FwjJ&~b|IOtCz4BHB22yy;W)VC)ul3rZ;Nj<@EHSotWy+`ioZ zz~X&Lo#ixntaDN;?R;M2mVJ%ob7LYSfhvnHoILQU_8naAG!Hw~u=FAX1xoU(-QHd( z>!&;1mK@XG>YfmYNIh|u3!~-+qeB*9yA2q&s6_P-Sa#OKcYqS5L0|W_CEm9*&mqEy zYcr#M&H9^ng16tUC)Fv=rlw95L$*_Q4Z`lu$3BQd;@poB?=bOr>D3=A*=ni*GtLC_m~Fs&VWguZhV_Dtanna|zDNv+1I^_XB0uR}O|6+;#R$dUBey zsLvwH+N$??|J3Nz)KQs(0_kC9aV?o7{6_RW(fPKa*$4@g_k@Kz$VldP9){+vx`O4j z)2WssWYUm}@S3@TVwuzU6I+(kF80BI(<&u}lFr1Q!EVR+mgKYgJ0DF`x7x8cRfsHM zTqmv_ixP!IOB5c7mUTSTQ01#kfYZokR?qk6tF-MbYMz?t`SKmd)ve_Bap~{n zKVb2%*=KJB@_Tqb-yCra>OX&|R+(!_sn5?k$B6YHFcqwO1)KutegLMAnd0YL7Mc(9 zswcgyjZ7vUX9(#;}$kKkf2_6fl1cIxoPpdmc-k(M| zLK;CW1vJK~(ce|n-=C?XcyzCnf6fh8>hnM^&A~!PtNP@DZ60#cnHQKLmrzeYtmMQj z9TrT;)yQ^4v;?FJX+xl5lWSK^ph4h*LE`nP*&-;PiO0P;Xa(w;yTaGk@%`LMu3zxC zEwE@%89T!}Muz?E2fd3xtQ-FRc{j~HC>Cx_LoJWQS+%I;MFH`^&*=dzagAKYpZCc3 z3u$$!Xj&uHFq~hPy;To@mlaeLG+JEab1fjBU3)vD))crUUSAbI{h3QM@lJEc7835J zQmbuvhe+detL;=Sot$erNDC^eUpkv~<;WV!$}2KnP(w>MF`gjK6#miJ&iJI979QCO zCAntUGNq0&zK%(UtBvv{f27?|1+k`=T#CU3`j+CzUT1X$k9QA6u<2^OImY*TS<@Xc zkMWDRzL2lS6l^Vu6)G4qm%7l?d!MoPitw)_^9gf>!H?=kcPa39u#&)T6FG)QCTpAA zQtF?d@#jk&xz01i-ZSQlyvf97?_1N@Xobnydz>?g!3mN>zrdhqk3ON{tfwW zrYpV=L1`x(AWJ!tKCWfOij59COA*T#^}mWlr!jd$l_Fue2lT-x$#yHBr2u_jJ`8_6hL6)DU7Xr^S1P5% zuz6Ecs%iQ4XYvawL&UT39x6xu45q(HlPA3Zhsn0T|BHC>!^eF0MgR@q-)q~r{;Ia^ zhaddSasAKG5#Vsp|0ulthi(KvPZIqu)BJykHst#6K}%f!3@veekGsFe6Xst8@Bel@ z`M04Zu79p|Yx||4WLd>L(0*Yg7#+wfO$}h7%cnj1w?<73sbUTIa>@*gVAbElN#P#i zt38g!QhX(B>po$!n<#43NgX{0_p$YDtg&ryw5X|h{89K3_zH%VaoFz_!y5LVE7rcT?$;zOgJgLd$Shjpr(<&z52`)IEy?!cXRdgdJt4H zb{M-;b+PW5S7WJ+f=-RuZ6fH%AKPblFqzCFj+rO-VJ7t2it~w^hY?v(r)-K#}Ez)mv8Y#DOyb{m%K4B}5DK-bsLSxnl@XG9Y^R*ZYMeM#a@(arw`P!aQtQ z*4zw&-5@_UPIyVgoG-!6j`41t#XW*eeRlZzgfj~qBIl4{pfT)NXIz+-U$U{){fQ?c z>T8&PQUDfN{@~QzU3y@p9$L|d>Gm2@?R?>9;-b4}psyK=U!6AJ#F0NFNHxcvSW|EK zz69{`uV0x)F{GS}^6}k3CVb{e5IkS#7~zCA#mbyp;Oer7d_3=9hDpWReLKwbtq6B+oj-+<}4tCofegGxQ*~qr2QrJVi)PN)F`dZ(~Tn9Nm~wc zrjF)5%!9!;IjF5PM(V0(ZqnRMEkqg9hGl{jSa#iv~$bl zEK`8ff6>Ec`CM$s?Z0nC6*IBgrW5G?vKu*~5aW{QJtg`Mzj3ns(?Ik+)%u5>%psWW zbz-uF;3oM@vN2z7^{{@(Sk$b8*$h2?TN(m7DVWxI@YmoQd==}>H}4bx-6G-k1{D4( zGW?oJFfjrei2ssF{B%~|gTNm%i60*AKaLFlA{P9N4XiNCfNmMTG`apia>^9Iu>7}j z4Q9q)!sPF{1~cO?-PL}{HJBN{N20%-Yy7;!Zxiz`Xa{D%X{28U`nNjhdUb7k9X2G_ z8$wbzkn>I))dwJ@28I6HAri`w0(jZlNMPaK$C@}-G0}~piX`7Gj8`_z zAMKF@DyqkAlMGrBi_9)p{fe0b;2Ut)!fjV?&rdpbXskQhJ@c9_F{k`i^sw`nw2`Bo z1}~l|Z=TG(V;O2YA#^MDB262<%q~D2R?8;px(2ZgJ2xe4wlADED6*f~RD;%q=-N5; zqeGlJ-vghnZn{0BuuKvgOcblsIX4YGFhE`KI=Q{0TqHHM5_cd@4bKgvYsc%unn2qq zUMm8Kh+cwNerhlWLlLN9V}n}v0WeV+Yn9icn06&BYoowo?2eqsmLDs9xI z{Z`0Yy=&;vcWbNMv5Ds(Te^Y4{CGDve%BP5>q08cYh(N_&lXt06QUA#m%VQV9VnyZ z1L1gWtz_O4!@nMU`&PlS)`b|;+GxUzx+9ymnqfgUkxa|j=IwIf-85AlFdrBiEKKQ* z<`-B=7(DF;o{dwc0M@342-{@?Y5vca0zUH_yEJv?!&E`OF;ejC$9z1bFePV^SUGgN)F8c`96>J=#M;V3$ls1iy`5odqenV*8CEfB$7 z;?;He$y)um!6M2=v{VmeI)j|W)5J$1l;g+EsY5~=!)*({UQS5;!7Kv;D%tpUgLC=p zeR~Bv#ds$cb1-|F*d2e{LI*}#s}eKIXt;@zL?xo3CC%)5dI44DRtyd`5fubpMd-;s z?-fW2N^quRqrM#s&<}X+;cXdb8xml|H9BR!9lm-^J_UukaFzQ)mk&k`||PNzKgII&d!&+B8lXUG;Q%GX8A0!S6B0#3W z{(C(6b*ue{c=FQ={^qv+-Av>EFP{8{WBuRBG?*FxxOx9y6(}+@{*qSwF`E3`@c)Wj zG5?V0{$E6sKLLjSDw+VM;-90*GN5MxlAApZsSemiKtBvVb&}fjb^qNVxZ<>)61H=j zwIj3+b#Sp~5j9<#(jE&63d#%9>+@QUv`VKnyM)O4YTgEOGjrd}46bcJ>(0dtPl3tY zmwht+Z9qK9g;i5pspjr4oNn9d13*`Nd>`cN>M|z{KC6@OzXT)R(?D@;yPeAFWj#{f zpEGEt?07hRjd+0e^G!R=E3Zl>UUN|Ei<`#Sx)_^ttWyz$tR}S+Ybl-FO0UWf`70LdZ0`cy-p(u*mjvM~ zx1Z#lcC5R(`T1VYzLDje@@t2S*0>Bf|5m^HFm|^)Tq|BSbfejOQiaT8A{+&1;fc>< zuxh*~fT`+dG!0x336Dk@tHBWKd2f)iY~GTsdW7v2cn&m%uZ1xNnH?i=?9UFi{f%zL zuZ!S{p|Dw54~(i?^XVlmBmPhp*qEl#BqGC!1r(uZZHRVP*%nOFHq1S-^vG~#?7dx4 z)r^odgRI~o%@Lw&)Jx-0_sF$6IaE;+b+MoCA+2QvB~*F66Znjuu{SKW7eoF{p~*$W z)3};_4+L@)ed;4#i(`l-FXx^K{pqKjRfBJVrLXkbx5eej{F1>An+)p3s)*L3F29wn zmA=$@V_bX=CYA;=hpn1efksmu1h*X_C_7UW_tUImiU^Lz|3tg@6`wt%5IGwvO`idk z^Q`I(bE*Q+=qlYRxtO8NJ2L;v!5uw9@2n&e%_||Ej6wJH3>VX?mrN4V`lg)$;o#ft zDyOt%ldJ}~E2vKP#O_ftqy7z0R8Ta9CH5VSP*@u&-1PuDZ%f;rC${RaboiswGw^=A zn}+P$f)zbT#5HYAXThPRL)>tMn*&$7%1ALnss*D4AiK{J)FMx69>!lT6W_UWdJoubUk`=quSz#$&s2ni{s8#?+0A^SwZ((cuGhSpG{?1@o^c+aDZ0(?6sSO#i#|;l~|*i^KoY z;s0~`(55bHk1L8~%gU7v5nhSQDI9zh=KRg8xrt26i^D9uZD?Z|Y}a2P{B62F>N98{ z3;%m{HkJp&#%SJ#`H4?no@NKCGhQ=nd`V)KlKnwu8!nx9m)0KC^^=QR?*nfhdlsx; zi*#G>8x`m6%*m@^sD&<0ZHZ(yWq0&D(ysz-sqlF_Up$=4Yqfj!w>$4HskfC845i#| zgY{!gX_2Kf?WGU+Y?HhkK=fNB*eKz=8LB_bb^R3La1Rt#%@@yRoL7pIzh={5Elp8M zMxgYib-JxlOEVBTT^cW-6svYlcYLqxWBIKZcacn)lNI!dx7X`#PA?jQljoZqL>l29 zp4ybONy4K&RYxKd26On^TK%n9M`>@eLL*|=-gb?!Zd*&<%w-f~t>t8fEgSih;fheR z8cAU=j$F$kC8DSY+*zmfFh+$w%7)hLq5G2ayxKm+oMX!sBn}>7IBNY0Qt7Sea^3L9aY`mASAJOagE6rmb zC0DM}4EHh$1+b@Zgme+R)?qgEVjB1OyHJn+Ufp#!-kw!qIMoxB*U%=1u} zTw|rP8PK?1pKqz7U}t>~q14(Bq4u_4&3#a1FMm*e>V1v>`5Vicaz^ys0Vtsy)5*pu z@MMjKe8gScCZS&_CdMcJt(K9NAod2P2l-3Tbkv3_2EOg<*z-|7R7RAntB4{oWYUd0 z9fg!M7L0ioN$}&Em`Mwnm6 z0OT9s09mV8nGM2ieo@g*SiyA~NkcV>Aa}7O+-lvjQ7*bt6?w+ui}Qdhfm_E!G7C%G z;ZQGJg%55p#CK&UItylcj;CfXas^+Mq@;zWVLPc4{y;o2>Jh7Y1BO?yUyOQEp4VWl z$bc6VUYLf!h(iw_r@81=97UO*?#8Av;4m;TDx|H>aQF#_4YFaTcu-|J8I&z}DW>Hd4?nD6_4MnEGNLnqUp@5ri(scLD`h#K45 z+1pwg8`3G6n!DH-I>IoB+8NuMSlXGxFi4u1+BsV~d(cV1Fo>8s83P8}(9Ze$_niPi zm6_=${{ZMFqhhaWX9@V0Dd3ksQw2aC^vl%z$*Md4CS-48@*fNt^UqlJ4|e^Gq`wc@Z}RECcLMr1hfK-d z$llpr7BHuOXV8A8bbmJPhiva32kkG~YyX?Z2|8LD+R%L;F5ye(pwo zAG5zWj`wexC96!QAa3t$VQKuI81+|c|99EMUl-V4AiDpiRezYt|8R}{qBHmB@%lw+ z?)UKmAn$*7@dF0zKOQf@xzK<9gZxF*=nqEy$z%Whzeq`EL!19-I)Bkl`tz9m5I_0j z(*NoB|CYn}vG)I?W&evt$v>F%kIViii}ANi`pv!m$D{R25Bz^H?H?Wg&rtBIY5%Gk ziTRg~+<=Af8$;luJS5l6h}61E;~fktb;S8rN|h8$=pZ1J(p$i&QkwKY#LjbX)%cWD zphP6UC*yqd$g^eiboSPIHJaEDx@}>cQfJpT+s6+oL_~UN)LaRA>4SsNBU_8vzy;+B z?k&mj?8^0m71_nyrzV1KMV+sU?FB=$ehA4zyNw$gPj;y1JB)BybtL=|#HH4TgQ9u; zX6{z5q>jnD<+fi>`!-dlA4OX6AWjhluBBUj{0)^RqI5Ft zQ1UhgJN*t-)Hx^)4x<5NpegsOhCr4jT&mS_~kj0>&Es&6>n|v=vFCiPmbI-PN zW@R&P7aFpBz;3fTYY!mYJ23{9pujJy>=vp_A7Z3O?Ux}BO`)nSE@$f#AU$u|-YQ_u z2HSl2S`9Cs-GUrZ5qyifM6cIq3+gvjU$t`LNH!2+>otfo5D901#Olto91B^cM;C&r zw{|RJU`rYwu~94@Bj&QPn^^TmjT8;xuxv$SbV_2`hUEPkBC^=q&BmNqO!R<9qLxmx z$!*{Ule3tSv=B=$>E1PQx|xQs6B4-d(3(r`e3f3SkA?ZLK7)G};x6~=Q?9BH@Z#Qk z;(SlXUZP;EPXd<&{$`jK$)yEGsw4R}j+cDu^tW9w8I zoO|py=R(OfCz!sKI;s0Hwl*2Frd|Q};#&Cfa^uyBi}L7Jpa=EsIFky!bg#2+@AIxi zH^w(2fxgDm(c%o73SwYE1kv*82Q)wI<{)|nIV>nXUWI84|3MN`u&!?8#jY%IoEOvU zO2o2^)8_$WJ@WGzL-F<_I4oj}?H6>kdYTr?qqWbl7u!V@O%Pf2htyx15c!fpo=Amk zKPgqHTF*#vWk~Ka7VFT21m<9}!&gbOpi8-1J`HHlviE(P?(I#iD`}*`y-LWrg1Cux z!;m4}i1f@;EZ21QuIZnJNQ=Z#vbN^XN`$pgBnzcV!6wv#xX>DO7AjPr4l%M}f$#xa z8N9OHv$Z2031x}dTw6<=zwsK7|t)@O3iJlMO)8NlhA4g6Y=YP=n<4x^LC{KhmL0%02q$q17c5%c}3b2!+} zu%%V%6!&au{OAHE23bgXk9spHLAWwzU!yB91oR+dp7mT~x^hsb0QwtlAgF=q!vxo6 zP#z(4j5k~gFpOAzhG4Cgx|dt0Wglg!y~fr+)jc8u=qoKcoD;WAFNUk-(i^Kfd|yE^ zS>T?1J@ypE!3J6m3h9IuJjkSvN~Nh)z%_Vq;8O17f|G!ZP@!@c*aXY80LkomAHD^s z@*a36FgK8d(?Bk=-?IIQ+OfMNeM&81pQl7 zApyddBWu;PmD@MX{Oj@+6RBW_S!Lho1e-qgYQtL9s_$~He0<3i@(&TX*e3JVT64r79y$J3gNKFeJ2a9W8@t9yZT@{ZjJ{q|B5oxjH1L*I{h6JC)vb!1jU zo>21?erQ6D~Y$_gfrGN~_5Gc!wmkS)Wvg*ibtU6Z~ zL9HyM4Brh=!kbM(C!}rLiec6w^-*ij3hOoPV(R2;^A*XnW1u@R^Dgs--bh;d?Zj`5 z(7O-ZuiO|aRC68YISRm=f$PrXtbsqwwgwDQoym)0*jia{yK{KLtboKKs!~=(-?IAa zSWZ1f#2T;ph}A>t(?j$cm^-!$K5Nv)wskmr8@B`%j$;GE#Jx|DGp-fjKOfiyLT{lc zitDaghla&8ssmbi23d)jYCLu(U!&~VCzR`4vuvk=5Js{&xnY=3kJ99CsVJGz8oR^f z>UV7NZ;)Q--!@U`9ABladnxrj64uU#J9r6z3;=a#yXE812h0ApV2O6|miiTusTm<^ zX$~RZ@!9l z?n?GeSMv5a&`|ggoBURqDxcFaCtE(I!No4}7T$kSxixS*cv|zYjjWzHjtrNIOulud ziy70d?zBP_;aZUBmgB4Fv*N{DE-;J>BoA=x&Q-B55_%%IT0S&vTptiE6RK#SMmwC( zYk;z6fcRh^(pbVG^MFvuD`dEslIu>bxdt8ZEk4%*Gw-1-Xn zkfz`LJlPrdQB;8BZkO%U@TivMjW0L%Bz1>CY9#{YWr3+yt?R-XjOF$k8hw4%2HK3) zlS56^wHrYT?rCME{dfUN26SV=Z)3JQTx>vj-snLC+&hS)m|!R!^DL66wt-f zc-ioEAP6SnUd>Rc*kN5$6qZ^fUey9T}4{rNIK) z7Tv_N94j@p?fHCXMUHZ!ihijIC)!~Nf{o_&ZaGMA+5vsw3AFlUiT@iLC@B030GAHq z8Zek28K@33(BLE$Nk}R!NZe0cs;;i>98Lii83a4!Q67#~@Y+lb5C6UWHy8!}3ns78 zSIwZ=il~7Nl@QC+;vOmy@CM*K39}ylEGj{sU|1OphBBO$HWG4Hih83%(xKX^&DC?m ziUmqFp86LX-hQnkK)w?MK@{b2S`SL-6y_|>1)Y$EKjSckId!+a_6LFn+k znfll^R5+$r}i1;xZ3E)--@A`a1mNYw#+u6ms2_1j@aB*>RTFc?o z6&@^aOlmJO6ykLqxnp_}P^@p{p=(=BZPoNevn86eU&_5?#prEH^mt&Tb1_T@5x*dE zgd-KG04h*d-7-P~IVcuSK&sxhNxfw3h&IaZZ2G7nS1i|Kq}6Z-Gwzk6khu3(g3`Vi z-qeIay^n^_+yDd3tdDL6}!*J;_+bDS{Sv?jYrL zXS4^ z%TZe{1#hg6G&@bo=&iEI&)2xmtY{7KklWo1DRS}S@b$^Rlc7{~ZXvwWfc8ZKv;M>+ zrku^Pyvp@P z@-9_TvxGHMw(9+tQhG!wV$5osK1V=&8*T@wEp8ScZ!s)jn`a0olYcy6Y;i7qU;$AA#L*P_~t3apIgT&X5@?keiHlcV7kYuPxnh2Vw z972>yBNd+SgqVGDB?&LV8d%b?ZQ$CTzCs+c(Y;!Hf;B_t-tVM1@Z*4n)eL1eMJMvETi5&H8m| zFZ+V5Hu<7JxDM|HH<$;pv$*yEuwTNkM=J?(v?hRSvk+v@3) z95(KBH1Q5}tI-%uoG4I}-cD`)SI9M#LBhC^m=K|WbIdo*Z^X3REJ|Q%pi`mcjl3}&>qnut{0@aA; z>Bc*dMwsG>#abx2M1>&GiWM=q6hgW&shJPXbd?hC#CeBx~S_E}lUfE+sLRlXc#xM5!eFf@RaS1s)e^LPt!Sg4rDt##ui$r3IW*HtADCG_+U))biN zpI{y^OW=#};B~BFBm2Ntybtn!Q9z zpQtGV^BH#;xcm4dQ-WqD z;^Z$J!MNFkr6~A#s5S_$2Q%Kihbvd@Te2y^|NMB-CUO|z&g`OWE_YcnlA9Te0A7LZ zq|N2`DqfhgfDC(Zef=HXMBS{i6oy@-8W^#L8EKkb`s5&yKxOF7$AS;aZB(QkH;JOL zMsjA4n?)Ko<3xKgjal9+2W39@t=rK$ehge+?;O@(5B>O^7Y;Kn!A1NoduQcGJ&!hq z#?-#W5c6MJy9eqmUu=*}&23iVosx&X_cGiE#$kIlAh>+xY!i0gdFz%SOkbesSLJPZ zUaw^WQ7xi%q&=-7FD9D8FfKav=l@~1I+Pv7GPuy2WU}VpDPZJ>i!J8>KUovoQhKPqBtr`1l<=ZdNacU>IJjH%Rmp| z68wE-=fZ>R`z&JcRrtebi}a`*@H?{i{Gqq{Z_Qso<_J2kJrhAPXf+u6ZiymggnSpk z4~Go(;LJ2xij1 zDx~TQ44+F9&qIsIUCW8s0s}ujxE!DDoWBI#Q~AQX%Py^Ll>o&yuJKrfE+j0ji%pA*#042W zqb1NnG)v>KzvrF62@|&uXyX&bH3ROzGgz|3Nq-G`wp_lZ?xnMo{(c%ky&L~i?p$XP z$7BrTxxZz!jrelKi$8qP)7wQjZ402Rpq;g;^#`YH4$sfgq3mbjP;0D#l^{6dq}sv2l0GIJ85WEV!uJt*{A4k&n|0q zx^P&T>Lv(xDlF;9HaVzv*5!pP9J1aXAyb3>h8G+S3lY%Q?Y6OVzvC*UkGamt?5qEY z07KUuFCHfkig_5?fC)8P_0A! z1%UR0Mf(*%`%B1MSW@IWY4H6$=6{dE1AIf-!^zpyR?^PQ{)f^7fZekMV5=T5l!7Mq zMy6CS4D#P4A->aml&ZqYfY-_{Z{FCL+5#9q0H6l|>3(pb-&sF^>VvbrBY@farQ#28 zaTR-UNfBAYH@{Pa0D@2*h8+L~GN=QnK}Nt2(~pC}fPUkE|1$vqUR@YAKzSzMEkIok zfIMX71pIITehc6Sf0U8{#sTmpKzD5xfcydLk5B&mFXN9_x-bCl4A6my5r&!T`v(Dh zk1h;=och5I0Wu|k9tO*I16f(vU|0e49}D10tUrVlSO8iHtUsuR?`18_KmPSyd4ugo zYa>>`g;;-xxB{*PIPA*;xF9P)rGgb;6g%VhD}MhzKvDy6JAl5`4{asD+ko2vC>0h! zPj(i-MOgrCEm;77Ju?g70)P_>EPz99KMUjm3LJok;sRU>@F~DZumJkKv;63c$qW!* zVgVS=40s+EKtClGW)2uOz|C0z3@Qs?Fuoh23j;Vq_mgM+j-~@1`-kop3oGDbfFHn^ z060x%*6*ai_s*Nz0ILB{1E{wLyu%961^J`Hu|E_wo35;pm?yB>o6Tzg1qc3u1)x*`>Ld=T(UV)1oHIFPS36cb%B$ z145Z1iuMQbSvyUcFF2$>i0iz&yPKCxf202utuRC5&c;jib5H851+1}O9gGHi=q#8H zEG^fv?T6u_>+;;A2%GX?yBia&%iWcIt=jjao-R83Xu=%v353ajC%@m?5ER26(xK zPL>lO4X$F{MKoQ6D@bTN25-YK;^hWDU`B^If^mS^K_`OEf>DDgg8=i=LzN+Kk@ivP zUOd)-x7TH_&_vqE)*XllzkZ%64l4#zjmM@5S6-SH9Y#*9XyMzTpj$eM}@_be(tkhIay2!sXhA~hs2s?SlDSrM1;lejED-d z_7lk>p{z|s#FtlN3Wa)gCCk&g5#I(cF}7aM9$1v8S+t6Be(?>{2HlNhzX}@re2dB! z3t9IUUh+?r_`l|oext~K`SZ|>Uchx+1!$pku6&P=S4f~%ShfO6I#0x1PW zkihEg;43~cgo!O_H8#vH#%R?q&MK7Wx-5RIua8{jS#@oe;n6Kzs+%%pCu8VvI+X^> zxZ%0%KfQd|4h7}`}%Cf z@QQM|1oTGTmBbx#_%)al-X5U>ZvcC??VjHJlWUv>#&`z-Z{2Xfo2doc zVw5Y$$aYo4@NwW%3>3x!f?nijACF<*n=s>HpWBt%B5UQ{KKR4Aa@|_bnJ;K~Ln(B+ zHSUTnhc|CwcgE7ZmeLn$7q!eASjQ}^(pMPEeXTNTUB7IlO3t9t(2z$FaJb5NER+`v z=IWJ7_AYbVR;#>L<{?FMv341shL`Nc&zaB54a$T`>)m{>VU4G)s?EG;Q_npTKeKuE z2AT{kRqN|W05pXSuzgocRqwo&e;A|X|qm+6+nWc?jNn{cKe^jl}3DIBCSvDgQxXGVkTm#tozWK3(o zm1hgUYTLcVqvQ2t1Z`O(}sGiY;C`C?K%gH*xPo$$$UW~?-dOH8j?0_S87tvnZer*TWwNr^0p0AWtwh%1hoav-N{9lF785uUH+p_ zV3&-8@w-|t#^IS8jktb6aV)@5R>^)LZVOt7k=I?t`<_m*pdPc6nlT9DzAWj1F;tPG z+(=?0yiv@B>QH1TLX6#=a7DwMAf;TG$~4B-Ni&1bGgj{>554NjYkO& z@k}E`lY~K=31FD)L1S`3z7q|a7$n4v8d>W~Ib@Sc+z-OvQ;YFDw&TIlZ`4GxR`=!K=Y!T1{xURpr1b$@^+sH;d>t5$Nm6 z+9Dn#w0o~Nzo;!AEROv-q<`4P6_vFit0i-u8r_Iu{Yc3CK}TfDNiSKg6=`1F^DzxXjzn`3{mY^u=4)iPk$nd%_~wj0$vs z$Bw|d-6J1e4IVFP2+0x6IC9hYcOr&{P=489f8d}d9i7~ui2`{HZueg(L=6+*oVm%( zx7`4Or{{c7!j_WDp{vR+Tj~Juc&$t$S_1k64ZW&Gmf?$x^TtOy%e%@PLkI1LImQU@ z*)%)ynUmduy5_bBFv@@x0=Vb^V3|v}TGN%<###JYPWKp@i=u-JyLLkvs4{nycr%m{ z2VQGhx)?bbW76orXN^e=M8uW3HPPs(;7#d6-V~&FV79i3U0qSUkqbp32#a5t)SARv zUg7gJbCkS}J^1C-k3g4*q)!r2zBCtin^vcZGUr>*d|A`%WlNW>#h7y8&KEeyYT33f!P0j(U9-D-AEmum8ToOvD}tvK@UOI|hz4%JP>r~>wN zf$4@(4Dby+kx1H6r1UT3!@J3str&55ive2eX}m*_Sye><`bA`VJ3SM``o^QvC+kT; zj;|&85-bcLyarddeA>`V^@=1r&M0?Q(%~IKEv~e2FymN+duS9pW8Fcrhk-FiFwG}NZ7-Qs)TP$qFOdF zp?3(?mlMVI-u71f$hE>HL-uT@7b`bTJ`t=$Ns}%`vl-bIOi+-NHcj^?wx=tb=of6M zA?NEQs!Y9unv?X{gi{l0r0kU>!~+Z1+T2(vB(sWV9%G3k`ja4tURCf{S6F!dx7OAK zSJ^0;XX@IATBSEb-Qy$!Ie!_9q|(o$4WA_5P1`af+=#Dha`s1tu0yqN^MM85U!JVa zv7$wykKb~Us%Wg@ipyBys~*uPltV~)uI*A5=eZcdysT8SK>OkbbcE>>&rZU-+bspXc(k3cLKg#7Z)8DgSg$N*1QQxsr9-;(@U-njZxi6^bG5 z;fNn*KvfEF^Ht(jpnSr**ZWIh9l4%^+~x&;H$ZVA(a|T&``r7Rqo05}y@ec5z%%)?&Rmy|f2E6zM*s&Fhzy`Z#)>1aR)7#22GHHu z3ffjJ{Q11zV&yJ#MUO_2(xu242m-#3Py;a|;g*hBc>&4lp3xO+Y|piA*)pq}2<5K8 z5@`aDaAcQK#i-ZHkqpYlg~?t0`I8l4)>;;AK}q#DcPOs^@0J~8i(p6W#E)mV5nvtR zODLx<)GKtpt{S5p=~5%2QBYY&?H`_?vTbzYtrqUwW$vF&DEL7?=*iyH6HuK4ooLRX zo`s^lY}lXzHbxpf%;*3OgXlG$*S=aTVLb>7RS+}D=^9WpAMznAxsqWA6Nin;!t$ZY zx*%iXT>fsxD{Z2*{rp+2+BHgcB_v`jz1XRnUSiG(HtS5r3Z#+#~>{dQV? zUeu0ul99)e6iE5ELLT^)2zB8xzB@c9w%D_NOz z1p;?T2G?6<9CyvqksuJGffh-EuNAFYZjcjYs54?V&bp#rj5tY^tih9&Vmuuk4Tbsp zMO25G;c^gBVx2`sn)$NmmSD>9GGcDGUq(heM!41(LwIA`K!OEDv8zAY*jOZpDG+*_IxBLs=pZmRI2KimVT$FNkX)7Ws?VYr=9Lp(OC2RfrV?x22v;wot-&A>Sz_-Pf z`Auudc$k=|d6?%+B#rAE9334SE|F)HdrJ+GKogb3upAxh8}jo^paKF$B*iWKib@YQ zS8)lmHV!oB9J0)9mQFrXvT`fGH$%|J=Z|GCvDU2!nVn9vCU#muQw!j(q|&c)XFc6g zZdM%F2kRHJOB07F7OU;n9hfhk-F;^spgl}i6>IkC`F}Sg0o(5qbA|ayA6l*ACFVmq_?b-eXdt}k zjBW0EwM;EDf0;>E%Ri@7d|xCzOrMQSgKg2j{HcvF0=pJBANb;Bn%N3rzQO%!tuZx) z_<()Dvn}Q8aqveFmVV$HJbQ_ougJ(;Vi%b|YVuHYFEd>$e$VC@VPirRQ3&^;m~Gwr zdWYBDmDI!pctjIrFxCjsVu^7!a#yKA?ded3ziFCrL*+{`Be9@>rO*KC--O;n4;zo^{?FhQ|0n+PnG5rlG zq;CUh%KQVA#4|8&+YGu*_&rFD zkHMRY!LkhfVdw3#nT1U|8ckCRD_E6VQU@U-N=*^0G zeI1bXR|^(KHId*Q%$3>{N~|wPDXN9&I7EVO@FQqx`?^Jy`F@)y9CuOnTXsB*IBGa| zyU_7;AFF0VU80}JQh6PWjT1P^G_%RGQjy4At2=4~hi>)}xj_6|!Z6j@;5?ZOK2GNW zT^;yWoe1Plp!3n4d{Bxg3V(=XElV_ZQKS=7WDj(ffy=-JnUyn^rxdc3H~z@*Q& zzZ=>7{mT0tM5u8c_YYDnf{2R4Vf#n+C&W(n55;OHZ%glb<-2NFQAJybm00N0Y0=ebRW{ z|MFs=M08EBAmRahCFm>6A2(m`2K-$`C$Z{@RF37#=Wp*_VSQT!{N zgSlo%(;#IVFJ66zkZ>Xok_#(g6&Y^=2Rrl-J1tUGc`IBF-oA$d!x5s{c%qm|3JFug zc8&V_G%1dm^vd||V09SWsZ!iJQbizEKP~L;xF*JI?MBsDwTjEsNGjO|Qc55AoeMD+ z##sTRjwpuv#}9UlLF`3(79%t%EYMWFUrqK8H^{6PuqY;!R;P(3j2fTpe&E-`;&^(k zj#m5CQWgB`l-9AkrRJ`=?!}mhozD3mJ7!-G1>4dspS{`p-9zT`-%PuqC$s4aXY@K?3PhzA{SRuYppm>l+sX(K~CZUof zFH1a_P{615b(T%08#&A7J1#_I8lU4F{dEbb+Z@t5{DmmT*}4SGMUd|B3B`N|q#^8e zT01)Sy$6vo?d|{!BqE}jZe52N5oL&)NM1N73IxZAGSE$0oX@4e<*TgkZjY4Cr|$4O zdlN!^nDxwzM^t97i|jkxKI#V(U~eH@7ptJF0Cu!MtTkjhS8+Pz!($BX z9e)A;z6LKXoWjVX7OUU{($bCDUEJ5*?wX9M(S$b^_WQ@G*3x=kUf0!5ZYO&vpH{13 z-R0V}?`a%}r{O?sI?>N`1cU-;JQ95_Q31MAAR*AA-1ASB$@16Fv=uCL(i=pF8k5Su zMI9}WPzO@yt~@I+D8u0~Imb1DjdVNm4#btTiOX;|l$i{upG$+-9)p$9tDd16*nYlu zq{{^SM|?`1u!ye@eA$lMk77<+eg`XF4PV}$E08Trg#09a_k9~}>h=eug!|?9#|Nl+ zc}d^()|g9qLFKj!2(OgCUs#`MvKi3C3OFUg*DT;*V3>jfmqSV+DI5(-Yi#2gnb-*s zWQ}o@ea^DPlGx*h-2@n}Q}hKwmp`Y|C<(bn^IUVgjAuV%n6|(t;I4hY9^|gAd9F4( z6D=IrMH%6?QV_Hj&K2FQ92Hj+$;zpSM+{Pf z$O3M>2yFuw0RxM!vTOFqLB?8py`{wB>uzyU>yV`ErR?9mK9`+DgPLSzbsk!)?R8wT zEMFGSd_<2ucqM#La}}x-8=-fN^sRZ@gs0a4dDqeDVT{EZZfW$(JQ z6TI<70$owMi1KVWmgnW!CYG~QZ|FJxDK}wwX~%;hyQOYLc>jZ*$MLbW!f=?YddjKS znw!zcNcuGA9>HVbY>!7bjNA$L3;_=aLEc}!33gZE6hRz1_D1p`a^`Qe zVIfwz({1+XZ?#8Vn31%fOkycHFNw1|3{&Lj79lUmd7YJ_Iz>jTBC)@`A8TOsSO0#E z#P7VAzC`_57ym22?Y7M)+}cs--o#q0`la$Uuj%2Vd@4u0;pMxg>}ILw^}WBm{e^Ea z0G_zsyBKuvU@ja0je1rQCR1whHJI@^2s{7xJg$PLdrQ|vU)<~{)O(rFc>Fq-!&KYX zR^5HLsYL(ARimgSRNIWVv+3U3N%*09MMzX$FlHQrIvtG~tC611@wBm!s#M2tS5Xn> zHb99g!W_<%v<*Guq6eWguAB~9uEqjR!>C^W`;@RoPIKD;dT$|&L#$%p6j$8C)rQY$ zyYo4CUjq*uc`=inUnCnhZu;YX-3L46^olkOvh((qgQ5E&Ag_WkJ^3L+Eu*<#9YP(OdA?epGTiJYx5$L=3HRIFk*oNb?v1$S_ zuCpO6y9x+4+1RcQ_cBnO4;oHXC@0B&5FSEQd|qB+ppk6x9Ak<#6%4%SkM=~p+%Q%V z&Tz`7_^g%h91(Qm7zHsJ%m}jXh?ywxv>kbv#BJw3W~VpjPyvtUbNDD5(kK~FliixvUiDeWT_FZifPP{HJ(z7IX8)M~K@_o~QM(f2z@G4{x?N zHk(OlI+(2o7PNnp!lvSt9kTWPnw7ebLey2rin2)+?a#d|VW^(|)!tOPlB}kFkv%k} zyVPYgm>C-~F1STW1cDKWK7`~kOr&nnxHNccXqf3scyTZA9h1F46$u&NqtSo+_hmI} z$o9gOK&z>G|4K2ecX2L>atN_PGUH(NXwoouwccZ`>~Tv*FhmGZ2rFywJK@WyiD>v724$<`3`c3r|G#&}XybLt&rfG)c6xdNpUVxLoIzIwscb;`oGqktSWO%~p)7yk!?U zw-{x%LsR8QUn%%d7OaFpWHT@+p@LFn*tH!4d)=yh6RiES=(plri|sLrE+b_0N?PvBjgsJS{1Hvk;2WYeEu#{# zU`tS#y~;@n_5EnL9Gn5Lunkr{@>MkI(LfQrS``Z!jUbHaZ^E60e1B$3B-*7EYoJ$- ztaC587JT1>)(M-Z@dWJl?V(6ofN6~)az&?UeL;e{g@}RJv{2_@5^Xb()Bg)n?CgnIKs&iP zuqbe!Y9wC6f805eatS$pCs8h*pTF=sNLWQ7>Fmu~DRC-UpDSYaZuip#du9(uhJ(M! z@m!pUEfrhn!hH3T~619i6k`M*s5Vx22G!VCc$>>oB^FY4BO9g)H zA(lSrv;&(b>W700MRuZ2@LjGHWBTd2yNrkSpPrq>I6f5q3YmCH@_N2VAicd#)IuF< z5{``EqP4TYz3&mwH^nJ&XP30={+^6t`0BMowIOMWu#jL}0b_$sDc3iN6r-Bu4L;=v z-_{s6BApO(5R0;AiEE81e;H!a>6BinP%`JRtGdnI10*7HC4IcDLtuL&E3}4e912HE zCE%GysI@s~U+G&v0t;M|D~e#pOD1#Y=(4^mslU%E4jyM-=~(Ntn?Lz1=4D&@+SqKJ zbf#R=-l?}VU1o$Un{TVKuXyIMm2??NN*O9ShFG61ZAvXowH`Q#Jrx>CMWC*auF`ec zW$b&GIl4hJK}d+6i1fP0k4>+tmJW_=njZNFOEc?K^93fy$FIOsCrvv1!dAYjTzwW) zgr!f;%jlvKZKq%1bR6a$;;Xyi@#u6D)25x_hRRJCP7y+Y+-3oXrB7wS5xSJdg8o?3 z;>%#7^_g`+MOx>JccE3-dnU63A?@NaByg!yC?=VxkAZCCN zuo3?tOIhsX6M=TC;bP|(Y(#+gWha2>uyhyb)I_$D>%H5A6L*jN{h`8|rDE^TBtbw= z_#Aw!ww6zM`#Mr{{vq^$o(A;_9WaT76{tGW69LuByU_N*5(ssF=-iQgiV*EI3pz9m zkEITeRdYg!sdt7euk=KGeanJW$Ujy%&UPl?%gEU#4}Z3G;lU}IPdXVGzvcr)%J|LUL~^$%WAB8?DAjK#S5{9U!%nZs1Rb8FVu+!NQu{Aa)Kg zT}9JETKl#(l7k}EjdR}*%yetc7BxE^VMYzPYpSpGQEPATzACSh5&GzhoF_kl3Os~_nE&LCG zNHqHg3lXk}rjTO`F^=e1Ge3FC^RN9cMzO-YmsdP2nDE;JnSoJGG<`)WUlK}#MsNk< zj10Z;_QeKozTCgGRmjNlRSdD}11}oNHvE8t34NGn!590148&i2A#}4rK4=Vpo!Nfm zhh+i3oROuU7!fJx`cJi<6LF9{>jUk=E(p5Ry@>t*5NQ#rPcbg7KDNFP0GJBT=C(~aVxcl*&W%dUpBoF zTvp}Z^`$K9o%4oU$53e8J&oGkj?K~@Fr9N^xwVs2))~-=ngyFp-Gh6hFXrEduo7N= zqhm1*^uhPA2_)0A{lsErE6|LzPRu_n(W9sB&3uo&)7}#&2eftap1`&5MTvCWislbW z6|e?=Bl;j`k;EzS_>3_??-vc~fu=0r3A;96$6mxtLh8t0fV_u4z)=8Aha@hfhrJu; zl1GC1PBCDO(ms`6v_Wa`%sAv^Z;Lu7_@OU(wEA4?m%TGpuzt#I#A4c&Ck3}lA@t^f z{Qz^f)1b)ujKe+Pn?E4&B!(vUbTBRASANY92!W5rtiY2EsFIHwxj{9J&QAD-dI@2< zcOcS_r;9o6p{>(ggb7w1{;=i`xc~Gg8PzOf@I_c*VDRN4`3~5Pyg}P*H7*6Ynq@#H zy1qf|B_OKt#{;b|WQrbvd4d~>tlVuWAUzXy7URQMt57JtAu%BJM~Dfuk+vwA)o@0p z+r0{D-9or)Mcm`O-IYfg_6?4FmApX&q9-uGgn{@XJbwJrc;LuHwct0*6QO2Z$_CdI zrr+5m80(CCDu)mvkP6~k$`=hErEDe8_(r8cLlE@hk1TNjH3BM_{DOLq-zYpFMgeVx zHCL#mi9IT?9|Ns>^#hp#@79L!djH%t+c+kKQZ4)JNqzGKWWGX#_xg#2{GRL(C5E`@ zj%Ue#1RB?bz=wRjnq|#b`w)nqzUxg}>$zH+aevYt;Rx(NK7&H>!Ud8J5p5*2Ppw6F z0gi)Rj5w)BiV`)d*qAO=T?v8#LID~B!s($jL+Rf64%XXYvw(})rJE7($7q14)%K&C zg^bH@+wgrL2)n^3EoBGNgM6h2$pKb<*15l+-pTkjVWFx2F_^u-njrm#gUXa|*k1Tn#MHYz zk$G$@a+14QrRJJy{Rco&KVo|DI@dLq4NP_?^`EAo(U;Sl_>0o5#mh;fClWDGYFLS~SH zX5DIp)b1nQ8Q(200;O4Jz-ixWyeAiO;Sn;x&w!5F)@+f49A9VNJ$~QtU2R8DFYn#` z91CQiObfA4kMyD^Z~WlTQxF=wKx;R|tY~>)+YifP9QSe;@dLPvMl-919Y{uck%VL; z!`;vQv_Lg;8@xb73qPOxe}T33pGdWOA?(S~0yW`-kJKE$xNl>6!~GStl2A%}agn@p zr%Qk=qm}CT%?7amHpB3{34E5NYEu!Ss$1$3m@Ci&rtL;_=No?H*NpkRK_w{q{`0P! z2h7=<^N0-iO)SKpJCKViklK8Zxlp_}#xD$f{|>Ba-)L!IM@~XO_iu^Gl_)I@%#A2L z@%vpkQb9YpE6v*TIHYkp2>n*D*9Ax~0x?x@PU+$os*N^u9Vtu59ewv3zDW&l2C4_2 zG~0Z{#`lvq%P$l@M}!$`))PP)O+DnAqW?CqJGA49Z%-bm91v1Iymd{IFKQ(r1Sh5e zO(Q-~(e5TSkP9Qkv&Sp3KL`3v4rutZ2(=(~k226S2-*AXxv%V!+WRjvoo(HEfw*EY zN9sE2aFt zns(qFTZZI<;gDB_>9Kg!(L)e#9V53iO+5&q50J3c52-`Nx4> z@C4|*953!S72iHz?;p>v-7g;*yu?P5s@p94-P3+~-*LIG#Q4Nn!cTycez#P9S-UVZ zn^Y)PwggO)adWeJ!RaL;97Gs~VKv4BcbSJ%ep<3JC zMkFcW44ftJ8>vRhXQy|lRmwg^p!-vjy$;+)@H^-P%Hw6#s^KN=AdN%54=|5XZsGXp z{D^suAiss*vpAQlTRB6G8mKTTI6=kVb8tu=q{Prz8%PN9-V5i< zs@^w}3raVwL1GDA8%RC?l*0Gn>E@XbkWhd`1jnbJy!A3+7oyQc-p=yR(lke4tf)rE zL{<`_q0Ng`?HW2+;0a<0vU-uN zrWhIJ5@}=Gc*es$gDsmiwX#;p5Qfi!Yq9+qG=P)tE8b6e>Hy6%o$h>kt3-+&?~c%K z8Dhp|fzl2uWpPMXKSMA`9FEFBIkY!{Lj!ce|52Im|GnGR|0OT!{{ch%SE$W@#hLy8 zVj-CSd%XVt+(P`Ty8kZ=!Tz5R%AfLqf+*>CAOl2dC8m*hP&<7u3aKO&8Wgzu$vr=VKHW!+ z3}3_DEPPdX*mKV!x{3W>8UwluFI1>e=bF@_FnKmsWu(7*o;jFUH1jb+inTYJ;Kzg2 zHuw3%7*~Nei&nLrs|}OK)vmri>BV+fJwKu$O-EUst7n~9I$1$$k;a=-TyK3fTdI#Z z_KkD=r5?Z|6pKPVtk715E{LgG+ojcb+bZCCPO>;l%9}}-$+Pv6&cp4{+Qg>@EXWv; zi@}@%k$Y3~Nzzfl9)gKhNYECP6+8yUWuW)SgnZaA8rc4@4YMAs5*8%RBR?ls-I7UV zk~M`(C0$U7p{M}931%eu89}sy^gAID&JZu}tV4>LrI-tq+4B8clG<|e;ZX9rv% z=x^h{-}V1@AKrfj3jQAi!pjRlApV|cy{907knJKf?Z~Zw4cs6hA?b^viHeHqk%y0H zdn=GFk?b{K{4%b(e8I9VK`^drTC5Yom%(4h{#E6pSyR%|vd>9Sa(UX=Oz@5+UozY` z^}4ji6W*MYA#ip5kaDZ3(PoEZ)3PF zk>FK<+}Dl_vs!0wsXiX`>`{cwOhH1nW$~-puD(7`AXdQ&ZDQIcBz7m*-_s&Zgr~Xb zvMWVDQG(W|J(?*W#M@pihKORv|w*6cDu{V0XOGA1*?CVrLimPM99RF6s!M zZuZph>zvIy8%gXfPPWVvRJ^c1vlsCH2Q+w zREb*8idgXMTm)*G*g#%9SK5c_4xTHd+wnG}`7CL^3|!@1uXU6i_r@UFXms0NW-e}4 z{8e+^>fY-*yoxowd~W^^i#YE~Jt`gweftaEbZ>p6+FpKDR!PN>rOjO8?mAo9mjTXl zClLjqD@bw$yYMrqnC(0fMKO;asXRa`Vue=r-pZoHEw=y6WvZ%vlBQ}b%eHP+2V?Ze z#gog6AmEKczR0f!T)T%x16F&2Cdq8Fs=EHj)L;* z_6cr{zzSo7jdxz)hh}H#z`o^~NG1MTPAb1F?MEXWJ!gc>9XlskmdYZW(|e<&9c@h( z1Y2i!XcdcrgmpJC8xZRj>7#Lzn(krKQd2980lU$K-W&C~1ooWtAHCxycC9MCUV64< z6%QLd9ZJmwS4(tzxMgi#_E9sJwl|-Wq7j=>2rnk87WqEJ$xElbu(9$App@k|1&>8V z2$uOXM_r#fMzuvdF+IvlQ%QzXGI(0cd`&Der{vkW*HmB1yXul6yvzJcaZd0{Ws1%N zQ7pD67U?`8Av6i3UMNt~Z05a(>7#u4iiaw7vp6+#Yr_FSaXn!;aXs$R58yOGW;y)p z1#Zx6Pl|A0&G_n~87CB886ffD2TmyMH-l-VwavKYXK+rsPv;UPmm|+~skbG>N8m$~ zwb~e03MMmbHpzGA29aQ9pm`ZHiyD?y_v{N?`BrQPUUv~{3rVX~g>i!Ue@ym8vl%sC1^2?*0YJ;rxM^5j2+qB>qQL5Ij9`|M6s? zJk)yQOon}82I)_@dsClTrBOI_lOKoRtE1xXadN%gt3RMmk%EJPp~2 zMTF%kb@E-N0R-UTW`M;pZ(wgjOA#Vpu=_Y5t&Vmf9*^TxMXGj2kpM zd6Sn?JqvXPE>o$Fse!i<#X3vimPm{QF=`-41Z8|0OhD}}$;B(|^JYeXT}ms*qJ9v> z9o!lb&E_H|8NPGYAaNw2-Dn=gpJfS!gXJ}KWmHc>I#Roh*rxK19tBcI=iH%SL2eZW zc(Zv5;&$HmuDQ3*8sBL&@a;4m1@#~9;n1EF{m_74@}@<1;j>^i?30~y>=hpy0OHAj zIzOeHT0c6;s;S$)D#&vUW7d>$10R#F=pg^1#(4{n4(lIJAMPjO;-v@w?o|{*KDvF% z=+UFJNC~NI_RP4WQRQ|1l&xRb!%VM-^DJE_H=I2x;ZP`07owSo%k>4h(5u1+yFB!V zFx3!=b<&2;M-Zj8){w4t1S$okso)kyR4QTT^kU&lLP4k+@DVOlU_h>Yc=y!@;jfn? z*>OaE(25MN%-8lxj)86mV}3ENRA8dc6THiiiev21mJq;^X2JevAV%F&yaZFmxU$|P zoI|r68>~{wH`%m1s^}$>>ZKLe(Sl81bmxxwbcZPRf3{=(ZGC%{N_}0xr zDncUNh_oW^$&(Sef2%2gktHetQ|Lp<%Fx^}&)}6yNni&z&ABUqN$$U6RRY_|XD1L# zBF1jbYb^f;v)W%9eN-i)3VN_XA> z-*5SiZy6{qSx4~M;&+%RSs>vc zWI^7`*{VJWKPf|*LS2!S@wRvZZ}j%vpf3@=wFI`cN~J2Sug~aev_Lz9mCdEdZjGp3 zIdOqu4e8TZ3YFQ>J<0XJ9BZ$~2rm0A_V-A0xU7-Rr z)YUPw@O4JpSPXSLDGJ|Pe11m|ONg5P)qxv&;|ijubpL| zENU!892}+W;AO?TXs;XGs|eO9jc5re(RM;FIO8gew85?R!ryy+ONkUEtPrdmRws^% zc4CIb;mA1`eOjSBR4kXGflH8*fIUB-jFG_4-sq3s3jy>z>bLwG2n2cT zp61mw^KQKlpn2mhCWod2gM+xFBojj>6vjGcW@_S| zoQV8NlYZ59nHXKU)q)kK9!DnvT65y6DGhj$OT!gGLtP&mfkP%9W*s`UdWsh#Z~7{rhxIp{`_w7URRImcFU| z?K8?^QSzZM!)44sQ(0$cBRpZ=vD;0guETBdfDt(FIEhoLAxk{`6g<=ef-(;15u;gB zg5b>5R3mpvy5WQ;Ih@)R1|`mLE^#2O`FW^Q*B`qxd074Jh=hFZdtBEnluAiS%F4=0 zE90QoeuLcne=D;5-YaC%Y_~d`Pb;D-maBE!%{JE-OKj2aC?Tf(Tuu==?7j>&1VH%H z#!AxZXGB2f@L~7D-vW+OVpuZj=fDjLM@>@qu!Ck-X=eKiwzChl;z zuzMjBI!*Wvlo4qK2h`Q!fgpk)5QV{1Bd`G4ku%RrfLpN`Yf+?ew`n zcxyxNe8!tteOr%-)74OW-V0Tp$V9wxd`7i~y=p6I-O(}I99S~O*R|_dTE&#D1Nn9Y zwq{1jA?C$I2dBWu8fh1Kq`{2YS(aU04bJeV&Ne#mFB$NnYDS}!!lqG%mS?-o=&_Dv zNwahE(AObF%)b7sq5NKbE;yY`Nnm@u(!thgtNC?@W2t>u(n)AYx4hL+#4a4p`LXiW zaur1w!)C0U)*?qQ|1s^X?&^?|Y~|}GeF{xt3H>q;ys;$IhtLRvoZpGXLIHnlhE|NK zB$O7x97GaCV?1N3J;bRX3w_e36Q=&v(mR|E&$&iBT#FaiRA5|qki{w16|T&0&yhQPGcUK|{961N_H)cUS14 z*UirbI>mX1U+a*cIFQdu^I2;UDyfIE;4Tu*(h!K_tdb{UR2Cs4A2kU>d2du^9SQEm zWXMfND|0K`*Y-P;4%Up019ym5<$Le^`P)I_^A*|G3%$p8o8ttTZrU zS&e`v1MQRX^;0Qha!fG=sw5(cXtj1PjcnB7!K>8Q8)0~FAi6j(<9XVB=NvRh-Tvry zvTBo0zWZ^c)0hY674?0aZe0cYH*7|msmCmQ@b>agRuoKCyW^OeFkWm^8k8eSzIup-c3X<3=l zi1v0zd^6o2qvPWe>xTieb9T2Ih=@D2EmxB`JV*k-oQNAc>I&VM5b5wB`|NjXhRCr= zJuUuUHw&mqsQ0vbsQ`Zp|8m*AGnaHn3#;dOl93x%!?^rK@qMKEH86j7+I`i&!m4ls zpB;IV8L$be_*|CYAdE%O()J9y6?XW%kEbLlNnEvG+_gVBZA!~t*i6P7_P(d#xdbEcxb5r|d8C>Ct1KqbcY z@V8+oX5?)g{?IdH`a7VlT0NI9{@y9e%)obM)jYcyNevJcK#Tmuy~-M-%hMeK&r@ zd!Mf`y)G5p<#67Q<8t~OKVMI8efI>to8$Ah-)5{%q#PeGY-!XVz~kM147*>ACfy}` zyYESxAE!`$1vcfVQ-SX&LxCgja0x~k5oO!sM1?FPGj9D(m!rgqf<+CUT{RcK_>Wa?Z8o^PQHeT0&PF-# z7fu|xNjw`)4|Ph{)o%O?M3{g$CNBV5-d)seDL>8rCAyh`|Fd1|R}gN-LNDyVZ?zvb zR#haKYOucJaK5zQtdjEh5b1LGMI zOxYfn%VALj*BJ|M$6irKanu`(96pD09VzN=?F`PWY|p7bsqk6a+RauOXm6__eYbHi z2xF>7LyGQ`>LW{r<3_C4mKtwOjK$ZMp{1TIBlgV>D9nENp-}w6@0m2KbXpo(92YaYawQAlSoB%oVGIAjh5O9~{!Sq{0lSoGyc!>@ z2r)DnT#6#YqO@d)n>8^6*?@!cCM;2RL~ZlG(e{?XjWk_?t{K|Q%*@Qp%*@bcZZk8x z&CJY9ZDwX{Gcz;WZLj*ByE{8G-<{aFarZ|_3T0JFS(%bTb}GiKA$+uVJPqKfb!@Vay~BM@vfnzentG6%9Ee*$>^KUEQrRsp zo_dw?^C=l;r%pG_it?SgKNysfWV)0hQ!V7h8p^Ax)};uSUK%T4&2GaCx&)MXew_xq zMk}aox4V>-IA1YDX6k9&gulDdGECX7Ojf&JMho3vNr}~qDY)SI&Wm=O=Kt)6bgaKFF5HZ|b%`_Xm#3-H5 z_qYAAM&z${r_@}8Z+dXOkSr#KJF9eo{vfacS4b6Cv%!j8K_Tm?cmCZJ?B@s7wE>432YPChfxah5je;Ai+8 zPqsP{4-XM=G;8L)2-{UX<=}P5eDdrqcQB$baF?&i(B|iKQGb1i^$ECIWRwP4h<+|- z$CqmXwW5XgQQXE&Y3`TpWku3Q|3^g#KwE(!bN}GJ07D|~?Z-wk#M{qr{Xz4m^f2{c z!d4-1>4}@#2tbWZT4J z;Uql#B(6)L^C`>;x^XyK3N9-9Y?R7S27Sglw8tX~dc>>m9T!XwSBjG#xlM%F#V!T` z4zATUS|p{c8-a)`!5DgwN)Ofqu0ERPzCDIx45Pxz3Y}scqxVdoZkU+@b zt-n$W}aBvIvXXkC& z3HG}vT*+uErRu6jWs80=Ug>eav8+JOg18)}0_iECeo9kN#>07^Fq$V6x0=Hh>rpuD zsq#z@tKB(&0E0zqup^&A!ZwV!swAIUxc}VS$ zChe(W7B;fBj?rtl zWgpgvdA%*`HsRP&yjxW2!yZ)uwLdN=Jt6&Clo7FeuCleS>H50Zcf#*{-B9t{WWM>c zk}}^mMMXtdtY<5GM&h@krP_3l5SukPSt8fqeYd42Cf0FfyR9$jt;wMI_@kExq=4{6 zJ#mK}q674ii77+S&0W)k^vx&&vliWG*;1 zkH5GdDXeQ)P$r8I>7xIo5a~z2SMIl#1lqT?Af}2rNxld$85(Gju>CBKGb4jDf_(;|aAxsjoL!&V_N4s74AQ6}3qa>#k0foJ$D ztvJ`ETuA1zz}kh>L<-xc*u_{TtKKS1C7EE4#`2His_G&x)lTn16cUAPqR(3oc`5t` z?+Z(sr?7ZOe&>wq$5jun5BPtF9V-r$yG6n^k6vsAZR;g4o4CaiVG!CsMW7nM?M|R- z3`_mE?xM-5PILPaWBqGo{+Gr5V(qq-Pg{A3jn`b$sj|~RphToY{Cjy@VNpQvPgn_6 zWmiXIm*+r=TEp&sT4T3;9q21k3L`9+^B`O|-bd)v$ z)^`yC=wZZ&hWUZ9E}TFI^R>XF#SxlJU#@F&$V&a8)w{NIjR9f)nDCsp$`QZ|Qt%hk{$0g~|m^B7gppg~%75%ViM737;k+J^l5lG~E3i?VD%f7cb zzV;f0H^^r<0g>wU!E`jg<9OzX^#+k)lBNTU;uBSa6o)?$k4ih9jjDVGb!I?VI{f0% zhJ5_dhdOPk<#YncDN%D^1<4U(keyNQ$lG}I?o&s?DY8p@!5KP>k55w92R)ZZ`#sRb zs#A5|D4|hCM2ZL0v%uuCXc$pfSG{=H3WE(eL&>%Bm-bca%uYQ{pm=b<9M33bzlXz^ zc=T#;vu+xhJBsUQ?atKvfPyXZkepY`;J9_G_9~~Hj)J%IW`6^s+2D@(oVC}UF}p26 zSWl7j+0H?S|Fqy8JfWcf2DWu`05^vMr{F!bUm%{eyxsSJd3+C!nr>$-jR~F`?ALUL znhD6Xh@YfiARKV|SiDioV>nO>eWB0_g%R*Db4o(E*ha8F&f^(n!;Y!V)m|z(J5(33 z9Bp+!a_BWB^!2gncd#5g2b^riT8U*^RUDUOnbQ3ab;RYyqNl3l%+$RJJhu~v0{S^R z*`4&w&<$rmXngSNAme!D27d4kDpsdh!TT6hY#cGyMR#ics-HoyF$LpFm`ZN`dBy4U zwss!mWQ3u_Kh%l9fxu(orKs8@4@pEwoAhJ-R3>Bn`m03wkffU{h?-!`wLjuAj7mTQ zGlGcuy60wmGUBR%`FSwCwlj{&d{Vl_>oAQA>#qFbH=O(4;@%2FF_%1VaX!zxh)qrs zU5~-K-6GDK_2F|Pg@oZ_3!nOVsRRZh8X8`-tRO;nR8GTBD%gBt8j)feeiAt{622^P zxf{GJUkk_A*=zYLHv+JVI>c+)8^&`QXw6M87SVr&*xrB{wff&yPPc%5~ z#!)|3nB?=c5Fea0&!NMu*a#pCH0<;2iZ`f@y7{hTXZ1<&MOxf%R$on20^P=xDzb0W zp6L8O(KnX@Wr%+v?2 zx38e_PRBl57m@k2M=>wO)xh(3a$EA%==t!tdl}(s+4i?xNL%3DleSjrCbv5x(dN?i zBPfTn4%)TTdu--pwnV8^G_*=SVzuE+KK@ofLhEIjbYg%bSgKLhh?5%Xa3G{K?sp!0wTW$2v7yc<$3b|IgLky)k6LaqZceelkX zNt7u3@E)Buf#ha=aa{W4igLLGdzZ&k1^p1tmTiRo^h5Y-r!kb70{?6-(6%FxMtnGd z%9Y=;isOJdutTUrDDLN9_c!B9I`tO_p?iR1%*|82@=+bbQDne~w#uv8l`p`K69(Bq ze$ic=aL&7w4YP-PrmKFoqA%2+pV=(B_Zq`?B{(y+7g`NbHnc%#K|An)4*Ag~DRFjD zN*NU`LA)F}BcvtmQGSPiwhkm|0O>Dy?EMqnDRuYG1(B^DBB@_bV;SgLiXhCpWrbZ; z__Zvg(PB>25q(F5K>*7I(Rcq(_r$EhfVLvL7Q=;vR3PjPbp^zhxl;ERl*N-UbVHsa zCIn&7p9QN(3f7lq1FAl+1 z#$Jh}uL@jWAkmSeTfTvS(ZF%fN=J3-_YN6H*8&&;wZItU^u&9BD=^mBE(icEt4S z46A4%x(&QP9T<>6cn^p{DTL{#2o1U3WDpB1h;S-=!3H?{hT~`Ycm`>B#vs?Z81&G zTa}W8`v7_>Uove$?ATnyfJvIr>szv-z+nd=Zpd3{8Qk;>+-a#bauUCzcs}R}nYljf z2l!S6zlr}zmAE(rUFl>#Qyyl>TqCl+R-WoBnP?EfWl1-|EmfKN3b;>4(vLLzybtBq zlVh}IjUBYrg&7(m$hU0~+co{g+AfI4s9u42^ZZtrru979M;{pm<6#`xNqSrZNnhWIu^KlP;iU6zlESXLwr2{ zJUO^=nGhguH&e%PhAZStg5(XzD>tm8?$C}xdv3wa_A!?Uy#Q6u9ty(GLe#UPnY_ZS zfC9C^?M2I9nP3#Evq4h+mK1~;^bI(Gho%(DjAn-=>IEg{YTPUJ$kR4vt!FyV}5XRFau514{IwZim?AWxzP9pLUyPupX$7N=g<*RfywcLc|B-EOUk z-IW4t@XVloP|uNo)V14afg$ta2faUIKYsgu8F{!(=dZmBP+yNqg(j1_ff>XH&pvw= z<4Shn+yEDSr;azGWN43aEeIRjwkR~3=~@BeX8jl1j(eb|qszJ{4=w$@b;Lr+@ z2HBB`g(}a3L7>UoEcG`GxPN--E_m81JRnmIeUJy@$9oaFlg{u<*Z_6}v0%t+1CM8! z+Dz8Xnw2#M%Z%2(rC5L5Ph9$Ylm0$PGXq zaD#PLltjX>f&?aYf^De77$RQC@k~jCrUnpIa=?Ud37|j?_jsXgJK%>cKwH62ME*$B zvf!gC?@)xz-fo%(ITajipB)EvoEcZK#iFHzOHDtJ55V;-xVx?i^6I<6_e610V<$Jb z7J(i!{9JqdT=CnUg+KL*gxP_QsHrjG4}5}4u$?*a9F5cnr0fKpV?_^*{J!sK1$}}l zl+GY`qU7wq5Pw1;>FZA*bc@K~`(1hmrJa7$t#z!5$wJN-_e(HZxeZqb*ebplp zibA*Dv<6@HTVvzrC{Gn;#5PN2Ld|$y);42YdxO{jKSSU`y@;pQ=oEF}TOeeC&kXnK z1^CUy&ZeFtoY+GQfzu&)(w}SxvGv^t5`5#w`0$X#=GFW1{Cxa42Vf$&@NI9k#|ATB zsWTWve(%foUuR0bnY$%pwdJh(D%}#;)BE&57ilqYIBF}ejm6$hTL=A zw_il|?k4XGcoryJP4JOZOO zx2rd`$yv!dL&;R0iHa_F8VpU+uIC@3SR$K2!XPC;wodff`#6xTCm*AjmZ?IkMDZ!0 z`H`=AScZ1}!v=!}kH6mdrvj})8QKIgd_*xc|BtJaN8>SzTu#XGJ@;Hro@om7Ri8cD zchCCSD^&C0w?PmbsAi(hi5W+!2RLeO4dNWl|VWS{LSlTbTnfn zzPtdHDv~Lle~z3UwYshnla08*Y%!dYY3%}~kl4F`j7~X=!`NGNsWZOFjJ)|~lnSZ) zY*tkzgz2}Ukt3F_kmcJq{^-it4o(fImYDa1?;_rPXaSb(-{dkB12FO;4O6GdTIrz- zzQH;+7rLB)KalOgLO~W;(yXwS2%j745>0X+$%L{IE}b6y5b&MUxk~$8dz(Cc2az;+ zzZcf|#C&&|rE`gfe^(HB=Aq_;DR6eS+Kwi6JblNnuI`c~rs(p>G}+Xq=7alQM&rii zNEkLyk1MTB1{WrBsn&g42N_Qe{t-U*$mm zO-uiuIne)KWEuY}J^$aN+W&XpkiRU1GQeQ)|0>F00Vo>&H5|hEH+B1e!XaG$4>rOA z5R3oa{*CTn{zn!8fI~a01W}4uH=B&puhv8$j#*mqcOvE5P6cFeQMl*#Q#izo!8( zS^&}V_YoJs#PRo8cEEuC;Z^`gu(GiN2oQkD10c%y+r|a}BRBys%LWi`{C&&{=#2%y z%m8`-+yb}|z%KttA=m)a3>!cO!OHq?=*a(0eQ*G*9qa(r$A6|h{!ZI}U0b66u0Z*( zB8>mEoc<;1@pn7`hT|{T1AtZjVLSf+M?L<|-+vHc0Msb|KAQg?_4v2t`X5jam36CN zMns=yn!8pXP1(I>Lg7{An|utbci@blI{D%eS3!Q;q=oA)80xd`X46@1jJF6wPWfvQ zF9huvWU@t;$%~9n6mhr9Wa4bH=Wu zp4fQZKRZ(LbX-U~u0<>zJg`FzP6XwGJw4<}S;bG9v9~JV!>{|FLRddtGoHzha&OrX z1X~ASqma6Sf0TipNaOWQlzBKNbfr-|3y#qU43)JDSwNjL=xJLxC{>ZxlR5CfSj*O3lJ>XH%axpPt0WNP zgeC1PqMRy5vO&TK_W{xF85ue`{<2)JiQjy>kBLlNGUMA!cJQ*~Xi#(LW)l%up?v-X z#+dyO{TE+%fKR~x=_LNI1@Q04hn9!4%5r+oGoREiV4ala)dbqpQ_FfXnWb*+U!B)D zhM80DeK;$_n2jjpFk~=h6wpM9jHIM#Q{-@99hXJ63GC2^CYCrEtf!~035k|#iBg&A zh_sg76szLoO{MoUlLEJ;6kT51dcND&I#*pY0ODg$5eR8?W3#2%`Z$JQD=_075gCOq z+ZJ!-2QTy$fj_r7g6d?)&qu{>2vZk@uTZ+&vacN2LjUaTipv?l_ZP0<1ACUk z&tG)sqa-U)aYt$vwYn|cPI?IDGxU^SRLnF1XGq3)hOq;Gc!Td=*=4`uY!4kLW{WHf zFMTKaGbnyeBharS5?eRYQ!}8WfTR-`ON^}N?^`{q5cx0~G>drX+@2+$tyj5Vn!20*2E|L# zrPuZ*ZP4lI(UaTIPfTZ5^OJ@tZoM$P4!oy*z1Q=f>0%K@D>c8X9-Q&Ur`h+)b%HaL zn~&afsHB4;j&F1{iIdsh_@Yl8!NoBjLj}IGH5!O{#&R`chk>IBuI!l{0@&Zm6L3}QGuPUWUO+7%y> z_A#sfDGS?mrc0SDjpq30a|ve5uG-GD;YTIP*Yll(oOt27h4!_|VZ;z~o2vSE%h7RW z{>~2&v-Y8`y^J!|CWiJ(E6zH6~U5gyAHsxY8Xphy?;AiV5y(&5NiUZ}%Bk7rG zG~xS5ScyVeQhAi9KUN>zYYgbc(8}{EoUpcrhkx>7;Vw)>%2d$yvsr4ezO(G&z5_1% ztZX&KtJ9!bJr0Yz;mDcq)q~VttDZkuf9k&68?0}+_g^!Nd|xS>YvNVCaD_`flX30P z8`6%nYM|xase4aVRqbA@y!I+CaEzF-TGcYEN-3tnstE(mgl$D90TqtAnxy{)Ji}5p z=yr2w#)&`e(xLFnjra7Y0LL!p$}g+I%UgxA#RtKuJ6o&AUq@%nP1<4>AvjxQWtzIG zbvZ$S_J>CdvxEtFKinTLvZG>tSKx1-@g@ zQEqT^?e_B}6dd z4;iNC_ZLTti9u6+BuaTxFA7&C*?P47NhgkAlha9@^V~Rm3fmg+y!-g9D4;JECX(gG z=E%iM-ErpZURP-*Xxc`xGAWiIZgY)66KpH9R!~90*nft{$HNE`XPT>{7`epO zIy|43X$--_zRUoT3lUc-vHI=Yq382f)A*Uy-w5q3Vx{iqdmO%gnVE-4lTm^C%}F{^ ztEP;d4n- zTY^NyRV`9_fin~-v+Tn7FTuJ1%u4~w7M;2b^mfeJc6b)HIs+l-x_01NRD`O7Joh61 zUB;!#nthji(1WYlF*jYiR30uaCK2K>S=pn8&eFgiBqRq5nr4$@OeLp@hDMSHXF^dw zFumX`>ix$yWtWEI7Y7FncDnrFhjuG_5PaOu4@kSwB1zcyHH1e9h#9=2Z^%Ofpy*FB z5u>coK_Ro-vjv#@?LtzfW*zz5LsPZ(&B`05l}+6?^qj&vEuZ~yWcfL!cPZ{@lQu@> zj$#t2#>UK~5MZ9ld+4b>zJ!>cwcj{9p|cU}-S9Cz ztp$6J(FM_npm1<#X!fvCRtW97udnpWecDQR8xSB46NVj_R;Gu*znsD3TStdc{(w64 z0_C7=)!XQ0A*PXrZf(>g%9pNRI>$V`v_Cp_P#Pt<=c-pOt7TdzH_+m5SI9DOELJU) zohs5GG0Z!tkO4hJ!}V7$PfTEx6qDnaW(MX$TO$B1sTxJ<$&;3Ccy|--IE%3;(8#by zxyT;@JN>D|8a{T-J#(#^rNS*oIo=B~EAL)dzyG;@nVrW5^jonePUi)rQd!@u3}3PG zG+mc?hr+Z798hcBlgPfPME*D$)J`KHZl;AQ@Jp!eg%?=S8;BQXfIiF=xJo!2T(D?i z3K~jsX)pSsOU6G@97;2P+pOupRgU0#7o z_ur!hN8aWqxAv#Wsm`RFmTdYS*g^RZ;DHV5e-U@6rS2Na9UQ*USK0DiJ|1~xO{3tc zRn8xlJ9@k+=bE=Em*?DcY?9zjjci2Ee7ILbr&O!P$s4-l4;45b*=1UR8?FlpW}Yjjg~wBntBQ_OTPfR$W6-v z9t**+R@eKvi#r)9?kpSn8EgulXep7NFI7r|DB}*_Moc(En>v>C;$9Yn2j$Zx@M1i& zMf=^%+dGTVlERul6OTetMlVgG7X-qo8_J1VQ=c@dDv{NGn)Cq==-{X^QMXWcGv~Z4 zb<9L|_S#OIII-bd^`IKdgKdaW*a1#P4Lv0(jDQycPoOVB2d(S^I24f)KOVN}&h z6ooIQCq)Q`=lyp6{d!195!PD&Rlp-}lbq4oot|-bv_j$!ZsxZTv9O%fad8Bnj|+6m zw~#4};sQUf6i&w{8wa)toGLE27$p>dUC{@SSz`Kk#$71jJ;c&NC%lfTiRu9h~JPC4VJJH@2 z&EPk^VS03!V&&x~7Ra^c52(oUrOm~MrTNaE`vTwHkP}<%@emJ6O3$VCpkUDPk@3K` zKrfaF5zgXcwG_VNu`FF-Yf>M9F$yf5)(aAvfP-*-*1Ldy=lT@vy(S>gbfco6K+MW5 zFXe%`(0U5is7a_|^3O>u*5XokL&L%%p!m+jQ`4;}^g+T&g zYubo*76_fO_Z41#%ohF|&$Z}5ldlsz=Nt(TdIPKwobO<>bmiEc7q5ByGSi#vyj6P( zZ>gBr9C1Vu6JkN^P9v9&lhgH#z30a1L+b0FnJP-{%7rr)%l4u4=Fz0CU#u_7dj7_y zFD!THd1WB3A@xpjUeA1YLp^~>!w7z*?vyM8Wh#{&cX#c-tu{qH?5-@V-!1IyA~aLB zl+Uc=q{)D5pPjvqv2q#wkRhz^Uv-j_q$`&mQsPo5l2My0%34*53l>qE=NI0JHm@F9 zvnblD^vG30IIHLL3`(xOv1N0Qo$>S!w1kwJg!64C-1@Rpv?{3s_U-Bhc#}|9qm4$< z*>m7dTyIDBYr#1X736et+kY~Q-q+Mldp`^wA; z4`C0x&n=y^s1AzB;XO8)HzF*d*j~z?sFO!+)qS_lu~@TjZr62vPo$}QS8HO{Be#cs zQKv*(WXswgTT}iN{X?(vYwFyYcU5<;RGI3=Oe0Y@KS^n^cdu^&zLe+lkbvw!lddB( zh2N%8jf#}MxMA2zhqQFD~9~M~;B3X>pSWz;;T&M9}w3t);jnX?aH74e6XQ0IOX)b58DABR0DdifPwtTX= zI8~f`Zn#QCGga!7S{H6}{KoNVUH3PMbNuYq2p~3xwDca4iSR3L{ZsMJ>OLo|#yOZ; zKMwF?pmH9<%7?@q%oZgmfD}S>H>{u~|Sn7Rr8CIdVZ(wNH9h zmq;dzclv4CC?C+cEH%rI!fBHz@z{T<+7sX+t(yd?I>~R19ib6iNP3B?u)WO56Gn^hF7D&?}RFtawTq1J}$xM#)+8d1c=C(ohjbE6LuVJ{5+=MtZaVkaj= z5DG74#p&chOgaV*)1<5j;>@10tO}*gcB<`oAnB6MF9#AbbR65^U3Q^W<9Nn76M34+ z*+UuH)&9k%ke0CU;r@Q}GjW-ARn`y$?1u6~u48lq$|sF@pf%&J8NV8{RJDxctg2K^ z{2IKeO}lz#djp0&-;|tAC|~@Ml_+H6;q^xqo-sTJI!+hrMRsP8+Zzzi=wc%^ruWFH zm4FbNAGq(}juuu7x03Sm4(HbH??j!KhcmC8JuFQ7f<52)b@0Vni zIM1saYu7(dWh&E_tKIbV>aTs{$BNXZH}SL-?%gAr1e-Ui@1#`ih&XKVw`&SAFH$^< zm*Ph>Tsopn-ndDB6oq}2w6pA+sZ zZxtrq!f|`TFwM*_1fK(JkH^_spF~Z3Ur_2Eb+KYsz9@A&b~5J7*Q;#0oe*4+{D($L zObJ#VhP)y@v?EJQgOR+EZT@_3!uftNU|R=50-ooCoHVD{+%0$!-6d%<@W2aa&7f!sjwrV}Dgw;W&vtAK61olWkW zPNCj)ASFe}?kDm$x)%|agkB$}pKTE0mExPTCE$1gFDt=YLMkt4#1zi_55!3f;@)v{ z7s#FvSn)Ws)!>`#Krv{HJ$P{h!hXT&%_lB~U(|2-FQ5DV%|L5SeJk0FGI)#K(O`M! zK#a`@w94S!zr#M~2<{1L(9)u4`o<4WNWx1C8AIXr_z3>sc;a920`N?55+cFAA>Qy_ z6JCPjJso!j;E*OXAY&I~5^0vL<3Aj;L=q#QS;y zZwrh|`k3deKbK(k(FWW{NL)?twtR2qn-x8w8M&F3(G1ge18;xJP?CMf#2LP@Ykn<2F~|}OJ7HC0 zsTbldPJ0%^e%u84Y1uJ>SdYR9OE)1NM{Fm+9oXGrk9r;CYKc`%RV}Y5KM}rD!Dl9p zoE~2sWF3C|bjoz%siCrS*$8pB?0oj4`o0~9vv3#oQj4q!-Dw#=63$~pBq<}j-J~o1 zk`?c<35^^s{YA9>WNO;(t9NYX3hRy<3t#7!-l)XHoX@0 z3Aw?dW$-VqM*mX*?7s#`{;K1RodI(Cer{dA5H7SpRJV z?r%l5{}6%uPr;yndibwn{cnRo|7v*u77+qO`~IGnf7haO{4M?kh^PS+^#7&y7oZf_ z|5p18u-AWA`wP(eFU{!xL=OIAK;|CKzI%qI>){fC2 zr4e&A(E1suD4I)D;@KD-en1L`OQfD&>@1Re&saHew>FNma;XSaA+Dv7K-<-G6a8$N z;1iz#26&Onc>%42HWoit2lAAiSh}e81fMh&aRe0E9c>;hOqt};Wg`gLReZMoY9m0d zgJVIay+ateSuFOp4FVaz9~VXcaO8f8JEP0Iv9U{jmNInapP1c4G$H0^dF`~b?+KsI zseOKj#*7zuNK(w2_1#{4zoY*iG_LA;^3om zJBBl|FeXNKNU!E6F|%}gkAa$-$j|j`uj^;OoUdon-RGP&dAd^hY;Lt{7(^0cnrgxj z-D7&KgD<<4>n-7NJkjAKt^4Ws=EMEX5GVqY`A^raTKXK9ZJb8}M6PF~L-m;)F5BS% zC;gtdvVPzkp~mnEWK1Z*D&|3srP3p6*{kBN+EQlBf7#!GJ-79pr$Y>G;;#@m_MjTfV4h?X*;nMzyW zb?|kPen4jnFE8-zN~|94R*&|?DPiBQUq?nU^6Mgm17-)$PQBTMYop(D2V9?9OIdWaQlSKvi8h}$WKmCqNGB40S>Ohi}A z)|9MoXtz|>m5&GiMgDQh0&OZ$PIE!J_u^;6@+wZgm{!NNSm zs^2X%x5&gZX>~>aDOf#UZ{0=f87lqc+tuu#mI;Ic?yLFR#ZLD7%P+T^&g?E>AEf~Pm=!|5~HN~u<{ zN2R0bV>V#cZI@}cc1*_78A4BZf@pnPiRUt}XV&*Ep}U}PatNYv^}-I_1ajqgW4!TI zlRtKQ@_HYE>U$F0ILeuzORHzVTf$q4YxA$AG_&BCZGv{92gQ@*o5EdEyljqa?_J-_yevnSn9u|zy^e26E1oJoQ4v(ovl(2LK52|ps4Oy?l?d~w zW#-w={jyZB3VXEj3FTM#T4~*AoxeM-*{ksjrex(og>4pbqnj;~A&lra!9V!}KiLNp zcVaNR59$oo7&qLY3H+uvbfyK0Gc|2u-k5&j_$xtghP`2ZmG1%p6L`26?x8Dq`3~9} zJ9n)1faaCW8`9V9{`mF$1I`zU@0pTII%3{{OD?@2t5`RjiB>s8NfTBU)0~xYJyv` z;M<;fuG?)2(zqY{fDc)Lw1M#f^63e;Gr_0}X^O@(7Fj8-N}wxUc8_EZ(3AST>(x45 zb;0VIHVfJ=n=N!aJM&pb?U~6By;;otd%K_CWp*lU4)Rp*$?Gk}a(BjP6IHX&vX1#Y z?^+|i&XZ2le4-v-#Yo7vxJQ;J4W35796^O4f8c@PO>`YD8XI2bP01q0Gxg24m033u zEmTchr|!yZpM0ap{Sh}SzMb%J^V;(g_D7F9077B8}Ew~oS05?a5A@Ese zDb;6!{kxd6e_=+>z_rT*xlWXe``JwFeW|Z7OIaenseZ}`bsavurA*V~nz}8Z$kQcR zmq3vZ*(Q9nFT$G50MvA`#)-;Xs1_qf`C=-_$+h7_SA_3SX7Y#ym`tIEW@AX*{y-s# zR=75O*uzz?r!TDaKI6sU>Vt%5(2t|O@M*Dk^u+}rE(NjEccvhXI@h8}|KIc_GmKZ9 z>}d?bUgTEoSn7ZTIKVjzZW?GX@G-(TTQOODgv*v#kTuq`GP2_P zxF=It#r;ux+EeFe=~ULjenab$>>0^QJ3|KTXw>4FtHN@Xew-yz3!;I9fKYv4U^v%U zYbk0it0NR(;Gp1{xIRxzkIzXtdn_WQAZtg_z5O8{wrSRk*hxM!-riK}XA&du zqO2Fw6C{VV9DKv(!Zr1|LxhD-H*jvc65*hZj`=~-)d=vEES(WxRT(gQO^q&btF~;~ z;l#5TmsB?~*tV=9HIbk`989iH^A4oJI?Rj`t#fH*ov1R>mD-swvX#m|5SAT8XR(v4 zaMoCw=y|HEJ6UcM;{33iejq%AMHo&UC1w?7M_H5rgIoRS7lR2`{b+x^-e#s37FE)c z!fAFenda)MqL{u!yCz8~)ZrpDLk8JJQCPOYS72%Syy0v*C3%h86JDd$I8ShayS&K9 zA;X)QsRZfnq3?5^;mO(sFKm;xBVqkfaa2Os@lEb+NOG`@ct10pMY*Uc=Nds9kdi#9 z@8nBULT)P;H*ApgJ0->J>Kj2`Q~W0zN~U8&AloU|)WR9_FN(R0Ta1>{jL*gE0NX1RA9dBfk7EuOpplWiS%Xh3Awkw< zs-Z%DUU+z5XP%JG2;mY8q9Uqklq_z6$zw0 zG;c5$zr!IvVEvgne?ZVy!bZIw8nGQEb_ba|8WkECL6VW=j1PC~EOsPJHN&r@DVl*R z#D?JKT)xN>_$^79oYBu`_BS{6oy{*((?6sNVMeFJi%9&mt)yII=$EDrl4H;NB0U&i z`q#MDs1F)b$8ad6Ra)QQ_4WB+XEWFyxjx_6s;x_0f;D7kZaW;8F7^;^AeSa_R*ulofEnl8+e_3s~CRS%K7F(vmP^7D&WUg`UlW2!=*w2XRFVMGA zm&hJcD2q)p7%%qbo!D*=H_p4Ii_)0XP@|Z9Y`}{%Pa(^EceaC!Eow8t_Lbfp4j)^- zfvN4y+vVYeITeSjWkQis5&5L=EC&VB!uOH%s%4W!bRf|b2 zjr>O%Gh=TuFxAxoX%Gzw#Uk8TTJzhj4nF4AjiH6^Izen#$BV$~wbtskuun|ao!E)w z3C~=j)I$ZgmJi(42_?(#0?jt|E?p+H8r8c~Hdgd7pb6*ArM_z>J?u>W6oU~Cr;t)^ z@j{e1zljr)xOX9HYp(g4i* z8ww+Bobewwpj(XADd>%>KQPez%J9|HtkoOT7%noCQ}>?8V&#qw#}Fv)4-Sg5E_mGv zhH{fe!b>qG7c_bw?I>y8SNd@AcBfq{`GBaZSsKUR)Bj+FhH}$ zT$wN@sgprS^NT_~IVp*?5K+cK1Igdg0&0zd6>Pu0wPpPyC{qijnbb_}A;9xQ{F^t5 zUYJC}5Hf_SDm*qTW1K-;ZXqe)D$gqy$Nj=ro$X;#n@X+zw&Qccf(SuyR@Kc|SmX?6 z(7xR9y(ng@`pGxzWc>?DfMb=jT7R^ zLet5c#;74&1sK1gY*1U7!sRI@4IK@NYWY=BoJaHOTB_RB9`!1ZC1HvDM#^$Z)B0a4 zp3P=V$Z)X~bNf_#L((~Bs8%gjEtt5?dI$X{X`CU1czYLBsN9lzFki z(yJ+2l_6ANm95k4-A{^VMC`-V>t!_kxmqU6p#Jb(c9ZpZ-L_51{JMK+g!k*O8K8O* zc3miO3VejA`CxspJz1E^&&sk34wVlIZ6B4Gm;w&b&c9Thk##8%p?OmE5HDseos^%e z;0>DBDA}&qv|09s7N$el^th#$vFaOuU@73H5;rBU88!Z_x}(iW-U&r-AJ^L;YnuHk z)OHsJAEG>F$rsN-ZEJP8%|uZuFw#ANYdR6nd9^q<#j7q(ZWK3Q`m5xs((pT zb=XQ&Z?7q{m!DT>+RSL?w)~0i$?vxFS$IPF()`o=RzB6X=ox?cty{jUzkT{w4vySY z`iJ);@43x7vTwcB;w?n;^qL{hf@4YZn3=n+=?`FjQ8>-8wg17{TR_DTwEdn0w?J@$ z1_+b2@9y4n&+d*q)lXG*O?7oopYE=z z-`{!%Ih0p*!@7n1mTl35z4g>*V1QzNSZ&aJkswT|V3?c65KnI<6OVo>K#y;aYWHj^ zVDKj`{K_DdlzTb@TD^_~NaHJ~t!ZF&0TY5y!ZG1?fjPDIbrp71 zi-=MZrue>FR*lFuypw7Xm+BNDN7kaXrjH|CZ7}!x;IS1au`FKmQ*~+pEP#U_g95f0 zv7aqfCw;$RKRtB#%&!v)k$J_%M37S{UKXc0cHQ!ZRn~47X7s{06;oyUkO3Y^M!b?hZ*WGc-TUSsrn@ndW*C&Y;bbGWP|aA-r|`ETweLvPV=k;{-x)Hf)eeKmpiR{d^Jry1l-1yJmS z;V4-cX(F4&rd_+1UHec=LvvZ@bB+}s>Y+q1oq8OloL((+O>jyk$HrBYlQsh#aUp`V zE?M5N#?}3mpEA83s>DK%k?y>k9g1BoQ4INpTFmfBhAZ5lv{1FS-O|xfZ?&n*!^Vu| zedY}A{iw+>5I@ZN8z|ZQh ze&YG8acGd|CA#)z)j5|Z=gqKDa5}Yz$!GZ*55X=hX0kJh& zr?Av_H$_dHa@9yC=5LbP(kLB+ZhGii8UV8lTff_aAEr3_VZ6k0)5x+DISJ!B(wREF zf%H0&&f=w}e$n-fFu|~&%X5yL+kT?jBtU9JKF14rT=YUrg!>U&zEE zO6cs)3~7#u0^Z;96j6zhPHcw!2iBay8^WnuCQJ!qjpY;6kWl%n=lQ)76zg!$e=p>U-s`yS*`nK7fnZLK=xw!Ebgr?R~=& zUfzo}OY7O3ovoIrD-W8FT)cC1soy!G@+z0thaOgU{#0DbdM`a>@4Bwz$C?ar%3DbI zNHj*OCE5;=L+>OJEBJA1Nu{pMlu68Sp;Gk>xeN@qF?1DY-+-;IyN9Kvkbh4G#nZ7&M0bg$ry~!WK4>B2!`#NV4k{zO_ z^5c1}YoP(rSfLdOS!Jf*OnX~!o{5Bm&ehkMzSc}u{%#GvfeXd5N|+qZh*!?GWRqt@ z#aU4iZJcYg#-*}Vxs{B&>os*q70#FrYIfCL8r=slTt*5ed*}m#^ ze|y*LYY=->JPb%K2!(p#;5t>tLp*&Ua&z&tUGB46CzSVY=-2v^wj;&h%EL!p$*E!4 zOQsC9CpJv}gQR7h5KUW?Kq(-*3;vuqf+~oeTMrLhk*Vnbe1a6-u`LlnI~xt-RAXdl zJS_7ZENm(*EiKVo&buE*KVt&0ed8R@(e^XGCycBWTqe;-`(xbzGBm;#<11Jp5^8k* z;P6zDeJhJY0RPC|tb|66=ITSAK#TP?-WZye%1mhHq&fd*qJRpqyd11=(dlb zy(-6r@n;`*AAQ^M~uf35O(A<*Ns+~Np|+S>Ab)O!Q~F`T`j z{@lYEEf2(u&gq$k64;>IvX{2Zj%jEHFUg+@oZ_fYS^!!Y+Uz8PN{?WzDj{jj=LC2l zrm&}eEW3!dI6`r#Oo^-83CXoAh(T+k-)@9X&$0Aq&(_Oz6yom(C5mlZeB!KMn31GY z+ZbJ#xxSRwxb5*qns3L;T~xiBkvBLeB?rlOeq1P`0dzC9@tFSpp*VqA(t7D{9R^Hm z88mmBHOVPzV_mRyEqT4kj-%_XTls467ZYjpEVVEX;f~Y2DZA!)b$W%nW%*?}W(^*w zYd@<(r;X+50h(HJ+p=bw`7#2XjS@f zrA@jxc?;+WPlThC@mTv|xUvrXQqhw<^e#~9BgPVFk%5jA(gyr=8lsYy5y&k$NFq=nUj%7|KZTE`SOdO-SMw~W^ z18bv7(D2WthnW8W8r_)L0Z=!nMb2r?^)_Mk-pluutg3!cV%zALFuU(W~b0 zjI(D&c(oxRL3clYf*|o#88=fd<(bNmZRHG|3w{uGZT?h{nVy~yTKj{%l=+(_zLgA> zQm0|J6%A`dZbX}`oE!d)L1-4T3A-HD2?-MBAYKL142BV>ODBH-=Lu0AlS=h3BNZb& zPB~0P=%vOm!DxgORpd(4Nm2cy!+6}3J(?Jz0+E>RT!>5Os+JlGg8G zTMF51Ryzw#ryOqVw-xU9RYWZh2VV~F+Z2v<{P%10hRG-mTE04`ie$;wB^*g*8)pEN zcvZtzoffrDb_5-*I=(oX2caD<5W4Z|VolwsjNBc<5>a4Fkk{wOYfHP?8FsrjrA^-w zJ=`#MP1rOY^6rHA$Muv$)QIdqta&a3q}z{Iy}I+zuXQ-2lvrsCQC443(JY%7A_h7Xjs&ZJD) zwV^YiMBEfdxAomGsajxMaOoGXnoP@$`SRYtDD~u~&Xa_1(LT0hxT_$z^P$zXXP4}p z?9HU?^wW4WX!=DZ)q-Hnc-;C;Iy&)N%ghBDskCxTF%4M_yRSZvLfm-aWqa9eToi3u z#5{J>`x1Ap*465PhZ=>fpp6vrFJ?Mv`NHh8KcFrS@ZH8S8Pi1WxZ?KfH(t8&H z%KO#Q$i>UlW!wrf)iPj>4%bG3=a4P0g(r;;m{UqQp7O9Ol}XIb4J_ir(Z=G*V1-OY zOdOoNOD8GGMS(c={E~%<%n`1`I04m|NEgDNpE(=l#xSVEC?7htUaBwrDt~p$VrMew zIy%~u=`JP}>5t!i^5^o$*)7=Z%&f(arj~DaTe=&s6L{=WOJ5uMTHqhjAtEVizoc;T zBDT5+JfC`iyI6$|)s-q%9c!>>MzWtxXOESeF}I3lFPYfD)oIb%B@}?`nL=~B z!W-6vb9cEaF;6fO>cOgAM;twAOx^9R)t*o7}(I9@up zmjfw=$Wd6&<~E}&m&L^=Bh!Bz#ucjPLZg%YPT^+*!OK}6ZuE1Kd0B4$p(Y|9LT+}< zMT}G%<{wpC_QaEdt z8OOo4wYEA3qpS;$htZm9O2vKX!w_4ldYnah%Sy8vb??q=nrL&yNWuvk=z5M2J5p}4 z82pK4j1VL1i!kMowBtP3Eh`ZxvT^F4&QkB}qyT zc5~F%i|U=8o<^Fp8MtzSnC}7w<@7jDI-Fvft*>^yANq@q+QRArY5L98OkerNssL_U z`(X*rzFQslg~G^s{*RNtgn)Np`|${ly<0lkQM3gL4<#a4&;WsA)#D*lDc z=y6gu^XXk!RUO#oOVtR@d%FG1#X#;=qShWIfu61Swdjs~oJ}mJa?)s3@%N&vbd3Hk=N9~LGf%_$n`1yp1o03FoY=JB!6*Ylk51D|8i80V% z&Wdq_ife=St<6GL*{n_9?W>H+EoumbzJ6lDyZUv2BiZZLfWE-hNq50wd>rBXZ%p>Q zGrEttPl)qlkYOH2J4R@zT$}dI!P1mlc^uyzvc_Bn?HX1H9!ygwHl#DzCsXB09xTpL zjzC7NFo*8=RuA8$TMm^P_=QBzXh-q1DtT=uj^kZdYjOj|M#fwkm12Im*P>fgR93po z11Phs+q7!3SqY77Qhq71bbk%~)H#1h=bkCQczj*lZp+@<;4sDO(Qc*TnW9&tG+Y_g zk(Ji(sAV+73yXehVnt22c+F;-OB15ML+D!$gWW~$I`Q$4f_UuL-FnFUL9Ovokm_?H zw&Pf;%(rMWa<2g_eNuv3Nl^@~-_D|ZUfHKGsaQdG9h=^!rqWi_m%+P8rq-c(e7G#G z_3jhz@zpUdcC2hRJSA5G)&cRn=dQA z6zHk3FIu-%#JWhUs5F1Ksia@s9w`uM5u$CuUPB~!t5C)_uPIY`u2`emWjj^w_@P&+ zXWBi|y3mlQUP0TUwxrgY63clk=i1Y{^->Dx*;RQ_l&iHf=PSV90|_BNPQS zon3fd2~XbU;2dd|gZ9Z$Et|cDhPHJYCrdQt1z2y^V3ov?CQU&)zB`$+=9&Ea*$PVM zadjV4Tnl#<)0hd9vD-X}GD?30HCoV0AV+5eVMGR-6~-U~V>77|$Ve-yFH=-F_3EsM z&hIsvj=P!uZoh~(|7(#)c(1$tH9#*ZA0MT(lyE3?G{uDH{O%+BAi8?`Y>B6Gw`0_%Nz-Fkm2jDDti z{*yV-t~P)EVEJIgiy=H8Z0jvxB_)EI#{$O_j|pp1q%#K`YdR(5jnfEJP37$CPojJ-*E7oTLxWK_zY}+k zs(H>GJEBzzYbl(tw4o3A8n9xO;Jk6onKresHGaJB-Rv!y?lVo)+!5SB1!Z3R6bLlE zcB_zP?ymTBO;Hn!m9EZUlI@7*+(g5|3JVj0xjjnBPq^VJE#Y3WLtSl@rYjV`6f|2) z2Im-NMIBvDs@Vta>6KD75|PBIqZGX|_r=9%7cVHLO3romg2|lL$D?z{IQV*=ZUdSg z)bRCCwp_}EymV*#VaZZt@(Wt?=j$JOlL_Lc1JvBBFfcbS0t{|{eOrQ# z+jaXm_x6!QS^!THLE7MH7+Sj+&Bf3Cg~BW*NhGPDOiD z<)#NZIV0lP)sapJ+yYbOu$OOgBF}>njL_YI8ljzvek=~COc1!A_n<63Sx7K{Duhs#3!^*EP0(VJ#_TlIRSWoGKfiUsNFRv%M;`i`+_U=yC#WsVSKbYh)Qy7bd}5e_A#`H-zXho zJsB4gJlF5NN@KfuDw(;GDe(pvCxRI1kvw^cFHE9$Bp~i`g*S-z0TFAMDarNe;l1ys z!B12ZHW*_>IJA&X-RS91Uu~hWBi;+%SV){FUO8=@jvlGJALsK-^4+%Yq^JQ20n*z638cQJL!LNNwx%8w657T77I5Y=ONHts?nYzAygR zdNEMMBE$~y?Mp`iKdq~4(2=((vJ-uEOl^iiFqrNe7S#fAaZ?eMm=;Oiv53W>SL7nS zi_DSiPw5<>(8-&#akfZV$1Hw}JRarCxAjoF!E#Oa@<(Ig6w}qR_2f6FGyG8CV0nD> z40{97U}nVJke$gnz8;zH?=8s_9v5Z_lSVFKgVJ@yLsMU? zGf?kGzsJH`y%9Ov(wvld`gp_N?iRQWD#J62!=jqg1wj0uUx&(=tKhWyBdgpx_PZF_ zLsx4IW70P9Os-*00-rEU$KhM&F_`dbxdp(6w5`zcw#_u;+^vZ*WKm6UHB2?P9)Km( zP!a-Y*6ro_DYZ8j84Ka8mK0`5p;norT11hAz`al6o9I{B+~4W z!r1=HgVqIV{cJBgn(t>~o`SsTdBXhVp}VH=TRVJo?9!PleeZ7jX@sOi{6f4KBcYw6YQ6i7or-;t=*Jxvf~qIJTlwD4rrs| z{?cfl&PM%%IE|h{a%;3c9U=QN=#6~xm!u#{P@=52U?SyQGqnrSU9GI1+@tk+h15g} zE*vqgpYR0#l24`?%!bt$mnZ%Xxi@T+ivirclsmTyg`hTmPz`P#b249M&~+qBz~9-6 z@BXSpoHAN2(qA{eyW%!}aXqP%@(*t2RP@8pvu>CLq(ZKg)lp9d&?f)@Wvuk2$(=Nh zr~byiKCpyYVpYw>S3Sk=g19LC#CN2=Inbi|EG5=?v*(&JRmDD}E;X8hV;0GDF3`JK zRBw^f`MZy^kiPge^SlM_uMIe7!(F~%4Z1?XXq)+VNdDoCWJ++6tqB71FO$SpDER7N4)y>c^>S5|~<;=DM1OWfs@hyw6@ z>edU(q`KICipfa;0;DqdavYV=y;IoLWn8hm%j2cPET+oweAL1oc=yY?qti$~iPtBS zx1?Rn`yhqXXWm5-d3z{NG(5cFx&lD2`8zh~N?v=&4&^dT_te`^_3>B7&l*U)PilG+ z?t!U6eu!sq7Z4AS;0l!DD#b^P{PP{%Z@pmnb(kp7Yrt@T|G-U$X!EVf@kW6k?&b!6 zt(syA#3fSJ=HshpBS;TZkuivn0re7FF*ROJD6WXKUql?lU#tLY5cf;{M_akGk{cg& z&q7%ql;2qA=7+wQuV2A(kK5eJmUEY1* z?i*}TknE>>Vk*?TpC*D#FfIiqzW%s^vlj|Rq%hygyj)2?`nXVcJEF&+pZEqiNx`Bt zU%1fneuzUBRzsL(kwIu(V*kBVN)2Ek^4v9hHiF#PTl>CO>4K;~%6LdwddPnW*HtX= z+1urdz^$0{sZ?j(YVK*Ph4w@$NHc@TrU+Da0`f=DOuj*p&ljJ` z{Hsr~1}Jz!U1$LU1pYABMi38gyP^RK5O=p+f6{l&{`Megl7bS5xx?Qt=kmDxK^j(j zC;{5V(~<8Fe4?NI6kZCH4-f~sBgfb8;U`8>`n12eBlMGq z9^plhg4GW}JYe-*dng#UWNvGd@l6W5TP0cbX{yg33)pmJAbc5r*p)V$7wQtmbvW)f z_>f@Nn(|nZAta4i@685j_8#?4@F5*B{4uo4XZ#*=HhRjL6uu{-o0f><(Xo{i#C{|b zVPVYJH1yD%4tNWUdE3r?@MnL&yhs4Y@VnY#I(vOf6`+mEeKu1vMY{VF4b}tAZN6=7 zj{6(OT>urp3cC%y01v;c{GK^=_U#4yqGS}ZW zIZ)(Q0g3$-f&JGzAY5*aSSeliYp=3R--zP68s;k^FUV3VpV5xC(O z+hg#99m}P?D1Gve@YngVN#b49tolbG@=cmC(OXux&3lhTsEH>cDzNg%yRs?SUg?oP z(Y?lp@|F;0G2F7z1IK(_no~Qk1fL!^&Xr}u%5)~@1hV5dM+k@Z0HG5s;LEF;twHFS zsG!p$nt zJ(tKmnB9-MXdAoJybXtsU1dGdFBRgA-6J`454MwmUIdMo%0v9G=r%}B$1l_dR0mmN zm*oTAt>ZpH@QZfmy76p{m{zhB~lNnPDyj>_;n z?bA?n^O1{q7zCt!lM?rG_H1qlH`SErf^#<+w=#SwF8hW~*C8A)aZ9h`7sDw7TU;Tx zQ5ON0GD5&dBtgQ#^AoO$yb7faS&cZQ;12PVpS&;fO!^xT@T-%-u_#f-1s;zSTbEKa zX7^wBfMG*p%u$`~fEk?;$#$7d)LblDn~H+SA?u^l{+XQdSv&scyO>_In-i#Q;@aQ8 zB!eEEv@=u?*o7G8AgrF5?z>{tpoqOlX4W+@P^=w$7p;Jkhp{PfT2G{C`QU$#+bcp&K2s&@Arm0?)NVy7F4?^d%0FQe&!Fn1A#;R zK&su`AB)*}#|EMb-@3QAj)oORx!Wk?b6;8CaOW|85YIOe*DIhx#qIw506PmdXc$1# zyc5=|2pvND3*jDaUR7Iy+AZ5xDM|eLfK(?Cj;TX_1n&gDpm5#M3u_#l?zWVpTQPtT z+l;~rG0o&2qTZwc8L@Tb4dV0DqI~}|Y#d+7^@2yz^U>B1>gq)VF$VCnnHz}!+u8+G zf?qgaestlvlIhRH%fKXfljl&Q;m&~-itLahZ+Hvv)dpG~^#WSTK7WE+r-M9l8Dz;U zNR9Fwl0V#e$Fn}1qd`N5DCv28`hsOp|sK5C{kR77G+@I~I z3f0b>`k{U@CWYx>2AH7Pdw%YYgLhay+!n%JvSTLzY0YV?<;!!geIR#&PXdlU5rVrf zT7|*E8;3GbW`*V&B{rHjld6OCk;7?{2xHsM38?jQCs^Fz{>Uluwv<-}9^_?f2pi;7 z6TFG-p~Sij4CdUD$C82}ySvfTg;Ug~$c!4^s{^2tCd7r7@deyizA4Zjob z)Vv3iqEkAF9`J^Y!kW)cXSPAUb=#$>-_LiA%Zt5Nuayeix9vx`S>ARGwv)6+5X#~A zZX&-m!9qV<-97z;+JvnG5VE{a|GnCi%v;q3CI|YLtve9zodDHx_{fv!lgutEWd&&H zuSLEA9PqTh=c&&?+h>EC93g-Dlnnk7Iv4i+lMhNWR1PANKQ(xN_4794;^z(OhuF_I zJ~t*?%Q4HBVx8`VIZ~FW>VG_l-a@hBx7a|)f18p50V^zevDdT>IPY}tL2iHPHESh! zHG&Uynr~~04G;$Qh>}cX89r`8oPfr@u`R?kkN!My<<1+^+hvF3pp;(l(l7to%eAI$ zjR1EsT$|(bqxQY?kv8x0-B(|r`opW}Pc){fpF(~zP3#~uOatLsVhDd64w@*+qAfaK zIv1Hh6!`ocsu_6&{*N=2CEv}P+%Fk06DZ8l>xr-dWuQ$EvM=q0`5BkBzfK5li`fqL z4as9F&EosEcjV*r&V_`J2`|0%YhyK|5x7HQbM$FY-yT7v4dLMsA@klhkCBhWI(X2% zekY5xZBrg2r}#C(%#Mu4b6ik_{v~uc2UQKCCwhNF0)ER4)Vv>?=dIH%?WOcaGpf61sq<<_=bol%sa-+!}*eq3fd0#kV+$ z7t)AVj0qfw^`FYbNyg4X<0x;!#+|T07|o8btI2nk9?&{$+3kzXV@G8efhU zsd~p&HlJ3AC=`shs9N`G<*UaH3;Yoatry&RJ(WseRr-;AZXSIZIrt$t^FGq;e&?qf zcJI3|jR+=q#(X=_R0hg{#4UchC;s8xZ@ZKAuwN%7$^iM{+0Y*x;?=msICA=oemov7 zr_aRhK9&CR%G0NIBVQg7CJ8J+3=SiL#5y&CwC#`V)DRxz1ch<70KUGV9n4*!64L(~C`b*DB$U=a;*Hdi9O|TT)vwuP zZ{q(H>odzc>rzbl+mF0rxcI&1=K>sD1m#GvvFMUeh18-$Xu zm$PrRabazv@Mj*S$Z>9|uT->U)ES z21bVfWW!+yM`X9unmkd3#-=DK!P_g9@g>IA$F%?UHG`Y9!gsTYVV@2QP#Q=i*%Qd? zb&%%~ep~yrMEu^@l#Kq&%Q)+-akp;i8^BlhqcgZ&_R znMr%V;Z_=+og3+=gI=WaF}A-sephl%oy9r<&^si4FQ4WY!?hLJ)W0;+8F94A=$0xI zd7@4-4n%du^!>4 z#h$>TnKdjW0&owU8Ej-7os`b~?#!)1pVO#9|H;dv(IrNp31R|~g#i(B)CO!n}mNOgVrH%UWzBmYG{5=4+6gN<2*OTONbW+NA%IcP2_fKToS*NTmccnp{>xW>uzJtp86e_#_ z+pS;A$St5F={rD%x?%`CX&qhSytu7F03TzhmXlx};>PR7%v6mLq*IRkZ;&iXC}?Syuk69zkEcF>sR2{c#b z21M)U0i~{jn&uu%BhgO!+P=^mo7v-*qY-Vk_OA*QyAHrF|Xp083*m>Wc{pQt) zBB0mwHlrkdY?B&~$?l7ck>oWZH=`c4=V#>4r+nQ@eq&qT)x)Bl(f=H^Of{++a;8ll zN+Wcd%qE>Sh}||QzNYm@T~jv@El#nk@kiHAF+{mI^BRrMJAV{)Xiw#aHO`yP;(>In zhitnvGZjLKdVcaS4mjPoe`0O#2Suw+4%Vp+;g}e*GwHyNDDl@~pMvV6J`PpYto)y< zEL1R_I8+(Q$l7&x33s3N?JD8AoA?F$m)j-Vg`M;mX~b{)Nh{f_H@<4jYT2lTk#Lsl zMkD-E=Kuov{T*Ny4`@SGQ5;4*xJAZG=IVqkKbr$Sn_K_;@U+0LNsd|XRZC>B?4xIv z(UsiyQiOzRleY=ycba}{t2a(dtn?tkT_%c1Bw_!!8>I9)060^Q8E?8t>WWXAjENjQ zi!4~tM6dC0Mzwn<8*?WE&o^CuGu3@Qc}d@il>LmWGLXzyYAW!8bR8XC^sAeAq*G#I^LTOP>RNcCg@!>^e2TTsYM<(an`fn@6O zs;ry<+5Q@37U`X0OPoF5{+j@l3(gV5%mzSf`kBW=k{ABP&pV4%9642RQC%7P^V3b? zCj~FIzw#C3le-ExlvGe^pwDbLcLy=?)H6NJD?EFYb2ap}{d#t$Z_{3A z-yUCW^g!CU@p1&GX?255i6DYCsPtYhV24}{@{*g8tg&vk`rme~FzxGXO3{$AQ?U!9 zua(;xvzh@#v1YwahA<9aD?IceL9kEs4i&Om&~&h=eVG$b!+Z|^%qTM|!ENt9^L?0a z^~G4op*g4si=gJOWy(JTBT37pO&)$^|1Xdvn`p-D91r;edeHb)?n8beQ za0C<#g3>*v-$p?n4mG!|L;7DPa!GEkvENE4u}-7^j6-s8k*?E+At2aK>_%+|T&Y+k5pd(EaNW*bjq}w)bBF4wr6XKlz~r^EWP(fL(}xCSZ>XA{OfJKa=>M z7zl$7y;Y4@%K9^?ae?}?B)Zq$M$~^sq47U4(I~zNn6Tk%bWo{Kv=G175jN$o+=ALv zZa${JY9qsMe&Y)I3m-&A#r1EI0@5z zYd$I0E!0e(Oad!C1F`{cT` zM-;}ByDnIc0|4wUaW8PFsjFF{7&Gx-Au16TCcfUtYg=>YJu&v=^=*53d9I~DumARp zcjw(=TR7?7-VJxp-6B4UXd1i=U-r%^R zX`gL|%o&EqEzvqVJi?YpXBRz=ZsDkDiybZAR*r2)B9r)(7bi0Sj?5|^2jgjI4639U z;ovAdGrkyB8Dq{S+`uA93SpKYs`n=mvEkMFLzV5`3t)U_~RD4eDSojg%bWUx#_>y!;xvrzMJ)?_S z#=49B!U#d_-c|6%GDbi~{@tS!jy0H*Q`QpEZqgP-Jwk}9shz}Q6&k!M*XhCM9$URW zxC?D%w^lN>drdYR?c%Dh#~MYLJeWV}NF@Oil%H-uI&D2PSkd4E6;_Bw#sAE6ygITnAm|*!{o4lZVYyP}BEY9IXH4?*k z#jX7%cp=VIki7BrZ~;(OCLwRT_MXh$R9n-t>Tf#L+;Q-bc~ZJ|+0ybNrX#l~sI1je zUM+V+RNqv3Hu~K`*o%jhYG$l9S!?o zGwOFK@?odZeYb|kR`?j{h!>uV3!7Qv)~bW9%J)RGrbs5O7qIs|ei@te%#z{MIQg;RDTwa-7+up`bLJO{okI5$0rI!_ z#Gkp@<;KO6aUM&e#dgAjS7YAd@zN zIqBTX4(|F6RnmkmT?T22m@y?0rT-VQ7s2D1Iidd@(TDZF5qb}k=P`B3}#FC^7}16r}L{!?T5KT@;U**X3vHH-5@2Ik+Jce4XM*Pr=VP$3gP&+}?`G{cWV*N;E{!o_r$29uToMHVCj`=6d!t|ld@)5(v{*mop zx&PMY`dIIyDDDqInh)v9< z-?3mHoBMylf_(tT{`Xk0f9?FgV!{3ihy0Ii`9EU8J~V^=@2Dv~#s6TzPH7*~34(0P zX0SuPeTVir3jXy5p{0#4OoyN)@O0UknswLk?Hq-*33)v3zQT>;lYg8(XL^3qlo-Jq zCc8*HR(*a@W-EfO%W7bBdKDz#xJ^CELiE2{=fq)TpK=l*g*0} zmPFT4C@tSmIp}vRc6a$zB`kC!3cT9TKAwbku!$mGkrO|QlA_cX z3!2fH41b@7jy1c`@4sU94J7!^7Rl%fAH-@Zb02Toe6+fC|K{o_Fe~r*lxDk1SrKkB ze)ZARefIrSOXkh^zxhjl9Kru@TkfBX%l`^gJNHG1HfOga{f^SWlBdv^7DH=1XLd+lH+qc*CdHiy-?}^Nj1bm}1dS4}wlR;>&( zcP&F-!>pFzdYrzYhH(i?*3;>_t;fbKK|GQ`1WDI{<$L|^%Ig1m`P_s3u^Z{C%-YOF zwH`g&{_v&w^1iZ;de;4YKis+t!WVR%)u^3nbYL1!?RtV?^t`{76nH3KzIOG5P0}VBWKNdymw`GVp3B%V5!aFIs2+eTq((^*YhOXY+pb zChbpJsExnIMVWoqyBeonlZ_>h&P~R&%yWB|zF)r__;|k)EPnykUF%{{+4M{9xwaK~ zS1?Vw@u$(D0jHVI-}GeN@A)(0CxIuc$-(PdlJ6Rg*2yBi+k#3SXAM7^NZ8LZ!FeOC zQ`3Y3%L7Wdq;-0sv*-Qg|bI_%cEJMIV4Nb>x0#+jm_ac~pFn zC49f5G=t2E**N3lIH}CQYPZxhSF6p+)JlpjN!3a_wVJdY84NMw*!NOq)p26B$pEzL zmu3*QTk4o=)beJoCPn9(#g5*mo5$Moqm_Djx8|g;;C19}xfwgzl#VVeF)iz`a=MvL zCeYTd_1M0jeEhuKq|egbQac$MtUWg~C4eqzJcAyU|KZT7 z+vilbRq*Vitih#c0HwO!o2jPX(+p)=b-ceQ?f%vk%`+zpC0OEc-@vQ|R8^bc-_mkZ z2b6zjUmEW@fPG$GqXMsG?HO1!fllob{JU#z>}|6^_=0lpa<*yyNxXYo3&%J1$MVmd z-7@c!XQ}>8-g{e(-Qi}K8VIx&RJ5lWlwW@@CzxA>nZ@^)-|5)lh<>{f|V~*h59qQ)W%$DE-PMj~TH{EOLpDFmu zH^>)oR-A8>|MiN&xHIQ?9VpRsg7MELUW3XE`cG|bW?6)(=2J9p0kh-ceKi=?pwfJ! z$IMn3y-xMaPGt5fgA$JL8o!OXSpNPuilXIWpXSBZmzuX#8<1Oh^@G*d{T6yXW2?zA!ul6IYp_cqr-y*(&=Je;Y0Y=KluzdS zo-*~?waf4vuU z&lydWz8|TlpG`9E;m%D6PKoB;@lKuB1I-}1Z>jHW6xZ0+=*pI;i_1jX04@c*yBzOP zwHv3nX9X@Jrt__baz^mUG^!(+5#)-3K76+oa{Ph`77Dqu)n#3HiD4k6E7O>Y!fl=EUCr; zrlEU_qfYBw+566TFLgfm5wK^o4yo%HD{{Gc&Ov}6dJ6XzE#B(7`VZomK>;%^1(7!_ zz=XXZb>FSZ#79^myz$S$XDzKGl!K9omSCqNN!Iamw6*ZhM~d7lLJMChGV8UX98X`r zs!Ps))-rfZ0rR{0m(J#LWXeeA-x`0u_gs52kZ6R6c}u-$R%JHPw0owSXC~rZ+VWLx zH33=W$+J?`b)I>J^3VGjzSYjK-+6`~?~@1B1ev(`3)Sm6ME}j#tGc+V1{Tw(Gfhac zn>1CdKh(=vt!(ZQf1A_Rz3E!}cKvV|Spr$4Z*Ujdq?ZgME3jtdin?B%#qSehR~K3+ zaQbm%)~wkB2wJ@9{Rd17Z(%rW<3<5vdrK`Wn!H9TOkk|vb4Ii(lQ*u|9I&nVbi}P+ zoV^!jioIm020OT|L00WjSvs9HsP$jdd&mZ8Uo!zR;njKdpY$@TOtdc20#NEUMl1xB zU^##`9m}oFCd>WmaK~PW3XJ2v`OJmeZ`jqGM;J@Vh$;A`K+y|8Q8ear{dm{rvb6Hm z{D@QD*LR_~q1K6^8?PAdX!i0R&w{&1p%Y!&G;&CBKhZa3>!PI1R-L8t$8&Ri5 zmY|ih!eQ82=egEa+mdJPt?Bx1GvWQ>!ISdop-<{7wchDzI$s`#0P}Etu2GDE6^n8w zoHkFGH5ntTUxRv0o73r8=JHicDyM}R=jz)UrKW%K5Yj2COZ0tD;{NNmqkeQ6&i{+A zFAayf4d1R5l`T&p>pbNNC4}sTqNpU=EZIpTYj$H6l`ILPgd`>@BD?Hk#u8)8SYqr$ zgTWXx3^T@Tuj73`ydVC@`+mER<98pwPxo~uivQWj*b}$F>=akGK4ayA zHB<=%Vgop+1L%@9H;bkJPB+wD!%uyj+;Xii8ax&x1_MM^PDZ;rj0t_vjWM=}bsevJ zpd{Szp@KYBGcz>-Dq(Ps`}2=y92|F`iA{F8c?W>j;R*Rl7_ZgCsryz*W8pI^2qz<( zX2IBIuW(o?J>uL_-W}#ctlH?WPike?pGYU1Q|J#>0OeVMw|%;Iuk+s~)&uz${PPlx zme=T?o;J%aonfktMOAGXDLdCfgX_n`?_zI|)R+Hb_=sj82RBC}Zg0+nd-ZikN%j&i zj(LuqQEM2i!b6YlXg*14pw~i!Zp)+|qr3kp`p&&p1}eZ9p_LsLsXw)1&z|17Wcc#Q zx3YofG31-w!aISA73~vK^3It{d#ONN@4dM~56a`#$`jnWm!l=Ho!NT3*uK57K6!#u z1sNe^+h7y;!-R4A#HfYrZM?btHurMiz7yC$-Z=kd$9q2b#GUK*owf(_*EZQV4>f-`g5J-<%8EbXIts4e zLg7QRD)IX9W%DYa;;otN%_W$E1OI%hW{cus2f?jj|GHw3PgCGhsO@F`gJ<e$RN6 z>_ti^pns2{a`sD`FX|@oepT&{a?KwU*82jV@QBpri3&|U)+#S@@ZJvTkLI5bBO14` z8@IGIhOyHmCg(>G?*!_Uo z{m9wY2>U0wW7AJpWjm#s?csU`suYLg`G7sG+p|PB&+tfRzUF186zA!o)Yy``(3x(( z!RSvlsPl5Rt@(AN+JumojIisy6YF}u{3ihcpQSUD$v#;P1HJ>NZBh+WGd9*{M!>`W z&i*b#nG+eJg$2aY7-nukWk!Ru9CNa@d*}g@5X|h$?rtZ5m{GFgQSs=F*nbxp3u!|Q zTKn7KBjAL%1o}FwDwT`cEUfWRFv4#=z+YBGcaG#AG=iiJ&v@)ap01arxgF4}349)q zC2KEJKhb#SzRZBo1m-5W^K0*^59fX!Q$LjL{uEr5D_0Ik5?@g0leMlx8iC06k&4}vDhu`aZyYyD^BgmzXDvUyG!nWkp5Q`3r}t&~?T!73I({@IB$<3xoc>vHRD zuMW}=5)XlKj|W;U(0D`ymMp*!<`kA^9bq-FLi<`{aFcPj!?To)3zS+*l47h9D>PRaIG z9(|LNjr^0pRx$$tI$gf=QstKaqRKCN$(5a&r`+!eGt%n`Gx_#5gJZ};aHPmjWKyEO zc~EZlBeMwJPMQ3A_lIu*wr48yXVp?=drP10Oj)*ggKfSXks-GZ&IZ~?HVv1`m6wj; zP4aMbJ*lKsTPLF@9FKwSGR5xUN?~HR_}X|G1>Bz~KBH^2l5Ohl&wS)SIUVpH!=(6& zZSl*RU8^^bD1nI@_&1mo}8}J%Sr%LihUA!1VYvkiWu!G-$LRqPMWrpewiqWvmmHsSAV3{_taZG_-xOXMD_29PwCaA?lJy-At&@-47AKY#HTC5|0|MFvcD25xJbdUZRWvbSi zP`G0An6jHplCv*H*`flUS2dyGp?DSm2_1SNW-}U{c9L?eFAmkl{<&l$SMH`dB{c!v zFy1G-%n1(n!KJaqrl0mI`^_teF_ny)5lj(Tz{;z=|Ndq#X(R7wP2s6U_to6*aOnE>*0qmwuA`>+;fA3AYCPoAU5hzV2(WDVom*c=@8? zy)ymG^l7v<`~&sTF^c_7Fv4i8B=bGdq$ic6HhrwSswKFo-qAiES?Z6uh#~rRqHDzp z=8i74i{V`rsZJN}m32?H~-DKIvyB}&l z18ksR<-8y0;ZbOjCelpsvasHl0MqK3;aGy^T(KXo=YZiu*Xi#|aI> zi~)o=y~kTm`?>8O&m1z{Gs;~rKj~_;OpTk8g6Ny2lu(X{>AlqF6DbHln^imfHg!y> z{gJ4dEPbE1Kfo^5Dx4H%IeoS4!9SZwkQ#P+x^$vtyLu$LK4~f-{!3oR%-HEA?PfQb zE!_y2;ZJptRKtq;Q)E-G^cg+uks|XAeR|mMmiJR0tg6Zbm?Gk(TX>)!fGxSJwjLKP zt8jrFc$xD!4dOv~!F&Pdr=Ie@99Wa9#Zr&b9@Tb8muBH-W(#>}Bj(1fDzWOVBU%U$@19f5)s$C0vxj0CF z!38jKtFTl%$h-voI75i8xRmwQMD7gU+f88DcEFRSWl`h*k3xQ&PQhuSIr5U_itwCw zHR6Cw4OPJD&WNLD?D1(+OOro0BIN~26aMa2DJT@ymp)L0oI@!+2fo_)sTyy>H9oif zmNI!bvvYzh-@#@*^$WYONS%C(h`OF}263d~v(e|;jt1sYxLHkI12j{JU`f0lR(m4$y8IjO`$KQEGSsIZtjBvMo*`PQcGrIfoJKbHrovF) z&LDLjMc(fr$^U&79sE7;@2v8igSRG7E;(dW(!N4sE9?+a+5eYpm10>fDzm~&=kg#; zVM6huD67T4>_|qz>65;ry33~?>qu-hHCQ01+VX#OLhJ->AYQOvfKf{B+fK4~;+%>UB)0iHadm>B2U1@k zh|gd@fj$vMzB7(uvv%vwnin^8HVomHZ$9m>1Kt>fS9Bn~owg=oa;i-|(-Rp9XHg=Wu;HJ}rA{sm6lN+z~7%ep>ioi;nBBPNarVIMxNrKTng!eEXRIW$tAL?~5f9#vy zrD8)xT=ll%Y?ugUc`>PZ0MMW3-8P_)^!ae~dXBCtg7B?_V3iBC?7)^8rX7?)oK)Aq z;T((Y1j*iUD}Q6&tz~xMJrV!vD5?x-eT|42Y{o)hbIi!^3IKPI>80iQYWtB20<*Iy z4#eoXr0?l}DeJWOMEJUl%Iop*^l6oeW9th;StE{1i=r)uTf|y$dP0z(<<_N&gQ(mD z7_sr_b{gwo<&gEdM0NKS_h*qW36xFUWmAL>hPqIDTE5T=c15B7x1%vIu%bWa2x}8^ zA9r#*Ow(A?Dl0p?juf}F=p7-CrE`6nHcgmIkFF(xS4MQ#PfpM87a(0)A2$&b2Xd(= zPsOD`JgepX3L1+#mNz+16yjU2^)>fjc4Fi+W$U`gamDT0OVO&kM7piKfF-mYLnZ-H z#n`YEg8sAhcVKV!K;$Wt2Qy)AE#QC&UPxIy(O4eh(9&SIXsSjn*yS8p9Mpb3w`Jmg zLiCqY8{NV_r+uC9?PdIvh!aTRH{wdT>npAOD8eYtdZ$5(%uUnB>*=s%4 zM`xdSPmFjU@rnx>0u+-qW^LoY1bL>$1L?#;>V1R3KD|T4p|2#Ulwizr;9;cg`Pzm? zkgE?Ni|Kya;hR1nIR?=v2$f>ER@rtKn`{egK3gYT!lT4H!bYd<>vhYpHWv~>RZfom zP6>fRLsO@c{mteC23|qk7nr|^_6X+ijCd9`Ks8OtE4MtIt(25m6<`ub>(_G3k|V3G zl^+M+KG;0+Rh#%e5nSvOlK+Mx?A!5Vmr(#1Qp&$y##|wsUg})-;BM|j2Wj)6f+uP^ zO(iM1)+=Y01!X=XU9h-^`D*a|GaEWLccvk*TpF%*-U%Ji`=O zXe}Q$4816dCz~r44~Ooq&%_AKS!_631-#gO5^b(>ilQ1|6mSPO<}>@))q^1J ze>MQSRy83OB7eYibt)ZKa$)lT65!n*CPE1m>oOdQIFG#r9*mw9G?Rx=jHcvA7r2Mk zrKn0=#f}CXb=>)n7!*8ajAAAd3Vk$`W&Q#M27A2AE-9y`lMTG4;DdKwK{cv_!c2{iGf32s6eSp5*-w6&?wYq~n{g1hp` zlp~8Bp-Yef$jb5Up9;xFj)kOoQPMxT6$)8uZF+gQt)CVlsDDAG*}d$mX|-F?pSgE> z%*b724SHv_2<^Dygah$|7Ip0X+&?PV{1(tN{!~Q@6EE0XvAXrI+@J+F*txJ{=DKqbkG{G$Q7i<=^8_AAev&x7famo3e*vPv@$SjCb#W4`Di_4=8P7&J^b6cSBc71HQ;c_BG;|;`3 zxU|A1>j?fw_F@@>rZ{Wb0egd8CT{Ci(aw*))rjKZ8C%hs-Sz)8I~0@VN7>awW^VXa-1xs!(Sy|VVde4Y z6$PAOeFIA9Z_@(#8JN}!KfP9?AOtYVK0*D}0{NXQ+BAEmMIq{Um{1skwbUBizr@yJ zkT0<9P^&3r;g% zegc$fOl17A;LRm@uO@+r^xFgU5j#%6L1f7R^+o@= zTcwDac?IgpU6C^t1v&quS3KFVKbO zs9i=0IFWYJ>2IO(VQtWB0aCbV$}h;ndOl|^mykzOJWRo_l~Xxtc`tihIXNq~8}*>0kw`<8zBn_<@`^si$kR2H=#T0zo9po;8%)E#>M2d?R_ zTm#;#O>dazoKR{U699%^+HFbn^Q=~^Nzn=hyZyN}wX>z^pAEY`NNSosU(gkU3ytTu zZN0*X%yyDeg?^voRVbC4t#?a>DTH%!&TRNQ-9AnzCo=!2zKL=x%RU{v>!##cZ33Q) zM%Dye%mHkeJ)S3jcApnDY9kf*Eq8w}jOT^UR9H}G@aJT4VjN1`@ zNcN1#8=s+P5o-koAV+Zt2l=xywvU?LRz>GuB3Ojuym3ofh-#`|=Fr+JPAy*^(*|1e zzXnR#6P|rCJ*I~)J;Pu3%v`C4&2vv>6AfR7Btjc0F*g+|?>92Es1kTF>`+0=Tx2`L zQ{+dQed2D}{g&I(zAbFeFUGgPyu3qRBXdE!O~1ocSE;kIyj3sa66(Mz8~659Le&Rg9I>G9P~u&V9U+R9NBg)|FrWpgS$=6 zQlOG}sCx(bG(vw_j5?eHm{3hSQh;!^5(p7l&ybubj3&Is%5(m%ORTF@2F~&K`+l=d z0uf6(u*;eL%v7a;HH5G8<#D_6Yo~`_m~MZUAAO9>rM3$ruWs&eAPUP!;x&2OP3{hd z#iQ@r7zXaR7;cNkkdbWGLEwB?D<>#!2_GJq65nN_UD=35ZIQg}DoK3}uBO?<+GD$7~2NFdU7yM4NO`diUl7fcp`Oo9Y%WL}Wi4=9DVkx$}DTO8its8=lYvGz-|WlA!lZlPI5 z3USUI@480P#18hYTy{Yd3O(j%;IGHWN}B4q9|vkYf$9)9%UrAs^^}MBWcM=>Bqs3% z-s{s<2Kt|IJ6Z?X0Qt}Zlk{{RC>4&|cTQ7`K2PWVn|sVJDqS;)o|l)7p?dJ+!MNxQMuoPdV{HhMjj z`1=Y0I`I9XI?mV41T&N&U9V>_}hMjVSg-xm#uGueI5B|8(=dF(kNlfX0X)ec$9S0PB9M5!`8TWOZiB%Mu(B+}Wv@gFleI5lh z2`c;(1Uv=)-sGPq@XU+_6k>mO)On^Mg7eUvVATHJ)Dzy+Tlb{pAEg&5Y%y~=PAb11 zp0VQPo%M+5i3qC8#0HxN9c2Y;d)ahOrNS^%uO-iz)fSpmh_m*DFsD$u<{P7aPEa%e z{f}>rW}jXnrc*-uMfo9EiaN)Wfr$AdI7P(0K6L+@9QrV}Z5}~)>zvj=D=xb4wk8n} zohrV~Tk!m>$(wVzV=hi+7)SB`WzFf98dh)p553Rpy?cFOucu`nlllVGx44SQ(5ZQ{$n&GYKG?I zMBwxrrj3s5r3+L#U#!)>o&El*vC{sH8v zpXP%*SZtce?!WzwDQRj{Gu-o9Sy4Y>9x&iAeBS}mcmC7eOy4tu`nSTPZH;eHLYxaX z!12CYbICZPHcb)%>0x9bG+%jDfGu}`AaH;;H2Cj>67`@A;=lQVQ@^PBy5`jcXlm{0 zLJe_FwWc5@?EO?(mJ4y#BzrW*0Puq9_ZRnkKIIrfbWQv&c=ywc^aN;KLN~3Q{NK7E zGNL?@llKCFxFX&BKpLKdI9LcX&Al73lk;Q}Ea?_j{p3nibjoI~a@{|BpGD=c=qAK4 zt);@KE|EaBGm|AabWvM|qta~K5<$(T_HA%6jyl69x@-dXA^>bk=WJq{UP4xmA(cuS zp^G8zP}hl~QtN{NYdYvL$51AXuooJUy%(0_GDp|?9%yRu3PiJd=B7wUeM2|p=^Svh zrR5wlp~iEJhlcda0v#bI_)B!L3u^0H&f6$|ZU>WK5qm$N7u!qQyIJ}_^=6@eJw8+$1 zZ`h+7s9OP=g<}he`L)5*XJWhyR!%a%qX^kFQo!A8;(|P8fveZOANUu7*TAk%@&(0x z{nIr!B08vSN9o;^gB)XiMbVw`1>)vz*!pXnRQM`dQVIbHYpYFzhyi`=oZa}lKoM;6 z)x@Rep#h_E1qK@^%ww|**xjIPX18ee)}voU0Ul>}6P@inr5h!#*j!-HJp|i55#Y^@ zK|tZ4=V-MPCy6^DzkCsm%_a>VI8VNn3!jF1irYTI21ES`4q$wdY5@`AD2dvy<*IU)z zfHJ%8Z_Xfei+ez3FSxn~v^;xqNTHLQ`KT~;zfQJ~8~h6QKV1Lf#?so;%(@q#btWqm z?&^cyoh?G(S$fK=A1S)qDP|c1Kf5i!gjV$}>d*wXOA(9hHkr5j3k#bxEpWR>AiqO6 zvtWYk`5B=Jzd>*A^?{Fs0y@a>V}2+vY3>=NJZS9_`Q%ZjW) zn3mnebMl3t^qd@{x?_anMtYWkR|Qc~37AfT^HSiY>HDUTzqm(N#=YNr0ffNS@H$uU zz*C;dG3djd7u;H-B@5dL{xWN0r@-Rprjw{C|4}`&)5OEIJky2l(%!t{gYf_AdSNu2 z2~YDky6$6Cp+SB})yWSrdCs&607Y~EkeANjuddLfS_mhlsQP_S#}pvDFLo&B0OQx^AR_8*~Pf-9d!I# zf&d4pDQvKNJNcr4SSYoG}fF90la!?=jQEC z5icT=xmK0pCJfG`7~?u^a2i?5fL3#Z?;6LXP6!~4L4<>(Z>@P-@N?J>1GpfN&h@(s zOB?g5xUyk_+11fD+C{e85MQ%dr z3VKK->$>Kt2wBwSRm0KcFvS&%Y~L#&l<5Azw%kO}bCNN2HQKC`Uhi~I2z_BnGgp5= z*!S6bBHlSTY;ukjZ5D8{VTtQ#T4*n28y|~{JaidcxlH*dKs$N~ror(>S^M1su3LZ9 z0Bp^Ju{=#o)N|lYSQ_OLS--Celc?o0XZTRI@Ikh>S2~cPl&%ClzafVa^1To4LE`P* z-b^reVy3@zQ|(*n5RRb*NS4CM7*sgqZZ-4foO4#^jo-&`-w+QtJ$}?ssd_fA1Y^5zIGE ztEXx*vGnz&dr{j^Co@Z*Ee}iwd&0({(k75y-^hKcV(p%=x&<%0XWow|YI(E_qR&?r z$P4&2X_pD3@K0MJxsM#fb|k3F^@eVXOS7SYRq`CS9BdYzdbN3hd&PYYXUhn2YA`BL zRU1Es#O^JTKH?8*Bu1q8kgy0CU9b+v(V5z4mP`&gKO|QOx+Ts4`1lp<8j7Ia&_kli z&Hyj`@Oe@)vuyY0>4uQIPILe#zl|wFCAAB9xk4+dB(@wy#wAwdrXQ3yTwG*)Q%nvK zOUn-ByzJkr-9QEHZ8>JJxQ2Yi3#QB-sw#_ZSrxQN(r&ZL;k3zB9+da?bbhY;j$DK5 z)RLq2LokZk-m?r)$t0TA!k9YX;Tjy@1*%s`%E!x9Ch`oi%F!auo|}y<7SIS7c_FqpQPCU&z)U+=IV`8f!Kvik7u|@1uF7;JoT*7dr1uk&k%60a;yV-$4 zRAI0UjdJFCE;?8=%1p8^@L>*`-FnYSacZ$q{2t)P6YiEjVA_6;Nss9=>JGz5CX*5F zwyn1P{<7p}lSbIflR}&1XHL^Du|r`M1l1CKRgG1$x5ATY5?*-%v>g{%Y#tHTSGR!I z8O;6MT(!FXg9UQMDi+ZJ-JKVvZdCJX1HgzkVH#Dih#%O^|H}KRN1f78cjg-^!TDRH z(Wz_M5eEY2>+t7+NKs zqFs567uIiMRV`xVjS34sF~#Z(jXuk>U8_pafr9MsbGC=613$8DWPoNxKC8sdPVX)x z&}`CiEB}k>^CwP|SuKedIr6@SkI)GCzG?mz3EWG)gu1=U6&rtHe^XBP3xb5Q2hJJn zcRl$!!54|j!%JLT@3JJ5x*6?fFl7?Fs~|DHq&yquEMLf^Xo(LvbbW&d(GBW5|Ar7r zbBcey&wVd+Rf;Kte2Qt^ri8ez9A_4qN3>GBfJ?C8&dhy|9$UG(_Fz`GQeTo;mJ&?R zz<~Ukj0`f8bY(#~lDk3DP%#BUleP^jy2w#+i~3cj`2EubV^C2^o|E;}L=67POkEeD z@XfDdz&eum~bt37#4z>VZ@Nq3yvNSF`4& zwJ#DQlKX5=v?>C<-oRmKF{Y18*4LJSCpgoA?4RW?fb=}`x z*PTv?cjw2!tV|o$xy}sn$@-t>o360T75c}1NW0)LvDT3@Sz+0Ov!p8uZwYEb(I0U3 zHL;?yya3hW-|1wby+ckfNg<_F=HYZ~JzdvAR_(dL(iQTc=#ul*Uao+Cz z*pCK*cFrjAWe`e;R0~tj7IK~oi|8Ns!W3(&k-P)oHc-@Q^~dei7sr7Z!i6_)wfY8s zz0`ldX|fXbDXP;rI_n&jk{kY;4f^B57o&s@90?O@i++x)skQt7m`m(d%rr}!D`KPz zeM}1Tu2`5kiahvO?k{Fq5TA68HdnJAwIf^n3kW;Fo7 z=qwo;oJ!t2{Pq5WEWB#kD6LtS+{bD{_*Zs%SBU24nz-o-EguZa;4VJ837ilpBFJ2s zX4g#^+kZ#njkkp{m12`)_*zO3n`|?%*a6 ze|0{WwZIX{b$!#UyI*kZ*}J8k{x>t%YLJ~|L3GOy@)!+;NH|O23{<+KE9c`$7ntqS z(mc_d^}-zPTXf5~T>V->0YNQx(`A^D>y+0kq)sy%f#oAjd)v9dO0x zN`wBcuz0Kh;bi@VMWa^{?{ROxD0Yhy?-f9TJFBo3dG%^Ie^IY9sI(Y4?0l=_Vge&N z5A;Z~uQTS2;a_IA!k1&Sn-H0wM5ew8h~;{mF5ZeP3h6>!&&~UX24Wpx#0}{K{DG3Y zVX;2@ZuD~)qZ~6VjWp7xx!1`^GsCAfwX>lWSYo@;&}Wk#h33CIBA8hnzPSC$}Dw%$;J^?S3ChE&eu8LNSmK)?rA-XP&$rRPU7iZX7%3nBRmN*=Ge(z5R zD@kE1YK8Sd>@h@a6UT#3r>YgxGEZbL7~DN~3WF0SV#DH?Yn1d@h5*TZ!8ntGdp?;? zR>WXOL1B#B7+n0$Yf8&jL&A!#IScgbYIfl0gC5fP@HnpRCkIcbIKl2O{xFjpsOs`~ z+s;kDvU2zxu<>tF#w3>1sx^q)G!1z7oa(pGn6;Heup#p5zsqBrCzwy%kJP7APEei6 zXZUFbd7Y$5M@m)o64O=gX<6k%UX63D#MMqat;?cU&n(^1@knV?TE9@KFeD1imL;2Z zOD=(_OR%w^oScu+JGb~qb;hey(sdwGD`ZfQ%vj2G^Hn9n_h10 z35UQ}P}2ez{Zl@N=dNUM#i5KEx#aq?v=Hr&I4PI&jW?g|+j_Vaw%E`@+o?_%oH#cu zOy~WD-`tl-iBD<<{TsN*Ax`$OAQ4#WBmY2(o>8gu9x$NgFuQCp{$+VLPB?72u*-LypMCu1+HP=_zGe?^xeY5KL;7ktgW+_Yjcq`Ld#)f**a8_y6)!Z2gyRo=u_;vx)v{nfmmKZC9RfPjY((Qqj&-9Rw|IZo(KJE z6;gA-Z)0%pVj3(zT8w0S_R3|F&rPbm;@2+nE-oxw1T)U|_WQU?7+$Wy3)}XQUl>$s zyz$z~ z9^K|_-pfo}`}295XQ1-%iA4yHaFMwC&ng*rU16o4D}6ZeTCv#i-(lw-!k|}1NY=nS zB~6LE5%8Dh(jivp3lsddi#760#N}4)A}7Y>r(~W1)%aKEhu*T9XWZw#A4m1O9_)k| z3|`F>;{KEx&F-s(c=109+|R2u3ckz*TO%dGvT94ONA=Zben7K-85SEA5#N9-!*Io= z#(-0XvF2+S$Bp6G@pz$tfCmV-Ru8Ok=xl>TWoqEVT4om;&5v~HURPdl4{@RSm4wB8 zjk4bBe`{&8R?;e`zOqy>L0YPbS#=cajX2k$;e3u!t`r{1sqZ1V{RndosC+kj1|KW| zuXq!2!Q6ec{#@I0w$Zi;l%?vPM2Ud3l2B6R6F%hUHlgL>WI}3Jr+2bV1Lxy4j-tMmp0qFRkkn zo)zxba;u+tM*2Y~Ss1(By)j?+tey$m5B!GN6=Wc9#f^>`)WHNGm-3$Q7B$q0SM|n} zAn=lTFMr7Wl~@Owab47p@-tOHdw}0^5mPHm&yLd430Y0apYsZ@8?YvG; zH<9b#c+X*W!P+C&_N7T!tt}_<)9;xP49)>yOWR^A0qWdyJfbRg%(3r@8m6C9*1FtZ zN{z^wms#wusNVjt@Kkv;?7LXLLXF8zl*4dsv78xg--=AmisIG}cnx0=J)0|cuJznt z#+~=mI@c@b)U{zzKLf2FYcTMw`nM*DUy7N@<;~7(D3uDPlGjPgFE-G?PY9d0A=V>j zs;grNC#EN*FjW_D5n`Hew=#V*_9A@^rk=x%{#jK(gm1b`%enbV`>#$f@*EcTvR^G* z$K$g5K86>rvGnv1MN^-K^iol)S&m^Q#@@ za+CMkBhF7|MbvrIq4pb)Z-KHeH>e2BU+LMFvzT9u8!@o3ujSl_Etm3unrp$anq%($ zrEi06h7(Cicg(CdV%;|giUk}OWwQB0yV{>;e1+E{U7gzI8kxRH?nCu8MEfGft2II} zulz5gapQ7e1%oMx^(JIrQ`oI@)a&7t{xS@mVf>(KmQ8CF{ij#t6(hOWwC3~sv98~? zWns|1bsOaVIzN3tXN+~bPb7zjWxmrRB>d&{!QvOzqrT+dk@Z5|=@Yef?z~%Mqzp)h zx4Q4`kvp)u9i72lr*R2Aor#B|ZZwh4!Sa6N8?a4v*En_m(l7OJ6#t36Nv+ld$fT!@ z!2K@o*)()Fj)o0%`@1~p7?*<=YX|uso3jOof>BQf$8T`qubs^mPdWufFyl=UBIAc! z&sL=BUo#xLJqVjM4vK6=5-=F$EV=6Np1!jf46f00A;;G0*PX~(xcrxPm3+8ZlUJz=dIct5VU89) z#_S=cGeF9#rO>`eiZZhS576%SPL?J93KppBI{|6ZHSP}>bo)O_E`c36uTM8d=z-9z z4}lM4PcCPdy(KF+ncdO3Wl|!BZ8Tb<4kTCCo`s*u+GG_fDq9Vez0Z`f3kLQ!Vx(rI z*rvhU)7nl0Ib~1EHte#nG{JA3S!XMc$C!$89m%VgE!Q#v3wZB$eq0B0P%+18;nRQW z`@9hcH*LP|q5rMD+z8jZ=%aWwwr|xOTI>@O7yf6l1-}KwaCR=uBL1hW%Z>*NY5ie2 zgQ20Xd8=gmbFNjmdplZJTuH5%Rtb@LU@uDoz>*hmra-3^HydU^bMZ`oNpS5BsKqtP zR-znGzy(MapZ&2=e86^Rm;2vA*9Qvd-to&_OE0~b@na#CODm5E&OXD^#`&XQ6v=-1 zbRSK3cfqKFcR$8G5_q)Yw!10!^ng~a5LQHH;q|ha>0Y1?ARpgnRm?JqNZ~8C(pxE`)&bv=|oO#P1qSeDt|9t zR&DC~YGYjqx@fO@4HWp_p0$t*6Sf$2dJm%btKvPgJQ6eR>vz$Hso7j?I6Ia5lMVK- z1+mo3d91m8;#yzJ+XVN~)XjINZbq>mADK3#SbqW1b}NNI@Lm-VhKw!AIv3Z(a06yL z6M0iBX2>t0?%uGgE~f&CwS@-$wf}$l*D2D?v2IE(c*a2x^t(JAu$M@j`g}!lbwbL!k^jHH!gxrmvrW-Yz_nhXL6`$q!_xzU{Q^!7SCgGBa^!XT| zKMrI8j}#t)SE$P5?uWROhpJz=-TqjX?KG!dsC9BBY)wm$x7P@I0}_4NZ86`#2;53J zyG`6w3%H$~OBrGAsn_O)MVk45)oOV>Tkr_|fZ94QZ3s_SyRDK15`UA&&HuwaS;FM* zy0vBkRl!JUs8s+H*;7j;VRVt)C@@c<-8Gc^eZ32#`7>6P!hQRP>)$Tjh30*me=}K= z1h|2Oe?Ss`qt!UPD>T=Fg59i%ZR*jV+}QUd{;AcdvuM?A-aVQ^!Deeb4o&=oJAP#) z;^5Dd$)vqU@msNDX46X0ox|SyvZg&rT*$3Q87M!7OcT%kegDrM-)}Bhy2BPq2~T@n z?1e<1;c%}k?mU#iR(3y_W;M=_)+c`KHB}?8VP5YQp?8PZ#b1pRZcHM2_Sg>#SV=!s zU<0Nq;8^v`n6s;9NiOf_qK=rIfb*U1WsWir#{%dd&oa}tg+LFC-!2n=RsXPNSu&zR zxfTUX6=CAYiI~*&;P`}bewj!_T z7FQ5wWBape?`CkLOkC72Hm7xOa|PcOagp718%O%CS-CXETSc{;Z>)`S;)_gl zG#S_C-EiLI@>LdjCp(BLuM&CO#<+aViSnc8NE?qW#b3`|%mhzm8KN$;qh5+1Yvrr{ zME{4ky2jj)bRA}Kt`6tMrlT~wSi^5?YD4llFW%#$)Tz>s_SQ#h1UYsziPgV}#1<)6 z@%jWbPL2ZY`BDj#B&INrQV1tvm`P=qTmS36p{r z?L4IND>F%?GJD(AoOJ6cUm!$b#8lKL_!Q?Ojn+JY?&C>iMy+6F*rWwF<#4yG@{^Y- zG4~mfH+KSh`n%j(?MEECY7ZB_)0ZU}6CP`AvzI2>#Fj40NaRmQ!`HnzivD51TXMs3 zQ;(mQ(GUmdTo2spS>pWfmyn|kNhUivBTe^do^RVLWq<46AY@iDom!aP%#*3>g^@uk zt>X!%kivO|&D-OT87=qRa5R)f1vNP|C`sO`R)@F`dv5H3Hb$D7!z(oMepr*%{-4X? z^ue)jVG*k?u0{^o|97cB;d6tu21gl4I_-aTGIGG;~6+MSDo_;cL)8FFp#QQNltfBGj_ zU61tHO(?TyQ+j*n{?>H0yL&pywvUT1#}KM?0EnI)6(12()zF8gbrj7TJe70Yn8hC& z-)}Wl43%_+MF29kiNpDyI4-l0uQN)Uu&tz?5@jZ3J#1udRtuvJeXu{2G{t%Pl!;KP zbX`SmO`c%s{hct8#l2hw=MQ%5ntc3y%SbIE_ih%eu`d3Ob07X2FcW5MjwAXGFcCz< z?mO?7;wGK$Eq`7d^Fft{&SsBU1&Tz-qm`lXYu((d!|{adZQ?s-Qk_5(!-r58vT6xl zW3O2$0c@Hov0)kr%O<>cOzu}e3fC|Bw(=Jj!0YDAZ(E(GVy=a6-zBr-%;}y5OrHV> z$V6xK0?>i!dodgNqeW-UUaiHUZzhM5wE0WO>(f(Z$dA1HtXC%d8&LP5fjLzQ1E1}5 zGeV;{EW*HnDf)Ia_9P4HID&bnwWq1A7<)mRt@D=UaBYzPa0_#G34MMQ*t-SUfN4=moTgKgT8kDGI}Ef-HEzh00?;K^h11&D@#*bvzYwe#^d> zu(Kp7{x9zcD?4i+t(&%AVJgjOMCWEgJXhE+WC24uhpj}6GU=mWSP5-sN5Y#(G3f93Q2I|A|`LP3zMs|4lY0n2k}NKQOxibAw8& zqy6s(o@J=+!VIARqf0(Z;Vw+4e78yw7Srypv5E5;K1H%5R_nkX4 z>;5-qt>mn|a@JYdM|M`eXa7DQ=pVUmOc)0;@(uX{dC6WO*|{seG14EktNY|=!FcNN z(LzEy<~>ry-3$Ljc^l)A9ROfzijK$l(@9@@cT7g_XLxL)XfkW}3&}v!7-T+h5&KbU zM~OuIT0KMuqnaoC(wk@OTyN9IXRAV5-~3Y2JNoeM)uko0M#*J=-gYZUm=wV^CRR0b z(Hy4il_!Z#*wB!mv!H*XO(L4u-h2um4{JtztIKw|(_9eBKUwkY-RBFMlo-@4i1$rA zB$##e_|XM?_#$g1UpQN?d=l)fW3#3Tn@FKI$ZIK?{vE(vqg|1(T@fBJZ@410wF&Q> z|L$W);dtlx4z^pd8?$_Q^=IX|W4B}1c0o~^h%a(S#z*ltK*KDFAE;r5PoPZ@i}T9Z z6d|uS!kW$ZgVzaM)FXF#&m6&1)wE5$19h)BB$>I+8iEyAX^-iFG$qKizaiIJ=Gz9B zCH77k(WK?)_LF5QSGK0{v5=ahu_ZQ)Zto_hxdZc(O&hXZt%I%XRAvC3HVjr_BjbvN zo|-v-yX)xH+#ZD2L2XzgO!t-2ut5s(b_35SFw7f|JI@n&CN>x|qhPx%qMG@@-ZRX1c;!33qp1$CUQh=WRf*)KLK=VRYl+8y9SOQt(WPp35`Ol`$$1MY*|-^RqMpSDmZhfQAZg*tC27 zCl^0m{UnBg+dHpws%-aJLzDa)uW!2gPyoPF3x0jGZp7RDFYs}-4`uQP6ZY2PqKDS7M8~UyLgad|G?Jq z%6xdEYA5^Qje_CVTj-$4EsfC9$^8~asD8^Ryag5Vs{aG4sQquPTh=2XpNIh8U##HY zwbC zZ_uoFn~%h%cArO|O_DvQFn%CV?OMrT^)V9-mZQ}4;wWuqp28`^r$4{`i`R zpG%z<)(Mku^c>X()LwFjtT{AEKX4DaVVO97!g1F7QTQj}Q%|l;p63)^1$e{vi;aH@ z=n)E)6by%C859SHXQlBfv_pg8KpQ;>kKxltoF-1C6ZbO3dii^oG&5u9CFkx)38;yH zi$qm@uk%OLQXIj!`|*Bn?mh@ebqML&!jaY^$$nAnn`taCGATcDB@*jGB~332!9Ac? zB%gG@@cc-8^{VXb7^nZOB-_8ZhyOt^{a-uxe}Sg|_1y1ixa+8>H-R?hpT8u0+&17) z(SrNqApuy8ASNjA#nTMyXTLI@g%V{vlN+vnBfFjTEL*^T`k&l|&( zRRd>|c}k|y&YtP#@_Unnb?(ickVUm7- zf(ZgRPGgdxlYTK?+!OkvO<9Rv6jV>+YKb>vJrbQxT)r`CkzH}aRqy?N_&AN6F?how zaP~1?p20h#*!SHjcxhd<0oH_y;1fXglZ{fUz!1C9<+SF7ibVZMy#{RiR{LwCqQMU^VjhcA!EH$aJ*(v3+~u$LbPx@O2R< zs$I7w^S)?=?636HF8fyh$7q3m9y3s<9Q=!$TV?X)UZmDhiv^BIw%8fSKO~UtGj2C= zooF)Iz1SA_DGC~@YoyWoJ9+n6Z5r~h&-^0M%WQ$y-G0v_b9?sFO2%@?cPA9$g%T-G z;mfCr(21m**QS=ydUo$u3Bt4<3w^sERdVE^uUmfh1XeQr`P0E=@AkR;kH;l{8cg}M za)$d)P5`>QPTKvsMK%De?uE3Rk?J4DfTlOh>ikv%qq9>=PsAn9Df_;H_i$XW^1;1d z!0Ns3s0gQ(L_Th};}IoUE`Jd}IULoz_U|ufBHDf4*u{9?1$9*~ zbI?b!EjqYO4+3TPS+a06{Y;(#4@eG~?Z3yi-wE--CtdNy>5qT)IH@V(DhUvxkI;xZ z)I=*N%kR<@kwq+{dZ-?%BL6H-_{&KNm49urGv`{=w*LuACQwpQ{TVCsx$HyE#cF6w z(#uD(((6VkKN2XZZfwz63r|GDN>%A$i~Q?fI987sIQ5(o5~h!jgh!KeEmeq2+a@#S zzsJ1(J-V%iX(t~U+jb~3{6OCH>iGNaF1@x3ngb>-%auvNZ~1*N-mrW2Rio2W78coO zQ34#J>oPw??TfW`;yyg#{Ty6ddw-DXsu%%~X1cQNu#4HoN&N?GLlIzA)A z53&A=85VD|*(&6It3J|r;}#xtz1}Ge3crq?cAySn;t$-q*oLE@JA+bF^64mV)9dw zu9L^}e>5c39yQ_$?-Yx1lcgBRPVyd&3pwqUPaT!1Dvmful_ztI6*?ePQxYC&S)C_` zh1DyY!w7KaE2zfQRMY6P;5fWn*V&9F+KOQIvcSX zDHT;7{|6ykhx3--fBy8STwNa*{Gnvvi!k^MX& zx2ps3arH=CJtK9p=~9H!QPIjl8fr#exEGk8M5IiiqdWR`z3~PAxOAV*7pAgjt{#AW z;{Z;5$A(6|nvJT~R`oeX-h@9KW0gi5g7H6cv7U^!&@?v}528QV9d$%#4k%kE4%+&iV~eG+>cPhs zTm0VP&hxefop|jH62~dZLS~kNbkCt{eQ&g1#)6HFPAmJNyT4Ta5LS~^s5%s?s-a3m z8`A1`2EvYL*>)~01^L2a}BAr$)9rxGr0Mz6! zsJ_V>`Q9F`ckqyW@{Wb7!F${Cl*DP`{(DlVmulz6EOrmc%-nMxe~>56&FXx3BNZx- zbAWB$^&Lg-`C+#^T(AgmxRugcp;7D9YulFk49hYjTqGX4796C@8=w_g;DfAkD7gblBK<_ zRV&9PT&l0vY%7u~EMz{wPg^i1_w8-0#t)PG;5av;hEE!8dLSe#mm8M|ujgg0J>emr zVdd+Waxr~Y^k+rKPwlCns}T~`Rv1du{G8`mPH-zXi@SQp`X!f3VBLdVt_@<@*XQ}G z$vH&f(?e>dJf+8ozAwF7jLXoL=?2oNEW50J~9eiOcHfMbboReodX=^c$Nh<-J~K^&&V4bt;_0`x?9 zvXSg*k6*+ad0y$#(3~FzCIEcqGR~omzQ}sku$2`B`LwrbY1;;zdw4RbQU{R5rl9a| zt>3{o%5^%nPZz#L;j!aCdRFw6_ z`*w-*KwO$?KUK zlQ1oWI5C7R4dCl64@1Qe@;!_me_k#w%V$adZ_xz{(5h@ zG&z?^drhOOa$v_eU}yN_#~6omwosm0(FddE!sGL(evPy832l;%sP$)#+`C7nJQKq~ zj%4*ZPc%|~Dy#iWW#z1mF8>{qcEMAmIxbOOU?I9b5>|e)pUzsrzF;;dQIgS@v=q}>nHDXw$sXf>%ZAL| zde%6P^Fy__LwI5qLBw$V4Rwo4<4+Y4uC%Mvuu{oMmdi<<(Zt}DNR7dy>wXaw3LTnF zLLL{r?7E2UCrp1zZfnzgHbduc|G6h+)+L<>@kPU_)_f3>Hxg|)V;)y2B2*yHoEIC< zVdqdS#>(0|q_Jjai&hs}PW*GUcEqLdE}Xrjxo~g7z0MadyJ6~{F;4#Y=L^mHeAOb} z&pYE=Gm{}Y3JN?*jsk`Iy4E80wBCJtGYrDPDj)Ge(nVfL7>(ON=h?0n`#KwX3#^(0 zla{?(OL_!n`_pG6o;lA?j7i!+Hck@Uqm}lz3yb&l9uhBSTZ@Rvlxk+k+H^}(wnQ3G zChoKZu`c6schr34TtgA~Ckz|ExkNCvr<#qwD<&Uf2B?#Z;Mg`KGbN-#$D+?kMa(U# z9IdM%sbBO{Q+8vzq)sgLwX!&S2m`9cv~2J`b9$d)&*z#kas8^q;9RPykUX&kO@)Ok zTgRP2uWa=u1-|T`tC-^kpT$bf`wSOt;Dzg|T9mb8oT_q)SgDpR~}U!pfXY`bv<>X}-uS1Yvex?)%%k%i#Mct| zpNKx!B}U5=$N{B|HJA?NLs|nLW$An1n@E{Rzn9tba$+faqQw-lXmRi%^psoYc5X}b zjd+u;zedQAJ9&jIK*lz$jr=J{S_8+pU8GEMS(7GMEMkrRfE12rQW#V?yzF}D2B*<| z1G;Z?;OHh4yd&vB0|+SgX9RzGG5@W2kxI4(*F)CxUj6r-K(g33tef%vT2JqB-UYD{WM zWsoAecQO3E1#}Np+Avk~)FbujNR9uV_VAU#+Vg_~a_c^s)0l-9mp*~x*`^jx-5!{L za4YGgHmrthADrdftF88LRQ%%wpQKt_OXrUxh-Miyf z%E0mV!Let?e&rpB3;H>nN(GC@if#6-K8vDhcgwf~F9tf;4uodI>c^xd>Cm^RB;%(tE!sFHu6W@QlQ=eB^q;^Uj_`Sb>Q z*Cyp$kFAm`sqWE5W|$YnCINKe9X0B~vDSlGD_We^r^k8&|Gr?O^)k-hg6dtPkQd z<{-J@@yX_@qc8ZwD{0@#vT7oujSvD$x5$%MjuC8mot=xV-Pa6sY$92>H;=v0)n()c zOAkt8N1o04BLecCVPjS*Ab)DT{lGh!P@h@qA+N%!0MtZ4jk-U~{J`a_usw-G>@hB^ zfbP)~FS>(;p<=v+^{cjlI=cLbij-px5e#tu2E7zbdlb|DiCQVd9&fzt)d7X1NW`fd z*}RPW9eFnNL;dgHB=g<-?-Iwt2^=z{kQw|SB3(*H(@U=FwE$=7FbSw1ptOmjFN0`*&3 zPeX9&hHviST9E%E<-E1=m>Syrd)_JdSD*6Ftmywu-m7e9Wanh3U}tOh4-L@&rqEaL z-$Kg&CiE5l&zbMv#{b2R^N&6Jj~)GQgWdn3Z~vC=-fC?AM&8oWc5b&BGq=#KiiewD zX`A73V^4-5%{m=7Vk-z$!f1U6CdrtSyoLca&GUuP&`R@fe|K?A+ z&A9*1Q}34cp9MLL{?;Oiz7OL9{mE>k=ODgZMG+YZfm}?N7|lML3ym0;kYvr^wLJ8! zN1~s;$`FHD>0oN0N>c2*P5ZCVF%s&$V=9hj#xEvm{d4@L^xAefK8Sn^q# ziZRFk=S zFb9b>y;td)wPwq2n=jIKDn_(W{cTmvY?u)dJ)a$giCx;1!33}UaAnATZsPpc5g)g$ z{pLh$bBu6FUtA4r)lM=s=7xOg=OwH+q4y?ds`3(L1~^~n>ss{@0QcOujDNq0FQm=+ zq<^MF!RRgc$ND`z*Yc z+2V$;Gv8h~0G!+ws>x*>K3DY{))iZ>hw4OVtpC!9wlaImLz|!(zmsb=zH0Q_bbQT2 zOMuRS&oEh`4m#SOpwq8qrd^z9X(r1voiG)@6J>^8%`#6mb^zoiuV^a;{+@^0u(4JL z?^D&JC(Wi<@>dDf%bbYH1W_rPU^Z!xVIV2dOk75byKgss&7|tNjJlK z)1?&aLq4;%uu6I^;#_~JU9WPjfcoJh(6F|!%XnU6jWVR^{L zqAB)KQ%qY^Ojc7&T~q8=VOm~cT1lZ!Mj^ej8Yw5IY+QVHZ*Nd<@7vy9KGQWL)3wK@ zYs#i;{HAN{y}c^8o}8d3$^KK9**7BC@ZE(T>mri^#lqRgq~nPZ#F4~(dk5X?y9NL{ zMp{Oa_*eA8Fupo4GFUejjGlGAks-U9pL2}{{UoIOxf#6sWUAo>!)L-N8qdn1l%C#? z;0YVIC8CmMz1|49WbL|sTJ}mXvRFGu-bSWbJ|WU%YOSQnbboqK^CnK{BK9;~|7@NK zd=Y|jKPX|k>XU7b1*CXu+nNVrAa}2B_GFvqn%z$9R_jvN)FE&A*Qspn!YA1+9=zi& zqRzcxm-Sx+p!uSl$t-WLi@Lp*TO#Z^#f8vI z8(NzLl^u#dDw#9F^+W0QbUtq<_MM5*pTtedEoY8%J#5{g?y>mP>zJt0pR>-M`?1_>aH8C6zD7=&o5{ML?T!98(cpv_n_Im> zz#@ICZ7VL@2aJY?aL)q|!MHWya_OUOlR6vv!6Pw}EA=LiH#leu(Z0P0yR1m0SgUX= zWo*NAA;Y-(WNX-P$q+R$c{CuPx-8{WpF_hKUsFb&jHc~za3PlyzN1OUw3OIMcJdmU zV6Pp|=@-5A(`{mMg-Nc?+S4oIg6yr}!V+xn4Pygn#6pQ~;Khp}_p;y%Nt;Vv7ERwS z$VXSL{3JfE8n3u5>S%@?6p&+v@48Yck0x$H*QK!yY6&WZsBh949L*MID*Ilsq4V*FbYGK=?A*%+LAP!T@?;i&@ZJr^|rUyt;v z|1l2EOOi)sio3aVACVHN-4>zHg6Ia+_G*NqrW+GLM*PF)_voq-tHP!ZJ6be_>80#R zH(>@_rSZSb&c%u7aI`-qkZFh30jw;P8(+j3&Cv^8SI+(%D4H9`pCs;Ic(fevJy4w^ zGp;o0%QG-m#)bL#I%L9jRRH!9hi*Y=4y-+0wqsZj-~{N&OI!mEQ4b$Lh{owo9)RV7 zdP!6km@(DEZ^OJMP4!UV7wl)2CUr<+pA1=JZ8Kx{#;&wZ+R+90rwg~I&c*RgrsKE4 zR|ZS%bn(sm4YToyL-smI$y~tztg8EYU*yGjIUe$U}>GsskZPo!zqC>YnSb9Na3pS|l#tte03h3u^qg5*|$ zPVwtIFZ6qkydRzzK8UB|XYhw}{3N%Q(9*OuhbvZaQ}ZVaZo0GdZ8&xx)#X!@X!^OH zg}{+v*THc!F>BXR>q>t#+I&8xrT78J z@AWuNnw!{rveq`5wl=#Js#r)+l}}L5ogGfMkX^$PzG?v%kPPy>ewmYhmviX0mALH* zf-9X>Hvpu|R}UOJum5qRn1{_@iMKQwd@}y5f~fsRlxba5#bCDN1;6oS)=js}4AdvS zYEXv3@zi}#P&e`@1{=REq2bODTcp_}q7AP9W&HD)((y62Jh(?`<7`Vj*UeU(&vd0B zY}GztyL3AyUfX;U3=F)W66l&a*93~>Msx+9^IEBlLY|FR4CyoI$8sLIPA7pFBRrDm zr7D!w%w9L1K3@sC5GI>2u(pXR>UFs4f^L|wqdu3ql+UY&UTd{(jW%7_YX1V6URjw& zp|~ba5&n5Ixz%47J&8lNm`QTM%$ygH6yfqL1%syC@+=+;3CZY1>~F6}cE?vo6Oul% zIyBw%YrzEJtEg=UZyvcilW`5D&1r}7rTq0N~!g_7`|XX5Fz+mK*4rb34M0O^^XDy_CDiC{0aycj?0UA zuvp*3PO7xpL2QMJ`+ncqQBzdG&CmHnT{3@8ob%19>5+=^*=mP6M5+&m=!|YV)4Scx zr_2JtWPKR$(M#RQCBwc3p_413-S4F#Cp+o6+9U^#>%ERH7s1;*Eh5}9GZ(wwiG)2~pL9E?5pJXnIanNBiAp;w0TGS- zrn#U@J4Ycq))NdFt=*q9irrnSoj$8up_>Jbu5LC@^-j0~q`o=5LiI}Qlt?JIGW?G{ zSjAi6)OZXz{r)78v=)6eihi`BJYO??rIr1gPh_rn6T0pMw)?ZcQ8WNuD=BsQt*fLp zN3-HSesU;XP_l;BXsUJ;w-r z)3KUedi<2xsf$*9S7}gjJdA`?4!h>R5ey+#!PKf#r_YVw5Ghcuj0-!h=vx)h*Oa9Q zFy?XD^>(S0nD3nz8m47j>3rrFZXlU%rlF(T?EKl|k-Ww&Kr8T0#aMCWXM=c+lb+yv zR{_qFp<3^Di_O~n`75=}oz=bZu8>_lV~czwQ>o|L$CX7?4OSvDq(NFA1EO}oewE#v zNeUkR1lp2pmuh!Mdrfvh=FdKBmXfA}-Sfe;WDACt$UEr(goSOXhXk=Ui5u@7x+&q; z!XHTrh_$VC@_rMqza4;8qJxJ~{P`Bh$65ZsQ&yk)X+b=dM1!*sZr+eMl>% zQeL3<9_Apycl)le*C*gld(zJc|lkh=m_}LRE(IRHQU7v)`7t zQopVS^wL#BEE`x1?R9>AXqo0SD(@5>lWt<8trC`vfXrP8DLQM%k?AJiLMs>e-Io~Q z(rmIt@&mqyw9I-9qvN7g+2=AHcHzaG9ohrae$Dq`Z$={3@SDDE2Nd(=8%soVnRGip z_NEqy(4za;uB48yh*wmBoVHWJE|TxO>Y{g~5Y|H&Kcl$9*OAFBeK5qZ3*bwfj&mWX zpSL5b18n}Ol}@mREjk)8Mu%Ji^g4^HkSwF`Uaao)mCm(Oj>N;^zDEPZHeZM2T1q0mq3^Fr=`DIsALT}>@lx0jSpzW)_2=MBRKah_ zv1Sz}#j6QZ<_Y^ob?nc2PrOJnCu#4uyMUOGqFh}8qh_(`B^F%5TlFc9W<8uY99>J_SH7go$a&WB1e3O=nq&UN6|@aCoKK8& z4VtFQ^_V5?qi>>(Ru`g}pxYNH(LO-n^}yT@`AtBB(cdO`8IQ}$Oo5#H&>8efBp*jA zSWiEA*74RngG={W7^bnm*^Ht>sR;R)_@?C-+l&U-`UMr4t|LO+SNZ5@ zTG+Nr`)_BgHSp3Ybv%U=YnFVE3hs1xCkDY|`u*;;br*tcG1~x?i`fn^C<3cPjG`t&h$)(J)3Kq4N3EwQNbO_bG(6E%$eq&QDJ*;TBZx!J{qkB6$KBYdwVsN^# zi(Xwj%$bSry>05sJr!n5>UrI|uAEJ&*QC0y{)jzw&JJT$^cCUd6SS0MBx(GqFfD{1 z0VqYaU|%=iybI$?5uO@Ix=fHrwoK-f*{?aA4go5Bf-Q!;Hd|zZSfw9*C8K#A<(xIJ zror2_RZr!hJLY+oGl{VWbTeHl1}#qtOWQ~O4q0v@ZsNiYKgkl4a@aU;xhBIZOs=^8 z0vv?8<6;BcqaFI*LvMuwd?kLHZ0oPil9;D%9ZAU6o1KAwNn!YJ*k6(2gxT#zG-UO}AnvJuVi?jibk3DNWH@(N0rRhxML%IJFlCO;yA=U6W!-o* za!{NG?osR_ahYp)ha#V3*4`LR8SdF>B-h34Je%&C?tt7WVX?^~ zn?oPM!@#M=G`r2uqB_giH#?Jh5^OloZKdro_AC^}+CyX!N^`4S!1+HhNE+@2_Ekqx zduh1HsPLgJIjW#65lYfoyG6)3L2xeSXCEtMM4~}9t~oC^0i&TcKO?m!O9;N46B_sL z`4sV>a*NiHusoUId{&3{h?;b@#CcprH;T$&_uL!cG>drhNX4?=lZVK&<@8lkEHjLW zUOgq_l26w0$8>y?He7zuqj1D{`~c6e=esgxz==>sZ0L~sS$d!JKIhW( z2Dm;A%APxS<-j5Fc0wR(Fs?x2!^G{kJMqf@$z@HpV7y;G&_-R3{Dz^u6U~lxyadv? z#O!>vhGE{j&HohFh&_zB(vIMpc{9I~Mciy>tt@sXG^u}ZS^QaZo~`;9Y4eWx%&zf$ z2q+TMJ83M%yR=|P77QHIk$@}V!4BxaZ*bt{cD+sf^9rS)ZA=#YW?PvqX@ov{L&GC_ zUo2SP(cz8mdi2^LpTT-6l3zYBtDiOwfl^*VWh($#mW;>g5xxv1(=Cz$WR45d0IiVM8 z((Rp1sq5H(&vPXw^K1}T@~d^Gg6WJP@K?Cw#4n=e5v=rF3?fA9gxKk7{{GszH$m^A zc&~WdHDEJ(#5r+4Px^CBX;&Nc+{aDl-0oj5@C5J*_Id>FZ|p@(RNATdHkiiEgQ}m+VK_E_;!IwuEI60_G%mp z8^X>iCe&Rs>86qc?7Dpc0&B64-{&e!fp}Clpw@l$s6$Re7%F_WM$Oe1=}d8|79^Q_ zN7qt0*}k_OK2k1<>_p=V8VYz}hsZV`A=$@0PwXO&3>{0a&paeWl6`W^enJNna$ zB?Rv{Ge?hDhAti!0SaylkH)pjJAQWc6yq^tLc3 zyCZ_3jPsd?*NsvW7THpQY`;in*!pP-9{`&=P=@DRpqWQek@j#uo2Tp4x3m3tac5AN z9k{F+Q*}S@F+?T%C&}3ZFS3Q;3Ec4oTEIqyx|`mE&eIrVh(VfX6e|w+dGn1ijVBWW z>b;vnr_RuEWEkZ(4%T& zXASERQPMjwC!6jK)%aT=TXfl)STI^RWSM@mKF&Bt(j0CZz!cT!!eA4%bpW~R5{~%} zAzPZ+aNXgy!f%3aGt6V#QN#RHvBZbayXLEWYSgjsx}E9yY(VC0(!fNG8ZoGfOH%?S zTZeK@BFZf2EXYTntkZpWMKy+Oe2>oquL1W~p$F6ty_9>2{*IA=y7~H04H3JY0|e0g~N^q!H(Y)CP@397^DuGyH96|>ct4a`c|)=A~fA4hahaLu+wGd z#PcN$JU3jj-2(gmB#f1u-M3g{K=BznFVb$5eFiT*;y!2|bP9QnI`+D3E196zHv9??ZAJM_3X_C=KY*sLR7WUw zU-@iuAP?=cTD`%a8h$NsSt3zVQ;enkld=UNS=OS(ZCHiERGSH56sGHTk6ifpZrrPX&XUKe)rGjI|%k6^u9@Y+Y$=GL`Jin}Z+PNN%`xTabe#Pog&H zXSC;W4rdRXYbDQ2K*Y{dQ2}J>A46T2=Ni`sf@f_r=As(b>jLT!mq+t8Ye!eqSm$`} zm-9Q>WCt%M7lOEhm56(M}*9om`zcbsNfZ0*RtP*+`)2?8v>?6V_t2)H}{{gCEEM_Pj_UKxv*-NQlt zWw9tR1K=~^aC5lJawEr#4+J%UQgkEpM}trLQEcvZy-tk-Z^iV;-5T2@ zmjLV~?r^&$*WbG!2kXEq#pat+u&@5gWXXzTVc)KZBUV>7cIDZSCR0TSyi6ZmZrYS` zznjJpzOjAooOngd)86!|B+qT0Xw6}{3TEuQo0_G+OYV@IpAC4A2aLWl)ys2N>W45b zE71G2os~7cLIrfSVxuKN)R%KIRizqanq5gXlDt+g_B_rLtNMVf8^^PIaYIIp=5=j8 z2JUV8!TU?cT>3wj!_q!twiB5yZlIRa5C(=qKur6n?{$`vsb{Wu z71G-_xRc+aR{Zo+nm}PlC@1On2)3fx_ZpH)Nhs#}jk1lSB*dg^m7ImM^!1cal>VZ? zsZf@{yQ?7Eiajej{jpmlU#Z;5B=7(d)_0iJQ7Z$Sb!q$=HNmcfO~QnNLyR|vbnO}p zu&;u@$QHCHdsVy}~{_eZU>s`ZHO7QCBy zT18HI++E+Zm$5+dgV~9oJc#k@O+%Dzvh_RZ;b6hd`sL!K;{cyCq|`lT4{hSt0Rn7degNMiRPYj?2wid%kkiN6ak#rZ}p z4hDt|FqfQXup;NDajg^u_Lzg*VA^DF>yDa=7$`B`@Kv#1gS|Tn)hzI?QfRC2vE-T< z0T$oA{klf_JARa7q>A@Jn-_~3E<~ePvUssh(MISOaS40Oix~q(yM()LOSPfVr&vsN zKe-zQ#%xMcruS^T6}IqJ-%vJXl(jEs4S+)6~l#0Nad(6X;%k#03| zo6EF^1NI!KwE=!(1NkdQi1~B|+S|LDmn5s*qYsAgpVhp9ds_xBOIS078#69V#2u=Q z>nGsP;2&(6_6vufA2Lwq(Y%bH#JXXL4<TYS2 zv7}4gp;?9<&`gz*<&KY=e!jSe@2aos-Yr}_nuKnWIR(hLV?zdNpC^NeoUK;*BM;jL zrO>h;^bQ!@nhvIY`HqC+ulS>k4+O>wsyRop;#LS3o0pWwqjL+0+>a~7<1OpF))UtT z*UltGF=L3&%%EI^S6SoQ^~6h`82W-@y}7DLmxsnHz-31w_~4>IDK!;PcF}aZ6{wO7 z>i{#Xb40VF5a$A~<`E|+Q;>wBR&Lqmem2XXtVpW{3R8Ha6eA`akspE2OEtOApX{#h z?>$@YJz$(^4fXIb>95+bY|$&X>%bCK??E7CSFKRI@-{4&Em)9i9~6zVOY5^>MRQwK z-68>Gtodwp>wA(MofSgdt%Y6Qa)nh3X+)`l%}c4Qu~X}AsX=aZ=vQt^f$k%u=U9d7 zLh|MO7?vBwN2TJc;x*H~DP z22&C!A1j)@d29&!LlOaVMs6M(=lPDKwJ<8cz+!-C266;xFa(L znE7zkl7cTGxCC9goC%bP?(GlE(wQtSwdvS6m_kdTU!rCV^}Y_6a||OHhp8nVFl+`h z>0kHPZ=}xHq~G)I_M6~N(4@)z*qf35{dPnuFEcnuHrsTJg&qP{XL2_#lZnnKyvAdV z_YPb&kgu8xGTj&>ks*ueE{J#x%)CW260ZvPhEe!l^Wl)a;Mc)la+MD^=w67!^B8i_ zwMCyXaA`qzyH0b4#l&Nj(kq5EoP&uBzbg`7)ZIHi7UmE0M=5K1>>n!O*etsuX)`D7 znz}S2J}2T|Pf`Ueh{@TgeJnn-h4xtj(xO{jg$~VMf9(oH;GKs2lJrvCC)yQ2hteVz zogbsqkoDs-*Mltx*$g7xe4^%lgzqpAHt?(dLcU@bPFPZ>A&?;<*>$E6tM$NpC^*u1 zVHRO=rmLPl-Hvko#T1DuSjNn4N+8%4+XZx)I1NDf*OXX(NwwXL*VU3R_v^C-omRj} zAo9@{9pF(dZxfHT%2gsf7 zx}mvK$aW@w7mAU-X0X_#8y7AFgx(yvz;707S#|);5+=}EK-6BsUgPA%k@>w>c$@gm+KWw9 zzZor#urz4xEBCa7Y_hqa=3#r}RL$PQ#TC932@)N!5ynZ>Xr|D%)pXW;OM|bY!!@E9^OSgqSzP*$Ne6qPu z^v0V&FM{eaNTV#US>W};lhAL67otX$vO2m@?b40X80cFrOeS9CGEN!-W$W^Nz?he5Y*ZfAQVs^BtxgmV6Xt*j`;@~12ExJ#j zPwJ6>IS`mNAKzKgyZqa2ACaQ6S^;ahru9vFwc77kXAL=MsH3b}y5i>rWk7yxm^xq_ zPAhw-TLjQpFR|uge*H_^5yXxcU98gpn%1KW_Q6bRB@>RCr#yGH*j+J-UNJUiJYYF& zsgBhymNH}Sa9R@fw=YG1w zCs!670M}@qE|y#u^nu=Q;#h6D)=WRjWZU&Q_QG5YF;AZr;=z2lXRwBhrN!fN#qPxh zx+mobp+7wFxA7$iA(WoQ&!C>@+=HP(iZZrfS*r!$knPi>n4PCiZsBvN2jLhxQy^Ct z)LW;U$X=yGa7uVb;@g)u_(yBN%o1b6GQEq_jHORa?!XYQ)|vM$(p|)GDg>s=fD?qV}v6I|yR0ASA*s{an}e{eC{z zb=}wf{oTLg{_hvZfy9|O&v(uo=b1d;uh#=HvtGF$-g?WC-f9S8Pg|B|tSYyy(^-pv z-Z29hhCa1SipzkmDRZ7SzWUNrGh=oxnh_XU=RoXN4E3!KM-hNb*Qo26beYPRMRU}F zE#3a$ydBIsx5eS~fyX`%x`DEsYooc%quYh7t`s(>a2Z8VM+o!kLV&KH3Lrkoy(O>6 z2_tD|`f*vTYV9jO0un@bZ0AJN97vo4Cut7`F~cN2``A1ob2|81U|e$XfSW|R0)0fc z%fldi^eP1JBiMf!yiSCqfrl6}fTyBHBz~|TxM+$oEV$WMt6M)VyEx}&Ows4uYNxJ! z-#E#aMLgGCbN=#ikBT4LJFu{Z7kii-YP@HMHtJMja(SQUx+d;jsSsa3d;-jPXPYzw zx>R@Avg2WOF-+ESaDf6yNr(~30ZUTKOegGgL5cl7FSfGj9dGpLKeUWTRP&kQDDEFp zU6(mf0!IH!M*<9sFJgDZ$iHp+vc7X1<93v&_rsYVE>QZo#4d+vwvZ*JM z0S{U%uM%O>Khn{XJ>K6==&Yw@M!t#Zq9mg}J&n}opG^g${sT}%Uzia`Wqgzx~soBak%@&tZETwqC^7<{11M9gg zPJ)ANOL5%n8Hho?)I5IA+(ml49R>h$_TtPK@HB12af`_Ect}{3o&G`PvUu}C2ZedzQMVpnCs9d(B!W433 z^LpGUrthT>T=JR>x{{WvdYv`D-crPahY8Z>Re=~jy#9t**17gt#Fw|0J7zM*)x~!PfU=#0}RW2XP);b{EPgiIJRMFbak@tFwCjj(m;5Igr-4Y zC$3s^L{Jzs+$N8SZ_#v27_|Z?pxS!jRV1iX_Izc; zEFP*Ab-ngA&D5>M?Hbc&5MN0nI?Tsoj$YvC$hlF>kQ7utXgRQrdbn@^$`sf~a9|or zOwzRCDYt`1EU`x(L)+I9JEgj-kno;1V9+e!lE%qrfvBtD{zl|4;ambtv{0?q1wH;^ z*Lj2VR19Onq)@e>a;%TzEKWrqK%Py9t7t6}|_2hCE>OVCmz^jcJ91>Xs@o6Wj%5fR%-}?9yUp)wFdL_{J8h1xcR*N9yHW_ZQ8u75iqI6FJEJD z8rWAbP#1*y*us8x8k4OKs}E_;AX=*{dI`gt74nifxz7_6M!%{Co!3S?@$6%Ot#29N zjWD9Flb-VY#r!qVNUsLRG|S|jpfj(A5~nix4N0g=`~=x`*oUM_o?)ZdLT;cXv}LJ0@?4A~~IL*14%gUsaOP zKT7G~4ENdI;ZdhVMf7%in@i^}E5DcH13j2wkVYnNMv#T%B!g2O%rQS%FB*D3-DL-S zWN3i}qyK zt}hL_kY$^-N?AoEfXcPR%v11=8Z|e-^}%2B&6QJhT~cJqhbq3Iszm!>)g;SqZ%l?Q zm!a0?1hBXc?(O zr2W<7VOGrOkh+!idL&8wt6SX0(IvtP8ID02%81fCrHfxg5pKK3?KSd6{lc7nBUmq9 z(UGqQ($C>|rb7=GGJ8FjiC<;pvQx4et!C)c27b?v811a4MeoY=qqh3xeq2TmhkAMy z?IlA~!u|ET>s=CI!nLm%QdNJ_Mph3J1=JJEg+}eaPRa|A(dET;p&R?G+h22#COked zT#}pF33CwMn_b!0drDUztXwnDW{#V_G+!&MIx?Dfr9oGgf7FuworGVW@~GM+Q+#*B zEk!tv8S zT?OO-qi+F&sN~EC0X#Dhd=ju&{J2f;30~lP-gczKB+CCr@5hn)s-ZO${Zfjx*xtsE z;X@4Ej}r|a4U9tIh)hfMRIviSMFOPoVi0ns?dw1g-+p70wL>LrJTU<&CCxx$z(P^_^q;3O3n03*-D1J5i zF1Q0|J(Sd@DoJHS3k!)FKC+jG$Ku6YEZeSm!Jb^ucl zE0`~wKqCAemtQopu1CX?j_B!$x_obpad8hg-0!jTT}bv?yO{5(g)R$1@ki7d%aiet z?;86+?xo)ucdn1`-^tv?8t*Xh=)txb&>vw2p|Vem7RN0aYq^-J_H`KKJSHDW$v`C7 zfBI|46l=?-0&UI=1T89e23=TRx5->}<#8)AY=5AeHK8J`c%kUvPw?AoyDrn2mlfebc@5zrUIa48w zi=!Ab{1xR*vU=rloYbx>CNt7-EVJtMA_Cda!LdA&?N;&uAvt_nW9!(u`!22LvJW%q zd-Y+OGrgTjapAV#-bgGmVEr30va{)9W0)5e5G1=4NQ$}1Nyb<~5L70n04huFp~7%S zkyMtkihV?xffzbyuLCi%0s-G{XB*vCN(z$mjD_60Z`tqALpZ5oE;?Kj-yIeB_~0{4 z-Nrn~iL0CHhj58@MUsrcs6p!5N@EB7{r+teN_U11seZ(BZ8CU>Tj6MHy~Q*>-9Oz50Ux<9X-?HY=Qdssz6e;!P=>691OSfcdHZea zj;={R0fvK>G$AIV)3)(`;8sJxPttSg5hMO`3;j^?=V#9Neoyeb$A)+SmHfBeS|5C6q$l@gJ+y(!ZIxisFOo7 zW8Cv;9xE@AdnZ6iH}KokBE6X4;YTf~qc(f-)jHaJZ9zG;QpVFez6PHgDA$P=RhOiv z-snoR-&-WE;%zm1+CyHw;wxAlxKk|lOfEyKj4oX0hD&DOC4rp(42hD7evbsr5P+!c z{`{kc{%-H`UkmKMg-fVX7$Wqtv61FQ!aAs3fN+>H)fxqmw!KulDIU^hR$5!UyS8_@2|r9ioRg$4r)wh8(xkLyuExr2xFL23cuv@aCe|-O`oao=7p69HR=vIf8EC1 zVu}S}qvB=x*W|c2n{U<=M?(Z!<~ah=c|>6BYkOa}>t9?_J(|2A2wo)k{yC3{;=gzt z#Z~mJfABbJzj+*U9w$>Slt9d-EzJC(ml@ZH*}Z?_aehJKfAcs2|IFjW_|+@@iN_iE z6OVJ_-|#q|kU#S{m;YNH#}$I(aZLZg;~3z09PPLh?B98ujKA_Ymw(}L;{V4yPR<`Z z&cN|S<==Unwm z&H(gSOHsv2YiLR}$$_m^-ua2b2(K{Em9wF5c!Qx#> zb*BswO{HS4sx6R_vjDf3!cW6cRbok3xoyL2>&*<+7`*k+g|}^A%*r{;-I`0!si;p= zTpE7ij^(Vg8fkRpl;`s1XMSEa0%}$}9D`SL&c`ESV9)Y%b3~GP-N=!KFxWjYVcaui zD^?bw=UQ3Y%5@Ck@r|1fv2arB&ZHP8QvqKTycYPheQA1h!I-3rcu}-HIJbh%MON~i z0&&wZt$kixV&H9Wq)%|QNpR}(5`sJZ7%6|yyL8hxM~g0Pd{!T&rXlvc*3}6xYtWP# z(s~lr^zuOD$Qo+sH+XPAZc|c^V1(2t9o<~KF{2yFbasL%jm+^bUyD|R9N@|Q4N$9p?7*B&+zFuN~OuSY?nl!CZhJ($^DAX9qaH6 z+0}uFuvTx<_FOvu(l+Nfv#^3}dofy1!`e}fj+)#F$|fj(z_|a_58_;Rz!yt!f>HkI zU4%PPLlp^F#wL;}NgMYqk`JJO4Lq~tC%{Q@8l&fv$}qdrO@<$!hi#6`^%sa~o>5Tf9FWD?_^J$BnY=~dw zjVk)dl#xV!sewdq=36k|lJ8*)G(T=nQ05!cy)q5l9qFlsK)!JC;0&&=BCPM5Mctn>KM6FGX_A^9nEPqAvB(KCc-e(19@0Re zc(w?jxmG4qlTkxRqsIM&Iy=H9hJMQ4$srn0tmKM56oAr z+$O5Tl+GrFAu#5Vsw2tLC@%h=F%@iE{jv zUemdRxfdm+Vw!yBcl_ibV6Zx`_}k5x1SJqS!>zMJI3p@7ZN~;Hq3FRZ%ilzZ@sydJ z2}katQ7YCKQTzu6vD<=AuV(zIj#Q|Q1@qr~{zPk&HEj2MS!O=Z(df8Y0DlfMMTUpUK_%d4R>()T39q2NwU*;Gb?N;3-?D?{8w)U%03RWKs zE&_$l@c#71l`!zlHZEYNt-ki$7w!H8!#7J{K6N+o{yLM|s_&d;QGSlUCH_+)_B&$k zj8|~3K!klzz=#zpT?Iz$|eOP#!k9;Qj_o&?u~PiW6RR~aq2?Nx{qkxsqzPFhsZ6iALCBC zbKKFg_&>#Cw=f?D$Cl7D74g8ESgw5H!?drPBxBlf`(^{b%ep!}LWv4x!3-4~u*&yORi31h>uZd=mDb!VWR4m|?dwmHl53HWRZp$Dei zf{^H^UOc~CWM`0b4|X1a^PV}ahr~eCrhJm-cr{^&jkYK7=d?Z_(oCHKjhsdf8=uQ* z0xM0;>5oSGhd*8*{kEv=OKhof;Zu3&7h__vbaAleG7rF50=yBM1UV=b{95UL`s>ruA=~2z!w+ zp&fFr0>b+PBTsI*Y1c%KM6HwzvBP~@4;x`;i?+b+$f?{V=*Di%MeP)0#Z-4}A#&_QYDE|S6hPxMdUtp;iz$W|_Zxko_XuBHM`&DYLR zH+}j}SgWXTXYH4jHK-mPl*>)?2N<-$2LZqU3+PcG+6@E}$Fv<_Sldw_Lsq2Ujzc&` zPp`DouXMk|KAlyKVtCq7!QEsJ?e^cu_#m1e(@?u;x-d;tGp?7V#uh26_`V$<`S`nmKH2e z2sAi@R-PAZjN^>~oDlnO!O4}a1mTFNk5f8@bD>8k1|dvDvQOsFDzSwt{(^P${g=_W zUOE7^EiQ2Bj=VDHSk_6`3icTb=IpOb!HWMH-|q!o4X|V|DVk7^gsLr+trpXNeUYYr zsxpRv+`D)_iw4ki?)OmltWS@Ge&17YbIza~$qG<9P!NintaMP!iN!L5sMc=tAT98 z(NXbfk;qlD+?Fc;8qlI*3(?(J zV67TE{zA<<>3jCwx9Z+ZyUJC4cgLEa5XM~RH0XnW?oR8MMOL8}voRmyG@e{6S-017 zj7WT|_5tnkvR7qb0+x?^e>7V7bfmLYaiist35V_MN#4%xm#vwY#PC+Xgzxp;f8lXf zX9!ksJkBYz9m-ktq;*dE7E|1B9>?~-cpQU&=5e%R+lwRD>AvsxLouZb{&37E~ZtgGiT3ef&GW?#d8?5Cw#cdP}D#|c>)$MHDfI39;`Hr(aKA3VoyJ`GfV@6upABpCXC5ab<~Z)}JWlR>=YR7!#Q)9X49s2_*`YL9{>lEdStf6mdKb+E`Vd9y57A z>7`<$9KDwvi+^;@NyWwn>lBJ0cwOPye(^KZu&+(tE5EY6SVMk!10TRdslIo4e53Fj zIs6lo(W6-70c0i!FMp9<#C3U z%;ig~Qv?gL{8K89&jqXZ2bzq@M*s$fzf?tXxRz-+;fN&MPJ^9KEhfh9-_;A~n>tIm z1EEHP-~u5R{vGBtwj-}^$bd`SM(`*a#1@H7FtUZGU4PYu0IzPg=1%f8}Q!TCeMU4^P1`n|jFOaXM z$V&5Jn+5w5hYSe-;}j^);VXh&F9gM)Th9261E6`ciaXi|822+d&o}-3n+k#6Kp?4KwT_{#+_@$BEiO=iP{RWdC+JsE`wKXY3!Vggm|XS@phflB-^l|kcq zQBK;pSEv!Kg?21L28~`_jsb$(hR=X#GdLdl94X_Hu`%DeFh=Y|A7xbJVPAg&rp!Il z0PgUORC_1PadeHaL5lil()*J80||Frt<9YLRpO z+WPg=*tD4au5(OokLr31;`5||iKNT53~1Mkan8c*#aa-~39K4}gpIxbU(CxcrQ zcBY@%jtcV;w2r&074mLmUA@FZ zNoa<_NO6I_xW$24mXg=GxZRl#NJ7a!U@-`|1aq{b2)r<(>n1w)g7Skz zp8dYzk(wk!Wc z^OHBe$B`UWbBSFd^Pm&lDFz>~7J$WyJ>g*2vCGn$!8<@c0_+bbJ*y#dqW~ zu6$k(O1m(rHohDx{Q~Cs3NVpNEFILh8Xzl|3|Pn-u||$s*M4A^9b_X@;w_9h@9er9Z2X!-8O3nN}T}XS!UP${XUNSU2 zxlHrXx=iz^yO2h{yO{F{5vr4)OH8v+9PS?;B%MBKoROTF@eTxen|Y6xkpV)hhJZLg5aa&%VXa8 zh;QWKymLO>Xs_v0Z6wuUrDU+edU$fI{_;w&qQGEq315zzZAP|Rh%y$L_?=d{gW46C z&d+$x7SONDWSL&&XrI1u%0FJoOrglZqUxzBLeck9T`5>G9;xTSo#v3sKQ2_x6H|n$)zANF9qVsOjJrSCvvH6WvUL=Ll44H<5{ zt*RLREaf?~=6LelP`^jD@Y5NUN9uM)wI#dJou|+otbg`(n2Gm58Xo}+!u&*}BH_8H zNn&^M5a3GZsppXQVA2Fp$GdxWnmJc2=p5rYXLIP)Vsp9DmF72L^w$J+L1azi2Tq=} zUaYH$;{rEIVr1k~H`5JBqy5R)t0Tnu%*IvnbDC^`RSBV~#A1A8W&l)-o zlzQnicJG*aH@kFcJ2TDGu<6O%qsV79B=`B3$11W1ki?d0`=;d|K_J8=n+ zq6)JQ*8&lcuKkd+c9eD+1$utTxZ`(?hoN(tbQVt%^UFWntjmx<0vc8e(WJ{XrJs-f z1s~nCM$?JRJTbAAUf)R&DZC+78l>;P!TdZRHGM*dOW`WPohpT8-yogvmF(#TE163( zcstN!Z=uBwE=#v(28O0>Pn9*oPfsW7OO1gJbA00EhKo%l)^;Acmb`xBU+Cx_Kvg=# zCnl$OjkRRKizglky;u5!{7g>1I=bC2((DS^^!vspQY$Btl0$5ahYkCt)EpBZ&LV^I z7>w_p4jEDN^PHdq$jjHN73#JG;CCIO#MuZ~?!GtrMojMUz{umlMCeeJuil*xIv%e^ zXfnoxzuL~YtEx})lVwATr#-ES^;iKe=_v8 z96d5x&RyE|)NLwfH~ygSSH+f8&HQWUNQ*Ud6TevPMa@=c9gWi`y>}^A5WcpErh*^F zh?<PPYs`B|evI$m2T^s)8=_fb+rU-y_l(ha35rtorok?NsNeJVUO zX2XI_36ndI9m5{kZ+&4%?lI#0LDdoVnS7GwHe2JX(Ros*xK3HA_TBEV&}bR8D#bc# zPq_*TnR_oLpY3VA;56Oi=%h;Zyb+JkO3-=a$8NgC0hDI;$a?6bnj)q9Y>}VY&f|j7ls0ao&MHoV=Zs) zXAL!nLk1NM=dzS6hUrdN*2x!gB`txTNfr);&~Q&1tDgo5UVbuc9xH5wRzH&oD~7)@ zhT+3CdJmse%jlBlg+r?PxT8OGJ)2!Jxvis{MpH|ND?(S-1h%BAD1G)AkI^tX^m$Qa zp8Qn!jkxTBM(x)Jb9VY}BfT$^H$QI#{IsT=8@t=HWv!ni=?QG5GDYQ!n+!eR#Z5x8 zoUVg9hWo-}*)7}#L+#^*^_z9lD7sYFCHGzI+sVeV1?@$pZlv9 z&95F|9ro~4!^+8P(Jd7tX3MoMpKkgazCF!T7IyY4DSU869tZFUo28{qNq*ZRu_<%< zq~J#T9dVYg%oQ@Un#%Nfh40f7c1(f{J}Igu*jLo-;OJ=aS1G^3Xd*v^?;HZvP4L7_ z-pVxi&Xjte>PF6z-H#CHc)fCawboSPy75~zKBl7Dp|~kr?+)#c-Zh+#apH{iD|I(1 zv1@Na&fb+0a7%Xhyc!MX`*`o%&&ILvn+d7cN3RAEm!!3bcS@(kCLOK?4otSIBM(-+BeR@Qho;gu1Rcj+g&X30{}U2heDL2y3aid!j%YwbDI)k?`1sPxL@r6LcAT z^jGs?Fr;5@bZ!_&w(^WS_7Lk(+UH5l-Ox`uTpZsRz%s4%+>^s{+0Pmu3 z!EHB(v}wXeAn45nb19y^OPh$sdy6%?KdH-=ucPG6UbxZgtu++C@P1?<>6IOh@RS)= zGeJaRfgyPW_#fm5izuJffU0gRuVUEas%othLTH>PgLk(xP6 zRnkTuCba)l^1(yxwLY6~eLDU+%3uFe@Z1B^{0A(g05_*SCQJe zO$C$22zcE_HrrHM1An|>ot;=F=SsnT_I9YsiW_~QTLeUX@?)C09H-Z>;N=U@k9{H8 z3)T?0X{k7KpafZqA(gaPDn!E6$j4)($)S?pr7Mou=U0`0>DSQIn(5mz0bOQIg!z?g zoZ0vk4n1@$C%&dXm!7u;JY!nimzQDr{v?aE?CxSg)eKv$hJS*)oo{bkI=pZAVB3mR zSHGFlTJ#q3129O4m}B*;DOE46gw=D<;+@7fZy&~plG=u(W{5u;rzA9lo+Os{Q42lt zc%%h3n)TWqvEc_bF<*s@7Nnlvi8YEyvevtUwr$qdO^-3cLF0J$if!3SHc;lV#&?*{nl}##e4gKBrH14j+#|1nwx|tf<}vD%JQirNk~pe8T^J?N{)|a z(7}gXmalV^KbE_x=U#t3kaYeLE=-I>A_-QKVx+gFHl*y_Goj^LGUEt$lK1E)H7MV7 zx(oBY2M^fSf0oD$e~Pqfr!(rf)G_-KXWn;;%q^UjX#oc8N#*FDHWt!*ppv5pS@2yK0UI)a`^-Q$Vo{d~+rGM)bzIXq1JywC0UeJfXp*M{wr2;gV`XMD zFRN#Z=0GymZ`)^<7nyaj>4Wi~+nYGp@;!GZp5-=OlmXWpF0o_PJeD7WluuKLuF#)W zP5tn%ikX18lIj;|`xhU7eSq|(#LUS}=ihlS6i>T5Cgm=S29DBlI z)6%T6SDynL-}t)x^r}nm)!T@c?WWC68x05?>d@?QKAW`j#r>K?^cqfRm;E6>oj>0D zlIlpHRP&PZ;NqTx)H&j2c&~gb2iDWnzVZC9N-`Q6WiE0!+h0{>2pSNm@79g2lfyx4nfUS0F! zrz-~iEMaNmC_fk4$5;s0kopA@4Y}B&?{O);>eQ42BO(+EOZW5-HV?9p?J}q`fvCH= zp5V)21eK0Sg8Vpe>qNVFqS`aLe)aQBy z>zbXw_tV+U6l?sjn;^|;QuZU2ZRhb>#P$3M{Zm?3@RHB_)_0HJFuoDWj6xzCUn^6@ zbJ)pG*^heGy~2JYn((o0t-H}c`Z$iLrgURJ_XbdWZ6LJSTnlzvp|mUAG^Swo*Y@P8#qk!pU9lAafa?`PNZqfy;1~e7(^q1J3u)CcWggTe4*Yb z#phDJQ0)l0Xt4s}89C92TAtA9{Z-ka< z8M*?-GQP|5GwuF@;Ra0f0z`QsbQp=Q_2&*MNF#G~65OE!BEiw^@1 zrPkwp%pcTUQjE$6$--POM(OC~38{xGn{bnO(OCiq)Y2MHmm?OhnG~fm*C>rrD zXhy&f-K&~TW&Q~;_B2+wckZU#9mg=k_mDQO^+x`q$Q~+`8w&!sc+z!5$4hyBsy_yk zSp*Iq`cewUjjEqBv=*nvRV7>=*NuhbY~!gWlBt;i8Kn#8v+5^3SLa?dUIQBEN4~+t zc$C}473#pAClEb>JxiGN5P80z{o)+Ycv=^6tf=vnX%B~l8jl2W1^7HTPb_e;k4d9C zuc33AkQ5yp_K-eo-JE*^o)BBQ_qHs~;G`(AN^vLPiS~)CF^?@%HI-I$ULl8egkV|f z&7r;&oAw^d+#m)WlY(28Bpr~3udJG8cUzcPn3{iu$^_m-^E3%;JN#OVOnq6_>9Hpp z+Kmd4Wgy3_dfhzTDWnV2g_56saj^V=U^{5J0k(|pkQ>R#sE9qKNa?oTWUAoXqM2y@ zZu(edj)bP=bVq$6<9p@uIJXy{4VgRUrLgmN-o#8|q#G$iH;;&n`PaKO83bnYo8^g07VCZ{B*6 z#WJpe@1%!*OG9dov3x5ttxSRR>u|t>zM8w99{nTkI&f|0vxGuyit0izZ+cM0^{GB`03gFd9!Pqe z3Z>K|N-ZDqcEuIfm`==1TZ{ISat!w-s9wARu8b?q}wvK;=e+3ETih3_o=Cw(Y99CFs-P#V9J&JKW&^(g&5NT%%u8ZBW+P#-av+RS$6T$M03w?Um|bg`oDq`(jcU$)qUtvo6l#fHZVim z7U>vWMxLr-;-T~Dxisne!L1jQR84q^&AFYv9EFzJtNqtOOz39nXY@yY@YyHLlh`rI zi599e2J?BO+XEE78$aqsd{eX^-g><~lTO`wkLiV__g5HkS716uZ5}l}kp2hK`6mFdj(Bx-zuak!v9gaH zt7-xwhF$)Bwap{0f}9HT2@Uf_ zpoL!*MmDHmL$Er+(`1u&H4041!nOLRcY0dim!}0opzj&taFr}xQve9WKCmS1bv)I4OG$!~Gq|~=LyCgOonOs@Bp_Up1*EmG{>4f;N zyUv=f3!-Q@sIJ8f;NzwID7=;$wcv0KL!YTk&_wt6RCtNZ?^pVhlFfI^;dCfFaet-* zmL<#4`FAgC1l${8MN(%xP~4@9yccXq@;O1SyNuIHBh^SmpUP(JH0c1a^d_?dsa>WRdx;is3##9_TzLstnDS*TZP7`dh;zqpBvmeFmav{QJKh zzL_zy^x+a_(k8v1TE)EEW5GQUujGf1{=ybho*Jn zE#{@;Xi_4gXi~zzRaS14xQ*Pgas1{s*x;|6PzgdC!OJ!W8$4)*r__0br!-98mL&NN zHdt~CJ7)-W*k+7bl%#ijo_l9NDP(EAEC`?Ki1{U!L{tA#%3)sS6Acg1Fzo>Gw_nDi z7Q8$KLdNXg)M^|;IlvYB9J{waYzK>l-U0{1)`j7D7uDTgUfaxn^PjT)_TzJxB!hAw zsW1yXiJTGA*I+>}y!Fg+UMR&0*SVyg^QsaLyh5WFrySH*Gz@eGM*Qc1m7BB6h>JkWWOQ zBjncSmUielt$X=^DEnDU_gU(BcLV)+@1dZj8J62hBW@KOoiOGvT4=6X# zy;Pb-1;3YW4Q1V>@PhNZs_9sex4NrFVgz5RWxk$3UG8ojnEZ-_ub}>9S8Q@hfYj&3 z7+!2?v-hvC70J_}hz^eX&xFUg2H1!@(u$6rd~ZnmaQ_be+2781 zZs!aj;(Vo`i@UDtIJ{q3h1&TTU# zk`N-2_?_uWh~X9%!DXNSoAH6BNebagriLg2FhnPVg}< zoHQj8crf>TTJM$dK{mi5I>HLQ?PJ-Zfys!^~7zq@c%-P`y-wE zYYh6^l>5hq{pT3;_a5}eeg7-3GmRZ^?tFMx2^G-AJFa8}mvUk;dj6{Dh1qdd6ZYC2 zOR7Tr;KL5EIiq?qeqUHll7G+Za7)az{aTid?x~XVE!h!TJvq4j}Q~ zf^KTz5BH{K;1gTzF?sJ-WbXs^^`~W?kHbm0%+eI|j|o#oEU6kyO0eCxYeT<0#-|8< zb3C|9oN+h&(OjL-M1pz!QzZ1T(m0q~a7do@^UoRg_fgyAs=6UvnLwfl@`#SH4#^n;ysqZ^*;P@*|{efADeXK(?NII zsB)4+RM;a+j& zL9=W<3)-+xW-W>of_uaH;TC?u-VR`~VQp%*@kaqO)9g-tjwfFjZu}%RKDl^?3=YqCder?En{rRWuUvR zgfFhb2uj#J(GU&mhVTIo3e+i*pxAdX91x_WTYUzPSZ`$#ahR zX#Gex1QYWjpU78fOzOmD%&)-t;Z0W@_F_=hsa*%YxV`tjAB~y# zxKuy1J)M?$YrLnwuD)gsLSuw*N<(3<&w~SUe6JA3pYSBs6Ykq#ps(UZEoPJuz)S`s zaChJ-FQ^Y1IWLtSYwUt(;1YEH*WmCqVI^HIZn(*)FqBRJ1|?(!KZ}^M`aOi zX18bcVqPW;mrjfwPoxpB%c8`8U0~7!l2wufrD~PDk z`e9#E*)SBZp#ddTk+V(ECr_)ayIAg$Sm;67cGZCddL+%=^pO}ZyE*BC>3_i#=S6{` zSToWuSXsVA3q>@Ehm~>`606&t8Dilp>F^-b$%m!`eH$F>pXJDsJd9|#-x*)u(HB1g~TG_ z5T}fqgR_%d_}?mny~P*)&R2ef$5n`8(xl%_Q1NZ>No(tMI3B6sQ2ZZ}-b{^il44xn$GHH-e!bM7b8E8g|m>a`5 zS~DZWZ^iFa+9hPoF4@S5a|L7>cOAElkWV+(78N#S;N7jAfe=SJTxQVibG0cRv2SIR z%h2X0^lSy!zV_2v>iwX7dY~d)SzD?&s&RL`Qk`y+Dv2h%U27S$$XNSP;#84LSKq({ zz&#;y(Y%{a+%N18d~RJfW}K-m9S*&&okl6?vz}w6DwU~gDeT>ws=hv3+4O$5=RD8y z4TX&&rG6t4NzzUox+;@j;lVz<{S9Lw}g6ZFV%-rD8Gh$b*QA5{3XXQ zWwnRx^}~(gbwukpW>i6=G{$_;=IX2Kh%wKc8%twv17PAKJ;{kaT|Ew6_oOQGXY+WC z4CN79zQeS29RE&n4Ehwbf}3Qe*mV{|OP&;ymrcGdlu}MP~Y>wHxQDfJT~% zqJoltv6_DvxVYr;x(wqk1{6_g?h(9Ey|P#C5hcotYnoE#hJt+1H_i z*&~Q%qb2nXHrv-wlW>=DE&LF@S84l#a-)B7l+>JmxD=)T(uqUNvG-H_kQ^U&^pVI3 z$5UgEKDzbRN*+5adx`!Ax{2=7_0!lN({Re(OZ?o3(+q{R5c(^~iNNP)!pSmFL@UVnj$AibR(&aNyjWo4?xpizzAC$Em)N@v<ZV`cV4_>N z1vTv^5`mlGu=Lextntg+#A~7%)$HvEhD?zV2MioX-Vm@2uVrn9P|dY>Jk*%`5k)zgS3>a%utJhJ7f?RbfV17LOLqTm}3$wrLVq@)iFal319u0aUuj_dXC)XYjj~DN!xS1 zxEZ4dA)m7^qng(Uv$$L?X9crYR)7z2UGG0_vb`OqUM{zucv*YKc2-CC@4UtN3SU?AqB; zxx~f3GCF?zu{Lx$8p3cVE`7gO8!9`-#SZsH`A?R5n-^r``PFYgdXP=p46k-h`~3IT z&I`MCaYsSF+D7S+bWu@Jea%|w^J17iz-p?0DA!&oL#41^a-3G~$wbD}HuWf@&G23X z;!_Z2pc*nA%##szph==vj-pE%L~>a-Rks}A*;r14 zN8E0Bfa_RM>Mob)8f;JleB4$sH>OYlF<0kZuV`%&CG z?%vu!XR6Niv|stdI}tRJ)VGmgxF>04DqPfA%m&oFcbQAY^v?9Zy!zZ&_*Qpza4q#G z%d(&y2oezW+9hpmh2UeB5+-41*2nTt(cYy|Yhp~ilIJ{0{P-fcW}1?-N8xH=vTZOT zwqHsZlErgW4sPq9Iw0D<`QmJsm1xZARICZyw!fwqQK?8Og(nm%f2)$kcxG}ZkCaQz zs{JM?XKSbEziS}GlOhv(l>K9VWSo$bp&o^r?B^u}0gfjdyM4t~YLjbS-1YH|rPyp8y;mflPX)n%*)_)bg)U7y#G#Vm!K^?cF5$REJC9 zag*EWsO?M-a1rntL#+Ang6_k;WHfxCHtyv*H^FW-BQ7#&%HFCoRkM|)tnm46!AfC0 z;OE`x`nEFR!v$=y(;7Wn+<4kgQ2!+^fR^wr40gZBS#!i%GRRewSt%Yp3G!Ft{!I9U z{0`5-VZC&H2_{8I9Rtww@u#tNaQx(aux&kD3ml9?b$N=H8#Remo3UMXWTc3*5`OOp zb!B*iIHpS-gB_cJNGcVp`} zCV4$Io%)H6LpQ#9oN>DehQr}-S9nDS991#|khN*hk092Mk@50(3K*J7?Q0Oi_^N!} z1iH>pclvmKp3C)hnVP_xQCR0`^?Q1KwVbJK+gTD@PptJf@0aunbI@P5+L0nvCI3s{ zm8&5QrCiUpkTBlAH)0rnHe$>xV}1Zz(kKEa&d%0|8O1>;KhfxGUR-BR-Iu7^#2^xQ z9;ZY#G@6J$oH&=;#i^pehkVa2DW6CqDYg*C?e*&=OwtTJY4!(okxb>Sg-}I;l9yc? zHKP)_ae4oEd26@gW!T-oNbACd41mzYyj)RdsReXEMKB^{4+Gr|v+h2H-(Y>+;yC>_ zf*xWqn1;JU={LhcXo!@&aX(9hRpC%nlID1-!$Jx0(TCGvaaQG_WXrqb<30;LWjwRl z^(C=vto=cc*I?(6!n`O5zzSs}3VQKrm@f$9$`2=kPp2 zseMJLP1oTBR&7i^zJDT@CQTQ8;+1E#yCIRq9|M*80) zhrt7yv5cBS((Og;2GRF+w&c(_h(~P87v?|?YRe2@1DEwQe^JE^% z&7*gxH`6POi}GiU5hVaS`Br>0lbh)3>hn;-(+?lq&$6S&l9ig>r2&4_8Yc@CvlO~V zoS_l;4N2*zWnQ|uV3FvU#JIS!^+H##(l(6Cec4uUu0y0!WS&pu!x?}$hJ0!g!Jy^g zeyxB#PL1zOn7JPWI+{)xlbsX3u8J3|x@r=~A1dBa`}7Di#{eQxV7}4)X(s$VOhrzS za59wsO#>Tj^5b-?MStt}=LvB}Gk{Fb{m&+1Q_NJCQ31|Q`^l*w{AN0OHivn!ZRbO$ zZB^B0=|{%v)Tzsxch2ICpUxD2E7}<5B0#ACj4rS?)f0Yu}D|cPQXXpp?#Are6(K? zkE~_dGk7u6^OY?<;;$CUC1QKX7^x4{HQzKi4&-+&y8u7fZWtu8D{J})u0vbiaMc-{ zk__WDG2El+O}w=T7D#pI&mZq(tCkG88Zi8k_CUvc`b=n}#pRZ1uTUS9lLV9&q(+3D zsOuQfHs@si{<`q^R50iM79n1m%A4lt?4n8cq#Mvd#b_>Q5UJ@w`DwJ@-)>pJ=^&EB zvCYz4XO;sU#LXwL4xS+gm+ubom|JSR)Al8*?CqI2|NI^JksIy>&66_+)jG&1Be${fc9pM z{FRCM;xgJ~G6OcOhI*<8Qz@L4L*rIj7-!l=XA1p}PA)9vn`CnMH>9Ad zp%RsnAM7~7tqiJq_apb6=a6%%14l*A_b)NolseQdI>t}u9GSD_0k39-(R4UOjgO=}m6vqQ=+%jpE z5HZ?Id_=w4kR_o>v_MDQL--o_D&vY85gh6ET~WtqpBMzv7-%iRDW+i~9!*&moh8Ku zSV?Qh&mzEBx+9>tqKL$@FdxHesqjm4_+nnoTxLbyF)+=lN~@{}cYhM?KaACwwmR@T zb!#IDK7V|3C+@S89`BAe%Id^?;}JvlD_J@o`V;Iey=~;*cD`iDI&1Z$FT4Ol4mY-gJz^J(!S^#U0Xo%75=Wr(&7r3rBNx;SvDosCF3mn*HP5F;{du|9Y@2&{Mqv04S`DIQ&cZ7QJ|ZpS z!NPN$ARqE9SuPFOE$;Uc=74=>9(DSFGEJ@DTj1{D-n7V(ITvJW9L;f-N6V8Cx6pH9 z2NwHS!;EV?eJq2sX9)1%(?iH~8zF>)o-u3)9$`1BUL|!J5sYkWcRN1twYV|*>+CF* zWkzCvrtD_8$|7T)Ajm}pE$DQkm4=Wcz)SF@!r3w9LKg}!p|RT;>JACN^tGdBR`44A zJ-bZJ_sGJGd@!z%E{Knm2xp8(!9g%__zbKjY!$H0Tq9ax8uzGnj@@br9jAI6Df;4=NIo$VPrEKJhVR*BrKLG#Lc^x%b{}A%DacIW2Dd#zSaZS>U z#nH5&Af0VhD$l{L5!Z>w-@a%aJ5fsY>;Z6ZalFK|Tx?X!S-9^LX(zT8ezOkJfn>U{ zJ)W+lDW-?3NUsc~Do4+JX4G zhSc6caIlfQ@uLwyaHHLfeP!et)BU=8t-$Qjv*$4XU~!SxF_T#F z3EBP%&YLSb|$UuawBd_FZ9 z{1~lR$_MEXW#7C;LXk%KiFn%djW3^ZmGXEI?b+vNvmM{UpsXQ_@RfL5xi(p8Clt!O z2`5Sco-e1U&x((r&&*P=H`J?Z+(((6J%Dn^?H;-m`uUzd)@b%s-yYKnl5N$d|JYh)A@(t7^edE0{VLH z+}mo`1^4jDzCk12+G0QaIR?ZM!ys`ZE6N4GLly$A=W0KejeZ7eT3EEn#r9zFBd&$W z!v~Lx=q0ovI3k(C5c@@eaMVx}3wq&Jb|AE&AF<|=0MId+@ot)!?^LubilGlFelqmj z1$?~RtCjMbvYCP&!%TyMuBw{^Jg{-l|CXXprjTG?)hVC+l<~bJ0Mms)dfT#=LivMf z3;O6%hkO#`wa<7i=PeTvI@yz)IRQ=-M2H9b`eV1&eW?YEbUQ|DU3oerbkY<0Q@fv- zSzm%Th)o?h`n~_6ew3@~RV?*j>MiYf$i|Oi;?|@Ygn9cD+V|mYd`ln)5`g^DCDOJg z@cE}W2S4W8sarR&OX!(87vW&q#J}Nr z1j)t!jBvv+*X^kr43)zQcIU&ndOZ0pG~Kg1?L_z-_!c$A$L}%-D?e`HWq4dP$azlH z=SDo?p|Qzd@m?Uh@FH_zI76>s>3h4G_EhuN{7u@8I;^Uk0f!B%BY5FNUE6Ns9DE{qhdj?asxH#7^!J*QWGMCipA z5FvL(zP=si$DjUBMf@w!+CMzoye=6Kp|7~T?Kn*J&|skVC{B9nv6rX5E7^nl-(RNq zPhq_(9X);c@j;x~1$1iYUzfHr{)X1nv^f5;41fL^bqKOZxkul>ampk0GD$&6yi)>F znejj&K*NdX2v8q*S{vjH^a-ey;;+0-)+qg($pbw`cQv)}fx@HYY|HwTbL)F4fKW@A zikWJ`C!BxLLawUAcXVnPd3CSmo6EaxWJb{rF* zE|%rQucrSAb+&C<8A9^cId?&mVx&!h1tb^T?mA-KzkC;z}%5`eC4o>y$o zV?U-8z?U5EtK0sPS)O>M*C;pNkY40@RjniFQFWyCF-_M&iaV${6=-n4~=d;7R zHlcT@{glMQ9EkA_@dwZ7t6;yGTdRFNXhHy9I~eZ#mFm>Bb%XEU!GiL2l|u(+AWSay zD=Xwd^L0Z3i0O?R4)Ix$azXCpLZl4S;xZzaT4!N4qnO4NSi*f0i{ge^w$G2$(iY{X zMY@MLkVE2D)E#M7=Ll-u7kPTPBQ7Gct%)Xbp>(A)>y0=~H$pk3us&!v!~(Q6q|td} z!_Ru+W_nBqc91(f(TGN5Dw!+EghtcyKcQ%Qm^YY|&Zh`3AmL>iki`b=!9T(4q_b5=fd|Q5Z71T|pcjfT^L0T)RuKEld=Qhv9@EYwDx+MD zb)a5?-0ASI*bs?nrg=pRvU*-Dj^{%6uu!8+6Yx;GX~vL!;c`e{11u=;n;`6yp`u%z za^jTmsjt@>KH=Y6l>wW-zik4U-|=w}`jOut-&-+qm;?Nt$8%1GFna{vFniv8y?QyD zKR_mn*95le7l8}{(0)R%r#T_SNAQN>nBHTfPg)>HdVAg=CPoF*V3yfECfh~1KG3$+ zY{6OL?}F3Hcf64T+=Cke7QYi$uu`gg1}(LE%p1HzG(OhRbA6EDhQM>uFUp!%9xp*3 zz$~h6=o(fZGQ4WKfSw<($_|m`nV&oeh&a)=*3+m*3ZIFvdz{l-;GPVx`3nq>B^qzr zjQkT$SuODQ1)W%I1^zG=PL+lczaBEoV(Z()>D4O^DLtN1tyCW;eJ{=gUmju3g^BWo zHrZ$y(U%ZQa?mF23#1o zk8nmjuE^!p44J|$(zJN-U@m(Lzk``*0cqjtf(-S3D;#;TjLbmYXbl(mNS2XE)zx?; z1^b)mfLXEgbhyaV?h>j(9AQJ4E-9y z&9bDZbABpGjz&2L+?r}gkXqH|404<=mmHQJ-73*;w(K3rMVHNQHYe7Do45dnw(lh1 z5aqpzuv?M4H?0czWG=7`=N;(I_;UTAPPdP&78>KXl3+HMARZMEJ>brwb9W|5eiF&+ z^5YNM%!mOI?_~A)A4}{K`=)N@vm|~ty%uKgU4+nT05yjA@V2J~h>NHm$ys{Ya6HY# z$OxtsA`2MEZ$HL&cy9=@Z0!|pTY+35u6S2URW4=8Z-Lo9 z&QrHa_8PAYn|XCecibx2ZiB?qS5R44Foro#e2(|@i4P!;-F#fBr-wkQmUQI{#56vG znXS6Y8(oG}sID&ivL_91V`7U_4PV{oO(|bpvzhbYCwqEf{4NTIY`kF1=r^^i%S!H+ zeGtGf5c)7{-Oc=Ss;TNN#M zuN+?g;*J3pNJt6IvC;GMBgBu7fkmI^_YMtw22Ogn2Hx})Lhubg9JWta_3#JEDr9Ec z4+3bJ()Z6P5T0+)EX%kBrCCe8Igrk{2t&W%X~2FWh<){$$n~n& z{B$JxoO64zi2@$IZsII*yMvBiU9K>!+Kk8aex}M_&06LeW1VlGF?XwO#|CxaPAC2G zR{NEK;Feywsb)!eht8=U)Iik-r**Zpe6hU3i6+Cl?cyUX|1$8rmufkhQxG&l@vs@) z_Vfy|7h1bnmu0ZhIUbwWIO8t2IMEDwMt|nU{jQO2zmq+Mqn}+8C8JJZHB4-kAx?fv zzUR`M@f%$h*Qc4TTNgw?t3`vuR6n5Yi8;G*GoZ2J$**O4FUqgM3NPR;7eKv@YSA?T zTyFbu%Fnzv^+faAqshmBz=ZM_K<`xP=q&HmG4rHEDgF_2woY&RNM;q{biyKd5!Zvp z`{)qr%uA!w!xed61mc-Vd2L;y>*XEKy9P@BYX6#tup6)Y1-bOr@6Jogsfz{O`K@MO z_3GoB3}7yR>sg0l6WfeKVL4cUKA7a{=N`kVSMEFQ8+t3=JE7>@fmLFKOpn{M%DKxf ziVxyX{11_7%3K0hl-93;`e)|Y1mZa2X~#f4sHPoD8{u_iEY2Wz%1&49A&K}O_Eb#zOaP&V8S3Qla~t#y|2Ge#_zbdXl%Wcu!hm?`C=|0k^*J( z&^(xbx77_Tt}T;w<`TW)gb!wlfn@z$s!>0J-V`SU?WQv%><#&I?E&g{w2#%qD>!G> zm9M?Vd5*jpL_knN)I4|ZTZP@!h&;g69HpMz4({2U(R+nn;Sv^uTLW>~30|9`@YUvQ z9@|0U?L*9WwCarvC$dGkO8oFsuCJq|>M&Xwz;AV$zIwzJZi;%}3UO=#c;X%K6@Kxe zQ+ZrEY@6Q(o_c_EHh9*~+?B9KelAV5{F!zrmEvV+U+=(8@le~N*wK5ICV$?D8Uu{k zt=LX8r;PD31XSJ1kC53AP;VAgtyvRdFkH zQpg0nOU|j4bH9@td(9&>mrei8V4u|Na*%&2^JE_aD$*&uBayDW>VPltcei(`bY+;f zmRv{_Sz4?Vm$L?JT?}#}J{HGYMes-v?rY3Jg&D&2H%n|tVdcjJg;RG~TCF4s!Nn2& zOzw~|RTvaBN+6ZVC_+80F!Tt1SZY+8X!#suu%9w<~Xu39lJcc(RFU7O6J zWN{lYAHH>A#o>bLB!4C5Q$9)a9y{p~dk1{$_UcHd7>|3g)Pu-@e|AHfvf7rm+00lV zwh|f2kUwL&yJ0>b6RNvJQnczGEIZ;=8EjGIfANfm;MAG1eT2_E05d_%zkKC#SI-Cs z$a8yB{Hl8Lz|XQFHZFCMK$;~x)iIXuClu@SK5WlhjK%g<^Xmf91GhB@p@b$b0B5T2b8Q$uPOSh52*drUYym3}C>mJMhGQI`?`+ zq6;*NwaE(c9u71t46AJxi6hIH4*bEA2m47=R}U*=DukZ?@ALnQ;U@#hGlS*~`+NWq zDfBOU1)|TIn%Cx9{`jbUYrL|i^|-8KZ>O5Y@Mv&F3zdwyb%2&d9&63T<>P|cCLyEU zivqxb^~0mnZS;m3mPG~U~zoKtXti>QGWB-Le~=2R(KLH=z?y61;$xtyJOUU^S32bNjsW$x!6|l94BGi+WEljS8K-BBVxi zi#Hgi>q@2=$8a+c|Ks@ax(U7?a{s*+d3rzO+4~!RF61r89f38&Z{AvD4zNs!^IMM2 zt36=0XH5S77A5jAWF+(=IM6r2SNH?5TB>twHedVk39Qf)yISf4v+tE=o$>|A^J>^- zy6KCFV(ee3W@qw@?M>G6z)~es{NU@7?NkK{(?}?@8RMZnL?#RJmIWj$WGrmzl#nkz zre0Pj5MNyHx3b?`Q^dp*#~zO;G&o)@d$@x24*(h z2j$1i{-Agb|i8n;@tysZF$BS-SroYH*`_k6jA{@DRI zydjd=!jKBK9-w=E@7=$;LC%M>{Gzw=)+t-v1Q>qv)PiupE~~i6g2fwa>3AwS(N3M- zqackOT~Bng>l6diI}~*|tG!nV%SfqZ5PmVw{=p89p~z1)HF3-eU4HQD=kJQijol=< zb#{Yh9TKT*J|=A9XK`5y1DZGF*gZv~cNMV^DpJ5vYW?wZE~3V~6e*J}CZtlahMcI8 zAq+B5Xj-va_YnD>vU>1A@?iLcUFL+gIi-|i*L6}hCt3x>F=)QDCzjE<tq~NlVPH4QAn^I|i^Vp7$j3dfMK%x#f3L+2K8WA=8zOoJAPf67ts-)i*^}%Mc3;A*Q!3r*(_|n3{@O0ELWU9+I?ehdPwOqfki*HxnYTEJ) zop4*fp(%GG^VMv3$sH>x1-TNvK$-70)aM)V_d{ap9CUc*BeEFgiSd@j@syz3;gk`G z7a<`^se>^%ScMnIo|k4dBP?vIsc~gDG#=%mTV--ryd%!UARY}3YB^6AB0>)dU_ z{3BZXE1O>DqVYra_AWLv&Ujip^_D>Ot^NGf%g)PzO<2(b?pLIX92r=8|@TP-gyFBPrAKc1#5&J>u*Pq-GdgiN#;x0-SBy1F=5 z>2pf3Hc$8cT1t<$Z z2ruXgGu@U{b)>=u<|4*@RGz(9zEX}97(VguqPx`8OfjZX+gId(v+*o*C6~n+s{)@ zz4T@VE-snsA7}V;vCeSx@i)W3n6L^blsdV+h)OO}#oFoB7)`Gs((lctu9+51#n<^Z#@FXQcB*ec)Jr z{<9)1U)tv%GW3u6(fLOo8CicZC385YFD>*xt=Ip{CH>1O$?97f!7<3Fh^c65(ERrj z{TubL{HN(*V`G7%V`gIcn%}=n&rjB`cl{q=)g#@BG_T30j+28huUWKRD0V zN+^9HD^<8JAM&qhd_f%}rmso=^O^sX^ZXO4_g~xof8;zI|5jH2k!q;$FV6G#4%0&# zId1D0GL~Xec|5XLsWm@H->PUisJ`d*R*HB+u^<>@2XkM2MppZf>@Vn*jD^4A&;`o9 zyZLHks8E@Oq@u7w_D>PJBPPZYhXe1;9GTp#=CjSQ0O0=aIaCL99`s+bl?qJLqV(TS zZl}DLYKEP5Vh7(S68*bp@OhctV-PVRm}?k!5oU1XSOGL4 z=%sHYc>%&g(9H4OcpJV(0_!CZseOHB^oB!F+{-x9mYTl}cb=seH))Y%4QwrI8qUao zZg#uirkZkKye!*z}^ z64ZeLDM^EwhQpbOaEw}>SCnb@&Y{N=>x!$MAAk?cVSQ0ulpI`><-vz-F?U@o@MH0a?@Vp}H}q21x8 z6nNwmxwA`-LAfNbr}YHnra|HS?fH2{Qp^}vGtA(!cYre! z2kQ_INt*O{_!9N=49$2XZ$e$6d{@J_@QGm)?D$qlT#P<_LVOZ4{RiEuKnLe8^fxM8 z?wqFS!#Bd7NQz_VWQlMk@9$M6X}fkOW;Qc@LquHd4nvQ?7ORcfnNGL4LBzAADxC)R zg;7kau@pK$&BG>n1a=b(J1g7#5~TT+Mr&?y`{-4Mo`!<1swR`mdxop{CL zaA=*7iSnQ7JXMVR|eZ5$qneOuBRqWp3UkQ^*Rz29Iu+VafybQ zS~~c?VIeOndmyrDcZ-Axe?9ItV?4~={g9!30ov<8YJMA96Q;OE-?Ec zzC%!vTmXMlyl~gZa}+Du<6eb?dQ5l2%ANiOI$q_`>E%%G{pLj!Dgh^XxcO#cvzHT5hTHI3)ic|*USEE~zZ01rA@Q0?@;XC}^VG)$O zM&AZd)j?dR+{P_}hV3slOP zBH4G=D{y<)(2=Wjr4MEz>-SqTmDJsdB%EMBXVlxy?+2FbL40|;Q@vhmL zBSV|!KFt&?(HUEOj$w5D5u&-_FDCBgi%k(ULdXe|YI{?!WpG&n(L_v%LffbQ6>&IS z#Q~ZQ7NtpA-ZYu8*!UH380@TkI5&cdU@oZR8(!@RW9Z&e-3rgy+c91MiMJ!tnaw9{~#z*&Pb0#x z$IRuB8&M4!Wn>*D=lxO$P8L;MU9b-ZbR z1V2PEJ+NY{M6$2Cl4-Y3K#HF+IsRNx?aa~=^kqwA8eZsD8U97BV-3Y!9`T zlj3HG|2@aV89vR#E!q=qJ{1uM&|`t~s_ks<;VIQO8g-ODR3jU(bqw5DTlmY81f)e; zu?dXS(%XX-t$XQMo$J%@ zmMy@*G0s}GI{Ji74Fkt{xKlsa86(x%^e(tBv3G4CHLr!r3J=s>ynUPlxlXuX;#hm{ zrXcC-+fMpIAe4rIIXZjHr^3$yIGGDwOVgbC^On2hZv2&UNP_3`dquw|+`5GDaFp%* zB)I$-x`7+Ag8TdL9k6^FZk$NCB_G`n22{RZkz?d90bXB)63^hBmo6sJRSZozf=F)v zN;#D05;;6oX!}>nAq)}7$n{k*yD;letL!~fq~LDZat2r6+9n=7tQ7A~oqh)^E)^l( zT8QT58U%IAe8$`N8OYxe+;IT`8I$T!L(htG$N3#$9xfG(8>%sLZ7@%j+Mq3T1c?#P z3J3eMY%L1={;k)va1fDPe>?{fdUVW*x(N5+1sb@6Mp#zfaAt+Ja5=#Lq}bGxjm2Dr z(?5#(Z_%odBDo2;O`kLl`C>W_?g|TUS*No1&mrK++6@rK?{Y6k@3S1VKF%1_wjzja z>-~vKiT@Ddo6DNS!w@BS&|%^3Ax7Ah zKs0}|D4#X?urHn#-9vwV_uVpb-wz?**7}M$VSStx<%nWcLR}%$^&k)?R&W;@pA3*@!v{?gd^OA+lio7 zdKSo}ZH^OZmi0`Ilp@SgOpY_1ARzHfru4iXChPB#UQW3MYYPsJ4Y_iWzzr2T4Dr%g6e(o{DHH%Nqd%zYilFdwI zv@>{xXkp9mVxgqhUEbSLNNlzRmaXP5_6o*rKAx?Gti`KBg-m*zX3Y^TwEuK@;APf^ z-=mhR(Rvy=gO+5fn49$3aI(7H&D_3aUz90-Vre<5cB`bRH9&Np>+VdP6jY*E(Tnu6 zcS#F(Q?yYssa=>*!>)7p)I9Bv6bvVQvwDw+GIR2vr_F9-w^&bYK3Nb?{v)T$*J3+1 z9|^F7r@z#G=o!!%h|cra&19w6(ow1-F>&ne42;u1ds7c9fWRB7V&!@fiJOOwL6fr(SE-6Ps>LqL2GtEj7Ep$1zGp1@3CrKhVjJns*ax0l!UQJBMkMT1yk)J@%76Bd zNGMKwKpC06NMGh7Qci>M=L8(H{Z+cDe5OHCqu-7^I?%DcYuQO{f4Dd+iDpSmQ=DN< zZ-po_eAVUERzyV5&V4O@v?Do_3kNk$DYJ8TlUh7M+t#W#zL$`uq>>WO8efg?(YVT$ zkIKaLGV@&JXvtYT^eEmTWSn+F8#vp1sqcwxCSqT&E#5c0bHN}b9Gtp3lsbyvzE$i% zY=!AK>+Nw&KLhyV(`0iO`OzGZ6+)J8jEU*Ze{7v>q8G$8$38Bnd)%%;!I!fyGt6M> z`l&%nM+*M&jr-t*C3bz-Z-+eOmdx9c68kpi>dHG;} z!=Sbffys82+x`2d8#CREKot?1r-DV&jNH^!Ew-px{E`V!@aI z-cF$fG7MoubM{1poKu_=35W63RUT$%SOap45rm41(EM@0C-7hcs}d#3lcpcekgCTb zrrpEW;&;AKAGFq5rT$Sr(j?OxGxbL4FCL`zg7|@ZSh2@6i`)GOVjcn~cwF|XoxJh; zge1(d25lp^25ggg9zf;CePS58^D`E`OeQl1|cfy3SO;94+ zPu(5kVU+3cmf*AGeP=mo@r!ymxoCb)`TAj155>`MBqwjC3RqQ)BO2x{n{#z?U+b=SVY_PKO6(Bu{f#vp{mHOSbV`cU77o^z-#$J;&;V_2 z&zlZU2&r$x7z3Zba*q3QK8~q}a;%?^$D>f$eK2pbs|f3AK*sqa8!iBD>6H0jspt51 zs8_$1PIylrD6mJ=juAJ}_JmMm42y7Hmzm;=N9mLc=ckpDp+@_Deb#X#NcN{yDp=81=JBU4s!%r52>!6Y4I?VY*x0BU#TWP_SD`*CTfc?aCO$6x zi*8Rq+{=L<72kdmUzC)cm~qT{p!2FOYaDXmeIL$~w1{GRF_Y1Wp-~KM4MqFSFA1zFq?6o&5yZo+mK1@~essX#u!7{!v#f$nQkifoxL-`H%Y#+#OHRTNr^!(mZ zyEXiT)^hZNp3F-Y_f~Qz+&&$z|L#80F-$UwF{6&pRPo2Vw0V&V)CJQ8C=bctbjmHR z^8`~6ecz@@AjLAzO?@n_ZvGvPO^bHk(YBV(%`B-IEU>#4NGPx}BwPw_Ca3Og#-%pUZgDgTYpljH9)JEIS(&rddu;d{@`HQzc1Khk-rc&~Fq!o1goYUVLEr!t?EI;KQ^@1c8YhDq{SgRKm+ zr+IR!v#F$nrN^E1+sa!tcQ`a`O3E&r*i%_>c1@XY$?3FBk_wjVT*|Di{+g9@DuBHG z_QR{W)sxQ@nzppIFE3-4|7v#0`^@td#!-1CwM+7~bL|>NCFvfdY@T{}eb=qw?qb~F z)3;-x(%ZQWg>va;K~@XeI(P8OO`CVgXJncB&B_tI+?Q%wKCg9dz2BAcGELjKtOL2R zvpr?DI;sBK>e_P3ZvSkx%~iLjEDI=B_^ZIiZg$eRO@K{sdZ29VU@-F>fo1)A8FrldDN|&-c{|Z`owcodr+K$k*uyqokDp)m*@WV zCgH)^l?G#8a`F9>%Jsq%yc);+uD$E1M({?)Nwznyf^AYWHk*}EqGmVC@JtI`61ycO zSn>G=wMUB=pKNg(`|kI;69=o$E^WRty|!hE(vEb+w7H$$UK^4UnnT*c0_DwB4E1iB zzLs&H7Ex^>TekMEO)s?T_N1?$xU}c@MXN+y(HJG zJl)`qqrud^*lz2T)o&+0)^knNeXI9aH!jGG`?BYBz<8xb<-AR9uT)26jp_8x^;PMZ z=x-sieWOCAn9kGuQTNAF8i#`2o<0m4?S44W-Np8iwT!#{38RBnK`Bdo_NzSKnY1RP zH0_9PszzdPQczBA^z?h~3CB|>5COARb<~Y(Hrl>E`un-J>isjLpUsR5${E#`73@1b zFI+#qe_~V9tjbZrejM zvo#{_Vg8GdNQKE(9eWCf-oygth zhjxVRxaaD4`RxS5loNJwttWoHrtzv~*9qCGvB3c*b}28$^t7e%Sf&$yFOvMlz6!LuZ^aBNt=hPtdn?{63wu?gQW9cMCaQQo z^{-^M_OFQjS9IIBT90>M+`4PsrP%$M$K)&a38vRBTRhCK3pe}fr!V63O+;(m>oVHP zu6~|*pe;A@mQ|F)6^j}lweFg{fd1Hb7q3^$9^EniJ*=2g`A>S~S26l&Pexs} z_rbqfzCA1UhjSz#r4fHDlFPHyM<~fI(7XEyY>gFUIn%7w~sAzXmG0Y zPV%)J@mj_ze_e5D+jRfw9{bzbcG*?gr>oV~lskQ6_4^})hM6rLy;eU~8eY%Y-!tca zn4e*4mD!c^bsix#TKf%0)J<|cF1JSU2kYRvBOh+ByRPw0)$U&%id(dYv{*>5y@zx+M)aA$Szp_ah*yz}LusWrCu?B0t?3clQ6+NadB!Y5|7 z&f&{(cS_6XcluMc#^1?Lk`;c{DP8b^|9xAdaiiR$tXy(*du+Yc4;NH-_b1ByroQGf zcenk|{EUm?SN$H!U-UiO-Z`h%c2|<$WX1EQ8;lgP#=3o-zgoLhJ6AdIu6@t#(Y0Jq z+$goP8#+{Sa{}N0RNOYQR-raNpkC)ih~Ok^wAX9==2hu6-}$N4UXV*K`{mWk+8j}@ zr~YHZ5ax>?j{8J^pwdS7TGsM`vPXCOb8Z2dGVM?Kn;I4I_SYsn*EcS$3Y2Yh;7gV7 zk4bNv=g?^xXr4RuyAf$JZlczyKQOg(i+hOHrM0$!Uyf*n>X@638KlL1ma;e}aZ=v$ z$1^|wQ!6(jPo`LZ&Vm{lBhBo*lzqxym0tH~4aiw>dB&1Vg*Pgp&bOLftTp{6ce=iN z8E~n8ff*I6_2Q@alcgSOuIn8bpYbG9eOLeDY`?$%-DA;!*|QGG3QJuryq7sNl{7j$P`!Wr!Aq;eYGcjCRjlUnDub}(^@%4|8zzdc?krOB zY+IG+a%}sWD*o!iDsfln4S&+2Vwp|T<3+wr$69tZHMLx6I=3Y;rFe2;N`cYoYj&ik z(0X}RqTXGD^VLV5u3k?a-TwO4%%;e_OE;eIH`#coq&(`~d?%0X5s&G*gOB;3WchKA z%e&=VuIgP~D1Vx~%7!I(oZh%nIZ^C6w@B%#y^+QFU&kh|UbM75-1eO7hCK$x(aDFc zqAD+}+26S0PQLi+mf~+ctB>w-O5T&KX&Y_e-J;}qK+7a%CuiXhX7BW7M&kIe%}#X% zW3T4!G>K`ncgo(E?3;EkUEAY9vW3or3&lpBHr?}>omo+9skwP(|KoJ#n1S2(Hp_}a zzVY0VFkkfuZMkf@`?*DXm#Q`k=eOTpx-x&7;hu`hm-|;QIKNbt$y(?cej@3H>5cM; ze@CA?Gws}il9oE(2O2(0o?f)xdw=(pb3xjdlnCwjp=)!hYV?^Kd67Dn-}l({kI3BE zw>U~RtxsuXa)i>uRcGd1-=ovrqwyiM-=WAdbYb6_^5xG9ntMYx8_FprUlVDszL3^s z(m&$T#@@w;TBiBl93Q`gKY7G2^tTIJtS(JiX@2p`-?mw*^tVT?X)Mx<(K&Z+XUxxO zr8zraTTP6zxS%$pE#}DRWdUbrJd5f!uyc$vb{OT`8K9Ipf<2a1|K^vZXQ^e)M2PIz ztA!1dHj;Z|%ckAl6aob@4NvXSsC?3{WEwE~kARW!nO}K*;z&j2}yBFiXh<}n&3pNt8%LQ47~1OcpMxQQXrXzFk64v9uH z@avJoq(hRC)JJZ?q6S1jnjMmi1iKt=U`R5S{(G}Sl9Aj;5x`^znM6J0EUHa_ll_9C%52~!uY z#SHvMsJI{U;~7M*dUz*BUp@%ph*m)I10lUibpU)%;Wt+ zxn1mCsMPxjjK%teV!mRe4^Dux!x%<0=)0W2iaCT%Oo=H>FB~Oi5IT^9K-&Y1LU|?t zMB5_>lELWU$6!%82$JOmv|o}VP&!WF7@SU^F`g-KZ)kfA#bIN_P!y^c0*nWzV+2eN zh+&vsI99;=P86hlR7L^e`P^C-_iC&uY;-IHQoz~~4bvo#W? zFghN9@fZd-)CiP6AjIe>n!x&n;lSjeD4aivCGqw+%!Wu>08b#D?!$ARqQ{eeg|3`tJM8}e1 zAR?o(F)WLY8^y4g4}+P&VtQc&%yubY8PzGpLA*r!1-JGI46|p5G7KgM$B>vDJd4>2 z#j|+7Jj-IbhpVrc{sb|L_DhR#KMojX&wycb0WQh1s9tC>i`gknaM)a+306RL1#|=| z2M@fUa`0l%KB9Z@IJj&iK4)l(CQ;j^!8f6H3UrvSqbZg_d7&vDvt63z#Hjs2N{)>Y zU_8nT4>17K3oVeCybuCVd!ZRT-vA7kgAs5Yuml!sY4B^<_;MUJ9yG)}OkRkWn5}UF z-X1R&Fxdn#<~L|TjO96ikr;mhcyiP>1R9%LjF@B5u>=hBb%0@dVPKl0W5f_(CKt18W_O4yFReGX|F?D7%vnU2lnnK_@h}d)(6dC@sgn# z9_xeVF@Mj%)&V+3uw{Vr!jd%VpMj1==PK+=;N#1(0`@M4+cU$F93~q_e&SsQ*8$i) z#*08>xg$(VET#a4#Y;#DKhYkG^6Fl-$K7-r+(O0e95rAQ9r8O#c`4OsOF=sX8cjQL4OudtkkW%0EiL{%(S0EYQQ zz-ZLRvXGXbcE!SS5S>RH0of>`d%!S%02qn32dM>?6N1;pd=va}2o_htoUvRP=&-m7 zs|C!LK$NCXd**1^k3!|3Da^+L9p?XF=9B2$;vlGCx&j}8`YTwG;A=!iz;q9*MqFO- z8yL@ER+yeSe9mx?j-zu1FwDPj94!7&y>J4S`*4tRVeLUk#`Fv^54C%kMOX}laEbX& z9?}hz7cgCHd_hy#+J)yxtX~246wqD+n$tiZ4E8RpqEP=rz=l1#h9E!;G?xNLM_}#2auD^CKnFn{kwXCg zg6fKg;XrjIPz0KTz$PGx<^rS`f)8p#0_5HZ26W&|P+lkq%KyYjf(Da8Fz{+<+yXi* zCPN>vMUA`*QwEJk@Gk6-V{{zWFK8L{o$xNUk0il(R9*u7AKnL##szqnNAqF{hVH*e zFf?}rOhDtl1Vi;K!O%T235Mn%u=k4X1xYZ}Pf9Q}FOgvA9F$ IW~QS50IIHY$p8QV literal 0 HcmV?d00001 diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 42336e1e6ee..538b23d4d3c 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -662,6 +662,13 @@ namespace wasm { // analysis the user of this class can ask which contents are possible at any // location. // +// This algorithm is so simple as to not really be worth a name, but if you are +// familiar with Abstract Interpretation then it can be seen as an efficient way +// to implement Abstract Interpretation with a transfer function that mostly +// just combines values. A more detailed comparison between the algorithms can +// be found in the PDF at /media/just_flow_stuff.pdf (the algorithm implemented +// here is called "Just Flow Stuff"). +// // This focuses on useful information for the typical user of this API. // Specifically, we find out: // From 26390745069e08044d0f63f0af184518b5640bc1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Mar 2025 15:17:54 -0700 Subject: [PATCH 360/622] Fix fuzztest compiler warnings [NFC] (#7380) First is an unused variable, second is an unneeded move. --- test/gtest/type-domains.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index b59bc2a0dd7..038a09a2460 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -725,7 +725,7 @@ fuzztest::Domain AvailableType(TypeBuilderPlan plan) { fuzztest::Domain AvailableSubType(TypeBuilderPlan plan, TypePlan super) { - if (auto* type = super.getNonRef()) { + if (super.getNonRef()) { // No subtyping among non-ref types. return fuzztest::Just(super); } else if (auto* ref = super.getRef()) { @@ -1046,7 +1046,7 @@ std::vector BuildHeapTypes(TypeBuilderPlan plan) { ; } assert(built); - return std::move(*built); + return *built; } auto ArbitraryDefinedHeapTypesAndPlan() { From 087c6138e0790398772f3bf691219a9a2cd0ae93 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Mar 2025 09:49:39 -0700 Subject: [PATCH 361/622] [Strings] Make string a subtype of ext, not any (#7373) StringLowering converts strings to externs, which makes sense as we lower stringrefs to imported JS strings. For the reverse transform it is convenient to just have strings be subtypes of ext, see #7370 - that makes it simple to switch stringref to externref and vice versa. This also adds support for internalizing externref strings, which we represent as anyref literals (basically a hidden subtype of anyref). --- CHANGELOG.md | 3 + README.md | 9 ++ scripts/bundle_clusterfuzz.py | 1 + scripts/clusterfuzz/run.py | 1 + scripts/fuzz_opt.py | 13 +-- src/literal.h | 3 + src/tools/execution-results.h | 12 ++- src/tools/fuzzing/fuzzing.cpp | 27 +++--- src/tools/fuzzing/heap-types.cpp | 2 +- src/wasm/literal.cpp | 27 +++++- src/wasm/wasm-type.cpp | 22 +++-- test/gtest/type-builder.cpp | 24 +++--- test/gtest/type-domains.cpp | 18 ++-- test/lit/ctor-eval/string.wast | 6 +- test/lit/exec/strings.wast | 34 ++++---- test/lit/passes/precompute-strings.wast | 10 +-- .../passes/string-lowering-instructions.wast | 12 ++- ...e-to-fuzz_all-features_metrics_noprint.txt | 82 +++++++++---------- 18 files changed, 186 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a8ce1af093..6e63e9ee5ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ Current Trunk - Add an option to preserve imports and exports in the fuzzer (for fuzzer harnesses where they only want Binaryen to modify their given testcases, not generate new things in them). + - `string` is now a subtype of `ext` (rather than `any`). This allows better + transformations for strings, like an inverse of StringLowering, but will + error on codebases that depend on being able to pass strings into anyrefs. v122 ---- diff --git a/README.md b/README.md index e634491229b..19334a96662 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,13 @@ There are a few differences between Binaryen IR and the WebAssembly language: Binaryen IR is more structured than WebAssembly as noted earlier). Note that Binaryen does support unreachable code in .wat text files, since as we saw Binaryen only supports s-expressions there, which are structured. + * Binaryen supports a `stringref` type. This is similar to the currently- + frozen [stringref proposal], with the difference that the string type is a + subtype of `externref` rather than `anyref`. Doing so allows toolchains to + emit code in a form that uses [js string builtins] which Binaryen can then + "lift" into stringref in its internal IR, optimize (for example, a + concatenation of "a" and "b" can be optimized at compile time to "ab"), and + then "lower" that into js string builtins once more. * Blocks * Binaryen IR has only one node that contains a variable-length list of operands: the block. WebAssembly on the other hand allows lists in loops, @@ -1039,3 +1046,5 @@ Windows and OS X as most of the core devs are on Linux. [minification]: https://kripken.github.io/talks/binaryen.html#/2 [unreachable]: https://github.com/WebAssembly/binaryen/issues/903 [binaryen_ir]: https://github.com/WebAssembly/binaryen/issues/663 +[stringref proposal]: https://github.com/WebAssembly/stringref/blob/main/proposals/stringref/Overview.md +[js string builtins]: https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md diff --git a/scripts/bundle_clusterfuzz.py b/scripts/bundle_clusterfuzz.py index 65714340a5e..0dee8c95157 100755 --- a/scripts/bundle_clusterfuzz.py +++ b/scripts/bundle_clusterfuzz.py @@ -107,6 +107,7 @@ '--disable-shared-everything', '--disable-fp16', '--disable-custom-descriptors', + '--disable-strings', ] with tarfile.open(output_file, "w:gz") as tar: diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index e3b59234e7f..4e7bf6659f0 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -88,6 +88,7 @@ '--disable-shared-everything', '--disable-fp16', '--disable-custom-descriptors', + '--disable-strings', ] diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index e12dc809bc5..e77b755e2e1 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -151,10 +151,12 @@ def randomize_feature_opts(): # The shared-everything feature is new and we want to fuzz it, but it # also currently disables fuzzing V8, so disable it most of the time. - # Same with custom descriptors. + # Same with custom descriptors and strings - all these cannot be run in + # V8 for now. if random.random() < 0.9: FEATURE_OPTS.append('--disable-shared-everything') FEATURE_OPTS.append('--disable-custom-descriptors') + FEATURE_OPTS.append('--disable-strings') print('randomized feature opts:', '\n ' + '\n '.join(FEATURE_OPTS)) @@ -815,8 +817,9 @@ def run(self, wasm, extra_d8_flags=[]): def can_run(self, wasm): # V8 does not support shared memories when running with # shared-everything enabled, so do not fuzz shared-everything - # for now. It also does not yet support custom descriptors. - return all_disallowed(['shared-everything', 'custom-descriptors']) + # for now. It also does not yet support custom descriptors, nor + # strings. + return all_disallowed(['shared-everything', 'custom-descriptors', 'strings']) def can_compare_to_self(self): # With nans, VM differences can confuse us, so only very simple VMs @@ -1573,7 +1576,7 @@ def can_run_on_wasm(self, wasm): return False # see D8.can_run - return all_disallowed(['shared-everything', 'custom-descriptors']) + return all_disallowed(['shared-everything', 'custom-descriptors', 'strings']) # Check that the text format round-trips without error. @@ -1758,7 +1761,7 @@ def can_run_on_wasm(self, wasm): return False if NANS: return False - return all_disallowed(['shared-everything', 'custom-descriptors']) + return all_disallowed(['shared-everything', 'custom-descriptors', 'strings']) # Test --fuzz-preserve-imports-exports, which never modifies imports or exports. diff --git a/src/literal.h b/src/literal.h index 91bbacbfa3c..4f9e3d535ad 100644 --- a/src/literal.h +++ b/src/literal.h @@ -57,6 +57,9 @@ class Literal { // Externalized i31 references have a gcData containing the internal i31 // reference as its sole value even though internal i31 references do not // have a gcData. + // + // Note that strings can be internalized, in which case they keep the same + // gcData, but their type becomes anyref. std::shared_ptr gcData; // A reference to Exn data. std::shared_ptr exnData; diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index f0b31cdf7db..fe96fc55771 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -303,11 +303,19 @@ struct ExecutionResults { } void printValue(Literal value) { - // Unwrap an externalized value to get the actual value. - if (Type::isSubType(value.type, Type(HeapType::ext, Nullable))) { + // Unwrap an externalized GC value to get the actual value, but not strings, + // which are normally a subtype of ext. + if (Type::isSubType(value.type, Type(HeapType::ext, Nullable)) && + !value.type.isString()) { value = value.internalize(); } + // An anyref literal is a string. + if (value.type.isRef() && + value.type.getHeapType().isMaybeShared(HeapType::any)) { + value = value.externalize(); + } + // Don't print most reference values, as e.g. funcref(N) contains an index, // which is not guaranteed to remain identical after optimizations. Do not // print the type in detail (as even that may change due to closed-world diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 2f8910db8cf..c72b239bda2 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3399,6 +3399,10 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { auto share = heapType.getShared(); switch (heapType.getBasic(Unshared)) { case HeapType::ext: { + if (wasm.features.hasStrings() && share == Unshared && oneIn(2)) { + // Shared strings not yet supported. + return makeConst(Type(HeapType::string, NonNullable)); + } auto null = builder.makeRefNull(HeapTypes::ext.getBasic(share)); // TODO: support actual non-nullable externrefs via imported globals or // similar. @@ -3429,10 +3433,6 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { HeapType::i31, HeapType::struct_, HeapType::array); - if (share == Unshared) { - // Shared strings not yet supported. - subtypeOpts.add(FeatureSet::Strings, HeapType::string); - } auto subtype = pick(subtypeOpts).getBasic(share); return makeConst(Type(subtype, nullability)); } @@ -5376,11 +5376,16 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { .getBasic(share); case HeapType::cont: return pick(HeapTypes::cont, HeapTypes::nocont).getBasic(share); - case HeapType::ext: - return pick(FeatureOptions() - .add(FeatureSet::ReferenceTypes, HeapType::ext) - .add(FeatureSet::GC, HeapType::noext)) - .getBasic(share); + case HeapType::ext: { + auto options = FeatureOptions() + .add(FeatureSet::ReferenceTypes, HeapType::ext) + .add(FeatureSet::GC, HeapType::noext); + if (share == Unshared) { + // Shared strings not yet supported. + options.add(FeatureSet::Strings, HeapType::string); + } + return pick(options).getBasic(share); + } case HeapType::any: { assert(wasm.features.hasReferenceTypes()); assert(wasm.features.hasGC()); @@ -5391,10 +5396,6 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { HeapType::struct_, HeapType::array, HeapType::none); - if (share == Unshared) { - // Shared strings not yet supported. - options.add(FeatureSet::Strings, HeapType::string); - } return pick(options).getBasic(share); } case HeapType::eq: diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index dc6e574c7e9..f5a6717ceb3 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -510,7 +510,7 @@ struct HeapTypeGeneratorImpl { candidates.push_back(HeapTypes::any.getBasic(share)); break; case HeapType::string: - candidates.push_back(HeapTypes::any.getBasic(share)); + candidates.push_back(HeapTypes::ext.getBasic(share)); break; case HeapType::none: return pickSubAny(share); diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index d1fe5cfcc03..de95fe565bf 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -76,10 +76,13 @@ Literal::Literal(std::shared_ptr gcData, HeapType type) type(type, gcData ? NonNullable : Nullable, gcData ? Inexact : Exact) { // TODO: Use exact types for more than just nulls. // The type must be a proper type for GC data: either a struct, array, or - // string; or an externalized version of the same; or a null. + // string; or an externalized version of the same; or a null; or an + // internalized string (which appears as an anyref). assert((isData() && gcData) || (type.isMaybeShared(HeapType::ext) && gcData) || - (type.isBottom() && !gcData)); + (type.isBottom() && !gcData) || + (type.isMaybeShared(HeapType::any) && gcData && + gcData->type.isMaybeShared(HeapType::string))); } Literal::Literal(std::shared_ptr exnData) @@ -153,6 +156,11 @@ Literal::Literal(const Literal& other) : type(other.type) { case HeapType::nocont: WASM_UNREACHABLE("null literals should already have been handled"); case HeapType::any: + // This must be an anyref literal, which is an internalized string. + assert(other.gcData && + other.gcData->type.isMaybeShared(HeapType::string)); + new (&gcData) std::shared_ptr(other.gcData); + return; case HeapType::eq: case HeapType::func: case HeapType::cont: @@ -169,7 +177,8 @@ Literal::~Literal() { if (type.isBasic()) { return; } - if (isNull() || isData() || type.getHeapType().isMaybeShared(HeapType::ext)) { + if (isNull() || isData() || type.getHeapType().isMaybeShared(HeapType::ext) || + type.getHeapType().isMaybeShared(HeapType::any)) { gcData.~shared_ptr(); } else if (isExn()) { exnData.~shared_ptr(); @@ -652,13 +661,14 @@ std::ostream& operator<<(std::ostream& o, Literal literal) { case HeapType::exn: o << "exnref"; break; - case HeapType::any: case HeapType::eq: case HeapType::func: case HeapType::cont: case HeapType::struct_: case HeapType::array: WASM_UNREACHABLE("invalid type"); + case HeapType::any: + // Anyref literals contain strings. case HeapType::string: { auto data = literal.getGCData(); if (!data) { @@ -2868,6 +2878,11 @@ Literal Literal::externalize() const { return Literal(std::make_shared(heapType, Literals{*this}), extType); } + if (heapType.isMaybeShared(HeapType::any)) { + // Anyref literals turn into strings (if we add any other anyref literals, + // we will need to be more careful here). + return Literal(gcData, HeapTypes::string.getBasic(share)); + } return Literal(gcData, extType); } @@ -2883,6 +2898,10 @@ Literal Literal::internalize() const { assert(gcData->values[0].type.getHeapType().isMaybeShared(HeapType::i31)); return gcData->values[0]; } + if (gcData->type.isMaybeShared(HeapType::string)) { + // Strings turn into anyref literals. + return Literal(gcData, HeapTypes::any.getBasic(share)); + } return Literal(gcData, gcData->type); } diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index b7cacd3e69a..b3ffd216351 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -407,6 +407,11 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, HeapType lubUnshared; switch (HeapType(a).getBasic(Unshared)) { case HeapType::ext: + if (bUnshared != HeapType::string) { + return std::nullopt; + } + lubUnshared = HeapType::ext; + break; case HeapType::func: case HeapType::cont: case HeapType::exn: @@ -437,9 +442,14 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, } break; case HeapType::array: - case HeapType::string: lubUnshared = HeapType::any; break; + case HeapType::string: + // String has already been handled: we sorted before in a way that ensures + // the type the string is compared to is of a higher index, which means it + // is a bottom type (string is the last type that is not a bottom type), + // but we have handled the case of either a or b being a bottom type + // earlier already. case HeapType::none: case HeapType::noext: case HeapType::nofunc: @@ -953,8 +963,9 @@ std::optional HeapType::getSuperType() const { case none: case exn: case noexn: - case string: return {}; + case string: + return HeapType(ext).getBasic(share); case eq: return HeapType(any).getBasic(share); case i31: @@ -1021,12 +1032,12 @@ size_t HeapType::getDepth() const { case HeapType::exn: break; case HeapType::eq: + case HeapType::string: depth++; break; case HeapType::i31: case HeapType::struct_: case HeapType::array: - case HeapType::string: depth += 2; break; case HeapType::none: @@ -1070,9 +1081,9 @@ HeapType::BasicHeapType HeapType::getUnsharedBottom() const { case i31: case struct_: case array: - case string: case none: return none; + case string: case noext: return noext; case nofunc: @@ -1530,8 +1541,9 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { aUnshared == HeapType::struct_ || aUnshared == HeapType::array || a.isStruct() || a.isArray(); case HeapType::i31: - case HeapType::string: return aUnshared == HeapType::none; + case HeapType::string: + return aUnshared == HeapType::noext; case HeapType::struct_: return aUnshared == HeapType::none || a.isStruct(); case HeapType::array: diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 39d388064ee..f75639fa3d2 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -706,7 +706,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(ext, i31, {}); assertLUB(ext, struct_, {}); assertLUB(ext, array, {}); - assertLUB(ext, string, {}); + assertLUB(ext, string, ext); assertLUB(ext, none, {}); assertLUB(ext, noext, ext); assertLUB(ext, nofunc, {}); @@ -779,7 +779,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(any, i31, any); assertLUB(any, struct_, any); assertLUB(any, array, any); - assertLUB(any, string, any); + assertLUB(any, string, {}); assertLUB(any, none, any); assertLUB(any, noext, {}); assertLUB(any, nofunc, {}); @@ -802,7 +802,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(eq, i31, eq); assertLUB(eq, struct_, eq); assertLUB(eq, array, eq); - assertLUB(eq, string, any); + assertLUB(eq, string, {}); assertLUB(eq, none, eq); assertLUB(eq, noext, {}); assertLUB(eq, nofunc, {}); @@ -824,7 +824,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(i31, cont, {}); assertLUB(i31, struct_, eq); assertLUB(i31, array, eq); - assertLUB(i31, string, any); + assertLUB(i31, string, {}); assertLUB(i31, none, i31); assertLUB(i31, noext, {}); assertLUB(i31, nofunc, {}); @@ -845,7 +845,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(struct_, struct_, struct_); assertLUB(struct_, cont, {}); assertLUB(struct_, array, eq); - assertLUB(struct_, string, any); + assertLUB(struct_, string, {}); assertLUB(struct_, none, struct_); assertLUB(struct_, noext, {}); assertLUB(struct_, nofunc, {}); @@ -865,7 +865,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(array, array, array); assertLUB(array, cont, {}); - assertLUB(array, string, any); + assertLUB(array, string, {}); assertLUB(array, none, array); assertLUB(array, noext, {}); assertLUB(array, nofunc, {}); @@ -885,14 +885,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(string, string, string); assertLUB(string, cont, {}); - assertLUB(string, none, string); - assertLUB(string, noext, {}); + assertLUB(string, none, {}); + assertLUB(string, noext, string); assertLUB(string, nofunc, {}); assertLUB(string, nocont, {}); assertLUB(string, defFunc, {}); assertLUB(string, defCont, {}); - assertLUB(string, defStruct, any); - assertLUB(string, defArray, any); + assertLUB(string, defStruct, {}); + assertLUB(string, defArray, {}); assertLUB(string, sharedAny, {}); assertLUB(string, sharedEq, {}); assertLUB(string, sharedI31, {}); @@ -1650,7 +1650,7 @@ TEST_F(TypeTest, TestDepth) { EXPECT_EQ(HeapTypes::ext.getDepth(), 0U); EXPECT_EQ(HeapTypes::i31.getDepth(), 2U); - EXPECT_EQ(HeapTypes::string.getDepth(), 2U); + EXPECT_EQ(HeapTypes::string.getDepth(), 1U); EXPECT_EQ(HeapTypes::none.getDepth(), size_t(-1)); EXPECT_EQ(HeapTypes::nofunc.getDepth(), size_t(-1)); @@ -1731,7 +1731,7 @@ TEST_F(TypeTest, TestSupertypes) { ASSERT_EQ(HeapTypes::i31.getSuperType(), HeapType::eq); ASSERT_EQ(HeapTypes::struct_.getSuperType(), HeapType::eq); ASSERT_EQ(HeapTypes::array.getSuperType(), HeapType::eq); - ASSERT_FALSE(HeapTypes::string.getSuperType()); + ASSERT_EQ(HeapTypes::string.getSuperType(), HeapType::ext); ASSERT_FALSE(HeapTypes::none.getSuperType()); ASSERT_FALSE(HeapTypes::noext.getSuperType()); ASSERT_FALSE(HeapTypes::nofunc.getSuperType()); diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index 038a09a2460..deb9d0a1dad 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -486,8 +486,10 @@ fuzztest::Domain AvailableStrictSubHeapType(TypeBuilderPlan plan, switch (type->getBasic(Unshared)) { case HeapType::ext: - return fuzztest::Just( - HeapTypePlan{HeapType(HeapTypes::noext.getBasic(share))}); + return matchingOrAbstract( + [](auto kind) { return false; }, + fuzztest::ElementOf({HeapType(HeapTypes::string.getBasic(share)), + HeapType(HeapTypes::noext.getBasic(share))})); case HeapType::func: return matchingOrAbstract( [](auto kind) { return kind == FuncKind; }, @@ -501,7 +503,6 @@ fuzztest::Domain AvailableStrictSubHeapType(TypeBuilderPlan plan, [](auto kind) { return kind == StructKind || kind == ArrayKind; }, fuzztest::ElementOf({HeapType(HeapTypes::eq.getBasic(share)), HeapType(HeapTypes::i31.getBasic(share)), - HeapType(HeapTypes::string.getBasic(share)), HeapType(HeapTypes::struct_.getBasic(share)), HeapType(HeapTypes::array.getBasic(share)), HeapType(HeapTypes::none.getBasic(share))})); @@ -523,10 +524,12 @@ fuzztest::Domain AvailableStrictSubHeapType(TypeBuilderPlan plan, case HeapType::exn: return fuzztest::Just( HeapTypePlan{HeapType(HeapTypes::noexn.getBasic(share))}); - case HeapType::string: case HeapType::i31: return fuzztest::Just( HeapTypePlan{HeapType(HeapTypes::none.getBasic(share))}); + case HeapType::string: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::noext.getBasic(share))}); case HeapType::none: case HeapType::noext: case HeapType::nofunc: @@ -594,16 +597,17 @@ AvailableStrictSuperHeapType(TypeBuilderPlan plan, HeapTypePlan sub) { case HeapType::i31: case HeapType::struct_: case HeapType::array: - case HeapType::string: return fuzztest::Just( HeapTypePlan{HeapType(HeapTypes::any.getBasic(share))}); + case HeapType::string: + return fuzztest::Just( + HeapTypePlan{HeapType(HeapTypes::ext.getBasic(share))}); case HeapType::none: return matchingOrAbstract( [](auto kind) { return kind == StructKind || kind == ArrayKind; }, fuzztest::ElementOf({HeapType(HeapTypes::any.getBasic(share)), HeapType(HeapTypes::eq.getBasic(share)), HeapType(HeapTypes::i31.getBasic(share)), - HeapType(HeapTypes::string.getBasic(share)), HeapType(HeapTypes::struct_.getBasic(share)), HeapType(HeapTypes::array.getBasic(share))})); case HeapType::noext: @@ -1043,7 +1047,7 @@ std::vector BuildHeapTypes(TypeBuilderPlan plan) { auto built = builder.build(); if (auto* err = built.getError()) { std::cerr << err->index << ": " << err->reason << "\n"; - ; + std::cerr << "Plan: " << plan << '\n'; } assert(built); return *built; diff --git a/test/lit/ctor-eval/string.wast b/test/lit/ctor-eval/string.wast index 045c6eec01d..d1e254f53c1 100644 --- a/test/lit/ctor-eval/string.wast +++ b/test/lit/ctor-eval/string.wast @@ -9,14 +9,14 @@ (export "test" (func $test)) - (func $test (result anyref) + (func $test (result externref) (global.get $global) ) ) -;; CHECK: (type $0 (func (result anyref))) +;; CHECK: (type $0 (func (result externref))) ;; CHECK: (export "test" (func $test_1)) -;; CHECK: (func $test_1 (type $0) (result anyref) +;; CHECK: (func $test_1 (type $0) (result externref) ;; CHECK-NEXT: (string.const "one") ;; CHECK-NEXT: ) diff --git a/test/lit/exec/strings.wast b/test/lit/exec/strings.wast index f4ba59844d8..e9dd9fd2d0b 100644 --- a/test/lit/exec/strings.wast +++ b/test/lit/exec/strings.wast @@ -477,20 +477,20 @@ ) ) - ;; CHECK: [fuzz-exec] calling extern - ;; CHECK-NEXT: [fuzz-exec] note result: extern => string("string") - (func $extern (export "extern") (result externref) - (extern.convert_any - (string.const "string") + ;; CHECK: [fuzz-exec] calling string.to.any + ;; CHECK-NEXT: [fuzz-exec] note result: string.to.any => string("six") + (func $string.to.any (export "string.to.any") (result anyref) + (any.convert_extern + (string.const "six") ) ) - ;; CHECK: [fuzz-exec] calling extern-intern - ;; CHECK-NEXT: [fuzz-exec] note result: extern-intern => string("string") - (func $extern-intern (export "extern-intern") (result anyref) - (any.convert_extern - (extern.convert_any - (string.const "string") + ;; CHECK: [fuzz-exec] calling string.to.any.and.back + ;; CHECK-NEXT: [fuzz-exec] note result: string.to.any.and.back => string("seven") + (func $string.to.any.and.back (export "string.to.any.and.back") (result externref) + (extern.convert_any + (any.convert_extern + (string.const "seven") ) ) ) @@ -623,11 +623,11 @@ ;; CHECK: [fuzz-exec] calling string.measure ;; CHECK-NEXT: [fuzz-exec] note result: string.measure => 5 -;; CHECK: [fuzz-exec] calling extern -;; CHECK-NEXT: [fuzz-exec] note result: extern => string("string") +;; CHECK: [fuzz-exec] calling string.to.any +;; CHECK-NEXT: [fuzz-exec] note result: string.to.any => string("six") -;; CHECK: [fuzz-exec] calling extern-intern -;; CHECK-NEXT: [fuzz-exec] note result: extern-intern => string("string") +;; CHECK: [fuzz-exec] calling string.to.any.and.back +;; CHECK-NEXT: [fuzz-exec] note result: string.to.any.and.back => string("seven") ;; CHECK-NEXT: [fuzz-exec] comparing compare.1 ;; CHECK-NEXT: [fuzz-exec] comparing compare.10 ;; CHECK-NEXT: [fuzz-exec] comparing compare.2 @@ -648,8 +648,6 @@ ;; CHECK-NEXT: [fuzz-exec] comparing eq.3 ;; CHECK-NEXT: [fuzz-exec] comparing eq.4 ;; CHECK-NEXT: [fuzz-exec] comparing eq.5 -;; CHECK-NEXT: [fuzz-exec] comparing extern -;; CHECK-NEXT: [fuzz-exec] comparing extern-intern ;; CHECK-NEXT: [fuzz-exec] comparing get_codeunit ;; CHECK-NEXT: [fuzz-exec] comparing invalid_code_point ;; CHECK-NEXT: [fuzz-exec] comparing isolated_high_code_point @@ -668,6 +666,8 @@ ;; CHECK-NEXT: [fuzz-exec] comparing slice-unicode ;; CHECK-NEXT: [fuzz-exec] comparing string.from_code_point ;; CHECK-NEXT: [fuzz-exec] comparing string.measure +;; CHECK-NEXT: [fuzz-exec] comparing string.to.any +;; CHECK-NEXT: [fuzz-exec] comparing string.to.any.and.back ;; CHECK-NEXT: [fuzz-exec] comparing surrogate_pair_code_point ;; CHECK-NEXT: [fuzz-exec] comparing unsigned_code_point ;; CHECK-NEXT: [fuzz-exec] comparing weird_code_point diff --git a/test/lit/passes/precompute-strings.wast b/test/lit/passes/precompute-strings.wast index 47eaddd47e0..4649eb194fc 100644 --- a/test/lit/passes/precompute-strings.wast +++ b/test/lit/passes/precompute-strings.wast @@ -12,7 +12,7 @@ ;; CHECK: (type $2 (func (result (ref string)))) - ;; CHECK: (type $3 (func (result anyref))) + ;; CHECK: (type $3 (func (result externref))) ;; CHECK: (type $4 (func (result (ref any)))) @@ -262,7 +262,7 @@ ) - ;; CHECK: (func $string.new-mutable (type $3) (result anyref) + ;; CHECK: (func $string.new-mutable (type $3) (result externref) ;; CHECK-NEXT: (string.new_wtf16_array ;; CHECK-NEXT: (array.new_fixed $array16 4 ;; CHECK-NEXT: (i32.const 65) @@ -274,7 +274,7 @@ ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $string.new-mutable (result anyref) + (func $string.new-mutable (result externref) ;; We do not precompute this because the array is mutable, and we do not yet ;; do an analysis to see that it does not "escape" into places that modify it. (string.new_wtf16_array @@ -289,10 +289,10 @@ ) ) - ;; CHECK: (func $string.new-immutable (type $3) (result anyref) + ;; CHECK: (func $string.new-immutable (type $3) (result externref) ;; CHECK-NEXT: (string.const "ABCD") ;; CHECK-NEXT: ) - (func $string.new-immutable (result anyref) + (func $string.new-immutable (result externref) ;; This array is immutable and we can optimize here. (string.new_wtf16_array (array.new_fixed $array16-imm 4 diff --git a/test/lit/passes/string-lowering-instructions.wast b/test/lit/passes/string-lowering-instructions.wast index 1fd470ff822..d9a9fe6e77c 100644 --- a/test/lit/passes/string-lowering-instructions.wast +++ b/test/lit/passes/string-lowering-instructions.wast @@ -236,7 +236,8 @@ (if (result stringref) (i32.const 0) (then - (ref.null none) ;; this will turn into noextern + (ref.null noextern) ;; The change from stringref to externref does not + ;; cause problems here, nothing needs to change. ) (else (local.get $ref) @@ -263,7 +264,7 @@ (local.get $ref) ) (else - (ref.null none) + (ref.null noextern) ) ) ) @@ -378,10 +379,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $struct-of-string - ;; Test lowering of struct fields from stringref to externref. + ;; Test lowering of struct fields from stringref to externref. This was more + ;; useful of a test when stringref was a subtype of anyref, but it is still + ;; useful to verify nothing here goes wrong. (Now we convert stringref to + ;; externref, a supertype, which is much simpler - same bottom type, etc.) (drop (struct.new $struct-of-string - (ref.null none) ;; This null must be fixed to be ext. + (ref.null noextern) ;; This null is already of the right type. (i32.const 10) (ref.null none) ;; Nothing to do here (field remains anyref). ) diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 55eb8b3f31a..57f9609abc3 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,58 +1,56 @@ Metrics total - [exports] : 16 + [exports] : 17 [funcs] : 19 [globals] : 4 [imports] : 12 [memories] : 1 [memory-data] : 17 - [table-data] : 6 + [table-data] : 5 [tables] : 2 - [tags] : 2 - [total] : 850 - [vars] : 49 - ArrayNewFixed : 5 + [tags] : 3 + [total] : 917 + [vars] : 42 + ArrayNewFixed : 1 AtomicCmpxchg : 1 - AtomicFence : 2 AtomicRMW : 1 - Binary : 91 - Block : 112 - Break : 5 - Call : 29 + Binary : 94 + Block : 127 + Break : 4 + Call : 40 CallIndirect : 1 - CallRef : 2 - Const : 171 - Drop : 8 - GlobalGet : 55 - GlobalSet : 48 + CallRef : 1 + Const : 170 + DataDrop : 2 + Drop : 16 + GlobalGet : 61 + GlobalSet : 54 I31Get : 2 - If : 37 - Load : 26 - LocalGet : 53 - LocalSet : 31 - Loop : 6 - MemoryCopy : 2 + If : 33 + Load : 23 + LocalGet : 70 + LocalSet : 42 + Loop : 9 + MemoryCopy : 3 MemoryFill : 1 - Nop : 6 - Pop : 2 - RefAs : 1 - RefEq : 3 - RefFunc : 11 + Nop : 7 + Pop : 4 + RefAs : 2 + RefEq : 5 + RefFunc : 6 RefI31 : 8 - RefIsNull : 1 - RefNull : 5 - RefTest : 1 - Return : 13 - SIMDExtract : 1 - Select : 8 + RefNull : 2 + Return : 12 + SIMDExtract : 3 + Select : 7 Store : 2 - StringConst : 12 - StringEq : 4 - StringWTF16Get : 1 - StructNew : 6 - Try : 3 + StringConst : 7 + StringEq : 3 + StructNew : 5 + Throw : 1 + Try : 4 TryTable : 4 - TupleExtract : 3 - TupleMake : 5 - Unary : 37 - Unreachable : 24 + TupleExtract : 13 + TupleMake : 3 + Unary : 36 + Unreachable : 27 From adfdb1bebdf2cacbff87e5ac5f462a8c1edddd7f Mon Sep 17 00:00:00 2001 From: Daniel Lehmann Date: Thu, 20 Mar 2025 20:03:45 +0100 Subject: [PATCH 362/622] Do not pass C++-only flag to C compiler [NFC] (#7382) And some minor drive-by fixes: gitignore Ninja in-tree build file, update mimalloc to latest stable release. This gets rid of warnings in the mimalloc cmake/make step. See https://github.com/microsoft/mimalloc/issues/1031#issuecomment-2740351301 and https://github.com/microsoft/mimalloc/issues/1038. --- .gitignore | 1 + CMakeLists.txt | 13 +++++++++---- third_party/CMakeLists.txt | 2 -- third_party/mimalloc | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 7acc6f9cd76..28caf910140 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ CMakeFiles /*.ninja /.ninja_deps /.ninja_log +/.ninja_log.last_upload /bin/ /lib/ /_deps/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b48ae745dd9..fdeb3e58b21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,9 +114,14 @@ find_package(Threads REQUIRED) function(add_compile_flag value) message(STATUS "Building with ${value}") - foreach(variable CMAKE_C_FLAGS CMAKE_CXX_FLAGS) - set(${variable} "${${variable}} ${value}" PARENT_SCOPE) - endforeach(variable) + # You can use the optional second argument to suppress passing C++-only flags to the C compiler. + if(ARGV1 STREQUAL "CXX_ONLY") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${value}" PARENT_SCOPE) + else() + foreach(variable CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + set(${variable} "${${variable}} ${value}" PARENT_SCOPE) + endforeach(variable) + endif() endfunction() function(add_debug_compile_flag value) @@ -269,7 +274,7 @@ else() # MSVC add_compile_flag("-fno-omit-frame-pointer") if(NOT BUILD_FUZZTEST) # fuzztest depends on RTTIs. - add_compile_flag("-fno-rtti") + add_compile_flag("-fno-rtti" "CXX_ONLY") endif() if(WIN32) add_compile_flag("-D_GNU_SOURCE") diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 9bcd9838acf..37929fb06c6 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -17,8 +17,6 @@ if(BUILD_LLVM_DWARF) endif() if(MIMALLOC_STATIC) - # Using a C++ compiler avoids warnings about -fno-rtti with a C compiler. - set(MI_USE_CXX ON) # We only need the static library, nothing else. set(MI_BUILD_STATIC ON) set(MI_BUILD_SHARED OFF) diff --git a/third_party/mimalloc b/third_party/mimalloc index e1123000597..891f9f4cf6a 160000 --- a/third_party/mimalloc +++ b/third_party/mimalloc @@ -1 +1 @@ -Subproject commit e1123000597b1abe38164e09d5713652e1e82e59 +Subproject commit 891f9f4cf6afbd213d7260b880795af523646c11 From d98e3c46bcff99ca3548aaebc2d15a276eb38ead Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 20 Mar 2025 14:27:17 -0700 Subject: [PATCH 363/622] [test] Use test-specific filename when running spec tests (#7384) These means that the intermediate files don't conflict with each other and you can inspect them by name after the test run. --- check.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/check.py b/check.py index 7385fb0f0cb..128cdae8b0f 100755 --- a/check.py +++ b/check.py @@ -225,25 +225,24 @@ def check_expected(actual, expected): # check binary format. here we can verify execution of the final # result, no need for an output verification - split_num = 0 actual = '' - with open('spec.wast', 'w') as transformed_spec_file: - for module, asserts in support.split_wast(wast): + with open(base, 'w') as transformed_spec_file: + for i, (module, asserts) in enumerate(support.split_wast(wast)): if not module: # Skip any initial assertions that don't have a module continue - print(' testing split module', split_num) - split_num += 1 - support.write_wast('split.wast', module) - run_opt_test('split.wast') # also that our optimizer doesn't break on it - result_wast_file = shared.binary_format_check('split.wast', verify_final_result=False) + print(f' testing split module {i}') + split_name = os.path.splitext(base)[0] + f'_split{i}.wast' + support.write_wast(split_name, module) + run_opt_test(split_name) # also that our optimizer doesn't break on it + result_wast_file = shared.binary_format_check(split_name, verify_final_result=False) with open(result_wast_file) as f: result_wast = f.read() # add the asserts, and verify that the test still passes transformed_spec_file.write(result_wast + '\n' + '\n'.join(asserts)) # compare all the outputs to the expected output - actual = run_spec_test('spec.wast') + actual = run_spec_test(base) check_expected(actual, os.path.join(shared.get_test_dir('spec'), 'expected-output', base + '.log')) From 3f341e501994f3b42b1732e5b1a776b86e1703e6 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 20 Mar 2025 16:42:31 -0700 Subject: [PATCH 364/622] Exclude the mimalloc files from the Binaryen install (#7386) Since mimalloc is linked statically into the Binaryen tools, none of its files need to be installed with Binaryen. Also use CMAKE_SYSTEM_NAME instead of LINUX, as the latter was introduced in CMake 3.25 --- CMakeLists.txt | 2 +- third_party/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fdeb3e58b21..c6ebf9f5381 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -468,7 +468,7 @@ if(BUILD_LLVM_DWARF) endif() if(MIMALLOC_STATIC) - if(NOT(LINUX AND BUILD_STATIC_LIB) OR EMSCRIPTEN) + if(NOT(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND BUILD_STATIC_LIB) OR EMSCRIPTEN) message(FATAL_ERROR "Statically linking mimalloc is only supported when building as a native, statically linked library on Linux.") endif() message(STATUS "Building with statically linked mimalloc allocator.") diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 37929fb06c6..ca0c2bc39a9 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -26,5 +26,5 @@ if(MIMALLOC_STATIC) # (They can still be enabled via MIMALLOC_VERBOSE=1 wasm-opt ...) add_compile_definitions(MI_DEBUG=0) - add_subdirectory(mimalloc) + add_subdirectory(mimalloc EXCLUDE_FROM_ALL) endif() From cd3b26d6e2e206e35911524049966dee48f98c2b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 21 Mar 2025 10:03:53 -0700 Subject: [PATCH 365/622] Require RefFunc to have the proper type (#7376) As a holdout from before GC was implemented, we previously allowed RefFunc expressions to have type `funcref` rather than a specific signature type matching that of the referenced function. Remove this allowance and start requiring the types to be correct and precise to eliminate the possibility of stale types inhibiting (or invalidating!) optimizations. Update various older passes to update the types of RefFuncs, including those in tables, to keep their output passing validation. Also update the kitchen sink example test to construct RefFunc expressions with the correct type via the C API. --- CHANGELOG.md | 4 ++ src/binaryen-c.cpp | 17 +++++--- src/binaryen-c.h | 2 +- src/ir/element-utils.h | 2 +- src/js/binaryen.js-post.js | 4 ++ src/passes/FuncCastEmulation.cpp | 34 +++++++++------ src/passes/I64ToI32Lowering.cpp | 33 ++++++++++++++ src/passes/LegalizeJSInterface.cpp | 53 ++++++++++------------- src/passes/PrintCallGraph.cpp | 2 +- src/passes/RemoveImports.cpp | 2 +- src/passes/RemoveUnusedModuleElements.cpp | 2 +- src/passes/ReorderFunctions.cpp | 2 +- src/wasm/wasm-validator.cpp | 22 +++++----- src/wasm/wasm.cpp | 1 + test/binaryen.js/expressions.js | 12 +++-- test/binaryen.js/kitchen-sink.js | 8 +++- test/binaryen.js/kitchen-sink.js.txt | 10 +++-- test/binaryen.js/validation_errors.js | 4 +- test/example/c-api-kitchen-sink.c | 15 +++++-- test/example/c-api-kitchen-sink.txt | 2 +- 20 files changed, 146 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e63e9ee5ae..1f6d3bb6f27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ Current Trunk - `string` is now a subtype of `ext` (rather than `any`). This allows better transformations for strings, like an inverse of StringLowering, but will error on codebases that depend on being able to pass strings into anyrefs. + - Require the type of RefFunc expressions to match the type of the referenced + function. It is no longer valid to type them as funcref in the IR. + - The C and JS APIs for creating RefFunc expressions now take a HeapType + instead of a Type. v122 ---- diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index b32b7d6ff22..82261c0dc63 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1606,12 +1606,11 @@ BinaryenExpressionRef BinaryenRefAs(BinaryenModuleRef module, Builder(*(Module*)module).makeRefAs(RefAsOp(op), (Expression*)value)); } -BinaryenExpressionRef -BinaryenRefFunc(BinaryenModuleRef module, const char* func, BinaryenType type) { - // TODO: consider changing the C API to receive a heap type - Type type_(type); +BinaryenExpressionRef BinaryenRefFunc(BinaryenModuleRef module, + const char* func, + BinaryenHeapType type) { return static_cast( - Builder(*(Module*)module).makeRefFunc(func, type_.getHeapType())); + Builder(*(Module*)module).makeRefFunc(func, HeapType(type))); } BinaryenExpressionRef BinaryenRefEq(BinaryenModuleRef module, @@ -6259,8 +6258,12 @@ bool TypeBuilderBuildAndDispose(TypeBuilderRef builder, auto* B = (TypeBuilder*)builder; auto result = B->build(); if (auto err = result.getError()) { - *errorIndex = err->index; - *errorReason = static_cast(err->reason); + if (errorIndex) { + *errorIndex = err->index; + } + if (errorReason) { + *errorReason = static_cast(err->reason); + } delete B; return false; } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 5feca3ba2e3..06604a510a8 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -961,7 +961,7 @@ BINARYEN_API BinaryenExpressionRef BinaryenRefAs(BinaryenModuleRef module, BinaryenExpressionRef value); BINARYEN_API BinaryenExpressionRef BinaryenRefFunc(BinaryenModuleRef module, const char* func, - BinaryenType type); + BinaryenHeapType type); BINARYEN_API BinaryenExpressionRef BinaryenRefEq(BinaryenModuleRef module, BinaryenExpressionRef left, BinaryenExpressionRef right); diff --git a/src/ir/element-utils.h b/src/ir/element-utils.h index e7ff54b9152..ed077fdc6d2 100644 --- a/src/ir/element-utils.h +++ b/src/ir/element-utils.h @@ -42,7 +42,7 @@ template inline void iterAllElementFunctionNames(const Module* wasm, T visitor) { for (auto& segment : wasm->elementSegments) { iterElementSegmentFunctionNames( - segment.get(), [&](Name& name, Index i) { visitor(name); }); + segment.get(), [&](const Name& name, Index i) { visitor(name); }); } } diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 6e8d7a958d2..5d6022d2550 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -3275,6 +3275,7 @@ Module['getFunctionInfo'] = function(func) { 'name': UTF8ToString(Module['_BinaryenFunctionGetName'](func)), 'module': UTF8ToString(Module['_BinaryenFunctionImportGetModule'](func)), 'base': UTF8ToString(Module['_BinaryenFunctionImportGetBase'](func)), + 'type': Module['_BinaryenFunctionGetType'](func), 'params': Module['_BinaryenFunctionGetParams'](func), 'results': Module['_BinaryenFunctionGetResults'](func), 'vars': getAllNested(func, Module['_BinaryenFunctionGetNumVars'], Module['_BinaryenFunctionGetVar']), @@ -4893,6 +4894,9 @@ Module['Function'] = (() => { Function['getName'] = function(func) { return UTF8ToString(Module['_BinaryenFunctionGetName'](func)); }; + Function['getType'] = function(func) { + return Module['_BinaryenFunctionGetType'](func); + } Function['getParams'] = function(func) { return Module['_BinaryenFunctionGetParams'](func); }; diff --git a/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp index 49ef2fd7a03..1a1324c99e8 100644 --- a/src/passes/FuncCastEmulation.cpp +++ b/src/passes/FuncCastEmulation.cpp @@ -162,17 +162,26 @@ struct FuncCastEmulation : public Pass { HeapType ABIType( Signature(Type(std::vector(numParams, Type::i64)), Type::i64)); // Add a thunk for each function in the table, and do the call through it. - std::unordered_map funcThunks; - ElementUtils::iterAllElementFunctionNames(module, [&](Name& name) { - auto iter = funcThunks.find(name); - if (iter == funcThunks.end()) { - auto thunk = makeThunk(name, module, numParams); - funcThunks[name] = thunk; - name = thunk; - } else { - name = iter->second; + std::unordered_map funcThunks; + for (auto& segment : module->elementSegments) { + if (!segment->type.isFunction()) { + continue; } - }); + for (Index i = 0; i < segment->data.size(); ++i) { + auto* ref = segment->data[i]->dynCast(); + if (!ref) { + continue; + } + auto [iter, inserted] = funcThunks.insert({ref->func, nullptr}); + if (inserted) { + iter->second = makeThunk(ref->func, module, numParams); + } + auto* thunk = iter->second; + ref->func = thunk->name; + // TODO: Make this exact. + ref->type = Type(thunk->type, NonNullable); + } + } // update call_indirects ParallelFuncCastEmulation(ABIType, numParams).run(getPassRunner(), module); @@ -180,7 +189,7 @@ struct FuncCastEmulation : public Pass { private: // Creates a thunk for a function, casting args and return value as needed. - Name makeThunk(Name name, Module* module, Index numParams) { + Function* makeThunk(Name name, Module* module, Index numParams) { Name thunk = std::string("byn$fpcast-emu$") + name.toString(); if (module->getFunctionOrNull(thunk)) { Fatal() << "FuncCastEmulation::makeThunk seems a thunk name already in " @@ -207,8 +216,7 @@ struct FuncCastEmulation : public Pass { {}, // no vars toABI(call, module)); thunkFunc->hasExplicitName = true; - module->addFunction(std::move(thunkFunc)); - return thunk; + return module->addFunction(std::move(thunkFunc)); } }; diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index 0f591871b3c..67dfd36d849 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -297,6 +297,39 @@ struct I64ToI32Lowering : public WalkerPass> { }); } + void visitRefFunc(RefFunc* curr) { + auto sig = curr->type.getHeapType().getSignature(); + + auto lowerTypes = [](Type types) { + bool hasI64 = false; + for (auto t : types) { + if (t == Type::i64) { + hasI64 = true; + break; + } + } + if (!hasI64) { + return types; + } + std::vector newTypes; + for (auto t : types) { + if (t == Type::i64) { + newTypes.push_back(Type::i32); + newTypes.push_back(Type::i32); + } else { + newTypes.push_back(t); + } + } + return Type(newTypes); + }; + + auto newParams = lowerTypes(sig.params); + auto newResults = lowerTypes(sig.results); + if (newParams != sig.params || newResults != sig.results) { + curr->type = curr->type.with(HeapType(Signature(newParams, newResults))); + } + } + void visitLocalGet(LocalGet* curr) { const auto mappedIndex = indexMap[curr->index]; // Need to remap the local into the new naming scheme, regardless of diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 427b8dfac0e..f7187b3d533 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -108,22 +108,14 @@ struct LegalizeJSInterface : public Pass { // for each illegal import, we must call a legalized stub instead for (auto* im : originalFunctions) { if (im->imported() && isIllegal(im)) { - auto funcName = makeLegalStubForCalledImport(im, module); - illegalImportsToLegal[im->name] = funcName; - // we need to use the legalized version in the tables, as the import - // from JS is legal for JS. Our stub makes it look like a native wasm - // function. - ElementUtils::iterAllElementFunctionNames(module, [&](Name& name) { - if (name == im->name) { - name = funcName; - } - }); + auto* func = makeLegalStubForCalledImport(im, module); + illegalImportsToLegal[im->name] = func; } } if (!illegalImportsToLegal.empty()) { - // fix up imports: call_import of an illegal must be turned to a call of a - // legal. the same must be done with ref.funcs. + // fix up imports: call of an illegal import must be turned to a call of a + // legal import. the same must be done with ref.funcs. struct Fixer : public WalkerPass> { bool isFunctionParallel() override { return true; } @@ -131,34 +123,37 @@ struct LegalizeJSInterface : public Pass { return std::make_unique(illegalImportsToLegal); } - std::map* illegalImportsToLegal; + std::unordered_map& illegalImportsToLegal; - Fixer(std::map* illegalImportsToLegal) + Fixer(std::unordered_map& illegalImportsToLegal) : illegalImportsToLegal(illegalImportsToLegal) {} void visitCall(Call* curr) { - auto iter = illegalImportsToLegal->find(curr->target); - if (iter == illegalImportsToLegal->end()) { + auto iter = illegalImportsToLegal.find(curr->target); + if (iter == illegalImportsToLegal.end()) { return; } - replaceCurrent( - Builder(*getModule()) - .makeCall( - iter->second, curr->operands, curr->type, curr->isReturn)); + replaceCurrent(Builder(*getModule()) + .makeCall(iter->second->name, + curr->operands, + curr->type, + curr->isReturn)); } void visitRefFunc(RefFunc* curr) { - auto iter = illegalImportsToLegal->find(curr->func); - if (iter == illegalImportsToLegal->end()) { + auto iter = illegalImportsToLegal.find(curr->func); + if (iter == illegalImportsToLegal.end()) { return; } - curr->func = iter->second; + curr->func = iter->second->name; + // TODO: Make this exact. + curr->type = Type(iter->second->type, NonNullable); } }; - Fixer fixer(&illegalImportsToLegal); + Fixer fixer(illegalImportsToLegal); fixer.run(getPassRunner(), module); fixer.runOnModuleCode(getPassRunner(), module); @@ -174,7 +169,7 @@ struct LegalizeJSInterface : public Pass { private: // map of illegal to legal names for imports - std::map illegalImportsToLegal; + std::unordered_map illegalImportsToLegal; bool exportedHelpers = false; Function* getTempRet0 = nullptr; Function* setTempRet0 = nullptr; @@ -274,7 +269,7 @@ struct LegalizeJSInterface : public Pass { // wasm calls the import, so it must call a stub that calls the actual legal // JS import - Name makeLegalStubForCalledImport(Function* im, Module* module) { + Function* makeLegalStubForCalledImport(Function* im, Module* module) { Builder builder(*module); auto legalIm = std::make_unique(); legalIm->name = Name(std::string("legalimport$") + im->name.toString()); @@ -315,14 +310,14 @@ struct LegalizeJSInterface : public Pass { } legalIm->type = Signature(Type(params), call->type); - const auto& stubName = stub->name; - if (!module->getFunctionOrNull(stubName)) { + auto* stubPtr = stub.get(); + if (!module->getFunctionOrNull(stub->name)) { module->addFunction(std::move(stub)); } if (!module->getFunctionOrNull(legalIm->name)) { module->addFunction(std::move(legalIm)); } - return stubName; + return stubPtr; } static Function* diff --git a/src/passes/PrintCallGraph.cpp b/src/passes/PrintCallGraph.cpp index 6a4b1d50048..27095726f53 100644 --- a/src/passes/PrintCallGraph.cpp +++ b/src/passes/PrintCallGraph.cpp @@ -96,7 +96,7 @@ struct PrintCallGraph : public Pass { CallPrinter printer(module); // Indirect Targets - ElementUtils::iterAllElementFunctionNames(module, [&](Name& name) { + ElementUtils::iterAllElementFunctionNames(module, [&](Name name) { auto* func = module->getFunction(name); o << " \"" << func->name << "\" [style=\"filled, rounded\"];\n"; }); diff --git a/src/passes/RemoveImports.cpp b/src/passes/RemoveImports.cpp index af216ecbdc0..57f38d4c744 100644 --- a/src/passes/RemoveImports.cpp +++ b/src/passes/RemoveImports.cpp @@ -51,7 +51,7 @@ struct RemoveImports : public WalkerPass> { // Do not remove names referenced in a table std::set indirectNames; ElementUtils::iterAllElementFunctionNames( - curr, [&](Name& name) { indirectNames.insert(name); }); + curr, [&](Name name) { indirectNames.insert(name); }); for (auto& name : names) { if (indirectNames.find(name) == indirectNames.end()) { curr->removeFunction(name); diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 4677ac3b1b9..8c1662b66e4 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -694,7 +694,7 @@ struct RemoveUnusedModuleElements : public Pass { // For now, all functions that can be called indirectly are marked as roots. // TODO: Compute this based on which ElementSegments are actually used, // and which functions have a call_indirect of the proper type. - ElementUtils::iterAllElementFunctionNames(module, [&](Name& name) { + ElementUtils::iterAllElementFunctionNames(module, [&](Name name) { roots.emplace_back(ModuleElementKind::Function, name); }); diff --git a/src/passes/ReorderFunctions.cpp b/src/passes/ReorderFunctions.cpp index aa8cbf4582e..f14fc8f381a 100644 --- a/src/passes/ReorderFunctions.cpp +++ b/src/passes/ReorderFunctions.cpp @@ -81,7 +81,7 @@ struct ReorderFunctions : public Pass { } } ElementUtils::iterAllElementFunctionNames( - module, [&](Name& name) { counts[name]++; }); + module, [&](Name name) { counts[name]++; }); // TODO: count all RefFunc as well // TODO: count the declaration section as well, which adds another mention // sort diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 5676ce55b58..3b7e33390f3 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2364,23 +2364,21 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) { "ref.func should have a non-nullable reference type")) { return; } + if (!shouldBeTrue(curr->type.isSignature(), + curr, + "ref.func must have a function reference type")) { + return; + } if (!info.validateGlobally) { return; } auto* func = getModule()->getFunctionOrNull(curr->func); - shouldBeTrue(!!func, curr, "function argument of ref.func must exist"); - shouldBeTrue(curr->type.isFunction(), + if (!shouldBeTrue(!!func, curr, "function argument of ref.func must exist")) { + return; + } + shouldBeTrue(func->type == curr->type.getHeapType(), curr, - "ref.func must have a function reference type"); - shouldBeTrue( - !curr->type.isNullable(), curr, "ref.func must have non-nullable type"); - // TODO: verify it also has a typed function references type, and the right - // one, - // curr->type.getHeapType().getSignature() - // That is blocked on having the ability to create signature types in the C - // API (for now those users create the type with funcref). This also needs to - // be fixed in LegalizeJSInterface and FuncCastEmulation and other places that - // update function types. + "function reference type must match referenced function type"); } void FunctionValidator::visitRefEq(RefEq* curr) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 81b2fc43516..5cdefe3d25a 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -818,6 +818,7 @@ void RefIsNull::finalize() { void RefFunc::finalize() { // No-op. We assume that the full proper typed function type has been applied // previously. + assert(type.isSignature()); } void RefFunc::finalize(Type type_) { type = type_; } diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index c05e820a745..0d1c9669084 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -1475,22 +1475,20 @@ console.log("# RefAs"); console.log("# RefFunc"); (function testRefFunc() { const module = new binaryen.Module(); + module.addFunction("a", binaryen.none, binaryen.none, [], module.nop()); + var type = binaryen.Function(module.getFunction("a")).type; var func = "a"; - const theRefFunc = binaryen.RefFunc(module.ref.func(func, binaryen.funcref)); + const theRefFunc = binaryen.RefFunc(module.ref.func(func, type)); assert(theRefFunc instanceof binaryen.RefFunc); assert(theRefFunc instanceof binaryen.Expression); assert(theRefFunc.func === func); - // TODO: check the type. the type is (ref func), that is, a non-nullable func, - // which differs from funcref. we don't have the ability to create such - // a type in the C/JS APIs yet. + assert(theRefFunc.type === type); theRefFunc.func = func = "b"; assert(theRefFunc.func === func); - theRefFunc.type = binaryen.f64; theRefFunc.finalize(); - // TODO The type is a subtype of funcref, but we can't check that in the JS - // API atm. + assert(theRefFunc.type === type); console.log(theRefFunc.toText()); assert( diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index acef392c377..04cf4ef8e4a 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -226,6 +226,10 @@ function test_core() { temp13 = makeInt32(10), temp14 = makeInt32(11), temp15 = makeInt32(110), temp16 = makeInt64(111); + // Create a function to reference. + module.addFunction("foobar", iIfF, binaryen.i32, [], module.i32.const(0)); + var foobarType = binaryen.Function(module.getFunction("foobar")).getType(); + var valueList = [ // Unary module.i32.clz(module.i32.const(-10)), @@ -597,8 +601,8 @@ function test_core() { // Reference types module.ref.is_null(module.ref.null(binaryen.externref)), module.ref.is_null(module.ref.null(binaryen.funcref)), - module.ref.is_null(module.ref.func("kitchen()sinker", binaryen.funcref)), - module.select(temp10, module.ref.null(binaryen.funcref), module.ref.func("kitchen()sinker", binaryen.funcref)), + module.ref.is_null(module.ref.func("foobar", foobarType)), + module.select(temp10, module.ref.null(binaryen.funcref), module.ref.func("foobar", foobarType)), // GC module.ref.eq(module.ref.null(binaryen.eqref), module.ref.null(binaryen.eqref)), diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 711972ad48b..bee1d298937 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -139,12 +139,16 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} (data $y1 "I am passive") (table $t0 1 funcref) (elem $e0 (i32.const 0) $"kitchen()sinker") + (elem declare func $foobar) (tag $a-tag (type $1) (param i32)) (export "mem" (memory $0)) (export "kitchen_sinker" (func $"kitchen()sinker")) (export "a-global-exp" (global $a-global)) (export "a-tag-exp" (tag $a-tag)) (start $starter) + (func $foobar (type $0) (param $0 i32) (param $1 i64) (param $2 f32) (param $3 f64) (result i32) + (i32.const 0) + ) (func $"kitchen()sinker" (type $0) (param $0 i32) (param $1 i64) (param $2 f32) (param $3 f64) (result i32) (local $4 i32) (block $the-body (result i32) @@ -2081,13 +2085,13 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} ) (drop (ref.is_null - (ref.func $"kitchen()sinker") + (ref.func $foobar) ) ) (drop - (select (result funcref) + (select (result (ref null $0)) (ref.null nofunc) - (ref.func $"kitchen()sinker") + (ref.func $foobar) (i32.const 1) ) ) diff --git a/test/binaryen.js/validation_errors.js b/test/binaryen.js/validation_errors.js index 57e37d8ba32..b7ee5d00ea8 100644 --- a/test/binaryen.js/validation_errors.js +++ b/test/binaryen.js/validation_errors.js @@ -7,7 +7,7 @@ ) ]) ); - mod.addExport("test", func); + mod.addExport("test", "test"); console.log(mod.validate()) })(); @@ -20,6 +20,6 @@ ) ]) ); - mod.addFunctionExport("test", "test", func); + mod.addFunctionExport("test", "test"); console.log(mod.validate()) })(); diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index 93a6281f885..fcf6c78d1fb 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -450,6 +450,15 @@ void test_core() { BinaryenTypeFloat64()}; BinaryenType iIfF = BinaryenTypeCreate(iIfF_, 4); + TypeBuilderRef typeBuilder = TypeBuilderCreate(1); + TypeBuilderSetSignatureType(typeBuilder, 0, iIfF, BinaryenTypeInt32()); + BinaryenHeapType kitchenSinkerType; + bool builtType = + TypeBuilderBuildAndDispose(typeBuilder, &kitchenSinkerType, NULL, NULL); + assert(builtType); + BinaryenType kitchenSinkerRefType = + BinaryenTypeFromHeapType(kitchenSinkerType, false); + BinaryenExpressionRef temp1 = makeInt32(module, 1), temp2 = makeInt32(module, 2), temp3 = makeInt32(module, 3), @@ -471,7 +480,7 @@ void test_core() { BinaryenExpressionRef funcrefExpr = BinaryenRefNull(module, BinaryenTypeNullFuncref()); funcrefExpr = - BinaryenRefFunc(module, "kitchen()sinker", BinaryenTypeFuncref()); + BinaryenRefFunc(module, "kitchen()sinker", kitchenSinkerRefType); BinaryenExpressionRef i31refExpr = BinaryenRefI31(module, makeInt32(module, 1)); @@ -1040,7 +1049,7 @@ void test_core() { module, temp10, BinaryenRefNull(module, BinaryenTypeNullFuncref()), - BinaryenRefFunc(module, "kitchen()sinker", BinaryenTypeFuncref())), + BinaryenRefFunc(module, "kitchen()sinker", kitchenSinkerRefType)), // GC BinaryenRefEq(module, BinaryenRefNull(module, BinaryenTypeNullref()), @@ -1331,7 +1340,7 @@ void test_core() { BinaryenRemoveElementSegment(module, "p2"); BinaryenExpressionRef funcrefExpr1 = - BinaryenRefFunc(module, "kitchen()sinker", BinaryenTypeFuncref()); + BinaryenRefFunc(module, "kitchen()sinker", kitchenSinkerRefType); BinaryenExpressionPrint(BinaryenTableSet( module, "0", BinaryenConst(module, BinaryenLiteralInt32(0)), funcrefExpr1)); diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 0f9ee08a0f7..9bfc45ddf72 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -2117,7 +2117,7 @@ BinaryenFeatureAll: 4194303 ) ) (drop - (select (result funcref) + (select (result (ref null $2)) (ref.null nofunc) (ref.func $"kitchen()sinker") (i32.const 1) From 835a178ae45586133cacb1c4e4d302972b4f6b6f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 21 Mar 2025 14:39:35 -0700 Subject: [PATCH 366/622] Fix RefFunc type updating in I64ToI32Lowering (#7390) We recently started updating RefFunc types in I64ToI32Lowering to ensure that their types matched the updated types of their functions. But the way we updated the types of RefFunc expressions and Functions were not the same. When updating Functions that return i64, we replace the result type with i32 and use a global to propagate the remaining bits to the caller. Previously when updating RefFunc result types, we would instead split i64s into pairs of i32s, depending on multivalue to lower the type. Update the logic for updating RefFunc results to match the existing logic for updating Functions. --- src/passes/I64ToI32Lowering.cpp | 42 ++++++++++--------- .../passes/flatten_i64-to-i32-lowering.wast | 23 ++++++++++ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index 67dfd36d849..0cc6b2bd35b 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -300,33 +300,35 @@ struct I64ToI32Lowering : public WalkerPass> { void visitRefFunc(RefFunc* curr) { auto sig = curr->type.getHeapType().getSignature(); - auto lowerTypes = [](Type types) { - bool hasI64 = false; - for (auto t : types) { - if (t == Type::i64) { - hasI64 = true; - break; - } - } - if (!hasI64) { - return types; + bool hasI64Param = false; + for (auto t : sig.params) { + if (t == Type::i64) { + hasI64Param = true; + break; } - std::vector newTypes; - for (auto t : types) { + } + auto params = sig.params; + if (hasI64Param) { + std::vector newParams; + for (auto t : sig.params) { if (t == Type::i64) { - newTypes.push_back(Type::i32); - newTypes.push_back(Type::i32); + newParams.push_back(Type::i32); + newParams.push_back(Type::i32); } else { - newTypes.push_back(t); + newParams.push_back(t); } } - return Type(newTypes); + params = Type(newParams); }; + auto results = sig.results; + // Update the results the same way we do when visiting functions. We use a + // global rather than multivalue to lower i64 results. + if (results == Type::i64) { + results = Type::i32; + } - auto newParams = lowerTypes(sig.params); - auto newResults = lowerTypes(sig.results); - if (newParams != sig.params || newResults != sig.results) { - curr->type = curr->type.with(HeapType(Signature(newParams, newResults))); + if (params != sig.params || results != sig.results) { + curr->type = curr->type.with(HeapType(Signature(params, results))); } } diff --git a/test/lit/passes/flatten_i64-to-i32-lowering.wast b/test/lit/passes/flatten_i64-to-i32-lowering.wast index e5e556c79ab..beb15b2e46d 100644 --- a/test/lit/passes/flatten_i64-to-i32-lowering.wast +++ b/test/lit/passes/flatten_i64-to-i32-lowering.wast @@ -665,3 +665,26 @@ ) ) ) + +;; Make sure we update the ref.func in the table with the correct return type. +(module + (table 1 1 funcref) + + (elem (i32.const 0) $f) + + ;; CHECK: (type $0 (func (result i32))) + + ;; CHECK: (global $i64toi32_i32$HIGH_BITS (mut i32) (i32.const 0)) + + ;; CHECK: (table $0 1 1 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $f) + + ;; CHECK: (func $f (type $0) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $f (result i64) + (unreachable) + ) +) From 7508e81f0d234252a2835620eb9a6cc536392265 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 21 Mar 2025 17:06:43 -0700 Subject: [PATCH 367/622] Parsing and binary writing for custom descriptors (#7387) Implement text and binary parsing as well as binary writing for `descriptor` and `describes` clauses, as specified in the custom-descriptors proposal. Also simplify some neighboring code dealing with shared types as a drive-by. --- scripts/test/fuzzing.py | 2 + src/parser/contexts.h | 13 +++--- src/parser/parsers.h | 47 +++++++++++++++++--- src/wasm-binary.h | 6 ++- src/wasm/wasm-binary.cpp | 48 +++++++++++++++------ test/lit/basic/custom-descriptors.wast | 60 ++++++++++++++++++++++++++ 6 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 test/lit/basic/custom-descriptors.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index abb7da98002..ba36f41bcf5 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -109,6 +109,8 @@ 'coalesce-locals-exact.wast', 'remove-unused-brs-exact.wast', 'exact.wast', + # TODO: fuzzer support for custom descriptors + 'custom-descriptors.wast', ] diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 3b9cb21c3e0..fa96c270670 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -982,7 +982,9 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { void addArrayType(ArrayT) {} void setOpen() {} void setShared() {} - Result<> addSubtype(HeapTypeT) { return Ok{}; } + void setDescribes(HeapTypeT) {} + void setDescriptor(HeapTypeT) {} + void setSupertype(HeapTypeT) {} void finishTypeDef(Name name, Index pos) { // TODO: type annotations typeDefs.push_back({name, pos, Index(typeDefs.size()), {}}); @@ -1157,10 +1159,11 @@ struct ParseTypeDefsCtx : TypeParserCtx { void setShared() { builder[index].setShared(); } - Result<> addSubtype(HeapTypeT super) { - builder[index].subTypeOf(super); - return Ok{}; - } + void setDescribes(HeapTypeT desc) { builder[index].describes(desc); } + + void setDescriptor(HeapTypeT desc) { builder[index].descriptor(desc); } + + void setSupertype(HeapTypeT super) { builder[index].subTypeOf(super); } void finishTypeDef(Name name, Index pos) { names[index++].name = name; } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index b54de9979c6..26c20767938 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -357,6 +357,8 @@ Result typeuse(Ctx&, bool allowNames = true); MaybeResult inlineImport(Lexer&); Result> inlineExports(Lexer&); template Result<> comptype(Ctx&); +template Result<> describedcomptype(Ctx&); +template Result<> describingcomptype(Ctx&); template Result<> sharecomptype(Ctx&); template Result<> subtype(Ctx&); template MaybeResult<> typedef_(Ctx&); @@ -2940,19 +2942,50 @@ template Result<> comptype(Ctx& ctx) { return ctx.in.err("expected type description"); } -// sharecomptype ::= '(' 'shared' t:comptype ')' => shared t -// | t:comptype => unshared t +// describedcomptype ::= '(' 'descriptor' typeidx ct:comptype ')' +// | ct:comptype +template Result<> describedcomptype(Ctx& ctx) { + if (ctx.in.takeSExprStart("descriptor"sv)) { + auto x = typeidx(ctx); + CHECK_ERR(x); + ctx.setDescriptor(*x); + CHECK_ERR(comptype(ctx)); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of described type"); + } + return Ok{}; + } + return comptype(ctx); +} + +// describingcomptype ::= '(' 'describes' typeidx ct:describedcomptype ')' +// | ct: describedcomptype +template Result<> describingcomptype(Ctx& ctx) { + if (ctx.in.takeSExprStart("describes"sv)) { + auto x = typeidx(ctx); + CHECK_ERR(x); + ctx.setDescribes(*x); + CHECK_ERR(describedcomptype(ctx)); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of describing type"); + } + return Ok{}; + } + return describedcomptype(ctx); +} + +// sharecomptype ::= '(' 'shared' t:describingcomptype ')' => shared t +// | t:describingcomptype => unshared t template Result<> sharecomptype(Ctx& ctx) { if (ctx.in.takeSExprStart("shared"sv)) { ctx.setShared(); - CHECK_ERR(comptype(ctx)); + CHECK_ERR(describingcomptype(ctx)); if (!ctx.in.takeRParen()) { return ctx.in.err("expected end of shared comptype"); } - } else { - CHECK_ERR(comptype(ctx)); + return Ok{}; } - return Ok{}; + return describingcomptype(ctx); } // subtype ::= '(' 'sub' typeidx? sharecomptype ')' | sharecomptype @@ -2963,7 +2996,7 @@ template Result<> subtype(Ctx& ctx) { } if (auto super = maybeTypeidx(ctx)) { CHECK_ERR(super); - CHECK_ERR(ctx.addSubtype(*super)); + ctx.setSupertype(*super); } CHECK_ERR(sharecomptype(ctx)); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 77e13326999..9d99e20ce75 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -349,9 +349,11 @@ enum EncodedType { Array = 0x5e, Sub = 0x50, SubFinal = 0x4f, - SharedDef = 0x65, - Shared = -0x1b, // Also 0x65 as an SLEB128 + Shared = 0x65, + SharedLEB = -0x1b, // Also 0x65 as an SLEB128 Rec = 0x4e, + Descriptor = 0x4d, + Describes = 0x4c, // block_type Empty = -0x40, // 0x40 }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index de6a5867b93..c3b6d310d12 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -285,7 +285,15 @@ void WasmBinaryWriter::writeTypes() { } } if (type.isShared()) { - o << uint8_t(BinaryConsts::EncodedType::SharedDef); + o << uint8_t(BinaryConsts::EncodedType::Shared); + } + if (auto desc = type.getDescribedType()) { + o << uint8_t(BinaryConsts::EncodedType::Describes); + writeHeapType(*desc); + } + if (auto desc = type.getDescriptorType()) { + o << uint8_t(BinaryConsts::EncodedType::Descriptor); + writeHeapType(*desc); } switch (type.getKind()) { case HeapTypeKind::Func: { @@ -1680,7 +1688,7 @@ void WasmBinaryWriter::writeHeapType(HeapType type) { int ret = 0; if (type.isShared()) { - o << S32LEB(BinaryConsts::EncodedType::Shared); + o << uint8_t(BinaryConsts::EncodedType::Shared); } switch (type.getBasic(Unshared)) { case HeapType::ext: @@ -2206,7 +2214,7 @@ HeapType WasmBinaryReader::getHeapType() { return types[type]; } auto share = Unshared; - if (type == BinaryConsts::EncodedType::Shared) { + if (type == BinaryConsts::EncodedType::SharedLEB) { share = Shared; type = getS64LEB(); // TODO: Actually s33 } @@ -2341,7 +2349,7 @@ void WasmBinaryReader::readTypes() { auto readHeapType = [&]() -> HeapType { int64_t htCode = getS64LEB(); // TODO: Actually s33 auto share = Unshared; - if (htCode == BinaryConsts::EncodedType::Shared) { + if (htCode == BinaryConsts::EncodedType::SharedLEB) { share = Shared; htCode = getS64LEB(); // TODO: Actually s33 } @@ -2467,7 +2475,6 @@ void WasmBinaryReader::readTypes() { builder.createRecGroup(i, groupSize); form = getInt8(); } - std::optional superIndex; if (form == BinaryConsts::EncodedType::Sub || form == BinaryConsts::EncodedType::SubFinal) { if (form == BinaryConsts::EncodedType::Sub) { @@ -2479,14 +2486,34 @@ void WasmBinaryReader::readTypes() { throwError("Invalid type definition with " + std::to_string(supers) + " supertypes"); } - superIndex = getU32LEB(); + auto superIdx = getU32LEB(); + if (superIdx >= builder.size()) { + throwError("invalid supertype index: " + std::to_string(superIdx)); + } + builder[i].subTypeOf(builder[superIdx]); } form = getInt8(); } - if (form == BinaryConsts::SharedDef) { + if (form == BinaryConsts::EncodedType::Shared) { builder[i].setShared(); form = getInt8(); } + if (form == BinaryConsts::EncodedType::Describes) { + auto descIdx = getU32LEB(); + if (descIdx >= builder.size()) { + throwError("invalid described type index: " + std::to_string(descIdx)); + } + builder[i].describes(builder[descIdx]); + form = getInt8(); + } + if (form == BinaryConsts::EncodedType::Descriptor) { + auto descIdx = getU32LEB(); + if (descIdx >= builder.size()) { + throwError("invalid descriptor type index: " + std::to_string(descIdx)); + } + builder[i].descriptor(builder[descIdx]); + form = getInt8(); + } if (form == BinaryConsts::EncodedType::Func) { builder[i] = readSignatureDef(); } else if (form == BinaryConsts::EncodedType::Cont) { @@ -2498,13 +2525,6 @@ void WasmBinaryReader::readTypes() { } else { throwError("Bad type form " + std::to_string(form)); } - if (superIndex) { - if (*superIndex > builder.size()) { - throwError("Out of bounds supertype index " + - std::to_string(*superIndex)); - } - builder[i].subTypeOf(builder[*superIndex]); - } } auto result = builder.build(); diff --git a/test/lit/basic/custom-descriptors.wast b/test/lit/basic/custom-descriptors.wast new file mode 100644 index 00000000000..dfec0b22413 --- /dev/null +++ b/test/lit/basic/custom-descriptors.wast @@ -0,0 +1,60 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + (rec + ;; CHECK-TEXT: (rec + ;; CHECK-TEXT-NEXT: (type $described (descriptor $middle (struct))) + ;; CHECK-BIN: (rec + ;; CHECK-BIN-NEXT: (type $described (descriptor $middle (struct))) + (type $described (descriptor $middle (struct))) + ;; CHECK-TEXT: (type $middle (describes $described (descriptor $describing (struct)))) + ;; CHECK-BIN: (type $middle (describes $described (descriptor $describing (struct)))) + (type $middle (describes $described (descriptor $describing (struct)))) + ;; CHECK-TEXT: (type $describing (describes $middle (struct))) + ;; CHECK-BIN: (type $describing (describes $middle (struct))) + (type $describing (describes $middle (struct))) + ) + + (rec + ;; CHECK-TEXT: (rec + ;; CHECK-TEXT-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) + ;; CHECK-BIN: (rec + ;; CHECK-BIN-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) + (type $shared-described (shared (descriptor $shared-describing (struct)))) + ;; CHECK-TEXT: (type $shared-describing (shared (describes $shared-described (struct)))) + ;; CHECK-BIN: (type $shared-describing (shared (describes $shared-described (struct)))) + (type $shared-describing (shared (describes $shared-described (struct)))) + ) + + + ;; CHECK-TEXT: (global $g (ref null $described) (ref.null none)) + ;; CHECK-BIN: (global $g (ref null $described) (ref.null none)) + (global $g (ref null $described) (ref.null none)) + ;; CHECK-TEXT: (global $shared (ref null $shared-describing) (ref.null (shared none))) + ;; CHECK-BIN: (global $shared (ref null $shared-describing) (ref.null (shared none))) + (global $shared (ref null $shared-describing) (ref.null (shared none))) +) +;; CHECK-BIN-NODEBUG: (rec +;; CHECK-BIN-NODEBUG-NEXT: (type $0 (descriptor $1 (struct))) + +;; CHECK-BIN-NODEBUG: (type $1 (describes $0 (descriptor $2 (struct)))) + +;; CHECK-BIN-NODEBUG: (type $2 (describes $1 (struct))) + +;; CHECK-BIN-NODEBUG: (rec +;; CHECK-BIN-NODEBUG-NEXT: (type $3 (shared (descriptor $4 (struct)))) + +;; CHECK-BIN-NODEBUG: (type $4 (shared (describes $3 (struct)))) + +;; CHECK-BIN-NODEBUG: (global $global$0 (ref null $0) (ref.null none)) + +;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $4) (ref.null (shared none))) From 8958df41152f526bff03f2009525c3ab2efa4274 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 24 Mar 2025 11:06:01 -0700 Subject: [PATCH 368/622] Validate descriptor declarations (#7392) To ensure soundness, there are very particular rules about how described and descriptor type declarations must relate to one another and their supertypes. Implement and test these rules. --- src/wasm-type.h | 14 ++++ src/wasm/wasm-type.cpp | 59 ++++++++++++++ test/spec/descriptors.wast | 158 +++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 test/spec/descriptors.wast diff --git a/src/wasm-type.h b/src/wasm-type.h index 5bcf750db89..b250e38c9c8 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -816,6 +816,20 @@ struct TypeBuilder { InvalidFuncType, // A non-shared field of a shared heap type. InvalidUnsharedField, + // A describes clause on a non-struct type. + NonStructDescribes, + // The described type is an invalid forward reference. + ForwardDescribesReference, + // The described type does not have this type as a descriptor. + MismatchedDescribes, + // A descriptor clause on a non-struct type. + NonStructDescriptor, + // The descriptor type does not describe this type. + MismatchedDescriptor, + // A non-shared descriptor on a shared type. + InvalidUnsharedDescriptor, + // A non-shared type described by a shared type. + InvalidUnsharedDescribes, }; struct Error { diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index b3ffd216351..ada53eb150b 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1464,6 +1464,20 @@ std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) { return os << "Continuation has invalid function type"; case TypeBuilder::ErrorReason::InvalidUnsharedField: return os << "Heap type has an invalid unshared field"; + case TypeBuilder::ErrorReason::NonStructDescribes: + return os << "Describes clause on a non-struct type"; + case TypeBuilder::ErrorReason::ForwardDescribesReference: + return os << "Describes clause is a forward reference"; + case TypeBuilder::ErrorReason::MismatchedDescribes: + return os << "Described type is not a matching descriptor"; + case TypeBuilder::ErrorReason::NonStructDescriptor: + return os << "Descriptor clause on a non-struct type"; + case TypeBuilder::ErrorReason::MismatchedDescriptor: + return os << "Descriptor type does not describe heap type"; + case TypeBuilder::ErrorReason::InvalidUnsharedDescriptor: + return os << "Heap type has an invalid unshared descriptor"; + case TypeBuilder::ErrorReason::InvalidUnsharedDescribes: + return os << "Heap type describes an invalid unshared type"; } WASM_UNREACHABLE("Unexpected error reason"); } @@ -2370,6 +2384,25 @@ bool isValidSupertype(const HeapTypeInfo& sub, const HeapTypeInfo& super) { if (sub.kind != super.kind) { return false; } + if (sub.descriptor) { + // A supertype of a type with a (descriptor $x) must either not have a + // descriptor or have a (descriptor $y) where $y is the declared supertype + // of $x. + if (super.descriptor && sub.descriptor->supertype != super.descriptor) { + return false; + } + } else { + // A supertype of a type without a descriptor must also not have a + // descriptor. + if (super.descriptor) { + return false; + } + } + // A supertype of a type must have a describes clause iff the type has a + // describes clause. + if (bool(sub.described) != bool(super.described)) { + return false; + } SubTyper typer; switch (sub.kind) { case HeapTypeKind::Func: @@ -2399,12 +2432,38 @@ validateType(HeapTypeInfo& info, std::unordered_set& seenTypes) { return TypeBuilder::ErrorReason::InvalidSupertype; } } + if (auto* desc = info.described) { + if (info.kind != HeapTypeKind::Struct) { + return TypeBuilder::ErrorReason::NonStructDescribes; + } + assert(desc->isTemp && "unexpected canonical described type"); + if (!seenTypes.count(HeapType(uintptr_t(desc)))) { + return TypeBuilder::ErrorReason::ForwardDescribesReference; + } + if (desc->descriptor != &info) { + return TypeBuilder::ErrorReason::MismatchedDescribes; + } + } + if (auto* desc = info.descriptor) { + if (info.kind != HeapTypeKind::Struct) { + return TypeBuilder::ErrorReason::NonStructDescriptor; + } + if (desc->described != &info) { + return TypeBuilder::ErrorReason::MismatchedDescriptor; + } + } if (info.isContinuation()) { if (!info.continuation.type.isSignature()) { return TypeBuilder::ErrorReason::InvalidFuncType; } } if (info.share == Shared) { + if (info.described && info.described->share != Shared) { + return TypeBuilder::ErrorReason::InvalidUnsharedDescribes; + } + if (info.descriptor && info.descriptor->share != Shared) { + return TypeBuilder::ErrorReason::InvalidUnsharedDescriptor; + } switch (info.kind) { case HeapTypeKind::Func: // TODO: Figure out and enforce shared function rules. diff --git a/test/spec/descriptors.wast b/test/spec/descriptors.wast new file mode 100644 index 00000000000..c054e61ee4d --- /dev/null +++ b/test/spec/descriptors.wast @@ -0,0 +1,158 @@ +(module + (rec + (type (descriptor 1 (struct))) + (type (describes 0 (struct))) + ) +) + +(assert_invalid + (module + (rec + (type (descriptor 1 (struct))) + (type (struct)) + ) + ) + "descriptor with no matching describes" +) + + +(assert_invalid + (module + (rec + (type (struct)) + (type (describes 0 (struct))) + ) + ) + "describes with no matching descriptor" +) + +(assert_invalid + (module + (rec + (type (describes 1 (struct))) + (type (descriptor 0 (struct))) + ) + ) + "forward describes reference" +) + +(assert_invalid + (module + (rec + (type (descriptor 1 (array i8))) + (type (describes 0 (struct))) + ) + ) + "descriptor clause on non-struct type" +) + +(assert_invalid + (module + (rec + (type (descriptor 1 (struct))) + (type (describes 0 (array i8))) + ) + ) + "describes clause on non-struct type" +) + +(module + (rec + (type (shared (descriptor 1 (struct)))) + (type (shared (describes 0 (struct)))) + ) +) + +(assert_invalid + (module + (rec + (type (shared (descriptor 1 (struct)))) + (type (describes 0 (struct))) + ) + ) + "unshared descriptor type" +) + +;; TODO: allow this? +(assert_invalid + (module + (rec + (type (descriptor 1 (struct))) + (type (shared (describes 0 (struct)))) + ) + ) + "unshared described types" +) + +(module + (rec + (type $super (sub (descriptor $super-desc (struct)))) + (type $super-desc (sub (describes $super (struct)))) + ) + (rec + (type $sub (sub $super (descriptor $sub-desc (struct)))) + (type $sub-desc (sub $super-desc (describes $sub (struct)))) + ) +) + +(module + (type $super (sub (struct))) + (rec + (type $other (sub (descriptor $super-desc (struct)))) + (type $super-desc (sub (describes $other (struct)))) + ) + (rec + (type $sub (sub $super (descriptor $sub-desc (struct)))) + (type $sub-desc (sub $super-desc (describes $sub (struct)))) + ) +) + +(assert_invalid + (module + (type $super (sub (struct))) + (type $super-desc (sub (struct))) + (rec + (type $sub (sub $super (descriptor $sub-desc (struct)))) + (type $sub-desc (sub $super-desc (describes $sub (struct)))) + ) + ) + "supertype of descriptor must be a descriptor" +) + +(assert_invalid + (module + (rec + (type $other (sub (descriptor $super-desc (struct)))) + (type $super-desc (sub (describes $other (struct)))) + (type $super (sub (descriptor $other-desc (struct)))) + (type $other-desc (sub (describes $super (struct)))) + ) + (rec + (type $sub (sub $super (descriptor $sub-desc (struct)))) + (type $sub-desc (sub $super-desc (describes $sub (struct)))) + ) + ) + "supertype of described type must be described by supertype of descriptor" +) + +(assert_invalid + (module + (rec + (type $super (sub (descriptor $super-desc (struct)))) + (type $super-desc (sub (describes $super (struct)))) + ) + (type $sub (sub $super (struct))) + ) + "supertype of type without descriptor cannot have descriptor" +) + +(assert_invalid + (module + (rec + (type $super (sub (descriptor $super-desc (struct)))) + (type $super-desc (sub (describes $super (struct)))) + ) + (type $sub (sub $super-desc (struct))) + ) + "supertype of non-descriptor type cannot be a descriptor" +) From def4095995db67eaf51dce8a34d32769e2f9ef59 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 24 Mar 2025 20:48:44 -0700 Subject: [PATCH 369/622] [NFC] Use a lambda instead of a macro in gtest (#7395) The length macro used in a type test in type-builder.cpp was causing extremely long compile times in some compilers. Use a lambda instead to fix it. This makes the error messages less useful when a test fails, but under normal circumstances the test should not be failing, so this is a good trade off. Fixes #7383. --- test/gtest/type-builder.cpp | 103 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index f75639fa3d2..0f8463d55da 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -1197,58 +1197,57 @@ TEST_F(TypeTest, TestTypeRelations) { Type i32 = Type::i32; Type unreachable = Type::unreachable; -#define assertLUB(a, b, lub, glb) \ - { \ - auto lub1 = Type::getLeastUpperBound(a, b); \ - auto lub2 = Type::getLeastUpperBound(b, a); \ - EXPECT_EQ(lub, lub1); \ - EXPECT_EQ(lub1, lub2); \ - if (lub != Type::none) { \ - EXPECT_TRUE(Type::isSubType(a, lub)); \ - EXPECT_TRUE(Type::isSubType(b, lub)); \ - } \ - auto glb1 = Type::getGreatestLowerBound(a, b); \ - auto glb2 = Type::getGreatestLowerBound(b, a); \ - EXPECT_EQ(glb, glb1); \ - EXPECT_EQ(glb, glb2); \ - EXPECT_TRUE(Type::isSubType(glb, a)); \ - EXPECT_TRUE(Type::isSubType(glb, b)); \ - if (a == b) { \ - EXPECT_TRUE(Type::isSubType(a, b)); \ - EXPECT_TRUE(Type::isSubType(b, a)); \ - EXPECT_EQ(lub, a); \ - EXPECT_EQ(glb, a); \ - } else if (lub == b) { \ - EXPECT_TRUE(Type::isSubType(a, b)); \ - EXPECT_FALSE(Type::isSubType(b, a)); \ - EXPECT_EQ(glb, a); \ - } else if (lub == a) { \ - EXPECT_FALSE(Type::isSubType(a, b)); \ - EXPECT_TRUE(Type::isSubType(b, a)); \ - EXPECT_EQ(glb, b); \ - } else if (lub != Type::none) { \ - EXPECT_FALSE(Type::isSubType(a, b)); \ - EXPECT_FALSE(Type::isSubType(b, a)); \ - EXPECT_NE(glb, a); \ - EXPECT_NE(glb, b); \ - } else { \ - EXPECT_FALSE(Type::isSubType(a, b)); \ - EXPECT_FALSE(Type::isSubType(b, a)); \ - } \ - \ - if (a.isRef() && b.isRef()) { \ - auto htA = a.getHeapType(); \ - auto htB = b.getHeapType(); \ - \ - if (lub == Type::none) { \ - EXPECT_NE(htA.getTop(), htB.getTop()); \ - EXPECT_NE(htA.getBottom(), htB.getBottom()); \ - } else { \ - EXPECT_EQ(htA.getTop(), htB.getTop()); \ - EXPECT_EQ(htA.getBottom(), htB.getBottom()); \ - } \ - } \ - } + auto assertLUB = [](Type a, Type b, Type lub, Type glb) { + auto lub1 = Type::getLeastUpperBound(a, b); + auto lub2 = Type::getLeastUpperBound(b, a); + EXPECT_EQ(lub, lub1); + EXPECT_EQ(lub1, lub2); + if (lub != Type::none) { + EXPECT_TRUE(Type::isSubType(a, lub)); + EXPECT_TRUE(Type::isSubType(b, lub)); + } + auto glb1 = Type::getGreatestLowerBound(a, b); + auto glb2 = Type::getGreatestLowerBound(b, a); + EXPECT_EQ(glb, glb1); + EXPECT_EQ(glb, glb2); + EXPECT_TRUE(Type::isSubType(glb, a)); + EXPECT_TRUE(Type::isSubType(glb, b)); + if (a == b) { + EXPECT_TRUE(Type::isSubType(a, b)); + EXPECT_TRUE(Type::isSubType(b, a)); + EXPECT_EQ(lub, a); + EXPECT_EQ(glb, a); + } else if (lub == b) { + EXPECT_TRUE(Type::isSubType(a, b)); + EXPECT_FALSE(Type::isSubType(b, a)); + EXPECT_EQ(glb, a); + } else if (lub == a) { + EXPECT_FALSE(Type::isSubType(a, b)); + EXPECT_TRUE(Type::isSubType(b, a)); + EXPECT_EQ(glb, b); + } else if (lub != Type::none) { + EXPECT_FALSE(Type::isSubType(a, b)); + EXPECT_FALSE(Type::isSubType(b, a)); + EXPECT_NE(glb, a); + EXPECT_NE(glb, b); + } else { + EXPECT_FALSE(Type::isSubType(a, b)); + EXPECT_FALSE(Type::isSubType(b, a)); + } + + if (a.isRef() && b.isRef()) { + auto htA = a.getHeapType(); + auto htB = b.getHeapType(); + + if (lub == Type::none) { + EXPECT_NE(htA.getTop(), htB.getTop()); + EXPECT_NE(htA.getBottom(), htB.getBottom()); + } else { + EXPECT_EQ(htA.getTop(), htB.getTop()); + EXPECT_EQ(htA.getBottom(), htB.getBottom()); + } + } + }; assertLUB(any, any, any, any); assertLUB(any, nullAny, nullAny, any); From 52a15b3e0e7f89ca95dc8815c18fbb3ce09501b7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 25 Mar 2025 11:12:29 -0700 Subject: [PATCH 370/622] [NFC] Fix help text for tools that emit binary (#7398) Our tools all stated that if -o is not provided, we write to stdout by default. But that is not true - we do nothing in that case. I believe the rationale is that we write text to stdout by default in say wasm2js, but for wasm-opt etc. we don't want to write binary to stdout (as that is dangerous). --- src/tools/wasm-as.cpp | 2 +- src/tools/wasm-ctor-eval.cpp | 2 +- src/tools/wasm-merge.cpp | 2 +- src/tools/wasm-metadce.cpp | 2 +- src/tools/wasm-opt.cpp | 2 +- test/lit/help/wasm-as.test | 2 +- test/lit/help/wasm-ctor-eval.test | 2 +- test/lit/help/wasm-merge.test | 2 +- test/lit/help/wasm-metadce.test | 3 +-- test/lit/help/wasm-opt.test | 3 +-- 10 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp index 73ae8213432..8d0dbd6495b 100644 --- a/src/tools/wasm-as.cpp +++ b/src/tools/wasm-as.cpp @@ -44,7 +44,7 @@ int main(int argc, const char* argv[]) { options .add("--output", "-o", - "Output file (stdout if not specified)", + "Output file", WasmAsOption, Options::Arguments::One, [](Options* o, const std::string& argument) { diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index b7e1d438a8c..4b819b73988 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -1374,7 +1374,7 @@ int main(int argc, const char* argv[]) { options .add("--output", "-o", - "Output file (stdout if not specified)", + "Output file", WasmCtorEvalOption, Options::Arguments::One, [](Options* o, const std::string& argument) { diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index cc35b072b49..045e98275d8 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -590,7 +590,7 @@ Input source maps can be specified by adding an -ism option right after the modu options .add("--output", "-o", - "Output file (stdout if not specified)", + "Output file", WasmMergeOption, Options::Arguments::One, [](Options* o, const std::string& argument) { diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index b79507f8019..bddf0c28ed6 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -423,7 +423,7 @@ int main(int argc, const char* argv[]) { options .add("--output", "-o", - "Output file (stdout if not specified)", + "Output file", WasmMetaDCEOption, Options::Arguments::One, [](Options* o, const std::string& argument) { diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 59622d5f938..a59bda728ef 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -99,7 +99,7 @@ int main(int argc, const char* argv[]) { options .add("--output", "-o", - "Output file (stdout if not specified)", + "Output file", WasmOptOption, Options::Arguments::One, [](Options* o, const std::string& argument) { diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 7514841a7a6..77ae850e4df 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -10,7 +10,7 @@ ;; CHECK-NEXT: wasm-as options: ;; CHECK-NEXT: ---------------- ;; CHECK-NEXT: -;; CHECK-NEXT: --output,-o Output file (stdout if not specified) +;; CHECK-NEXT: --output,-o Output file ;; CHECK-NEXT: ;; CHECK-NEXT: --validate,-v Control validation of the output module ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index bed94623224..9a5fbdcda26 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -9,7 +9,7 @@ ;; CHECK-NEXT: wasm-ctor-eval options: ;; CHECK-NEXT: ----------------------- ;; CHECK-NEXT: -;; CHECK-NEXT: --output,-o Output file (stdout if not specified) +;; CHECK-NEXT: --output,-o Output file ;; CHECK-NEXT: ;; CHECK-NEXT: --emit-text,-S Emit text instead of binary for the ;; CHECK-NEXT: output file diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index 0c51047952b..a8b95194950 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -25,7 +25,7 @@ ;; CHECK-NEXT: wasm-merge options: ;; CHECK-NEXT: ------------------- ;; CHECK-NEXT: -;; CHECK-NEXT: --output,-o Output file (stdout if not specified) +;; CHECK-NEXT: --output,-o Output file ;; CHECK-NEXT: ;; CHECK-NEXT: --input-source-map,-ism Consume source maps from the specified ;; CHECK-NEXT: files diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 1a7c495fabd..2b8403a5911 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -51,8 +51,7 @@ ;; CHECK-NEXT: wasm-opt options: ;; CHECK-NEXT: ----------------- ;; CHECK-NEXT: -;; CHECK-NEXT: --output,-o Output file (stdout if not -;; CHECK-NEXT: specified) +;; CHECK-NEXT: --output,-o Output file ;; CHECK-NEXT: ;; CHECK-NEXT: --input-source-map,-ism Consume source map from the ;; CHECK-NEXT: specified file diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 1a6be04783d..dad51681164 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -9,8 +9,7 @@ ;; CHECK-NEXT: wasm-opt options: ;; CHECK-NEXT: ----------------- ;; CHECK-NEXT: -;; CHECK-NEXT: --output,-o Output file (stdout if not -;; CHECK-NEXT: specified) +;; CHECK-NEXT: --output,-o Output file ;; CHECK-NEXT: ;; CHECK-NEXT: --emit-text,-S Emit text instead of binary for ;; CHECK-NEXT: the output file From 6a6e08057c07ea4792a2ddcacfffc4d49ceea7fc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 25 Mar 2025 11:13:08 -0700 Subject: [PATCH 371/622] Release version 123 (#7394) Given the big speedup in our official release binaries for Linux, this seems useful to get to users quickly. --- CHANGELOG.md | 18 ++++++++++++++++-- CMakeLists.txt | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6d3bb6f27..290e0aebab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,16 +15,30 @@ full changeset diff at the end of each section. Current Trunk ------------- +v123 +---- + + - We now support "exact" references from the custom descriptors proposal, + and emit such references when the feature is enabled. As a result, using + `-all` will enable that feature (among all others), and cause GC-using + binaries to use that feature, which most VMs do not yet support. To avoid + such VM errors, either enable only the features you want, or disable it: + `-all --disable-custom-descriptors`. + - Use mimalloc allocator for Linux static builds, making our official release + binaries a lot faster. (#7378) - Add an option to preserve imports and exports in the fuzzer (for fuzzer harnesses where they only want Binaryen to modify their given testcases, not - generate new things in them). + generate new things in them). (#7300) - `string` is now a subtype of `ext` (rather than `any`). This allows better transformations for strings, like an inverse of StringLowering, but will error on codebases that depend on being able to pass strings into anyrefs. + (#7373) - Require the type of RefFunc expressions to match the type of the referenced - function. It is no longer valid to type them as funcref in the IR. + function. It is no longer valid to type them as funcref in the IR. (#7376) - The C and JS APIs for creating RefFunc expressions now take a HeapType instead of a Type. + - MergeSimilarFunctions: Do a return_call when possible (necessary for + correctness in wasm files that depend on calls for control flow). (#7350) v122 ---- diff --git a/CMakeLists.txt b/CMakeLists.txt index c6ebf9f5381..90edcb2c858 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.16.3) # to reduce this for compatability with emsdk. set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version") -project(binaryen LANGUAGES C CXX VERSION 122) +project(binaryen LANGUAGES C CXX VERSION 123) include(GNUInstallDirs) # The C++ standard whose features are required to build Binaryen. From aee292be6e47c34f0efa036ab85720cbb0095bd0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 25 Mar 2025 12:46:44 -0700 Subject: [PATCH 372/622] [strings] Add a StringLifting pass (#7389) This converts imported string constants into string.const, and imported string instructions into string.* expressions. After this pass they are represented using stringref and we can optimize them fully (e.g. precomputing a string.concat of two constants). Typically a user would later lower then back down using StringLowering. This pass allows users to avoid emitting stringref directly, which means they are emitting standard wasm which can run in VMs, leaving wasm-opt entirely optional. Also refactor a few shared constants with StringLowering into a helper file. Left as TODOs: contents of the strings custom section, and casts (see comments in source). Fixes most of #7370 --- CHANGELOG.md | 4 + scripts/test/fuzzing.py | 3 + src/passes/CMakeLists.txt | 2 + src/passes/StringLifting.cpp | 253 ++++++++++++++++++ src/passes/StringLowering.cpp | 6 +- src/passes/pass.cpp | 3 + src/passes/passes.h | 1 + src/passes/string-utils.cpp | 26 ++ src/passes/string-utils.h | 34 +++ test/lit/help/wasm-metadce.test | 3 + test/lit/help/wasm-opt.test | 3 + test/lit/help/wasm2js.test | 3 + .../lit/passes/string-lifting-validation.wast | 22 ++ test/lit/passes/string-lifting.wast | 253 ++++++++++++++++++ 14 files changed, 612 insertions(+), 4 deletions(-) create mode 100644 src/passes/StringLifting.cpp create mode 100644 src/passes/string-utils.cpp create mode 100644 src/passes/string-utils.h create mode 100644 test/lit/passes/string-lifting-validation.wast create mode 100644 test/lit/passes/string-lifting.wast diff --git a/CHANGELOG.md b/CHANGELOG.md index 290e0aebab6..a97be02a49b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ full changeset diff at the end of each section. Current Trunk ------------- + - Add a `--string-lifting` pass that raises imported string operations and + constants into stringref in Binaryen IR (which can then be fully optimized, + and typically lowered back down with `--string-lowering`). + v123 ---- diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index ba36f41bcf5..342c452a588 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -93,6 +93,9 @@ 'names.wast', # huge amount of locals that make it extremely slow 'too_much_for_liveness.wasm', + # has (ref extern) imports, which the fuzzer cannot create values for when + # it removes unknown imports + 'string-lifting.wast', # TODO: fuzzer support for stack switching 'stack_switching.wast', 'stack_switching_contnew.wast', diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 83a17d36608..6fab09b1bc2 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -15,6 +15,7 @@ FILE(GLOB passes_HEADERS *.h) set(passes_SOURCES param-utils.cpp pass.cpp + string-utils.cpp test_passes.cpp AbstractTypeRefining.cpp AlignmentLowering.cpp @@ -96,6 +97,7 @@ set(passes_SOURCES SignaturePruning.cpp SignatureRefining.cpp SignExtLowering.cpp + StringLifting.cpp StringLowering.cpp Strip.cpp StripTargetFeatures.cpp diff --git a/src/passes/StringLifting.cpp b/src/passes/StringLifting.cpp new file mode 100644 index 00000000000..f6a99e1351b --- /dev/null +++ b/src/passes/StringLifting.cpp @@ -0,0 +1,253 @@ +/* + * Copyright 2025 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. + */ + +// +// Lift JS string imports into wasm strings in Binaryen IR, which can then be +// fully optimized. Typically StringLowering would be run later to lower them +// back down. +// + +#include "ir/utils.h" +#include "pass.h" +#include "passes/string-utils.h" +#include "support/string.h" +#include "wasm-builder.h" +#include "wasm.h" + +namespace wasm { + +struct StringLifting : public Pass { + // Maps the global name of an imported string to the actual string. + std::unordered_map importedStrings; + + // Imported string functions. Imports that do not exist remain null. + Name fromCharCodeArrayImport; + Name intoCharCodeArrayImport; + Name fromCodePointImport; + Name concatImport; + Name equalsImport; + Name compareImport; + Name lengthImport; + Name charCodeAtImport; + Name substringImport; + + void run(Module* module) override { + // Whether we found any work to do. + bool found = false; + + // Imported string constants look like + // + // (import "\'" "bar" (global $string.bar.internal.name (ref extern))) + // + // That is, they are imported from module "'" and the basename is the + // actual string. Find them all so we can apply them. + // + // TODO: parse the strings section for non-UTF16 strings. + for (auto& global : module->globals) { + if (!global->imported()) { + continue; + } + if (global->module == WasmStringConstsModule) { + importedStrings[global->name] = global->base; + found = true; + } + } + + auto array16 = Type(Array(Field(Field::i16, Mutable)), Nullable); + auto refExtern = Type(HeapType::ext, NonNullable); + auto externref = Type(HeapType::ext, Nullable); + auto i32 = Type::i32; + + // Find imported string functions. + for (auto& func : module->functions) { + if (!func->imported() || func->module != WasmStringsModule) { + continue; + } + auto sig = func->type.getSignature(); + if (func->base == "fromCharCodeArray") { + if (sig != Signature({array16, i32, i32}, refExtern)) { + Fatal() << "StringLifting: bad signature for fromCharCodeArray: " + << sig; + } + fromCharCodeArrayImport = func->name; + found = true; + } else if (func->base == "fromCodePoint") { + if (sig != Signature(i32, refExtern)) { + Fatal() << "StringLifting: bad signature for fromCodePoint: " << sig; + } + fromCodePointImport = func->name; + found = true; + } else if (func->base == "concat") { + if (sig != Signature({externref, externref}, refExtern)) { + Fatal() << "StringLifting: bad signature for concta: " << sig; + } + concatImport = func->name; + found = true; + } else if (func->base == "intoCharCodeArray") { + if (sig != Signature({externref, array16, i32}, i32)) { + Fatal() << "StringLifting: bad signature for intoCharCodeArray: " + << sig; + } + intoCharCodeArrayImport = func->name; + found = true; + } else if (func->base == "equals") { + if (sig != Signature({externref, externref}, i32)) { + Fatal() << "StringLifting: bad signature for equals: " << sig; + } + equalsImport = func->name; + found = true; + } else if (func->base == "compare") { + if (sig != Signature({externref, externref}, i32)) { + Fatal() << "StringLifting: bad signature for compare: " << sig; + } + compareImport = func->name; + found = true; + } else if (func->base == "length") { + if (sig != Signature({externref}, i32)) { + Fatal() << "StringLifting: bad signature for length: " << sig; + } + lengthImport = func->name; + found = true; + } else if (func->base == "charCodeAt") { + if (sig != Signature({externref, i32}, i32)) { + Fatal() << "StringLifting: bad signature for charCodeAt: " << sig; + } + charCodeAtImport = func->name; + found = true; + } else if (func->base == "substring") { + if (sig != Signature({externref, i32, i32}, refExtern)) { + Fatal() << "StringLifting: bad signature for substring: " << sig; + } + substringImport = func->name; + found = true; + } else { + std::cerr << "warning: unknown strings import: " << func->base << '\n'; + } + } + + if (!found) { + // Nothing to do. + return; + } + + struct StringApplier : public WalkerPass> { + bool isFunctionParallel() override { return true; } + + const StringLifting& parent; + + StringApplier(const StringLifting& parent) : parent(parent) {} + + std::unique_ptr create() override { + return std::make_unique(parent); + } + + bool modified = false; + + void visitGlobalGet(GlobalGet* curr) { + // Replace global.gets of imported strings with string.const. + auto iter = parent.importedStrings.find(curr->name); + if (iter != parent.importedStrings.end()) { + // Encode from WTF-8 to WTF-16. + auto wtf8 = iter->second; + std::stringstream wtf16; + bool valid = String::convertWTF8ToWTF16(wtf16, wtf8.str); + if (!valid) { + Fatal() << "Bad string to lift: " << wtf8; + } + + replaceCurrent(Builder(*getModule()).makeStringConst(wtf16.str())); + modified = true; + } + } + + void visitCall(Call* curr) { + // Replace calls of imported string methods with stringref operations. + if (curr->target == parent.fromCharCodeArrayImport) { + replaceCurrent(Builder(*getModule()) + .makeStringNew(StringNewWTF16Array, + curr->operands[0], + curr->operands[1], + curr->operands[2])); + } else if (curr->target == parent.fromCodePointImport) { + replaceCurrent( + Builder(*getModule()) + .makeStringNew(StringNewFromCodePoint, curr->operands[0])); + } else if (curr->target == parent.concatImport) { + replaceCurrent( + Builder(*getModule()) + .makeStringConcat(curr->operands[0], curr->operands[1])); + } else if (curr->target == parent.intoCharCodeArrayImport) { + replaceCurrent(Builder(*getModule()) + .makeStringEncode(StringEncodeWTF16Array, + curr->operands[0], + curr->operands[1], + curr->operands[2])); + } else if (curr->target == parent.equalsImport) { + replaceCurrent(Builder(*getModule()) + .makeStringEq(StringEqEqual, + curr->operands[0], + curr->operands[1])); + } else if (curr->target == parent.compareImport) { + replaceCurrent(Builder(*getModule()) + .makeStringEq(StringEqCompare, + curr->operands[0], + curr->operands[1])); + } else if (curr->target == parent.lengthImport) { + replaceCurrent( + Builder(*getModule()) + .makeStringMeasure(StringMeasureWTF16, curr->operands[0])); + } else if (curr->target == parent.charCodeAtImport) { + replaceCurrent( + Builder(*getModule()) + .makeStringWTF16Get(curr->operands[0], curr->operands[1])); + } else if (curr->target == parent.substringImport) { + replaceCurrent(Builder(*getModule()) + .makeStringSliceWTF(curr->operands[0], + curr->operands[1], + curr->operands[2])); + } + } + + void visitFunction(Function* curr) { + // If we made modifications then we need to refinalize, as we replace + // externrefs with stringrefs, a subtype. + if (modified) { + ReFinalize().walkFunctionInModule(curr, getModule()); + } + } + }; + + StringApplier applier(*this); + applier.run(getPassRunner(), module); + applier.walkModuleCode(module); + + // TODO: Add casts. We generate new string.* instructions, and all their + // string inputs should be stringref, not externref, but we have not + // converted all externrefs to stringrefs (since some externrefs might + // be something else). It is not urgent to fix this as the validator + // accepts externrefs there atm, and since toolchains will lower + // strings out at the end anyhow (which would remove such casts). Note + // that if we add a type import for stringref then this problem would + // become a lot simpler (we'd convert that type to stringref). + + // Enable the feature so the module validates. + module->features.enable(FeatureSet::Strings); + } +}; + +Pass* createStringLiftingPass() { return new StringLifting(); } + +} // namespace wasm diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 66841f29995..d1ed2e987f9 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -38,6 +38,7 @@ #include "ir/type-updating.h" #include "ir/utils.h" #include "pass.h" +#include "passes/string-utils.h" #include "support/string.h" #include "wasm-builder.h" #include "wasm.h" @@ -243,7 +244,7 @@ struct StringLowering : public StringGathering { std::stringstream utf8; if (useMagicImports && String::convertUTF16ToUTF8(utf8, c->string.str)) { - global->module = "'"; + global->module = WasmStringConstsModule; global->base = Name(utf8.str()); } else { if (assertUTF8) { @@ -359,9 +360,6 @@ struct StringLowering : public StringGathering { Name charCodeAtImport; Name substringImport; - // The name of the module to import string functions from. - Name WasmStringsModule = "wasm:js-string"; - // Creates an imported string function, returning its name (which is equal to // the true name of the import, if there is no conflict). Name addImport(Module* module, Name trueName, Type params, Type results) { diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index e2137a8c98b..b665b022062 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -518,6 +518,9 @@ void PassRegistry::registerPasses() { registerPass("string-gathering", "gathers wasm strings to globals", createStringGatheringPass); + registerPass("string-lifting", + "lift string imports to wasm strings", + createStringLiftingPass); registerPass("string-lowering", "lowers wasm strings and operations to imports", createStringLoweringPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 81d7cdcc7ff..f179732a356 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -161,6 +161,7 @@ Pass* createSimplifyLocalsNoStructurePass(); Pass* createSimplifyLocalsNoTeeNoStructurePass(); Pass* createStackCheckPass(); Pass* createStringGatheringPass(); +Pass* createStringLiftingPass(); Pass* createStringLoweringPass(); Pass* createStringLoweringMagicImportPass(); Pass* createStringLoweringMagicImportAssertPass(); diff --git a/src/passes/string-utils.cpp b/src/passes/string-utils.cpp new file mode 100644 index 00000000000..21b2afb8221 --- /dev/null +++ b/src/passes/string-utils.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "passes/string-utils.h" +#include "support/name.h" + +namespace wasm { + +const Name WasmStringsModule = "wasm:js-string"; + +const Name WasmStringConstsModule = "'"; + +} // namespace wasm diff --git a/src/passes/string-utils.h b/src/passes/string-utils.h new file mode 100644 index 00000000000..26fbc5e7faf --- /dev/null +++ b/src/passes/string-utils.h @@ -0,0 +1,34 @@ +/* + * Copyright 2025 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_passes_string_utils_h +#define wasm_passes_string_utils_h + +#include "support/name.h" + +namespace wasm { + +// The name of the module to import from, for imported JS strings. See +// https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md +extern const Name WasmStringsModule; + +// The name of the module to import string constants from, for magical imported +// JS strings. +extern const Name WasmStringConstsModule; + +} // namespace wasm + +#endif // wasm_passes_string_utils_h diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 2b8403a5911..53daee199c2 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -491,6 +491,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --string-gathering gathers wasm strings to globals ;; CHECK-NEXT: +;; CHECK-NEXT: --string-lifting lift string imports to wasm +;; CHECK-NEXT: strings +;; CHECK-NEXT: ;; CHECK-NEXT: --string-lowering lowers wasm strings and ;; CHECK-NEXT: operations to imports ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index dad51681164..f97e55ef61e 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -503,6 +503,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --string-gathering gathers wasm strings to globals ;; CHECK-NEXT: +;; CHECK-NEXT: --string-lifting lift string imports to wasm +;; CHECK-NEXT: strings +;; CHECK-NEXT: ;; CHECK-NEXT: --string-lowering lowers wasm strings and ;; CHECK-NEXT: operations to imports ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 6db49301db1..e74bb416857 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -455,6 +455,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --string-gathering gathers wasm strings to globals ;; CHECK-NEXT: +;; CHECK-NEXT: --string-lifting lift string imports to wasm +;; CHECK-NEXT: strings +;; CHECK-NEXT: ;; CHECK-NEXT: --string-lowering lowers wasm strings and ;; CHECK-NEXT: operations to imports ;; CHECK-NEXT: diff --git a/test/lit/passes/string-lifting-validation.wast b/test/lit/passes/string-lifting-validation.wast new file mode 100644 index 00000000000..cf6be39b184 --- /dev/null +++ b/test/lit/passes/string-lifting-validation.wast @@ -0,0 +1,22 @@ +;; The imported js string method here has an error in its signature, which +;; should be reported. + +(module + (type $array16 (array (mut i16))) + + (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (param (ref null $array16) i32 i64) (result (ref extern)))) + + (func $string.new.gc (param $ref (ref $array16)) + (drop + (call $fromCharCodeArray + (local.get $ref) + (i32.const 7) + (i64.const 8) ;; this i64 should be an i32 in the signature + ) + ) + ) +) + +;; RUN: not wasm-opt %s --string-lifting -all 2>&1 | filecheck %s +;; CHECK: Fatal: StringLifting: bad signature for fromCharCodeArray: (func (param (ref null $array.0) i32 i64) (result (ref extern))) + diff --git a/test/lit/passes/string-lifting.wast b/test/lit/passes/string-lifting.wast new file mode 100644 index 00000000000..78da0c040a8 --- /dev/null +++ b/test/lit/passes/string-lifting.wast @@ -0,0 +1,253 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --string-lifting -S -o - | filecheck %s + +(module + ;; CHECK: (type $array16 (array (mut i16))) + (type $array16 (array (mut i16))) + + ;; CHECK: (type $1 (func (param externref externref) (result i32))) + + ;; CHECK: (type $2 (func (param externref) (result i32))) + + ;; CHECK: (type $3 (func (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (type $4 (func (param externref externref) (result (ref extern)))) + + ;; CHECK: (type $5 (func (param (ref null $array16) i32 i32) (result (ref extern)))) + + ;; CHECK: (type $6 (func (param i32) (result (ref extern)))) + + ;; CHECK: (type $7 (func (param externref (ref null $array16) i32) (result i32))) + + ;; CHECK: (type $8 (func (param externref i32) (result i32))) + + ;; CHECK: (type $9 (func)) + + ;; CHECK: (type $10 (func (param (ref $array16)))) + + ;; CHECK: (type $11 (func (result externref))) + + ;; CHECK: (type $12 (func (param externref (ref $array16)) (result i32))) + + ;; CHECK: (type $13 (func (param externref) (result externref))) + + ;; CHECK: (type $14 (func (param externref))) + + ;; CHECK: (import "\'" "foo" (global $string_foo (ref extern))) + (import "\'" "foo" (global $string_foo (ref extern))) + ;; CHECK: (import "\'" "bar" (global $string_bar (ref extern))) + (import "\'" "bar" (global $string_bar (ref extern))) + + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $array16) i32 i32) (result (ref extern)))) + (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (param (ref null $array16) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $6) (param i32) (result (ref extern)))) + (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $4) (param externref externref) (result (ref extern)))) + (import "wasm:js-string" "concat" (func $concat (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $array16) i32) (result i32))) + (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (param externref (ref null $array16) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + (import "wasm:js-string" "equals" (func $equals (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + (import "wasm:js-string" "compare" (func $compare (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) + (import "wasm:js-string" "length" (func $length (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) + (import "wasm:js-string" "charCodeAt" (func $charCodeAt (param externref i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "substring" (func $substring_foo (type $3) (param externref i32 i32) (result (ref extern)))) + (import "wasm:js-string" "substring" (func $substring_foo (param externref i32 i32) (result (ref extern)))) + + ;; A function from the right module, but an unsupported name. + ;; CHECK: (import "wasm:js-string" "wrong-name" (func $wrong-base (type $3) (param externref i32 i32) (result (ref extern)))) + (import "wasm:js-string" "wrong-name" (func $wrong-base (param externref i32 i32) (result (ref extern)))) + + ;; A function that is right in all ways but the module. + ;; CHECK: (import "oops" "substring" (func $wrong-module (type $3) (param externref i32 i32) (result (ref extern)))) + (import "oops" "substring" (func $wrong-module (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (func $func (type $9) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result (ref string)) + ;; CHECK-NEXT: (string.const "bar") + ;; CHECK-NEXT: (string.const "bar") + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + (drop + (global.get $string_foo) + ) + ;; Test multiple uses of the same constant, and that we update types. + (drop + (select (result externref) + (global.get $string_bar) + (global.get $string_bar) + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $string.new.gc (type $10) (param $ref (ref $array16)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.new.gc (param $ref (ref $array16)) + (drop + (call $fromCharCodeArray + (local.get $ref) + (i32.const 7) + (i32.const 8) + ) + ) + ) + + ;; CHECK: (func $string.from_code_point (type $11) (result externref) + ;; CHECK-NEXT: (string.from_code_point + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.from_code_point (result externref) + (call $fromCodePoint + (i32.const 1) + ) + ) + + ;; CHECK: (func $string.concat (type $4) (param $a externref) (param $b externref) (result (ref extern)) + ;; CHECK-NEXT: (string.concat + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.concat (param $a externref) (param $b externref) (result (ref extern)) + (call $concat + (local.get $a) + (local.get $b) + ) + ) + + ;; CHECK: (func $string.encode (type $12) (param $ref externref) (param $array16 (ref $array16)) (result i32) + ;; CHECK-NEXT: (string.encode_wtf16_array + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $array16) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.encode (param $ref externref) (param $array16 (ref $array16)) (result i32) + (call $intoCharCodeArray + (local.get $ref) + (local.get $array16) + (i32.const 10) + ) + ) + + ;; CHECK: (func $string.eq (type $1) (param $a externref) (param $b externref) (result i32) + ;; CHECK-NEXT: (string.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.eq (param $a externref) (param $b externref) (result i32) + (call $equals + (local.get $a) + (local.get $b) + ) + ) + + ;; CHECK: (func $string.compare (type $1) (param $a externref) (param $b externref) (result i32) + ;; CHECK-NEXT: (string.compare + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.compare (param $a externref) (param $b externref) (result i32) + (call $compare + (local.get $a) + (local.get $b) + ) + ) + + ;; CHECK: (func $string.length (type $2) (param $ref externref) (result i32) + ;; CHECK-NEXT: (string.measure_wtf16 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.length (param $ref externref) (result i32) + (call $length + (local.get $ref) + ) + ) + + ;; CHECK: (func $string.get_codeunit (type $2) (param $ref externref) (result i32) + ;; CHECK-NEXT: (stringview_wtf16.get_codeunit + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.get_codeunit (param $ref externref) (result i32) + (call $charCodeAt + (local.get $ref) + (i32.const 2) + ) + ) + + ;; CHECK: (func $string.slice (type $13) (param $ref externref) (result externref) + ;; CHECK-NEXT: (stringview_wtf16.slice + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.slice (param $ref externref) (result externref) + ;; Note how the internal name has _foo appended, but that does not confuse + ;; use - the module and base names of the import are what matter. + (call $substring_foo + (local.get $ref) + (i32.const 2) + (i32.const 3) + ) + ) + + ;; CHECK: (func $wrong (type $14) (param $ref externref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $wrong-base + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $wrong-module + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $wrong (param $ref externref) + ;; We do nothing with functions with the wrong base or module name. + (drop + (call $wrong-base + (local.get $ref) + (i32.const 4) + (i32.const 5) + ) + ) + (drop + (call $wrong-module + (local.get $ref) + (i32.const 6) + (i32.const 7) + ) + ) + ) +) From 1f01a77521f2cbcf739bc77ae6ea9df52ec54cc5 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Tue, 25 Mar 2025 17:40:24 -0700 Subject: [PATCH 373/622] [Outlining] Remove overlapping sequences (#7146) While determining whether repeat sequences of instructions are candidates for outlining, remove sequences that overlap, giving weight to sequences that are longer and appear more frequently. --- src/passes/Outlining.cpp | 2 + src/passes/hash-stringify-walker.cpp | 42 ++++++++++++ src/passes/stringify-walker.h | 2 + src/support/CMakeLists.txt | 1 + src/support/intervals.cpp | 65 +++++++++++++++++++ src/support/intervals.h | 56 ++++++++++++++++ test/gtest/CMakeLists.txt | 1 + test/gtest/intervals.cpp | 95 ++++++++++++++++++++++++++++ test/lit/passes/outlining.wast | 68 ++++++++++++++++++++ 9 files changed, 332 insertions(+) create mode 100644 src/support/intervals.cpp create mode 100644 src/support/intervals.h create mode 100644 test/gtest/intervals.cpp diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index 0c5c22f1865..0faa7653e15 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -280,6 +280,8 @@ struct Outlining : public Pass { DBG(printHashString(stringify.hashString, stringify.exprs)); // Remove substrings that are substrings of longer repeat substrings. substrings = StringifyProcessor::dedupe(substrings); + // Remove substrings with overlapping indices. + substrings = StringifyProcessor::filterOverlaps(substrings); // Remove substrings with branch and return instructions until an analysis // is performed to see if the intended destination of the branch is included // in the substring to be outlined. diff --git a/src/passes/hash-stringify-walker.cpp b/src/passes/hash-stringify-walker.cpp index c9173c6a58c..69163391f3c 100644 --- a/src/passes/hash-stringify-walker.cpp +++ b/src/passes/hash-stringify-walker.cpp @@ -15,6 +15,7 @@ */ #include "stringify-walker.h" +#include "support/intervals.h" namespace wasm { @@ -147,6 +148,47 @@ std::vector StringifyProcessor::dedupe( return result; } +std::vector StringifyProcessor::filterOverlaps( + const std::vector& substrings) { + // A substring represents a contiguous set of instructions that appear more + // than once in a Wasm binary. For each appearance of the substring, an + // Interval is created that lacks a connection back to its originating + // substring. To fix, upon Interval creation, a second vector is populated + // with the index of the corresponding substring. + std::vector intervals; + std::vector substringIdxs; + + // Construct intervals + for (Index i = 0; i < substrings.size(); i++) { + auto& substring = substrings[i]; + for (auto startIdx : substring.StartIndices) { + intervals.emplace_back( + startIdx, startIdx + substring.Length, substring.Length); + substringIdxs.push_back(i); + } + } + + // Get the overlapping intervals + std::vector result; + std::vector> startIndices(substrings.size()); + std::vector indices = IntervalProcessor::filterOverlaps(intervals); + for (auto i : indices) { + // i is the idx of the Interval in the intervals vector + // i in substringIdxs returns the idx of the substring that needs to be + // included in result + auto substringIdx = substringIdxs[i]; + startIndices[substringIdx].push_back(intervals[i].start); + } + for (Index i = 0; i < startIndices.size(); i++) { + if (startIndices[i].size() > 1) { + result.emplace_back(SuffixTree::RepeatedSubstring( + {substrings[i].Length, std::move(startIndices[i])})); + } + } + + return result; +} + std::vector StringifyProcessor::filter( const std::vector& substrings, const std::vector& exprs, diff --git a/src/passes/stringify-walker.h b/src/passes/stringify-walker.h index 6f50f58d40a..5e7f6731d8c 100644 --- a/src/passes/stringify-walker.h +++ b/src/passes/stringify-walker.h @@ -21,6 +21,7 @@ #include "ir/module-utils.h" #include "ir/stack-utils.h" #include "ir/utils.h" +#include "support/intervals.h" #include "support/suffix_tree.h" #include "wasm-ir-builder.h" #include "wasm-traversal.h" @@ -264,6 +265,7 @@ using Substrings = std::vector; struct StringifyProcessor { static Substrings repeatSubstrings(std::vector& hashString); static Substrings dedupe(const Substrings& substrings); + static Substrings filterOverlaps(const Substrings& substrings); // Filter is the general purpose function backing subsequent filter functions. // It can be used directly, but generally prefer a wrapper function // to encapsulate your condition and make it available for tests. diff --git a/src/support/CMakeLists.txt b/src/support/CMakeLists.txt index 5c793662e1c..d264979e7a8 100644 --- a/src/support/CMakeLists.txt +++ b/src/support/CMakeLists.txt @@ -7,6 +7,7 @@ set(support_SOURCES debug.cpp dfa_minimization.cpp file.cpp + intervals.cpp istring.cpp json.cpp name.cpp diff --git a/src/support/intervals.cpp b/src/support/intervals.cpp new file mode 100644 index 00000000000..70599503812 --- /dev/null +++ b/src/support/intervals.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2024 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "intervals.h" +#include "support/index.h" +#include + +using namespace wasm; + +std::vector +IntervalProcessor::filterOverlaps(std::vector& intervals) { + if (intervals.size() == 0) { + return std::vector(); + } + + std::vector> intIntervals; + for (Index i = 0; i < intervals.size(); i++) { + auto& interval = intervals[i]; + intIntervals.push_back({interval, i}); + } + + std::sort(intIntervals.begin(), intIntervals.end()); + + std::vector> kept; + kept.push_back(intIntervals[0]); + for (auto& candidate : intIntervals) { + auto& former = kept.back(); + if (former.first.end <= candidate.first.start) { + kept.push_back(candidate); + continue; + } + + // When two intervals overlap with the same weight, prefer to keep the + // interval that ends sooner under the presumption that it will overlap with + // fewer subsequent intervals. + if (former.first.weight == candidate.first.weight && + former.first.end > candidate.first.end) { + former = candidate; + } else if (former.first.weight < candidate.first.weight) { + former = candidate; + } + } + + std::vector result; + for (auto& intPair : kept) { + result.push_back(intPair.second); + } + + return result; +} diff --git a/src/support/intervals.h b/src/support/intervals.h new file mode 100644 index 00000000000..b7a632496f5 --- /dev/null +++ b/src/support/intervals.h @@ -0,0 +1,56 @@ +/* + * Copyright 2024 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. + */ + +// Helpers for handling a generic range of values + +#ifndef wasm_support_intervals_h +#define wasm_support_intervals_h + +#include +#include + +namespace wasm { + +struct Interval { + unsigned start; + unsigned end; + // The weight is used to determine which interval to keep when two overlap, + // higher is better + unsigned weight; + Interval(unsigned start, unsigned end, unsigned weight) + : start(start), end(end), weight(weight) {} + + bool operator<(const Interval& other) const { + return start != other.start ? start < other.start + : weight != other.weight ? weight < other.weight + : end < other.end; + } + + bool operator==(const Interval& other) const { + return start == other.start && end == other.end && weight == other.weight; + } +}; + +struct IntervalProcessor { + // Given a vector of Interval, returns a vector of the indices that, mapping + // back to the original input vector, do not overlap with each other, i.e. the + // interval indexes with overlapping interval indexes already removed. + static std::vector filterOverlaps(std::vector&); +}; + +} // namespace wasm + +#endif // wasm_suport_intervals diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index b72094a0204..396403b3080 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -11,6 +11,7 @@ set(unittest_SOURCES dfa_minimization.cpp disjoint_sets.cpp interpreter.cpp + intervals.cpp json.cpp lattices.cpp local-graph.cpp diff --git a/test/gtest/intervals.cpp b/test/gtest/intervals.cpp new file mode 100644 index 00000000000..264084d8d10 --- /dev/null +++ b/test/gtest/intervals.cpp @@ -0,0 +1,95 @@ +#include "support/intervals.h" +#include "ir/utils.h" +#include "gtest/gtest.h" + +using namespace wasm; + +TEST(IntervalsTest, TestEmpty) { + std::vector intervals; + std::vector expected; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} + +TEST(IntervalsTest, TestOne) { + std::vector intervals; + intervals.emplace_back(0, 2, 5); + std::vector expected{0}; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} + +TEST(IntervalsTest, TestNoOverlapFound) { + std::vector intervals; + intervals.emplace_back(0, 4, 2); + intervals.emplace_back(4, 8, 2); + std::vector expected{0, 1}; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} + +TEST(IntervalsTest, TestOverlapFound) { + std::vector intervals; + intervals.emplace_back(0, 2, 5); + intervals.emplace_back(1, 4, 10); + intervals.emplace_back(4, 5, 15); + std::vector expected{1, 2}; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} + +TEST(IntervalsTest, TestOverlapAscendingSequence) { + std::vector intervals; + intervals.emplace_back(0, 3, 6); + intervals.emplace_back(2, 6, 2); + intervals.emplace_back(3, 15, 5); + intervals.emplace_back(4, 11, 1); + intervals.emplace_back(6, 20, 15); + intervals.emplace_back(12, 18, 3); + intervals.emplace_back(14, 21, 5); + intervals.emplace_back(23, 28, 5); + std::vector expected{0, 4, 7}; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} + +TEST(IntervalsTest, TestOverlapDescendingSequence) { + std::vector intervals; + intervals.emplace_back(9, 15, 5); + intervals.emplace_back(8, 11, 1); + intervals.emplace_back(3, 15, 5); + intervals.emplace_back(3, 6, 2); + intervals.emplace_back(2, 10, 3); + intervals.emplace_back(0, 3, 6); + std::vector expected{5, 2}; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} + +TEST(IntervalsTest, TestOverlapRandomSequence) { + std::vector intervals; + intervals.emplace_back(21, 24, 1); + intervals.emplace_back(0, 5, 1); + intervals.emplace_back(11, 18, 1); + intervals.emplace_back(28, 35, 1); + intervals.emplace_back(5, 11, 1); + intervals.emplace_back(18, 21, 1); + intervals.emplace_back(35, 40, 1); + intervals.emplace_back(24, 28, 1); + std::vector expected{1, 4, 2, 5, 0, 7, 3, 6}; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} + +TEST(IntervalsTest, TestOverlapInnerNested) { + std::vector intervals; + intervals.emplace_back(0, 12, 3); + intervals.emplace_back(2, 4, 2); + intervals.emplace_back(3, 6, 5); + intervals.emplace_back(12, 15, 4); + std::vector expected{2, 3}; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} + +TEST(IntervalsTest, TestOverlapOuterNested) { + std::vector intervals; + intervals.emplace_back(0, 3, 6); + intervals.emplace_back(4, 11, 1); + intervals.emplace_back(12, 18, 3); + intervals.emplace_back(2, 15, 6); + std::vector expected{0, 1, 2}; + ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected); +} diff --git a/test/lit/passes/outlining.wast b/test/lit/passes/outlining.wast index a02e6f2cd89..83b772519ed 100644 --- a/test/lit/passes/outlining.wast +++ b/test/lit/passes/outlining.wast @@ -1006,3 +1006,71 @@ (loop (nop)) ) ) + +;; Test that no attempt is made to outline overlapping repeat substrings +;; In the below module, the Outlining optimization identifies two substrings +;; that each repeat twice. During filtering, one of the repeat substrings is +;; found to have an overlapping interval with itself. Because an interval is +;; dropped, only one of the substrings repeats enough times (minimum twice) +;; to warrant outlining. +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (func $outline$ (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + + ;; CHECK: (func $a (type $0) + ;; CHECK-NEXT: (call $outline$) + ;; CHECK-NEXT: (call $outline$) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a + i32.const 0 ;; Begin substring 1 + drop + i32.const 1 + drop + i32.const 2 + drop + i32.const 3 + drop ;; End substring 1 + i32.const 0 ;; Begin substring 1 repeat + drop + i32.const 1 + drop + i32.const 2 + drop + i32.const 3 + drop ;; End substring 1 repeat && Begin substring 2 + i32.const 1 + drop + i32.const 2 + drop ;; End substring 2 && Begin substring 2 repeat + i32.const 1 + drop + i32.const 2 + drop ;; End substring 2 repeat + ) +) From 0997f9b3f1648329607d9bb54e29605c9081fbba Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Tue, 25 Mar 2025 21:14:34 -0700 Subject: [PATCH 374/622] [Outlining] Separating Filter Branch & Return Tests (#7401) In the interest of improved test readability, moved the return part of the FilterBranches test into its own test. Also condensed the test to remove the unnecessary consts. --- test/gtest/stringify.cpp | 88 +++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/test/gtest/stringify.cpp b/test/gtest/stringify.cpp index 897c01b5182..cad0fd37d9d 100644 --- a/test/gtest/stringify.cpp +++ b/test/gtest/stringify.cpp @@ -343,47 +343,51 @@ TEST_F(StringifyTest, FilterLocalSets) { SuffixTree::RepeatedSubstring{2u, (std::vector{6, 16})}})); } -// TODO: Switching to the new parser broke this test. Fix it. +// TODO: Add support for outlining the below block. This block is valid to +// outline because the branch within has a target that would also be included +// in the outlined function. +TEST_F(StringifyTest, FilterBranches) { + static auto branchesModuleText = R"wasm( + (module + (func $a + (block $top + (br $top) + ) + ) + (func $b + (block $top + (br $top) + ) + ) + ) + )wasm"; + Module wasm; + parseWast(wasm, branchesModuleText); + HashStringifyWalker stringify = HashStringifyWalker(); + stringify.walkModule(&wasm); + auto substrings = StringifyProcessor::repeatSubstrings(stringify.hashString); + auto result = StringifyProcessor::filterBranches(substrings, stringify.exprs); -// TEST_F(StringifyTest, FilterBranches) { -// static auto branchesModuleText = R"wasm( -// (module -// (func $a (result i32) -// (block $top (result i32) -// (br $top) -// ) -// (i32.const 7) -// (i32.const 1) -// (i32.const 2) -// (i32.const 4) -// (i32.const 3) -// (return) -// ) -// (func $b (result i32) -// (block $top (result i32) -// (br $top) -// ) -// (i32.const 0) -// (i32.const 1) -// (i32.const 2) -// (i32.const 5) -// (i32.const 3) -// (return) -// ) -// ) -// )wasm"; -// Module wasm; -// parseWast(wasm, branchesModuleText); -// HashStringifyWalker stringify = HashStringifyWalker(); -// stringify.walkModule(&wasm); -// auto substrings = -// StringifyProcessor::repeatSubstrings(stringify.hashString); -// auto result = -// StringifyProcessor::filterBranches(substrings, stringify.exprs); + EXPECT_EQ(result, (std::vector{})); +} -// EXPECT_EQ( -// result, -// (std::vector{ -// // sequence i32.const 1, i32.const 2 is at idx 6 and 21 -// SuffixTree::RepeatedSubstring{2u, (std::vector{6, 21})}})); -// } +TEST_F(StringifyTest, FilterReturn) { + static auto branchesModuleText = R"wasm( + (module + (func $a (result i32) + (return (i32.const 0)) + ) + (func $b (result i32) + (return (i32.const 0)) + ) + ) + )wasm"; + Module wasm; + parseWast(wasm, branchesModuleText); + HashStringifyWalker stringify = HashStringifyWalker(); + stringify.walkModule(&wasm); + auto substrings = StringifyProcessor::repeatSubstrings(stringify.hashString); + auto result = StringifyProcessor::filterBranches(substrings, stringify.exprs); + + EXPECT_EQ(result, (std::vector{})); +} From fbf20108c97ea1058056f870478181ea4f90f12a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 26 Mar 2025 09:36:57 -0700 Subject: [PATCH 375/622] Revert exact reference types (#7402) We decided that Custom Descriptors should introduce exact heap types rather than exact reference types. Although these new features are very similar, the APIs we need to change for them are completely different. One option would have been to keep the existing exact reference type implementation while additionally implementing exact heap types, but there are not enough free bits in the type implementation to have both at once without increasing the alignment of HeapTypeInfo allocations. Portably increasing the alignment is annoying enough that it's easier to just eagerly remove exact reference types to free up the bit for use with exact heap types. Fully or partially revert the following PRs: - #7371 - #7365 - #7360 - #7357 - #7356 - #7355 - #7354 - #7353 - #7347 - #7342 - #7328 Keep the new `.with(...)` Type APIs and the relevant parts of the type relations gtest that were introduced as part of the reverted work. --- scripts/test/fuzzing.py | 8 - src/ir/manipulation.h | 5 +- src/ir/type-updating.cpp | 1 - src/ir/type-updating.h | 1 - src/literal.h | 2 +- src/parser/contexts.h | 18 +- src/parser/parsers.h | 73 +- src/passes/OptimizeInstructions.cpp | 30 +- src/passes/RemoveUnusedBrs.cpp | 4 +- src/tools/fuzzing.h | 6 - src/tools/fuzzing/fuzzing.cpp | 75 +- src/wasm-binary.h | 11 - src/wasm-builder.h | 10 +- src/wasm-type.h | 31 +- src/wasm/literal.cpp | 4 +- src/wasm/wasm-binary.cpp | 56 +- src/wasm/wasm-stack.cpp | 52 +- src/wasm/wasm-type.cpp | 110 +-- src/wasm/wasm-validator.cpp | 4 - src/wasm/wasm.cpp | 22 +- test/gtest/type-builder.cpp | 161 +---- test/lit/basic/exact-references.wast | 672 ------------------ test/lit/basic/reference-types.wast | 16 +- .../lit/ctor-eval/materialize-null-local.wast | 26 - test/lit/exec/exact.wast | 21 - test/lit/heap-types.wast | 4 +- test/lit/passes/cfp.wast | 4 +- test/lit/passes/coalesce-locals-exact.wast | 47 -- test/lit/passes/code-pushing-gc.wast | 4 +- test/lit/passes/dae-gc-refine-params.wast | 2 +- test/lit/passes/dae-gc.wast | 2 +- test/lit/passes/flatten_all-features.wast | 6 +- test/lit/passes/global-refining.wast | 18 +- test/lit/passes/gufa-refs.wast | 52 +- test/lit/passes/gufa-vs-cfp.wast | 4 +- test/lit/passes/heap2local-rmw.wast | 38 +- test/lit/passes/heap2local.wast | 178 ++--- test/lit/passes/local-subtyping-exact.wast | 39 - test/lit/passes/local-subtyping-nn.wast | 4 +- test/lit/passes/local-subtyping.wast | 2 +- test/lit/passes/merge-blocks.wast | 2 +- test/lit/passes/monomorphize-context.wast | 4 +- .../passes/optimize-instructions-exact.wast | 33 - .../passes/optimize-instructions-gc-tnh.wast | 4 +- test/lit/passes/optimize-instructions-gc.wast | 4 +- test/lit/passes/precompute-gc.wast | 2 +- test/lit/passes/remove-unused-brs-exact.wast | 40 -- test/lit/passes/remove-unused-brs-gc.wast | 22 +- .../lit/passes/remove-unused-types-exact.wast | 16 - test/lit/passes/signature-refining_gto.wat | 4 +- test/lit/passes/ssa.wast | 4 +- .../passes/type-refining-isorecursive.wast | 6 +- test/lit/passes/type-refining-rmw.wast | 4 +- test/lit/passes/type-refining.wast | 14 +- test/passes/precompute_all-features.txt | 2 +- 55 files changed, 337 insertions(+), 1647 deletions(-) delete mode 100644 test/lit/basic/exact-references.wast delete mode 100644 test/lit/ctor-eval/materialize-null-local.wast delete mode 100644 test/lit/exec/exact.wast delete mode 100644 test/lit/passes/coalesce-locals-exact.wast delete mode 100644 test/lit/passes/local-subtyping-exact.wast delete mode 100644 test/lit/passes/optimize-instructions-exact.wast delete mode 100644 test/lit/passes/remove-unused-brs-exact.wast delete mode 100644 test/lit/passes/remove-unused-types-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 342c452a588..623b8d4b5a3 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -104,14 +104,6 @@ 'stack_switching_resume.wast', 'stack_switching_resume_throw.wast', 'stack_switching_switch.wast', - # TODO: fuzzer support for exact references - 'exact-references.wast', - 'optimize-instructions-exact.wast', - 'local-subtyping-exact.wast', - 'remove-unused-types-exact.wast', - 'coalesce-locals-exact.wast', - 'remove-unused-brs-exact.wast', - 'exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/ir/manipulation.h b/src/ir/manipulation.h index c766fb8719e..e7816af9fce 100644 --- a/src/ir/manipulation.h +++ b/src/ir/manipulation.h @@ -40,9 +40,10 @@ template inline Nop* nop(InputType* target) { } template -inline RefNull* refNull(InputType* target, HeapType type) { +inline RefNull* refNull(InputType* target, Type type) { + assert(type.isNullable() && type.getHeapType().isBottom()); auto* ret = convert(target); - ret->finalize(Type(type.getBottom(), Nullable, Exact)); + ret->finalize(type); return ret; } diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 65886bd2bd9..6dd91e0960e 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -342,7 +342,6 @@ Type GlobalTypeRewriter::getTempType(Type type) { if (type.isRef()) { auto heapType = type.getHeapType(); if (auto it = typeIndices.find(heapType); it != typeIndices.end()) { - // TODO: Handle exactness. return typeBuilder.getTempRefType(typeBuilder[it->second], type.getNullability()); } diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h index e4e2c536992..fe1cd2806aa 100644 --- a/src/ir/type-updating.h +++ b/src/ir/type-updating.h @@ -497,7 +497,6 @@ class TypeMapper : public GlobalTypeRewriter { auto heapType = type.getHeapType(); auto iter = mapping.find(heapType); if (iter != mapping.end()) { - // TODO: Handle exactness. return getTempType(Type(iter->second, type.getNullability())); } return getTempType(type); diff --git a/src/literal.h b/src/literal.h index 4f9e3d535ad..c3cf2c15f70 100644 --- a/src/literal.h +++ b/src/literal.h @@ -246,7 +246,7 @@ class Literal { } } static Literal makeNull(HeapType type) { - return Literal(Type(type.getBottom(), Nullable, Exact)); + return Literal(Type(type.getBottom(), Nullable)); } static Literal makeFunc(Name func, HeapType type) { return Literal(func, type); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index fa96c270670..4c56780323b 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -127,9 +127,7 @@ struct NullTypeParserCtx { TypeT makeF64() { return Ok{}; } TypeT makeV128() { return Ok{}; } - TypeT makeRefType(HeapTypeT, Nullability, Exactness) { return Ok{}; } - - HeapTypeT getHeapTypeFromRefType(TypeT) { return Ok{}; } + TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; } TupleElemListT makeTupleElemList() { return Ok{}; } void appendTupleElem(TupleElemListT&, TypeT) {} @@ -259,13 +257,10 @@ template struct TypeParserCtx { TypeT makeF64() { return Type::f64; } TypeT makeV128() { return Type::v128; } - TypeT - makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { - return Type(ht, nullability, exactness); + TypeT makeRefType(HeapTypeT ht, Nullability nullability) { + return Type(ht, nullability); } - HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } - std::vector makeTupleElemList() { return {}; } void appendTupleElem(std::vector& elems, Type elem) { elems.push_back(elem); @@ -1122,13 +1117,10 @@ struct ParseTypeDefsCtx : TypeParserCtx { : TypeParserCtx(typeIndices), in(in), builder(builder), names(builder.size()) {} - TypeT - makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { - return builder.getTempRefType(ht, nullability, exactness); + TypeT makeRefType(HeapTypeT ht, Nullability nullability) { + return builder.getTempRefType(ht, nullability); } - HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } - TypeT makeTupleType(const std::vector types) { return builder.getTempTupleType(types); } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 26c20767938..33e9d20fdc9 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -30,8 +30,6 @@ using namespace std::string_view_literals; template Result absheaptype(Ctx&, Shareability); template Result heaptype(Ctx&); -template -MaybeResult maybeReftypeAbbrev(Ctx&); template MaybeResult maybeReftype(Ctx&); template Result reftype(Ctx&); template MaybeResult tupletype(Ctx&); @@ -460,85 +458,68 @@ template Result heaptype(Ctx& ctx) { // | 'i31ref' => i31ref // | 'structref' => structref // | 'arrayref' => arrayref -// | ... -template -MaybeResult maybeReftypeAbbrev(Ctx& ctx) { +// | '(' ref null? t:heaptype ')' => ref null? t +template MaybeResult maybeReftype(Ctx& ctx) { if (ctx.in.takeKeyword("funcref"sv)) { - return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable); } if (ctx.in.takeKeyword("externref"sv)) { - return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable); } if (ctx.in.takeKeyword("anyref"sv)) { - return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable); } if (ctx.in.takeKeyword("eqref"sv)) { - return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable); } if (ctx.in.takeKeyword("i31ref"sv)) { - return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable); } if (ctx.in.takeKeyword("structref"sv)) { - return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable); } if (ctx.in.takeKeyword("arrayref"sv)) { - return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable); } if (ctx.in.takeKeyword("exnref"sv)) { - return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable); } if (ctx.in.takeKeyword("stringref"sv)) { - return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable); } if (ctx.in.takeKeyword("contref"sv)) { - return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeContType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullref"sv)) { - return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullexternref"sv)) { - return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullfuncref"sv)) { - return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullexnref"sv)) { - return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullcontref"sv)) { - return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable); } - return {}; -} -// reftype ::= ... -// | '(' 'exact' (ref null ht):shorthand ')' => ref null exact ht -// | '(' ref null? exact? ht:heaptype ')' => ref null? t -template MaybeResult maybeReftype(Ctx& ctx) { - if (ctx.in.takeSExprStart("exact"sv)) { - auto rt = maybeReftypeAbbrev(ctx); - CHECK_ERR(rt); - if (!rt) { - return ctx.in.err("expected reftype shorthand"); - } - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of reftype"); - } - return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact); + if (!ctx.in.takeSExprStart("ref"sv)) { + return {}; } - if (ctx.in.takeSExprStart("ref"sv)) { - auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; - auto exactness = ctx.in.takeKeyword("exact"sv) ? Exact : Inexact; - auto type = heaptype(ctx); - CHECK_ERR(type); - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of reftype"); - } - return ctx.makeRefType(*type, nullability, exactness); + auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; + + auto type = heaptype(ctx); + CHECK_ERR(type); + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); } - return maybeReftypeAbbrev(ctx); + return ctx.makeRefType(*type, nullability); } template Result reftype(Ctx& ctx) { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 916a493bca6..c8c192685a2 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2277,14 +2277,10 @@ struct OptimizeInstructions // emit a null check. bool needsNullCheck = ref->type.getNullability() == Nullable && curr->type.getNullability() == NonNullable; - // Same with exactness. - bool needsExactCast = ref->type.getExactness() == Inexact && - curr->type.getExactness() == Exact; // If the best value to propagate is the argument to the cast, we can // simply remove the cast (or downgrade it to a null check if - // necessary). This does not work if we need a cast to prove - // exactness. - if (ref == curr->ref && !needsExactCast) { + // necessary). + if (ref == curr->ref) { if (needsNullCheck) { replaceCurrent(builder.makeRefAs(RefAsNonNull, curr->ref)); } else { @@ -2293,9 +2289,9 @@ struct OptimizeInstructions return; } // Otherwise we can't just remove the cast and replace it with `ref` - // because the intermediate expressions might have had side effects or - // we need to check exactness. We can replace the cast with a drop - // followed by a direct return of the value, though. + // because the intermediate expressions might have had side effects. + // We can replace the cast with a drop followed by a direct return of + // the value, though. if (ref->type.isNull()) { // We can materialize the resulting null value directly. // @@ -2303,7 +2299,7 @@ struct OptimizeInstructions // would be, aside from the interesting corner case of // uninhabitable types: // - // (ref.cast (ref func) + // (ref.cast func // (block (result (ref nofunc)) // (unreachable) // ) @@ -2350,20 +2346,18 @@ struct OptimizeInstructions } [[fallthrough]]; case GCTypeUtils::SuccessOnlyIfNull: { + auto nullType = Type(curr->type.getHeapType().getBottom(), Nullable); // The cast either returns null or traps. In trapsNeverHappen mode // we know the result, since by assumption it will not trap. if (getPassOptions().trapsNeverHappen) { - replaceCurrent( - builder.makeBlock({builder.makeDrop(curr->ref), - builder.makeRefNull(curr->type.getHeapType())}, - curr->type)); + replaceCurrent(builder.makeBlock( + {builder.makeDrop(curr->ref), builder.makeRefNull(nullType)}, + curr->type)); return; } // Otherwise, we should have already refined the cast type to cast - // directly to null. We do not further refine the cast type to exact - // null because the extra precision is not useful and doing so would - // increase the size of the instruction encoding. - assert(curr->type.isNull()); + // directly to null. + assert(curr->type == nullType); break; } case GCTypeUtils::Unreachable: diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 72dea0405e1..47a1f1c6553 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -859,8 +859,8 @@ struct RemoveUnusedBrs : public WalkerPass> { if (Type::isSubType(expr->type, type)) { return expr; } - if (type.isNonNullable() && expr->type.isNullable() && - Type::isSubType(expr->type.with(NonNullable), type)) { + if (HeapType::isSubType(expr->type.getHeapType(), + type.getHeapType())) { return builder.makeRefAs(RefAsNonNull, expr); } return builder.makeRefCast(expr, type); diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index bb43fa39fa2..383e5af70c1 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -360,10 +360,6 @@ class TranslateToFuzzReader { // instruction for EH is supposed to exist only at the beginning of a 'catch' // block, so it shouldn't be moved around or deleted freely. bool canBeArbitrarilyReplaced(Expression* curr) { - // TODO: Remove this once we better support exact references. - if (curr->type.isExact()) { - return false; - } return curr->type.isDefaultable() && !EHUtils::containsValidDanglingPop(curr); } @@ -525,9 +521,7 @@ class TranslateToFuzzReader { Type getLoggableType(); bool isLoggableType(Type type); Nullability getNullability(); - Exactness getExactness(); Nullability getSubType(Nullability nullability); - Exactness getSubType(Exactness exactness); HeapType getSubType(HeapType type); Type getSubType(Type type); Nullability getSuperType(Nullability nullability); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index c72b239bda2..2b958590384 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1566,22 +1566,20 @@ void TranslateToFuzzReader::recombine(Function* func) { } std::vector ret; - ret.push_back(type); + auto heapType = type.getHeapType(); + auto nullability = type.getNullability(); - if (type.isNonNullable()) { - auto nullable = getRelevantTypes(type.with(Nullable)); - ret.insert(ret.end(), nullable.begin(), nullable.end()); - } - if (type.isExact()) { - auto inexact = getRelevantTypes(type.with(Inexact)); - ret.insert(ret.end(), inexact.begin(), inexact.end()); - // Do not consider exact references to supertypes. - return ret; + if (nullability == NonNullable) { + ret = getRelevantTypes(Type(heapType, Nullable)); } - for (auto heapType = type.getHeapType().getSuperType(); heapType; - heapType = heapType->getSuperType()) { - ret.push_back(type.with(*heapType)); + while (1) { + ret.push_back(Type(heapType, nullability)); + auto super = heapType.getSuperType(); + if (!super) { + break; + } + heapType = *super; } return ret; @@ -4904,17 +4902,9 @@ static auto makeArrayBoundsCheck(Expression* ref, Function* func, Builder& builder, Expression* length = nullptr) { - // The reference might be a RefNull, in which case its type is exact. But we - // want to avoid creating exact-typed locals until we support them more widely - // in the fuzzer, so adjust the type. TODO: remove this once exact references - // are better supported. - Type refType = ref->type; - if (refType.isExact()) { - refType = refType.with(Inexact); - } - auto tempRef = builder.addVar(func, refType); + auto tempRef = builder.addVar(func, ref->type); auto tempIndex = builder.addVar(func, index->type); - auto* teeRef = builder.makeLocalTee(tempRef, ref, refType); + auto* teeRef = builder.makeLocalTee(tempRef, ref, ref->type); auto* teeIndex = builder.makeLocalTee(tempIndex, index, index->type); auto* getSize = builder.makeArrayLen(teeRef); @@ -4941,7 +4931,7 @@ static auto makeArrayBoundsCheck(Expression* ref, // An additional use of the length, if it was provided. Expression* getLength = nullptr; } result = {builder.makeBinary(LtUInt32, effectiveIndex, getSize), - builder.makeLocalGet(tempRef, refType), + builder.makeLocalGet(tempRef, ref->type), builder.makeLocalGet(tempIndex, index->type), getLength}; return result; @@ -5330,23 +5320,6 @@ Nullability TranslateToFuzzReader::getNullability() { return Nullable; } -Exactness TranslateToFuzzReader::getExactness() { - // Without GC, the only heap types are func and extern, neither of which is - // exactly inhabitable. To avoid introducing uninhabitable types, only - // generate exact references when GC is enabled. We don't need custom - // descriptors to be enabled even though that is the feature that introduces - // exact references because the binary writer can always generalize the exact - // reference types away. - // - // if (wasm.features.hasGC() && oneIn(8)) { - // return Exact; - // } - // - // However, we cannot yet handle creating exact references in general, so for - // now we always generate inexact references when given the choice. TODO. - return Inexact; -} - Nullability TranslateToFuzzReader::getSubType(Nullability nullability) { if (nullability == NonNullable) { return NonNullable; @@ -5354,13 +5327,6 @@ Nullability TranslateToFuzzReader::getSubType(Nullability nullability) { return getNullability(); } -Exactness TranslateToFuzzReader::getSubType(Exactness exactness) { - if (exactness == Exact) { - return Exact; - } - return getExactness(); -} - HeapType TranslateToFuzzReader::getSubType(HeapType type) { if (oneIn(3)) { return type; @@ -5453,18 +5419,9 @@ Type TranslateToFuzzReader::getSubType(Type type) { if (!funcContext && heapType.isMaybeShared(HeapType::exn)) { return type; } - if (type.isExact()) { - // The only other possible heap type is bottom, but we don't want to - // generate too many bottom types. - if (!heapType.isBottom() && oneIn(20)) { - heapType = heapType.getBottom(); - } - } else { - heapType = getSubType(heapType); - } + heapType = getSubType(heapType); auto nullability = getSubType(type.getNullability()); - auto exactness = getSubType(type.getExactness()); - auto subType = Type(heapType, nullability, exactness); + auto subType = Type(heapType, nullability); // We don't want to emit lots of uninhabitable types like (ref none), so // avoid them with high probability. Specifically, if the original type was // inhabitable then return that; avoid adding more uninhabitability. diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 9d99e20ce75..c20e8e5ab0e 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -304,13 +304,6 @@ enum SegmentFlag { UsesExpressions = 1 << 2 }; -enum BrOnCastFlag { - InputNullable = 1 << 0, - OutputNullable = 1 << 1, - InputExact = 1 << 2, - OutputExact = 1 << 3, -}; - enum EncodedType { // value types i32 = -0x1, // 0x7f @@ -334,7 +327,6 @@ enum EncodedType { eqref = -0x13, // 0x6d nonnullable = -0x1c, // 0x64 nullable = -0x1d, // 0x63 - exact = -0x1e, // 0x62 contref = -0x18, // 0x68 nullcontref = -0x0b, // 0x75 // exception handling @@ -1135,8 +1127,6 @@ enum ASTNodes { I31GetS = 0x1d, I31GetU = 0x1e, RefI31Shared = 0x1f, - RefTestRT = 0x20, - RefCastRT = 0x21, // Shared GC Opcodes @@ -1509,7 +1499,6 @@ class WasmBinaryReader { Type getType(); // Get a type given the initial S32LEB has already been read, and is provided. Type getType(int code); - Type getTypeNoExact(int code); HeapType getHeapType(); HeapType getIndexedHeapType(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 13c124783a6..8344355b64a 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -670,11 +670,11 @@ class Builder { } RefNull* makeRefNull(HeapType type) { auto* ret = wasm.allocator.alloc(); - ret->finalize(Type(type.getBottom(), Nullable, Exact)); + ret->finalize(Type(type.getBottom(), Nullable)); return ret; } RefNull* makeRefNull(Type type) { - assert(type.isNullable() && type.isNull() && type.isExact()); + assert(type.isNullable() && type.isNull()); auto* ret = wasm.allocator.alloc(); ret->finalize(type); return ret; @@ -1270,7 +1270,7 @@ class Builder { return makeConst(value); } if (value.isNull()) { - return makeRefNull(type.getHeapType()); + return makeRefNull(type); } if (type.isFunction()) { return makeRefFunc(value.getFunc(), type.getHeapType()); @@ -1435,8 +1435,8 @@ class Builder { return maybeWrap(makeConstantExpression(Literal::makeZeros(curr->type))); } if (curr->type.isNullable()) { - return maybeWrap( - ExpressionManipulator::refNull(curr, curr->type.getHeapType())); + return maybeWrap(ExpressionManipulator::refNull( + curr, Type(curr->type.getHeapType().getBottom(), Nullable))); } if (curr->type.isRef() && curr->type.getHeapType().isMaybeShared(HeapType::i31)) { diff --git a/src/wasm-type.h b/src/wasm-type.h index b250e38c9c8..6b337530a24 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -62,7 +62,6 @@ using Tuple = TypeList; enum Nullability { NonNullable, Nullable }; enum Mutability { Immutable, Mutable }; -enum Exactness { Inexact, Exact }; // HeapType name information used for printing. struct TypeNames { @@ -319,10 +318,10 @@ class Type { // Construct from a heap type description. Also covers construction from // Signature, Struct or Array via implicit conversion to HeapType. - Type(HeapType heapType, Nullability nullable, Exactness exact = Inexact) - : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | - (exact == Exact ? ExactMask : 0)) { - assert(!(heapType.getID() & (TupleMask | NullMask | ExactMask))); + Type(HeapType heapType, Nullability nullable) + : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0)) { + assert(heapType.isBasic() || + !(heapType.getID() & (TupleMask | NullMask | ExactMask))); } // Predicates @@ -371,8 +370,6 @@ class Type { bool isRef() const { return !isBasic() && !(id & TupleMask); } bool isNullable() const { return isRef() && (id & NullMask); } bool isNonNullable() const { return isRef() && !(id & NullMask); } - bool isExact() const { return isRef() && (id & ExactMask); } - bool isInexact() const { return isRef() && !(id & ExactMask); } HeapType getHeapType() const { assert(isRef()); return HeapType(id & ~(NullMask | ExactMask)); @@ -397,20 +394,11 @@ class Type { Nullability getNullability() const { return isNullable() ? Nullable : NonNullable; } - Exactness getExactness() const { - assert(isRef()); - return isExact() ? Exact : Inexact; - } // Return a new reference type with some part updated to the specified value. - Type with(HeapType heapType) { - return Type(heapType, getNullability(), getExactness()); - } + Type with(HeapType heapType) { return Type(heapType, getNullability()); } Type with(Nullability nullability) { - return Type(getHeapType(), nullability, getExactness()); - } - Type with(Exactness exactness) { - return Type(getHeapType(), getNullability(), exactness); + return Type(getHeapType(), nullability); } private: @@ -730,8 +718,7 @@ struct TypeBuilder { return t; } assert(t.isRef()); - return getTempRefType( - map(t.getHeapType()), t.getNullability(), t.getExactness()); + return getTempRefType(map(t.getHeapType()), t.getNullability()); }; auto copyType = [&](Type t) -> Type { if (t.isTuple()) { @@ -784,9 +771,7 @@ struct TypeBuilder { // TypeBuilder's HeapTypes. For Ref types, the HeapType may be a temporary // HeapType owned by this builder or a canonical HeapType. Type getTempTupleType(const Tuple&); - Type getTempRefType(HeapType heapType, - Nullability nullable, - Exactness exact = Inexact); + Type getTempRefType(HeapType heapType, Nullability nullable); // Declare the HeapType being built at index `i` to be an immediate subtype of // the given HeapType. diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index de95fe565bf..9e015501a0a 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -72,9 +72,7 @@ Literal::Literal(const uint8_t init[16]) : type(Type::v128) { } Literal::Literal(std::shared_ptr gcData, HeapType type) - : gcData(gcData), - type(type, gcData ? NonNullable : Nullable, gcData ? Inexact : Exact) { - // TODO: Use exact types for more than just nulls. + : gcData(gcData), type(type, gcData ? NonNullable : Nullable) { // The type must be a proper type for GC data: either a struct, array, or // string; or an externalized version of the same; or a null; or an // internalized string (which appears as an anyref). diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index c3b6d310d12..c18416a727f 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1559,12 +1559,6 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) { void WasmBinaryWriter::writeType(Type type) { if (type.isRef()) { - // Exact references are introduced by the custom descriptors feature, but - // can be used internally even when it is not enabled. In that case, we have - // to generalize the types to be inexact before writing them. - if (!wasm->features.hasCustomDescriptors()) { - type = Type(type.getHeapType(), type.getNullability(), Inexact); - } // The only reference types allowed without GC are funcref, externref, and // exnref. We internally use more refined versions of those types, but we // cannot emit those without GC. @@ -1581,12 +1575,6 @@ void WasmBinaryWriter::writeType(Type type) { type = Type(type.getHeapType().getTop(), Nullable); } } - // If the type is exact, emit the exact prefix and continue on without - // considering exactness. - if (type.isExact()) { - o << S32LEB(BinaryConsts::EncodedType::exact); - type = Type(type.getHeapType(), type.getNullability(), Inexact); - } auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { switch (heapType.getBasic(Unshared)) { @@ -2175,7 +2163,7 @@ Signature WasmBinaryReader::getBlockType() { return Signature(Type::none, getType(code)); } -Type WasmBinaryReader::getTypeNoExact(int code) { +Type WasmBinaryReader::getType(int code) { Type type; if (getBasicType(code, type)) { return type; @@ -2191,17 +2179,6 @@ Type WasmBinaryReader::getTypeNoExact(int code) { WASM_UNREACHABLE("unexpected type"); } -Type WasmBinaryReader::getType(int code) { - if (code == BinaryConsts::EncodedType::exact) { - auto type = getTypeNoExact(getS32LEB()); - if (!type.isRef()) { - throwError("invalid exact prefix on non-reference type"); - } - return Type(type.getHeapType(), type.getNullability(), Exact); - } - return getTypeNoExact(code); -} - Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { @@ -2362,7 +2339,7 @@ void WasmBinaryReader::readTypes() { } return builder.getTempHeapType(size_t(htCode)); }; - auto makeTypeNoExact = [&](int32_t typeCode) { + auto makeType = [&](int32_t typeCode) { Type type; if (getBasicType(typeCode, type)) { return type; @@ -2387,17 +2364,6 @@ void WasmBinaryReader::readTypes() { } WASM_UNREACHABLE("unexpected type"); }; - auto makeType = [&](int32_t typeCode) { - if (typeCode == BinaryConsts::EncodedType::exact) { - auto type = makeTypeNoExact(getS32LEB()); - if (!type.isRef()) { - throwError("unexpected exact prefix on non-reference type"); - } - return builder.getTempRefType( - type.getHeapType(), type.getNullability(), Exact); - } - return makeTypeNoExact(typeCode); - }; auto readType = [&]() { return makeType(getS32LEB()); }; auto readSignatureDef = [&]() { @@ -4271,30 +4237,16 @@ Result<> WasmBinaryReader::readInst() { return builder.makeRefTest(Type(getHeapType(), NonNullable)); case BinaryConsts::RefTestNull: return builder.makeRefTest(Type(getHeapType(), Nullable)); - case BinaryConsts::RefTestRT: - return builder.makeRefTest(getType()); case BinaryConsts::RefCast: return builder.makeRefCast(Type(getHeapType(), NonNullable)); case BinaryConsts::RefCastNull: return builder.makeRefCast(Type(getHeapType(), Nullable)); - case BinaryConsts::RefCastRT: - return builder.makeRefCast(getType()); case BinaryConsts::BrOnCast: case BinaryConsts::BrOnCastFail: { auto flags = getInt8(); auto label = getU32LEB(); - auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) - ? Nullable - : NonNullable; - auto dstNull = (flags & BinaryConsts::BrOnCastFlag::OutputNullable) - ? Nullable - : NonNullable; - auto srcExact = - (flags & BinaryConsts::BrOnCastFlag::InputExact) ? Exact : Inexact; - auto dstExact = - (flags & BinaryConsts::BrOnCastFlag::OutputExact) ? Exact : Inexact; - auto in = Type(getHeapType(), srcNull, srcExact); - auto cast = Type(getHeapType(), dstNull, dstExact); + auto in = Type(getHeapType(), (flags & 1) ? Nullable : NonNullable); + auto cast = Type(getHeapType(), (flags & 2) ? Nullable : NonNullable); auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; return builder.makeBrOn(label, kind, in, cast); } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 506b11a085e..060b01b04ee 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2260,38 +2260,22 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { void BinaryInstWriter::visitRefTest(RefTest* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->castType.isExact() && - parent.getModule()->features.hasCustomDescriptors()) { - // Fall back to the general form with a reftype immediate. - o << U32LEB(BinaryConsts::RefTestRT); - parent.writeType(curr->castType); + if (curr->castType.isNullable()) { + o << U32LEB(BinaryConsts::RefTestNull); } else { - // Use the special-case form with heap type immediate. - if (curr->castType.isNullable()) { - o << U32LEB(BinaryConsts::RefTestNull); - } else { - o << U32LEB(BinaryConsts::RefTest); - } - parent.writeHeapType(curr->castType.getHeapType()); + o << U32LEB(BinaryConsts::RefTest); } + parent.writeHeapType(curr->castType.getHeapType()); } void BinaryInstWriter::visitRefCast(RefCast* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->type.isExact() && - parent.getModule()->features.hasCustomDescriptors()) { - // Fall back to the general form with a reftype immediate. - o << U32LEB(BinaryConsts::RefCastRT); - parent.writeType(curr->type); + if (curr->type.isNullable()) { + o << U32LEB(BinaryConsts::RefCastNull); } else { - // Use the special-case form with heap type immediate. - if (curr->type.isNullable()) { - o << U32LEB(BinaryConsts::RefCastNull); - } else { - o << U32LEB(BinaryConsts::RefCast); - } - parent.writeHeapType(curr->type.getHeapType()); + o << U32LEB(BinaryConsts::RefCast); } + parent.writeHeapType(curr->type.getHeapType()); } void BinaryInstWriter::visitBrOn(BrOn* curr) { @@ -2314,24 +2298,8 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { } assert(curr->ref->type.isRef()); assert(Type::isSubType(curr->castType, curr->ref->type)); - uint8_t flags = 0; - if (curr->ref->type.isNullable()) { - flags |= BinaryConsts::BrOnCastFlag::InputNullable; - } - if (curr->castType.isNullable()) { - flags |= BinaryConsts::BrOnCastFlag::OutputNullable; - } - if (parent.getModule()->features.hasCustomDescriptors()) { - // If custom descriptors (and therefore exact references) are not - // enabled, then these flags wouldn't be recognized, and we will be - // generalizing all exact references to be non-exact anyway. - if (curr->ref->type.isExact()) { - flags |= BinaryConsts::BrOnCastFlag::InputExact; - } - if (curr->castType.isExact()) { - flags |= BinaryConsts::BrOnCastFlag::OutputExact; - } - } + uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | + (curr->castType.isNullable() ? 2 : 0); o << flags; o << U32LEB(getBreakIndex(curr->name)); parent.writeHeapType(curr->ref->type.getHeapType()); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index ada53eb150b..5cdb76c19dd 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -790,19 +790,11 @@ Type Type::getLeastUpperBound(Type a, Type b) { return Type(elems); } if (a.isRef() && b.isRef()) { - auto heapTypeA = a.getHeapType(); - auto heapTypeB = b.getHeapType(); - if (auto heapType = HeapType::getLeastUpperBound(heapTypeA, heapTypeB)) { + if (auto heapType = + HeapType::getLeastUpperBound(a.getHeapType(), b.getHeapType())) { auto nullability = (a.isNullable() || b.isNullable()) ? Nullable : NonNullable; - auto exactness = (a.isInexact() || b.isInexact()) ? Inexact : Exact; - // The LUB can only be exact if the heap types are the same or one of them - // is bottom. - if (heapTypeA != heapTypeB && !heapTypeA.isBottom() && - !heapTypeB.isBottom()) { - exactness = Inexact; - } - return Type(*heapType, nullability, exactness); + return Type(*heapType, nullability); } } return Type::none; @@ -836,7 +828,6 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } auto nullability = (a.isNonNullable() || b.isNonNullable()) ? NonNullable : Nullable; - auto exactness = (a.isExact() || b.isExact()) ? Exact : Inexact; HeapType heapType; if (HeapType::isSubType(heapA, heapB)) { heapType = heapA; @@ -845,13 +836,7 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } else { heapType = heapA.getBottom(); } - // If one of the types is exact, but the GLB heap type is different than its - // heap type, then we must make the GLB heap type bottom. - if ((a.isExact() && heapType != heapA) || - (b.isExact() && heapType != heapB)) { - heapType = heapA.getBottom(); - } - return Type(heapType, nullability, exactness); + return Type(heapType, nullability); } const Type& Type::Iterator::operator*() const { @@ -1506,24 +1491,14 @@ bool SubTyper::isSubType(Type a, Type b) { if (a == Type::unreachable) { return true; } + if (a.isRef() && b.isRef()) { + return (a.isNullable() == b.isNullable() || !a.isNullable()) && + isSubType(a.getHeapType(), b.getHeapType()); + } if (a.isTuple() && b.isTuple()) { return isSubType(a.getTuple(), b.getTuple()); } - if (!a.isRef() || !b.isRef()) { - return false; - } - if (a.isNullable() && !b.isNullable()) { - return false; - } - if (a.isInexact() && !b.isInexact()) { - return false; - } - auto heapTypeA = a.getHeapType(); - auto heapTypeB = b.getHeapType(); - if (b.isExact() && !heapTypeA.isBottom()) { - return heapTypeA == heapTypeB; - } - return isSubType(heapTypeA, heapTypeB); + return false; } bool SubTyper::isSubType(HeapType a, HeapType b) { @@ -1671,69 +1646,44 @@ std::ostream& TypePrinter::print(Type type) { } else if (type.isRef()) { auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { - if (type.isExact()) { - os << "(exact "; - } // Print shorthands for certain basic heap types. switch (heapType.getBasic(Unshared)) { case HeapType::ext: - os << "externref"; - break; + return os << "externref"; case HeapType::func: - os << "funcref"; - break; + return os << "funcref"; case HeapType::cont: - os << "contref"; - break; + return os << "contref"; case HeapType::any: - os << "anyref"; - break; + return os << "anyref"; case HeapType::eq: - os << "eqref"; - break; + return os << "eqref"; case HeapType::i31: - os << "i31ref"; - break; + return os << "i31ref"; case HeapType::struct_: - os << "structref"; - break; + return os << "structref"; case HeapType::array: - os << "arrayref"; - break; + return os << "arrayref"; case HeapType::exn: - os << "exnref"; - break; + return os << "exnref"; case HeapType::string: - os << "stringref"; - break; + return os << "stringref"; case HeapType::none: - os << "nullref"; - break; + return os << "nullref"; case HeapType::noext: - os << "nullexternref"; - break; + return os << "nullexternref"; case HeapType::nofunc: - os << "nullfuncref"; - break; + return os << "nullfuncref"; case HeapType::nocont: - os << "nullcontref"; - break; + return os << "nullcontref"; case HeapType::noexn: - os << "nullexnref"; - break; + return os << "nullexnref"; } - if (type.isExact()) { - os << ')'; - } - return os; } os << "(ref "; if (type.isNullable()) { os << "null "; } - if (type.isExact()) { - os << "exact "; - } printHeapTypeName(heapType); os << ')'; } else { @@ -1977,9 +1927,8 @@ size_t RecGroupHasher::hash(Type type) const { return digest; } assert(type.isRef()); - wasm::rehash(digest, type.getNullability()); - wasm::rehash(digest, type.getExactness()); - hash_combine(digest, hash(type.getHeapType())); + rehash(digest, type.getNullability()); + rehash(digest, hash(type.getHeapType())); return digest; } @@ -2110,7 +2059,6 @@ bool RecGroupEquator::eq(Type a, Type b) const { } if (a.isRef() && b.isRef()) { return a.getNullability() == b.getNullability() && - a.getExactness() == b.getExactness() && eq(a.getHeapType(), b.getHeapType()); } return false; @@ -2321,10 +2269,8 @@ Type TypeBuilder::getTempTupleType(const Tuple& tuple) { return impl->tupleStore.insert(tuple); } -Type TypeBuilder::getTempRefType(HeapType type, - Nullability nullable, - Exactness exact) { - return Type(type, nullable, exact); +Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) { + return Type(type, nullable); } void TypeBuilder::setSubType(size_t i, std::optional super) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 3b7e33390f3..e33d5c3ef0d 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2288,10 +2288,6 @@ void FunctionValidator::visitRefNull(RefNull* curr) { curr->type.isNullable(), curr, "ref.null types must be nullable")) { return; } - if (!shouldBeTrue( - curr->type.isExact(), curr, "ref.null types must be exact")) { - return; - } shouldBeTrue( curr->type.isNull(), curr, "ref.null must have a bottom heap type"); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 5cdefe3d25a..ad84449be61 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -800,7 +800,7 @@ void MemoryGrow::finalize() { void RefNull::finalize(HeapType heapType) { assert(heapType.isBottom()); - type = Type(heapType, Nullable, Exact); + type = Type(heapType, Nullable); } void RefNull::finalize(Type type_) { type = type_; } @@ -923,7 +923,6 @@ static void populateTryTableSentTypes(TryTable* curr, Module* wasm) { // wasm spec defines when GC is enabled (=== non-nullable types are allowed). // If GC is not enabled then we emit a nullable type in the binary format in // WasmBinaryWriter::writeType. - // TODO: Make this exact. Type exnref = Type(HeapType::exn, NonNullable); for (Index i = 0; i < curr->catchTags.size(); i++) { auto tagName = curr->catchTags[i]; @@ -978,7 +977,6 @@ void RefI31::finalize() { if (value->type == Type::unreachable) { type = Type::unreachable; } else { - // TODO: Make this exact. assert(type.isRef() && type.getHeapType().isMaybeShared(HeapType::i31)); } } @@ -1014,12 +1012,10 @@ void CallRef::finalize() { // unreachable instead (and similar in other GC accessors), although this // would currently cause the parser to admit more invalid modules. if (type.isRef()) { - // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } else if (type.isTuple()) { Tuple elems; for (auto t : type) { - // TODO: Make this exact. elems.push_back( t.isRef() ? Type(t.getHeapType().getBottom(), NonNullable) : t); } @@ -1155,7 +1151,6 @@ void StructGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { - // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1231,7 +1226,6 @@ void ArrayGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { - // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1313,13 +1307,11 @@ void RefAs::finalize() { break; case AnyConvertExtern: type = Type(HeapTypes::any.getBasic(valHeapType.getShared()), - value->type.getNullability(), - Inexact); + value->type.getNullability()); break; case ExternConvertAny: type = Type(HeapTypes::ext.getBasic(valHeapType.getShared()), - value->type.getNullability(), - Inexact); + value->type.getNullability()); break; default: WASM_UNREACHABLE("invalid ref.as_*"); @@ -1332,15 +1324,11 @@ void StringNew::finalize() { (end && end->type == Type::unreachable)) { type = Type::unreachable; } else { - // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } -void StringConst::finalize() { - // TODO: Make this exact. - type = Type(HeapType::string, NonNullable); -} +void StringConst::finalize() { type = Type(HeapType::string, NonNullable); } void StringMeasure::finalize() { if (ref->type == Type::unreachable) { @@ -1363,7 +1351,6 @@ void StringConcat::finalize() { if (left->type == Type::unreachable || right->type == Type::unreachable) { type = Type::unreachable; } else { - // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } @@ -1389,7 +1376,6 @@ void StringSliceWTF::finalize() { end->type == Type::unreachable) { type = Type::unreachable; } else { - // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 0f8463d55da..1e676b7194a 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -428,32 +428,6 @@ TEST_F(TypeTest, CanonicalizeUses) { EXPECT_NE(built[4], built[6]); } -TEST_F(TypeTest, CanonicalizeExactRefs) { - TypeBuilder builder(4); - - // Types that vary in exactness or nullability of references are different. - Type a = builder.getTempRefType(builder[0], Nullable, Inexact); - Type b = builder.getTempRefType(builder[1], NonNullable, Inexact); - Type c = builder.getTempRefType(builder[2], Nullable, Exact); - Type d = builder.getTempRefType(builder[3], NonNullable, Exact); - - builder[0] = Struct({Field(a, Mutable)}); - builder[1] = Struct({Field(b, Mutable)}); - builder[2] = Struct({Field(c, Mutable)}); - builder[3] = Struct({Field(d, Mutable)}); - - auto result = builder.build(); - ASSERT_TRUE(result); - auto built = *result; - - EXPECT_NE(built[0], built[1]); - EXPECT_NE(built[0], built[2]); - EXPECT_NE(built[0], built[3]); - EXPECT_NE(built[1], built[2]); - EXPECT_NE(built[1], built[3]); - EXPECT_NE(built[2], built[3]); -} - TEST_F(TypeTest, CanonicalizeSelfReferences) { TypeBuilder builder(5); // Single self-reference @@ -1173,26 +1147,18 @@ FUZZ_TEST(TypeFuzzTest, TestHeapTypeRelationsFuzz) #endif // FUZZTEST TEST_F(TypeTest, TestTypeRelations) { - Type any = Type(HeapType::any, NonNullable, Inexact); - Type nullAny = Type(HeapType::any, Nullable, Inexact); - Type exactAny = Type(HeapType::any, NonNullable, Exact); - Type nullExactAny = Type(HeapType::any, Nullable, Exact); + Type any = Type(HeapType::any, NonNullable); + Type nullAny = Type(HeapType::any, Nullable); HeapType defined = Struct(); - Type def = Type(defined, NonNullable, Inexact); - Type nullDef = Type(defined, Nullable, Inexact); - Type exactDef = Type(defined, NonNullable, Exact); - Type nullExactDef = Type(defined, Nullable, Exact); + Type def = Type(defined, NonNullable); + Type nullDef = Type(defined, Nullable); - Type none = Type(HeapType::none, NonNullable, Inexact); - Type nullNone = Type(HeapType::none, Nullable, Inexact); - Type exactNone = Type(HeapType::none, NonNullable, Exact); - Type nullExactNone = Type(HeapType::none, Nullable, Exact); + Type none = Type(HeapType::none, NonNullable); + Type nullNone = Type(HeapType::none, Nullable); - Type func = Type(HeapType::func, NonNullable, Inexact); - Type nullFunc = Type(HeapType::func, Nullable, Inexact); - Type exactFunc = Type(HeapType::func, NonNullable, Exact); - Type nullExactFunc = Type(HeapType::func, Nullable, Exact); + Type func = Type(HeapType::func, NonNullable); + Type nullFunc = Type(HeapType::func, Nullable); Type i32 = Type::i32; Type unreachable = Type::unreachable; @@ -1251,165 +1217,54 @@ TEST_F(TypeTest, TestTypeRelations) { assertLUB(any, any, any, any); assertLUB(any, nullAny, nullAny, any); - assertLUB(any, exactAny, any, exactAny); - assertLUB(any, nullExactAny, nullAny, exactAny); assertLUB(any, def, any, def); assertLUB(any, nullDef, nullAny, def); - assertLUB(any, exactDef, any, exactDef); - assertLUB(any, nullExactDef, nullAny, exactDef); assertLUB(any, none, any, none); assertLUB(any, nullNone, nullAny, none); - assertLUB(any, exactNone, any, exactNone); - assertLUB(any, nullExactNone, nullAny, exactNone); assertLUB(any, func, Type(Type::none), unreachable); assertLUB(any, nullFunc, Type(Type::none), unreachable); - assertLUB(any, exactFunc, Type(Type::none), unreachable); - assertLUB(any, nullExactFunc, Type(Type::none), unreachable); assertLUB(any, i32, Type(Type::none), unreachable); assertLUB(any, unreachable, any, unreachable); assertLUB(nullAny, nullAny, nullAny, nullAny); - assertLUB(nullAny, exactAny, nullAny, exactAny); - assertLUB(nullAny, nullExactAny, nullAny, nullExactAny); assertLUB(nullAny, def, nullAny, def); assertLUB(nullAny, nullDef, nullAny, nullDef); - assertLUB(nullAny, exactDef, nullAny, exactDef); - assertLUB(nullAny, nullExactDef, nullAny, nullExactDef); assertLUB(nullAny, none, nullAny, none); assertLUB(nullAny, nullNone, nullAny, nullNone); - assertLUB(nullAny, exactNone, nullAny, exactNone); - assertLUB(nullAny, nullExactNone, nullAny, nullExactNone); assertLUB(nullAny, func, Type(Type::none), unreachable); assertLUB(nullAny, nullFunc, Type(Type::none), unreachable); - assertLUB(nullAny, exactFunc, Type(Type::none), unreachable); - assertLUB(nullAny, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullAny, i32, Type(Type::none), unreachable); assertLUB(nullAny, unreachable, nullAny, unreachable); - assertLUB(exactAny, exactAny, exactAny, exactAny); - assertLUB(exactAny, nullExactAny, nullExactAny, exactAny); - assertLUB(exactAny, def, any, exactNone); - assertLUB(exactAny, nullDef, nullAny, exactNone); - assertLUB(exactAny, exactDef, any, exactNone); - assertLUB(exactAny, nullExactDef, nullAny, exactNone); - assertLUB(exactAny, none, any, exactNone); - assertLUB(exactAny, nullNone, nullAny, exactNone); - assertLUB(exactAny, exactNone, exactAny, exactNone); - assertLUB(exactAny, nullExactNone, nullExactAny, exactNone); - assertLUB(exactAny, func, Type(Type::none), unreachable); - assertLUB(exactAny, nullFunc, Type(Type::none), unreachable); - assertLUB(exactAny, exactFunc, Type(Type::none), unreachable); - assertLUB(exactAny, nullExactFunc, Type(Type::none), unreachable); - assertLUB(exactAny, i32, Type(Type::none), unreachable); - assertLUB(exactAny, unreachable, exactAny, unreachable); - - assertLUB(nullExactAny, nullExactAny, nullExactAny, nullExactAny); - assertLUB(nullExactAny, def, nullAny, exactNone); - assertLUB(nullExactAny, nullDef, nullAny, nullExactNone); - assertLUB(nullExactAny, exactDef, nullAny, exactNone); - assertLUB(nullExactAny, nullExactDef, nullAny, nullExactNone); - assertLUB(nullExactAny, none, nullAny, exactNone); - assertLUB(nullExactAny, nullNone, nullAny, nullExactNone); - assertLUB(nullExactAny, exactNone, nullExactAny, exactNone); - assertLUB(nullExactAny, nullExactNone, nullExactAny, nullExactNone); - assertLUB(nullExactAny, func, Type(Type::none), unreachable); - assertLUB(nullExactAny, nullFunc, Type(Type::none), unreachable); - assertLUB(nullExactAny, exactFunc, Type(Type::none), unreachable); - assertLUB(nullExactAny, nullExactFunc, Type(Type::none), unreachable); - assertLUB(nullExactAny, i32, Type(Type::none), unreachable); - assertLUB(nullExactAny, unreachable, nullExactAny, unreachable); - assertLUB(def, def, def, def); assertLUB(def, nullDef, nullDef, def); - assertLUB(def, exactDef, def, exactDef); - assertLUB(def, nullExactDef, nullDef, exactDef); assertLUB(def, none, def, none); assertLUB(def, nullNone, nullDef, none); - assertLUB(def, exactNone, def, exactNone); - assertLUB(def, nullExactNone, nullDef, exactNone); assertLUB(def, func, Type(Type::none), unreachable); assertLUB(def, nullFunc, Type(Type::none), unreachable); - assertLUB(def, exactFunc, Type(Type::none), unreachable); - assertLUB(def, nullExactFunc, Type(Type::none), unreachable); assertLUB(def, i32, Type(Type::none), unreachable); assertLUB(def, unreachable, def, unreachable); assertLUB(nullDef, nullDef, nullDef, nullDef); - assertLUB(nullDef, exactDef, nullDef, exactDef); - assertLUB(nullDef, nullExactDef, nullDef, nullExactDef); assertLUB(nullDef, none, nullDef, none); assertLUB(nullDef, nullNone, nullDef, nullNone); - assertLUB(nullDef, exactNone, nullDef, exactNone); - assertLUB(nullDef, nullExactNone, nullDef, nullExactNone); assertLUB(nullDef, func, Type(Type::none), unreachable); assertLUB(nullDef, nullFunc, Type(Type::none), unreachable); - assertLUB(nullDef, exactFunc, Type(Type::none), unreachable); - assertLUB(nullDef, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullDef, i32, Type(Type::none), unreachable); assertLUB(nullDef, unreachable, nullDef, unreachable); - assertLUB(exactDef, exactDef, exactDef, exactDef); - assertLUB(exactDef, nullExactDef, nullExactDef, exactDef); - assertLUB(exactDef, none, def, exactNone); - assertLUB(exactDef, nullNone, nullDef, exactNone); - assertLUB(exactDef, exactNone, exactDef, exactNone); - assertLUB(exactDef, nullExactNone, nullExactDef, exactNone); - assertLUB(exactDef, func, Type(Type::none), unreachable); - assertLUB(exactDef, nullFunc, Type(Type::none), unreachable); - assertLUB(exactDef, exactFunc, Type(Type::none), unreachable); - assertLUB(exactDef, nullExactFunc, Type(Type::none), unreachable); - assertLUB(exactDef, i32, Type(Type::none), unreachable); - assertLUB(exactDef, unreachable, exactDef, unreachable); - - assertLUB(nullExactDef, nullExactDef, nullExactDef, nullExactDef); - assertLUB(nullExactDef, none, nullDef, exactNone); - assertLUB(nullExactDef, nullNone, nullDef, nullExactNone); - assertLUB(nullExactDef, exactNone, nullExactDef, exactNone); - assertLUB(nullExactDef, nullExactNone, nullExactDef, nullExactNone); - assertLUB(nullExactDef, func, Type(Type::none), unreachable); - assertLUB(nullExactDef, nullFunc, Type(Type::none), unreachable); - assertLUB(nullExactDef, exactFunc, Type(Type::none), unreachable); - assertLUB(nullExactDef, nullExactFunc, Type(Type::none), unreachable); - assertLUB(nullExactDef, i32, Type(Type::none), unreachable); - assertLUB(nullExactDef, unreachable, nullExactDef, unreachable); - assertLUB(none, none, none, none); assertLUB(none, nullNone, nullNone, none); - assertLUB(none, exactNone, none, exactNone); - assertLUB(none, nullExactNone, nullNone, exactNone); assertLUB(none, func, Type(Type::none), unreachable); assertLUB(none, nullFunc, Type(Type::none), unreachable); - assertLUB(none, exactFunc, Type(Type::none), unreachable); - assertLUB(none, nullExactFunc, Type(Type::none), unreachable); assertLUB(none, i32, Type(Type::none), unreachable); assertLUB(none, unreachable, none, unreachable); assertLUB(nullNone, nullNone, nullNone, nullNone); - assertLUB(nullNone, exactNone, nullNone, exactNone); - assertLUB(nullNone, nullExactNone, nullNone, nullExactNone); assertLUB(nullNone, func, Type(Type::none), unreachable); assertLUB(nullNone, nullFunc, Type(Type::none), unreachable); - assertLUB(nullNone, exactFunc, Type(Type::none), unreachable); - assertLUB(nullNone, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullNone, i32, Type(Type::none), unreachable); assertLUB(nullNone, unreachable, nullNone, unreachable); - - assertLUB(exactNone, exactNone, exactNone, exactNone); - assertLUB(exactNone, nullExactNone, nullExactNone, exactNone); - assertLUB(exactNone, func, Type(Type::none), unreachable); - assertLUB(exactNone, nullFunc, Type(Type::none), unreachable); - assertLUB(exactNone, exactFunc, Type(Type::none), unreachable); - assertLUB(exactNone, nullExactFunc, Type(Type::none), unreachable); - assertLUB(exactNone, i32, Type(Type::none), unreachable); - assertLUB(exactNone, unreachable, exactNone, unreachable); - - assertLUB(nullExactNone, nullExactNone, nullExactNone, nullExactNone); - assertLUB(nullExactNone, func, Type(Type::none), unreachable); - assertLUB(nullExactNone, nullFunc, Type(Type::none), unreachable); - assertLUB(nullExactNone, exactFunc, Type(Type::none), unreachable); - assertLUB(nullExactNone, nullExactFunc, Type(Type::none), unreachable); - assertLUB(nullExactNone, i32, Type(Type::none), unreachable); - assertLUB(nullExactNone, unreachable, nullExactNone, unreachable); } TEST_F(TypeTest, TestSubtypeErrors) { diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast deleted file mode 100644 index e6c1599ea48..00000000000 --- a/test/lit/basic/exact-references.wast +++ /dev/null @@ -1,672 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: wasm-opt %s -all -o %t.text.wast -g -S -;; RUN: wasm-as %s -all -g -o %t.wasm -;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast -;; RUN: wasm-as %s -all -o %t.nodebug.wasm -;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast -;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT -;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN -;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG - -;; Also check that if we emit a binary without custom descriptors enabled, the -;; types are generalized to be inexact. - -;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o %t.noexact.wasm -;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s --check-prefix=NO-EXACT - -(module - ;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) - ;; CHECK-BIN: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) - ;; NO-EXACT: (type $foo (struct (field anyref) (field (ref any)) (field (ref null $foo)) (field (ref $foo)))) - (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) - - - ;; CHECK-TEXT: (type $1 (func (param (exact anyref)) (result (ref exact any)))) - - ;; CHECK-TEXT: (type $2 (func (param anyref) (result anyref))) - - ;; CHECK-TEXT: (type $3 (func (param (exact i31ref)))) - - ;; CHECK-TEXT: (type $4 (func (param (exact eqref)))) - - ;; CHECK-TEXT: (type $5 (func (param (exact anyref)))) - - ;; CHECK-TEXT: (type $6 (func (param (exact anyref)) (result (exact anyref)))) - - ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) - ;; CHECK-BIN: (type $1 (func (param (exact anyref)) (result (ref exact any)))) - - ;; CHECK-BIN: (type $2 (func (param anyref) (result anyref))) - - ;; CHECK-BIN: (type $3 (func (param (exact i31ref)))) - - ;; CHECK-BIN: (type $4 (func (param (exact eqref)))) - - ;; CHECK-BIN: (type $5 (func (param (exact anyref)))) - - ;; CHECK-BIN: (type $6 (func (param (exact anyref)) (result (exact anyref)))) - - ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) - ;; NO-EXACT: (type $1 (func (param anyref) (result anyref))) - - ;; NO-EXACT: (type $2 (func (param anyref) (result (ref any)))) - - ;; NO-EXACT: (type $3 (func (param i31ref))) - - ;; NO-EXACT: (type $4 (func (param eqref))) - - ;; NO-EXACT: (type $5 (func (param anyref))) - - ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) - (import "" "g1" (global $g1 (exact anyref))) - - ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact any))) - ;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact any))) - ;; NO-EXACT: (import "" "g2" (global $g2 (ref any))) - (import "" "g2" (global $g2 (ref exact any))) - - ;; CHECK-TEXT: (import "" "g3" (global $g3 (ref null exact $foo))) - ;; CHECK-BIN: (import "" "g3" (global $g3 (ref null exact $foo))) - ;; NO-EXACT: (import "" "g3" (global $g3 (ref null $foo))) - (import "" "g3" (global $g3 (ref null exact $foo))) - - ;; CHECK-TEXT: (import "" "g4" (global $g4 (ref exact $foo))) - ;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo))) - ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) - (import "" "g4" (global $g4 (ref exact $foo))) - - ;; CHECK-TEXT: (func $ref-test (type $3) (param $0 (exact i31ref)) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.test (ref exact i31) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.test (exact i31ref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-test (type $3) (param $0 (exact i31ref)) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (ref exact i31) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (exact i31ref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-test (type $3) (param $0 i31ref) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test (ref i31) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test i31ref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $ref-test (param (ref null exact i31)) - (drop - (ref.test (ref exact i31) - (local.get 0) - ) - ) - (drop - (ref.test (ref null exact i31) - (local.get 0) - ) - ) - ) - - ;; CHECK-TEXT: (func $ref-cast (type $4) (param $0 (exact eqref)) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.cast (ref exact eq) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.cast (exact eqref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.cast (ref exact none) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast (type $4) (param $0 (exact eqref)) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.cast (ref exact eq) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.cast (exact eqref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.cast (ref exact none) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-cast (type $4) (param $0 eqref) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.cast (ref eq) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.cast eqref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.cast (ref none) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $ref-cast (param (ref null exact eq)) - (drop - (ref.cast (ref exact eq) - (local.get 0) - ) - ) - (drop - (ref.cast (ref null exact eq) - (local.get 0) - ) - ) - (drop - (ref.cast (ref exact i31) - (local.get 0) - ) - ) - ) - - ;; CHECK-TEXT: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) - ;; CHECK-TEXT-NEXT: (block $label (result anyref) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact eqref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (ref exact eq) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact i31ref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) - ;; CHECK-BIN-NEXT: (block $block (result anyref) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact eqref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (ref exact eq) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact i31ref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) - ;; NO-EXACT-NEXT: (block $block (result anyref) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block anyref eqref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block anyref (ref eq) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block anyref i31ref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $br-on-cast (param anyref) (result anyref) - (drop - (br_on_cast 0 anyref (ref null exact eq) - (local.get 0) - ) - ) - (drop - (br_on_cast 0 anyref (ref exact eq) - (local.get 0) - ) - ) - (drop - (br_on_cast 0 anyref (ref null exact i31) - (local.get 0) - ) - ) - (local.get 0) - ) - - ;; CHECK-TEXT: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) - ;; CHECK-TEXT-NEXT: (block $label (result anyref) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact eqref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (ref exact eq) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact i31ref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) - ;; CHECK-BIN-NEXT: (block $block (result anyref) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact eqref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (ref exact eq) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact i31ref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) - ;; NO-EXACT-NEXT: (block $block (result anyref) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref eqref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref (ref eq) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref i31ref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $br-on-cast-fail (param anyref) (result anyref) - (drop - (br_on_cast_fail 0 anyref (ref null exact eq) - (local.get 0) - ) - ) - (drop - (br_on_cast_fail 0 anyref (ref exact eq) - (local.get 0) - ) - ) - (drop - (br_on_cast_fail 0 anyref (ref null exact i31) - (local.get 0) - ) - ) - (local.get 0) - ) - - ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (ref.as_non_null - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-BIN-NEXT: (ref.as_non_null - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-ref-as-non-null (type $2) (param $0 anyref) (result (ref any)) - ;; NO-EXACT-NEXT: (ref.as_non_null - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $valid-ref-as-non-null (param (ref null exact any)) (result (ref exact any)) - (ref.as_non_null - (local.get 0) - ) - ) - - ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) - ;; CHECK-TEXT-NEXT: (block $label - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (br_on_null $label - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) - ;; CHECK-BIN-NEXT: (block $block - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_null $block - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-null (type $5) (param $0 anyref) - ;; NO-EXACT-NEXT: (block $block - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_null $block - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $valid-br-on-null (param (ref null exact any)) - (drop - (block (result (ref exact any)) - (br_on_null 1 - (local.get 0) - ) - ) - ) - ) - - ;; CHECK-TEXT: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (br_on_non_null $label - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (unreachable) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) - ;; CHECK-BIN-NEXT: (br_on_non_null $block - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-non-null (type $2) (param $0 anyref) (result (ref any)) - ;; NO-EXACT-NEXT: (block $block (result (ref any)) - ;; NO-EXACT-NEXT: (br_on_non_null $block - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (unreachable) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $valid-br-on-non-null (param (ref null exact any)) (result (ref exact any)) - (br_on_non_null 0 - (local.get 0) - ) - (unreachable) - ) - - ;; CHECK-TEXT: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) - ;; CHECK-TEXT-NEXT: (block $label (result (exact anyref)) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (br_on_cast $label (exact anyref) (exact anyref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (unreachable) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) - ;; CHECK-BIN-NEXT: (block $block (result (exact anyref)) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block (exact anyref) (exact anyref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-cast (type $1) (param $0 anyref) (result anyref) - ;; NO-EXACT-NEXT: (block $block (result anyref) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block anyref anyref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (unreachable) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $valid-br-on-cast (param (ref null exact any)) (result (ref null exact any)) - (drop - (block (result (ref exact any)) - (br_on_cast 1 (ref null exact any) (ref null exact any) - (local.get 0) - ) - ) - ) - (unreachable) - ) - - ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (exact anyref)) - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (exact anyref) (exact anyref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (unreachable) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-cast-fail (type $2) (param $0 anyref) (result (ref any)) - ;; NO-EXACT-NEXT: (block $block (result (ref any)) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref anyref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (unreachable) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - (func $valid-br-on-cast-fail (param (ref null exact any)) (result (ref exact any)) - (drop - (block (result (ref null exact any)) - (br_on_cast_fail 1 (ref null exact any) (ref null exact any) - (local.get 0) - ) - ) - ) - (unreachable) - ) -) -;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) - -;; CHECK-BIN-NODEBUG: (type $1 (func (param (exact anyref)) (result (ref exact any)))) - -;; CHECK-BIN-NODEBUG: (type $2 (func (param anyref) (result anyref))) - -;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact i31ref)))) - -;; CHECK-BIN-NODEBUG: (type $4 (func (param (exact eqref)))) - -;; CHECK-BIN-NODEBUG: (type $5 (func (param (exact anyref)))) - -;; CHECK-BIN-NODEBUG: (type $6 (func (param (exact anyref)) (result (exact anyref)))) - -;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) - -;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any))) - -;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0))) - -;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0))) - -;; CHECK-BIN-NODEBUG: (func $0 (type $3) (param $0 (exact i31ref)) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact i31) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (exact i31ref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $1 (type $4) (param $0 (exact eqref)) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact eq) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact eqref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact none) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $2 (type $2) (param $0 anyref) (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact eqref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (ref exact eq) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact i31ref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $3 (type $2) (param $0 anyref) (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact eqref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (ref exact eq) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact i31ref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $4 (type $1) (param $0 (exact anyref)) (result (ref exact any)) -;; CHECK-BIN-NODEBUG-NEXT: (ref.as_non_null -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (exact anyref)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_null $block -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $6 (type $1) (param $0 (exact anyref)) (result (ref exact any)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) -;; CHECK-BIN-NODEBUG-NEXT: (br_on_non_null $block -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $7 (type $6) (param $0 (exact anyref)) (result (exact anyref)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (exact anyref)) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (exact anyref) (exact anyref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $8 (type $1) (param $0 (exact anyref)) (result (ref exact any)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 7c4f2a23e76..82b1f8da4e3 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -961,7 +961,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block2 (result eqref) - ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) + ;; CHECK-BIN-NEXT: (ref.cast nullref ;; CHECK-BIN-NEXT: (br_if $block2 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -987,7 +987,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block5 (result funcref) - ;; CHECK-BIN-NEXT: (ref.cast (exact nullfuncref) + ;; CHECK-BIN-NEXT: (ref.cast nullfuncref ;; CHECK-BIN-NEXT: (br_if $block5 ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1023,7 +1023,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block9 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) + ;; CHECK-BIN-NEXT: (ref.cast nullref ;; CHECK-BIN-NEXT: (br_if $block9 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1043,7 +1043,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block11 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) + ;; CHECK-BIN-NEXT: (ref.cast nullref ;; CHECK-BIN-NEXT: (br_if $block11 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -2298,7 +2298,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block2 (result eqref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block2 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2324,7 +2324,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block5 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullfuncref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullfuncref ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block5 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2360,7 +2360,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block9 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block9 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2380,7 +2380,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block11 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block11 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) diff --git a/test/lit/ctor-eval/materialize-null-local.wast b/test/lit/ctor-eval/materialize-null-local.wast deleted file mode 100644 index 1f880f6816b..00000000000 --- a/test/lit/ctor-eval/materialize-null-local.wast +++ /dev/null @@ -1,26 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: wasm-ctor-eval %s -all --ctors=test --kept-exports=test --ignore-external-input -S -o - \ -;; RUN: | filecheck %s - -;; Check that materializing a non-data null local, which at time of writing uses -;; Builder::makeConstantExpression, does not trigger an assertion failure. - -(module - (func $test (export "test") (param $0 externref) - (local $3 anyref) - (local.set $3 - (any.convert_extern - (local.get $0) - ) - ) - ) -) -;; CHECK: (type $0 (func (param externref))) - -;; CHECK: (export "test" (func $test_1)) - -;; CHECK: (func $test_1 (type $0) (param $0 externref) -;; CHECK-NEXT: (local $3 anyref) -;; CHECK-NEXT: (nop) -;; CHECK-NEXT: ) diff --git a/test/lit/exec/exact.wast b/test/lit/exec/exact.wast deleted file mode 100644 index 2667e0e4dce..00000000000 --- a/test/lit/exec/exact.wast +++ /dev/null @@ -1,21 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. - -;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s - -(module - ;; CHECK: [fuzz-exec] calling convert-null-extern - ;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null - (func $convert-null-extern (export "convert-null-extern") (result (exact nullref)) - (local externref) - ;; The value produced by this cast must be exact to avoid triggering an - ;; assertion. - (ref.cast (exact nullref) - (any.convert_extern - (local.get 0) - ) - ) - ) -) -;; CHECK: [fuzz-exec] calling convert-null-extern -;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null -;; CHECK-NEXT: [fuzz-exec] comparing convert-null-extern diff --git a/test/lit/heap-types.wast b/test/lit/heap-types.wast index baf77196611..4613f4894fd 100644 --- a/test/lit/heap-types.wast +++ b/test/lit/heap-types.wast @@ -12,7 +12,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.test (ref exact none) + ;; CHECK-NEXT: (ref.test (ref none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -29,7 +29,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index ab9b7f8a745..0bc4372ec35 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -1034,7 +1034,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) @@ -1233,7 +1233,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) diff --git a/test/lit/passes/coalesce-locals-exact.wast b/test/lit/passes/coalesce-locals-exact.wast deleted file mode 100644 index 1568d3634cf..00000000000 --- a/test/lit/passes/coalesce-locals-exact.wast +++ /dev/null @@ -1,47 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; Check that TypeUpdating::handleNonDefaultableLocals handles locals with exact -;; reference types correctly, and in particular that it preserves the exactness -;; of the types. - -;; RUN: wasm-opt %s -all --coalesce-locals -S -o - | filecheck %s - -(module - ;; CHECK: (func $test (type $0) (param $0 (exact i31ref)) (result (ref exact i31)) - ;; CHECK-NEXT: (local $1 (exact i31ref)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (param (exact i31ref)) (result (ref exact i31)) - (local $l (ref exact i31)) - ;; This dead set will be optimized out. - (local.set $l - (ref.as_non_null - (local.get 0) - ) - ) - (block $l - ;; This remaining set does not structurally dominate the get. - (local.set $l - (ref.as_non_null - (local.get 0) - ) - ) - ) - ;; This will have to be fixed up and the local made nullable. - (local.get $l) - ) -) diff --git a/test/lit/passes/code-pushing-gc.wast b/test/lit/passes/code-pushing-gc.wast index fb9ff12f7da..1aac5c55cf7 100644 --- a/test/lit/passes/code-pushing-gc.wast +++ b/test/lit/passes/code-pushing-gc.wast @@ -7,7 +7,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $out (result (ref func)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) + ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -48,7 +48,7 @@ ;; CHECK-NEXT: (ref.func $br_on_no) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) + ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index a93f3e7347f..8cefbe88184 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -262,7 +262,7 @@ ) ;; This function is called in ways that allow us to make the first parameter ;; non-nullable. - ;; CHECK: (func $various-params-null (type $13) (param $x (ref exact none)) (param $y (ref null $"{i32}")) + ;; CHECK: (func $various-params-null (type $13) (param $x (ref none)) (param $y (ref null $"{i32}")) ;; CHECK-NEXT: (local $temp i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 015e515c02b..1f39567d146 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -110,7 +110,7 @@ ) ;; CHECK: (func $bar (type $2) (param $0 i31ref) - ;; CHECK-NEXT: (local $1 (exact nullref)) + ;; CHECK-NEXT: (local $1 nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/flatten_all-features.wast b/test/lit/passes/flatten_all-features.wast index 6c117d0a907..b601b8a1295 100644 --- a/test/lit/passes/flatten_all-features.wast +++ b/test/lit/passes/flatten_all-features.wast @@ -3581,8 +3581,8 @@ ;; CHECK: (func $subtype (type $7) (result anyref) ;; CHECK-NEXT: (local $0 eqref) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (exact nullref)) - ;; CHECK-NEXT: (local $3 (exact nullref)) + ;; CHECK-NEXT: (local $2 nullref) + ;; CHECK-NEXT: (local $3 nullref) ;; CHECK-NEXT: (local $4 eqref) ;; CHECK-NEXT: (local $5 eqref) ;; CHECK-NEXT: (local $6 eqref) @@ -3680,7 +3680,7 @@ ;; CHECK: (type $0 (func (result funcref))) ;; CHECK: (func $0 (type $0) (result funcref) - ;; CHECK-NEXT: (local $0 (ref exact nofunc)) + ;; CHECK-NEXT: (local $0 (ref nofunc)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index 7be5a9e4e2a..927dfd1f26c 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -11,8 +11,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) @@ -32,8 +32,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) @@ -202,16 +202,16 @@ ;; (ref null func) to nullfuncref only when not exported, and if exported, then ;; only when immutable in open world. (module - ;; CHECK: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) - ;; CLOSD: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CHECK: (global $mut (mut nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $mut (mut nullfuncref) (ref.null nofunc)) (global $mut (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm (exact nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $imm (exact nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $imm nullfuncref (ref.null nofunc)) + ;; CLOSD: (global $imm nullfuncref (ref.null nofunc)) (global $imm (ref null func) (ref.null nofunc)) ;; CHECK: (global $mut-exp (mut funcref) (ref.null nofunc)) ;; CLOSD: (global $mut-exp (mut funcref) (ref.null nofunc)) (global $mut-exp (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm-exp (exact nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $imm-exp nullfuncref (ref.null nofunc)) ;; CLOSD: (global $imm-exp funcref (ref.null nofunc)) (global $imm-exp (ref null func) (ref.null nofunc)) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 6142550638d..6d48d651a5f 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -955,7 +955,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 1 ;; CHECK-NEXT: (local.get $child) @@ -970,7 +970,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) @@ -1072,9 +1072,9 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (exact nullref)) + ;; CHECK-NEXT: (block $block (result nullref) ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1085,9 +1085,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block0 (result (exact nullref)) + ;; CHECK-NEXT: (block $block0 (result nullref) ;; CHECK-NEXT: (br $block0 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1098,13 +1098,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block1 (result (exact nullref)) + ;; CHECK-NEXT: (block $block1 (result nullref) ;; CHECK-NEXT: (br $block1 - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1237,7 +1237,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) @@ -1577,7 +1577,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $null ;; CHECK-NEXT: (array.new_default $null @@ -1775,10 +1775,10 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $x - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $storage - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $pass-through ;; CHECK-NEXT: (ref.null none) @@ -1928,7 +1928,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2153,7 +2153,7 @@ ;; CHECK-NEXT: (pop (tuple anyref anyref)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2213,7 +2213,7 @@ ;; CHECK: (func $func (type $1) (result (ref $"{}")) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (ref exact none)) + ;; CHECK-NEXT: (block $block (result (ref none)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2539,10 +2539,10 @@ ;; CHECK: (func $test-nulls (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -2554,9 +2554,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (select (result i31ref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.i31 @@ -3256,7 +3256,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -3793,7 +3793,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $chars ;; CHECK-NEXT: (local.get $chars) @@ -6056,7 +6056,7 @@ ) (module - ;; CHECK: (type $0 (func (result i64 (exact nullref) i32))) + ;; CHECK: (type $0 (func (result i64 nullref i32))) ;; CHECK: (type $array (sub (array (mut i8)))) (type $array (sub (array (mut i8)))) @@ -6096,7 +6096,7 @@ ;; CHECK: (func $loop-tuple-br_on (type $2) ;; CHECK-NEXT: (tuple.drop 3 - ;; CHECK-NEXT: (loop $loop (type $0) (result i64 (exact nullref) i32) + ;; CHECK-NEXT: (loop $loop (type $0) (result i64 nullref i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index eb26820b74d..bd731d89150 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -1087,7 +1087,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct3 2 ;; CHECK-NEXT: (local.get $ref) @@ -1329,7 +1329,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $create3) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local-rmw.wast b/test/lit/passes/heap2local-rmw.wast index f0b397dcc99..33fef1cb506 100644 --- a/test/lit/passes/heap2local-rmw.wast +++ b/test/lit/passes/heap2local-rmw.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -107,7 +107,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -140,7 +140,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -173,7 +173,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -206,7 +206,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -239,7 +239,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -270,7 +270,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -312,7 +312,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -345,7 +345,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -378,7 +378,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -411,7 +411,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -444,7 +444,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -477,7 +477,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -508,7 +508,7 @@ ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -550,7 +550,7 @@ ;; CHECK-NEXT: (local $2 (ref null $struct)) ;; CHECK-NEXT: (local $3 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -581,7 +581,7 @@ ;; CHECK-NEXT: (local $4 (ref null $struct)) ;; CHECK-NEXT: (local $5 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -623,7 +623,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -657,7 +657,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 8b0384671c3..619a33a23c5 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -102,7 +102,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -135,7 +135,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -162,7 +162,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -193,7 +193,7 @@ ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) @@ -259,7 +259,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -318,7 +318,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -388,7 +388,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) @@ -418,7 +418,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -456,7 +456,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -494,7 +494,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -559,7 +559,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -673,7 +673,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -713,7 +713,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -771,7 +771,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -821,7 +821,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -901,7 +901,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -927,7 +927,7 @@ ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -975,7 +975,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (local $2 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new_default $struct.recursive) ;; CHECK-NEXT: ) @@ -1070,7 +1070,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) @@ -1104,7 +1104,7 @@ ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (loop $outer ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -1271,7 +1271,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1287,7 +1287,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1303,7 +1303,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1343,7 +1343,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1362,7 +1362,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1411,7 +1411,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1422,7 +1422,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1553,7 +1553,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1749,7 +1749,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1800,7 +1800,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1851,7 +1851,7 @@ ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $block - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) @@ -1905,7 +1905,7 @@ ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1938,7 +1938,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1977,7 +1977,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2061,7 +2061,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2096,7 +2096,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2138,7 +2138,7 @@ ;; CHECK-NEXT: (local.get $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2174,7 +2174,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2216,7 +2216,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2253,7 +2253,7 @@ ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2287,7 +2287,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2334,7 +2334,7 @@ ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (local $7 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2351,7 +2351,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2396,7 +2396,7 @@ ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2450,7 +2450,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2472,7 +2472,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2494,7 +2494,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) @@ -2560,7 +2560,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2582,7 +2582,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2648,7 +2648,7 @@ ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2697,7 +2697,7 @@ ;; CHECK-NEXT: (local $2 (ref $A)) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2736,7 +2736,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2776,7 +2776,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2819,9 +2819,9 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) - ;; CHECK-NEXT: (block (result (exact nullref)) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2870,7 +2870,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2907,7 +2907,7 @@ ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -3005,7 +3005,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3151,7 +3151,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3197,7 +3197,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) @@ -3327,11 +3327,11 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3391,7 +3391,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -3500,7 +3500,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3512,7 +3512,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3533,7 +3533,7 @@ ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $24 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) @@ -3706,7 +3706,7 @@ ;; CHECK: (func $array.nested.refinalize.get (type $3) (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3726,7 +3726,7 @@ ;; CHECK: (func $array.nested.refinalize.set (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3756,7 +3756,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3849,7 +3849,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3867,7 +3867,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3916,7 +3916,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3983,7 +3983,7 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4030,7 +4030,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4070,7 +4070,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -4173,7 +4173,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4253,7 +4253,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4319,7 +4319,7 @@ ;; CHECK: (func $array.cast.struct (type $0) (result (ref struct)) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4341,7 +4341,7 @@ ;; CHECK: (func $array.cast.struct.null (type $3) (result structref) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4380,7 +4380,7 @@ ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (local $array (ref array)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4405,7 +4405,7 @@ ;; CHECK-NEXT: (local.tee $struct ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4442,7 +4442,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result structref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -4513,7 +4513,7 @@ ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) @@ -4565,7 +4565,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4620,7 +4620,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null exact (shared none))) + ;; CHECK-NEXT: (block (result (ref null (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4666,7 +4666,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null exact (shared none))) + ;; CHECK-NEXT: (block (result (ref null (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/local-subtyping-exact.wast b/test/lit/passes/local-subtyping-exact.wast deleted file mode 100644 index 725c7d499fb..00000000000 --- a/test/lit/passes/local-subtyping-exact.wast +++ /dev/null @@ -1,39 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; Check that LocalSubtyping handles exact references properly when it -;; determines that a local that would otherwise be non-nullable must be nullable -;; because of control flow dominance constraints. - -;; RUN: wasm-opt %s -all --local-subtyping -S -o - | filecheck %s - -(module - ;; CHECK: (func $test (type $0) (param $0 (exact nullref)) (result anyref) - ;; CHECK-NEXT: (local $1 (exact nullref)) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - (func $test (param (exact nullref)) (result anyref) - (local (exact nullref)) - (if - (i32.const 0) - (then - (local.set 1 - ;; This would let the local be (ref exact none) if it dominated the get. - (ref.as_non_null - (local.get 0) - ) - ) - ) - ) - (local.get 1) - ) -) diff --git a/test/lit/passes/local-subtyping-nn.wast b/test/lit/passes/local-subtyping-nn.wast index f2237156f01..3754230d82f 100644 --- a/test/lit/passes/local-subtyping-nn.wast +++ b/test/lit/passes/local-subtyping-nn.wast @@ -8,7 +8,7 @@ (import "out" "i32" (func $i32 (result i32))) ;; CHECK: (func $non-nullable (type $1) - ;; CHECK-NEXT: (local $x (ref exact none)) + ;; CHECK-NEXT: (local $x (ref none)) ;; CHECK-NEXT: (local $y (ref $0)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.as_non_null @@ -41,7 +41,7 @@ ) ;; CHECK: (func $uses-default (type $2) (param $i i32) - ;; CHECK-NEXT: (local $x (exact nullref)) + ;; CHECK-NEXT: (local $x nullref) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $i) ;; CHECK-NEXT: (then diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 777df93524f..795a0a01cd1 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -273,7 +273,7 @@ ) ;; CHECK: (func $multiple-iterations-refinalize-call-ref-bottom (type $0) - ;; CHECK-NEXT: (local $f (exact nullfuncref)) + ;; CHECK-NEXT: (local $f nullfuncref) ;; CHECK-NEXT: (local $x (ref none)) ;; CHECK-NEXT: (local.set $f ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 7a517ccbc27..86181f7a488 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -20,7 +20,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $label$1 (result i31ref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label$1 (exact nullref) (ref exact none) + ;; CHECK-NEXT: (br_on_cast $label$1 nullref (ref none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/monomorphize-context.wast b/test/lit/passes/monomorphize-context.wast index 92b483a7ae1..7f9cb2f566e 100644 --- a/test/lit/passes/monomorphize-context.wast +++ b/test/lit/passes/monomorphize-context.wast @@ -277,7 +277,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast (exact nullref) +;; ALWAYS-NEXT: (ref.cast nullref ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -555,7 +555,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast (exact nullref) +;; ALWAYS-NEXT: (ref.cast nullref ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast deleted file mode 100644 index 61769b78f34..00000000000 --- a/test/lit/passes/optimize-instructions-exact.wast +++ /dev/null @@ -1,33 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; Check that optimizations on casts involving exact reference types work -;; correctly. - -;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s - -(module - ;; CHECK: (func $cast-any-to-exact-none (type $0) (param $0 anyref) (result (exact nullref)) - ;; CHECK-NEXT: (ref.cast (exact nullref) - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-any-to-exact-none (param anyref) (result (exact nullref)) - ;; This will not be changed, but should not trigger an assertion. - (ref.cast (exact nullref) - (local.get 0) - ) - ) - ;; CHECK: (func $cast-null-to-exact-none (type $1) (result (exact nullref)) - ;; CHECK-NEXT: (local $0 nullref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - (func $cast-null-to-exact-none (result (exact nullref)) - (local nullref) - (ref.cast (exact nullref) - (local.get 0) - ) - ) -) diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index 3893e048e14..c930f2fc19a 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -509,7 +509,7 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: (block ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result (exact nullref)) + ;; TNH-NEXT: (block (result nullref) ;; TNH-NEXT: (drop ;; TNH-NEXT: (call $get-i32) ;; TNH-NEXT: ) @@ -532,7 +532,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (block ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result (exact nullref)) + ;; NO_TNH-NEXT: (block (result nullref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (call $get-i32) ;; NO_TNH-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 86804d7ddbd..4b88507ffc7 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -1570,7 +1570,7 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $a ;; CHECK-NEXT: (ref.null none) @@ -1590,7 +1590,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.cast nullref diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast index 94adaa53fbb..fc4cc7c2ee9 100644 --- a/test/lit/passes/precompute-gc.wast +++ b/test/lit/passes/precompute-gc.wast @@ -28,7 +28,7 @@ ;; CHECK: (func $test-fallthrough (type $func-return-i32) (result i32) ;; CHECK-NEXT: (local $x funcref) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (block (result (exact nullfuncref)) + ;; CHECK-NEXT: (block (result nullfuncref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $test-fallthrough) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/remove-unused-brs-exact.wast b/test/lit/passes/remove-unused-brs-exact.wast deleted file mode 100644 index 3bebd07de02..00000000000 --- a/test/lit/passes/remove-unused-brs-exact.wast +++ /dev/null @@ -1,40 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; RUN: wasm-opt %s -all --remove-unused-brs -S -o - | filecheck %s - -;; Check that we optimize the cast correctly when the fallthrough has exact -;; type. In particular, we should not insert a ref.as_non_null, which would -;; trap. - -(module - ;; CHECK: (func $br_on_cast_fail (type $0) (param $0 (exact nullref)) - ;; CHECK-NEXT: (local $1 nullref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $br_on_cast_fail (param (ref null exact none)) - (local $1 nullref) - (drop - (block $block (result (ref none)) - (drop - (br_on_cast_fail $block nullref nullref - (local.tee $1 - (local.get 0) - ) - ) - ) - (return) - ) - ) - ) -) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index c26fe3a95ba..2ee6171cbcb 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -63,7 +63,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -119,7 +119,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.cast (ref null $struct) ;; CHECK-NEXT: (local.tee $any @@ -438,7 +438,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $nullable-struct2) ;; CHECK-NEXT: ) @@ -507,7 +507,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (local.get $nullable-struct2) @@ -653,7 +653,7 @@ ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (ref.test (ref exact none) + ;; CHECK-NEXT: (ref.test (ref none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -663,13 +663,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result (exact nullref)) + ;; CHECK-NEXT: (if (result nullref) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -681,7 +681,7 @@ ;; CHECK-NEXT: (then ;; CHECK-NEXT: (block $something (result (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $something ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -697,8 +697,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result (exact nullref)) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (select (result nullref) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (block $nothing ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -805,7 +805,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result (exact nullref)) + ;; CHECK-NEXT: (select (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/remove-unused-types-exact.wast b/test/lit/passes/remove-unused-types-exact.wast deleted file mode 100644 index 6cb9c1ddd74..00000000000 --- a/test/lit/passes/remove-unused-types-exact.wast +++ /dev/null @@ -1,16 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; RUN: wasm-opt %s -all --closed-world --remove-unused-types -S -o - | filecheck %s - -;; Test that a simple type rewrite handles exact references in heap type -;; definitions correctly. In particular, the function should continue returning -;; an exact nullref and the call expression should have the same type. - -(module - ;; CHECK: (func $return-exact (type $0) (result (exact nullref)) - ;; CHECK-NEXT: (call $return-exact) - ;; CHECK-NEXT: ) - (func $return-exact (result (exact nullref)) - (call $return-exact) - ) -) diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat index 1eedcd4f67c..c69eeb24455 100644 --- a/test/lit/passes/signature-refining_gto.wat +++ b/test/lit/passes/signature-refining_gto.wat @@ -9,11 +9,11 @@ ;; CHECK-NOT: (type $A (type $A (struct (field (mut (ref null $A))))) - ;; CHECK: (type $0 (func (param (ref exact none)))) + ;; CHECK: (type $0 (func (param (ref none)))) ;; CHECK: (type $1 (func (param funcref i32))) - ;; CHECK: (func $struct.get (type $0) (param $0 (ref exact none)) + ;; CHECK: (func $struct.get (type $0) (param $0 (ref none)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast index a0b18b0777d..0d696f75a0d 100644 --- a/test/lit/passes/ssa.wast +++ b/test/lit/passes/ssa.wast @@ -36,9 +36,9 @@ ;; CHECK: (func $refine-to-null (type $3) (result (ref $A)) ;; CHECK-NEXT: (local $0 (ref null $A)) - ;; CHECK-NEXT: (block $label (result (ref exact none)) + ;; CHECK-NEXT: (block $label (result (ref none)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label (exact nullref) (ref exact none) + ;; CHECK-NEXT: (br_on_cast $label nullref (ref none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/type-refining-isorecursive.wast b/test/lit/passes/type-refining-isorecursive.wast index 025b8cec9f5..537f99e668e 100644 --- a/test/lit/passes/type-refining-isorecursive.wast +++ b/test/lit/passes/type-refining-isorecursive.wast @@ -5,11 +5,11 @@ ;; The types should be refined to a set of three mutually recursive types. ;; CHECK: (rec - ;; CHECK-NEXT: (type $2 (sub (struct (field (exact nullexternref)) (field (ref $0))))) + ;; CHECK-NEXT: (type $2 (sub (struct (field nullexternref) (field (ref $0))))) - ;; CHECK: (type $1 (sub (struct (field (exact nullfuncref)) (field (ref $2))))) + ;; CHECK: (type $1 (sub (struct (field nullfuncref) (field (ref $2))))) - ;; CHECK: (type $0 (sub (struct (field (exact nullref)) (field (ref $1))))) + ;; CHECK: (type $0 (sub (struct (field nullref) (field (ref $1))))) (type $0 (sub (struct nullref anyref))) (type $1 (sub (struct nullfuncref anyref))) (type $2 (sub (struct nullexternref anyref))) diff --git a/test/lit/passes/type-refining-rmw.wast b/test/lit/passes/type-refining-rmw.wast index 7739fceb0f4..a4bb0a85cae 100644 --- a/test/lit/passes/type-refining-rmw.wast +++ b/test/lit/passes/type-refining-rmw.wast @@ -6,7 +6,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared any))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) @@ -75,7 +75,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared eq))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 02357e8b6c0..622e7422011 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1101,7 +1101,7 @@ ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1180,7 +1180,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut (exact nullref))))) + ;; CHECK-NEXT: (type $A (struct (field (mut nullref)))) (type $A (struct (field (mut anyref)))) ;; CHECK: (type $B (struct (field (mut nullref)))) (type $B (struct (field (mut (ref null $A))))) @@ -1234,7 +1234,7 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut (ref exact noextern))))) + ;; CHECK-NEXT: (type $A (struct (field (mut (ref noextern))))) (type $A (struct (field (mut externref)))) ;; CHECK: (type $1 (func)) @@ -1252,7 +1252,7 @@ ;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref) ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.cast (ref exact noextern) + ;; CHECK-NEXT: (ref.cast (ref noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1304,7 +1304,7 @@ ;; CHECK: (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: (ref.cast (ref exact noextern) + ;; CHECK-NEXT: (ref.cast (ref noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1581,7 +1581,7 @@ (type $never (sub (struct (field i32)))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $optimizable (struct (field (mut (exact nullfuncref))))) + ;; CHECK-NEXT: (type $optimizable (struct (field (mut nullfuncref)))) (type $optimizable (struct (field (mut (ref null func))))) ;; CHECK: (type $2 (func)) @@ -1604,7 +1604,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref exact none)) + ;; CHECK-NEXT: (block (result (ref none)) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index 1372035920f..189adadcec1 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -286,7 +286,7 @@ ) ) (drop - (block $l2 (result (exact nullexternref)) + (block $l2 (result nullexternref) (drop (block $l3 (global.set $global-mut From a5f0423cdec170a11aaeb168d436ca802321704f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 26 Mar 2025 11:11:30 -0700 Subject: [PATCH 376/622] Use fewer bits for BasicHeapType (#7404) Now that we aren't supporting exact reference types, we no longer need to leave bit 2 free for use by the Type representation. Shift the basic HeapType representations down to start at bit 2 instead of bit 3. --- src/wasm-type.h | 14 ++++++-------- test/example/c-api-kitchen-sink.txt | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 6b337530a24..a72ba9d2cfa 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -95,13 +95,13 @@ class HeapType { // should also be passed by value. uintptr_t id; - static constexpr int TypeBits = 3; + static constexpr int TypeBits = 2; static constexpr int UsedBits = TypeBits + 1; static constexpr int SharedMask = 1 << TypeBits; public: - // Bits 0-2 are used by the Type representation, so need to be left free. - // Bit 3 determines whether the basic heap type is shared (1) or unshared (0). + // Bits 0-1 are used by the Type representation, so need to be left free. + // Bit 2 determines whether the basic heap type is shared (1) or unshared (0). enum BasicHeapType : uint32_t { ext = 1 << UsedBits, func = 2 << UsedBits, @@ -278,7 +278,7 @@ class Type { // bit 0 set. When that bit is masked off, they are pointers to the underlying // vectors of types. Otherwise, the type is a reference type, and is // represented as a heap type with bit 1 set iff the reference type is - // nullable and bit 2 set iff the reference type is exact. + // nullable. // // Since `Type` is really just a single integer, it should be passed by value. // This is a uintptr_t rather than a TypeID (uint64_t) to save memory on @@ -287,7 +287,6 @@ class Type { static constexpr int TupleMask = 1 << 0; static constexpr int NullMask = 1 << 1; - static constexpr int ExactMask = 1 << 2; public: enum BasicType : uint32_t { @@ -320,8 +319,7 @@ class Type { // Signature, Struct or Array via implicit conversion to HeapType. Type(HeapType heapType, Nullability nullable) : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0)) { - assert(heapType.isBasic() || - !(heapType.getID() & (TupleMask | NullMask | ExactMask))); + assert(heapType.isBasic() || !(heapType.getID() & (TupleMask | NullMask))); } // Predicates @@ -372,7 +370,7 @@ class Type { bool isNonNullable() const { return isRef() && !(id & NullMask); } HeapType getHeapType() const { assert(isRef()); - return HeapType(id & ~(NullMask | ExactMask)); + return HeapType(id & ~NullMask); } bool isFunction() const { return isRef() && getHeapType().isFunction(); } diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 9bfc45ddf72..78f1c73552f 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -20,17 +20,17 @@ BinaryenTypeAuto: -1 BinaryenPackedTypeNotPacked: 0 BinaryenPackedTypeInt8: 1 BinaryenPackedTypeInt16: 2 -BinaryenHeapTypeExt: 16 -BinaryenHeapTypeFunc: 32 -BinaryenHeapTypeAny: 64 -BinaryenHeapTypeEq: 80 -BinaryenHeapTypeI31: 96 -BinaryenHeapTypeStruct: 112 -BinaryenHeapTypeArray: 128 -BinaryenHeapTypeString: 160 -BinaryenHeapTypeNone: 176 -BinaryenHeapTypeNoext: 192 -BinaryenHeapTypeNofunc: 208 +BinaryenHeapTypeExt: 8 +BinaryenHeapTypeFunc: 16 +BinaryenHeapTypeAny: 32 +BinaryenHeapTypeEq: 40 +BinaryenHeapTypeI31: 48 +BinaryenHeapTypeStruct: 56 +BinaryenHeapTypeArray: 64 +BinaryenHeapTypeString: 80 +BinaryenHeapTypeNone: 88 +BinaryenHeapTypeNoext: 96 +BinaryenHeapTypeNofunc: 104 BinaryenFeatureMVP: 0 BinaryenFeatureAtomics: 1 BinaryenFeatureBulkMemory: 16 From d262701cdefc678cd278286d3ddfd8accd5824f1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 26 Mar 2025 11:43:22 -0700 Subject: [PATCH 377/622] [Strings] Allow customizing the module name for string constants in StringLifting/Lowering (#7399) Previously we hardcoded "'" (a single quote). --- scripts/test/fuzzing.py | 1 + src/passes/StringLifting.cpp | 8 ++- src/passes/StringLowering.cpp | 9 ++- src/passes/string-utils.cpp | 2 +- src/passes/string-utils.h | 6 +- .../passes/string-lifting-custom-module.wast | 37 +++++++++++ ...string-lowering-imports-custom-module.wast | 62 +++++++++++++++++++ 7 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 test/lit/passes/string-lifting-custom-module.wast create mode 100644 test/lit/passes/string-lowering-imports-custom-module.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 623b8d4b5a3..e426dbbd181 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -96,6 +96,7 @@ # has (ref extern) imports, which the fuzzer cannot create values for when # it removes unknown imports 'string-lifting.wast', + 'string-lifting-custom-module.wast', # TODO: fuzzer support for stack switching 'stack_switching.wast', 'stack_switching_contnew.wast', diff --git a/src/passes/StringLifting.cpp b/src/passes/StringLifting.cpp index f6a99e1351b..5518c71534e 100644 --- a/src/passes/StringLifting.cpp +++ b/src/passes/StringLifting.cpp @@ -19,6 +19,10 @@ // fully optimized. Typically StringLowering would be run later to lower them // back down. // +// A pass argument allows customizing the module name for string constants: +// +// --pass-arg=string-constants-module@MODULE_NAME +// #include "ir/utils.h" #include "pass.h" @@ -56,11 +60,13 @@ struct StringLifting : public Pass { // actual string. Find them all so we can apply them. // // TODO: parse the strings section for non-UTF16 strings. + Name stringConstsModule = + getArgumentOrDefault("string-constants-module", WasmStringConstsModule); for (auto& global : module->globals) { if (!global->imported()) { continue; } - if (global->module == WasmStringConstsModule) { + if (global->module == stringConstsModule) { importedStrings[global->name] = global->base; found = true; } diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index d1ed2e987f9..84dfb7cb049 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -23,7 +23,10 @@ // // StringLowering does the same, and also replaces those new globals with // imported globals of type externref, for use with the string imports proposal. -// String operations will likewise need to be lowered. TODO +// +// A pass argument allows customizing the module name for string constants: +// +// --pass-arg=string-constants-module@MODULE_NAME // // Specs: // https://github.com/WebAssembly/stringref/blob/main/proposals/stringref/Overview.md @@ -235,6 +238,8 @@ struct StringLowering : public StringGathering { } void makeImports(Module* module) { + Name stringConstsModule = + getArgumentOrDefault("string-constants-module", WasmStringConstsModule); Index jsonImportIndex = 0; std::stringstream json; bool first = true; @@ -244,7 +249,7 @@ struct StringLowering : public StringGathering { std::stringstream utf8; if (useMagicImports && String::convertUTF16ToUTF8(utf8, c->string.str)) { - global->module = WasmStringConstsModule; + global->module = stringConstsModule; global->base = Name(utf8.str()); } else { if (assertUTF8) { diff --git a/src/passes/string-utils.cpp b/src/passes/string-utils.cpp index 21b2afb8221..3b349ccb8e5 100644 --- a/src/passes/string-utils.cpp +++ b/src/passes/string-utils.cpp @@ -21,6 +21,6 @@ namespace wasm { const Name WasmStringsModule = "wasm:js-string"; -const Name WasmStringConstsModule = "'"; +const char* WasmStringConstsModule = "'"; } // namespace wasm diff --git a/src/passes/string-utils.h b/src/passes/string-utils.h index 26fbc5e7faf..37fb8ea6298 100644 --- a/src/passes/string-utils.h +++ b/src/passes/string-utils.h @@ -25,9 +25,9 @@ namespace wasm { // https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md extern const Name WasmStringsModule; -// The name of the module to import string constants from, for magical imported -// JS strings. -extern const Name WasmStringConstsModule; +// The default module to import string constants from, for magical imported JS +// strings. +extern const char* WasmStringConstsModule; } // namespace wasm diff --git a/test/lit/passes/string-lifting-custom-module.wast b/test/lit/passes/string-lifting-custom-module.wast new file mode 100644 index 00000000000..a8f5f2da307 --- /dev/null +++ b/test/lit/passes/string-lifting-custom-module.wast @@ -0,0 +1,37 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Similar to string-lifting but the strings are imported from a +;; custom module name. + +;; RUN: foreach %s %t wasm-opt -all --string-lifting --pass-arg=string-constants-module@strings -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (import "\'" "foo" (global $string_foo (ref extern))) + (import "\'" "foo" (global $string_foo (ref extern))) + + ;; CHECK: (import "strings" "bar" (global $string_bar (ref extern))) + (import "strings" "bar" (global $string_bar (ref extern))) + + ;; CHECK: (func $func (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $string_foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "bar") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + ;; The first string has the default "'" module name, but we overrode it, so + ;; nothing changes. + (drop + (global.get $string_foo) + ) + ;; Here we imported with the right one given the pass-arg, so we turn this + ;; into a string.const. + (drop + (global.get $string_bar) + ) + ) +) diff --git a/test/lit/passes/string-lowering-imports-custom-module.wast b/test/lit/passes/string-lowering-imports-custom-module.wast new file mode 100644 index 00000000000..5884e98a267 --- /dev/null +++ b/test/lit/passes/string-lowering-imports-custom-module.wast @@ -0,0 +1,62 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Similar to string-lowering-imports but the strings are imported from a +;; custom module name. The string constant should be imported from "strings", as +;; the pass-arg specifies. + +;; RUN: wasm-opt %s -all --string-lowering-magic-imports --pass-arg=string-constants-module@strings -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (array (mut i16))) + + ;; CHECK: (type $1 (func (param externref externref) (result i32))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + + ;; CHECK: (type $4 (func (param i32) (result (ref extern)))) + + ;; CHECK: (type $5 (func (param externref externref) (result (ref extern)))) + + ;; CHECK: (type $6 (func (param externref (ref null $0) i32) (result i32))) + + ;; CHECK: (type $7 (func (param externref) (result i32))) + + ;; CHECK: (type $8 (func (param externref i32) (result i32))) + + ;; CHECK: (type $9 (func (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (import "strings" "foo" (global $"string.const_\"foo\"" (ref extern))) + + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) + + ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (export "const" (func $const)) + + ;; CHECK: (func $const (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $"string.const_\"foo\"") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $const (export "const") + (drop + (string.const "foo") + ) + ) +) From ca5d9dba1e7360a5bf4b8fd9957e45f7a5026e5b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 26 Mar 2025 14:26:22 -0700 Subject: [PATCH 378/622] [Strings] Support the custom section for strings in StringLifting (#7409) --- src/passes/StringLifting.cpp | 41 ++++++++++- test/lit/passes/string-lifting-section.wast | 77 +++++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/string-lifting-section.wast diff --git a/src/passes/StringLifting.cpp b/src/passes/StringLifting.cpp index 5518c71534e..ec09a599916 100644 --- a/src/passes/StringLifting.cpp +++ b/src/passes/StringLifting.cpp @@ -27,6 +27,7 @@ #include "ir/utils.h" #include "pass.h" #include "passes/string-utils.h" +#include "support/json.h" #include "support/string.h" #include "wasm-builder.h" #include "wasm.h" @@ -58,8 +59,6 @@ struct StringLifting : public Pass { // // That is, they are imported from module "'" and the basename is the // actual string. Find them all so we can apply them. - // - // TODO: parse the strings section for non-UTF16 strings. Name stringConstsModule = getArgumentOrDefault("string-constants-module", WasmStringConstsModule); for (auto& global : module->globals) { @@ -72,6 +71,44 @@ struct StringLifting : public Pass { } } + // Imported strings may also be found in the string section. + for (auto& section : module->customSections) { + if (section.name == "string.consts") { + // We found the string consts section. Parse it. + auto copy = section.data; + json::Value array; + array.parse(copy.data()); + if (!array.isArray()) { + Fatal() + << "StringLifting: string.const section should be a JSON array"; + } + + // We have the array of constants from the section. Find globals that + // refer to it. + for (auto& global : module->globals) { + if (!global->imported() || global->module != "string.const") { + continue; + } + // The index in the array is the basename. + Index index = std::stoi(std::string(global->base.str)); + if (index >= array.size()) { + Fatal() << "StringLifting: bad index in string.const section"; + } + auto item = array[index]; + if (!item->isString()) { + Fatal() + << "StringLifting: string.const section entry is not a string"; + } + if (importedStrings.count(global->name)) { + Fatal() + << "StringLifting: string.const section tramples other const"; + } + importedStrings[global->name] = item->getIString(); + } + break; + } + } + auto array16 = Type(Array(Field(Field::i16, Mutable)), Nullable); auto refExtern = Type(HeapType::ext, NonNullable); auto externref = Type(HeapType::ext, Nullable); diff --git a/test/lit/passes/string-lifting-section.wast b/test/lit/passes/string-lifting-section.wast new file mode 100644 index 00000000000..88165c208f6 --- /dev/null +++ b/test/lit/passes/string-lifting-section.wast @@ -0,0 +1,77 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Lower first to generate the string.consts custom section, then lift it back. + +;; RUN: foreach %s %t wasm-opt -all --string-lowering --string-lifting -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (array (mut i16))) + + ;; CHECK: (type $1 (func (param externref externref) (result i32))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + + ;; CHECK: (type $4 (func (param i32) (result (ref extern)))) + + ;; CHECK: (type $5 (func (param externref externref) (result (ref extern)))) + + ;; CHECK: (type $6 (func (param externref (ref null $0) i32) (result i32))) + + ;; CHECK: (type $7 (func (param externref) (result i32))) + + ;; CHECK: (type $8 (func (param externref i32) (result i32))) + + ;; CHECK: (type $9 (func (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (import "string.const" "0" (global $"string.const_\"bar\"" (ref extern))) + + ;; CHECK: (import "string.const" "1" (global $"string.const_\"foo\"" (ref extern))) + + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) + + ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (func $consts (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "bar") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $consts + ;; These strings get lowered into the custom section, then lifted back up, + ;; so they do not change. We will see above the imported globals that were + ;; created for them (and not cleaned up), two imports from "string.const". + (drop + (string.const "foo") + ) + (drop + (string.const "bar") + ) + (drop + (string.const "foo") + ) + ;; TODO: test utf-8 etc. + ) +) + From 2f075b5c272398e79019861c4a7a05ae9e62e9cf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 26 Mar 2025 14:49:51 -0700 Subject: [PATCH 379/622] Avoid repeated work in RemoveUnusedModuleElements::addReferences() [NFC] (#7407) Globals that refer to globals lead to more work. This work cannot be infinite, as globals only refer to previous ones, but this can end up as exponential time, so this is actually important to optimize here. For exponential time, it is enough to have a chain of these: (global $global$N+1 (ref $A) (struct.new $A (global.get $global$N) (global.get $global$N) )) That is, two references from each global to its predecessor, causing us to double the work each time we scan back. Fixes #7405 (where the above pattern appears) --- src/passes/RemoveUnusedModuleElements.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 8c1662b66e4..be924f35221 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -544,7 +544,14 @@ struct Analyzer { finder.walk(curr); for (auto element : finder.elements) { - referenced.insert(element); + // Avoid repeated work. Note that globals with multiple references to + // previous globals can lead to exponential work, so this is important. + // (If C refers twice to B, and B refers twice to A, then when we process + // C we would, naively, scan B twice and A four times.) + auto [_, inserted] = referenced.insert(element); + if (!inserted) { + continue; + } auto& [kind, value] = element; if (kind == ModuleElementKind::Global) { From 5f6ba291de1e1e496bcb6c4d242651d26d3bd892 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 26 Mar 2025 17:19:59 -0700 Subject: [PATCH 380/622] Initial support for exact heap types (#7396) The custom descriptors proposal has moved exactness from reference types to defined (but not abstract) heap types. Since we only use a bit in the heap type representation to represent sharedness for abstract heap types, we can conveniently reuse the same bit to represent exactness for heap types. Implement basic support for representing exact heap types and taking them into account in canonicalization. Also ensure that other operations like getting the rec group of a heap type or looking up its structure work properly on exact heap types. --- src/wasm-type.h | 31 ++++++++++--- src/wasm/wasm-type.cpp | 31 +++++++++---- test/gtest/type-builder.cpp | 87 +++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 13 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index a72ba9d2cfa..47b08ef9cc6 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -62,6 +62,7 @@ using Tuple = TypeList; enum Nullability { NonNullable, Nullable }; enum Mutability { Immutable, Mutable }; +enum Exactness { Inexact, Exact }; // HeapType name information used for printing. struct TypeNames { @@ -98,10 +99,12 @@ class HeapType { static constexpr int TypeBits = 2; static constexpr int UsedBits = TypeBits + 1; static constexpr int SharedMask = 1 << TypeBits; + static constexpr int ExactMask = SharedMask; public: - // Bits 0-1 are used by the Type representation, so need to be left free. - // Bit 2 determines whether the basic heap type is shared (1) or unshared (0). + // Bits 0-1 are used by the Type representation, so need to be left free. Bit + // 2 determines whether a basic heap type is shared (1) or unshared (0). For + // non-basic heap types, bit 2 determines whether the type is exact instead. enum BasicHeapType : uint32_t { ext = 1 << UsedBits, func = 2 << UsedBits, @@ -126,7 +129,7 @@ class HeapType { constexpr HeapType(BasicHeapType id) : id(id) {} // But converting raw TypeID is more dangerous, so make it explicit - explicit HeapType(TypeID id) : id(id) {} + explicit constexpr HeapType(TypeID id) : id(id) {} // Choose an arbitrary heap type as the default. constexpr HeapType() : HeapType(func) {} @@ -167,8 +170,12 @@ class HeapType { bool isBottom() const; bool isOpen() const; bool isShared() const { return getShared() == Shared; } + bool isExact() const { return getExactness() == Exact; } Shareability getShared() const; + Exactness getExactness() const { + return !isBasic() && (id & ExactMask) ? Exact : Inexact; + } // Check if the type is a given basic heap type, while ignoring whether it is // shared or not. @@ -217,8 +224,6 @@ class HeapType { // Get the index of this non-basic type within its recursion group. size_t getRecGroupIndex() const; - constexpr TypeID getID() const { return id; } - // Get the shared or unshared version of this basic heap type. constexpr BasicHeapType getBasic(Shareability share) const { assert(isBasic()); @@ -226,6 +231,22 @@ class HeapType { : (id & ~SharedMask)); } + constexpr HeapType with(Exactness exactness) const { + assert((!isBasic() || exactness == Inexact) && + "abstract types cannot be exact"); + return HeapType(exactness == Exact ? (id | ExactMask) : (id & ~ExactMask)); + } + + // The ID is the numeric representation of the heap type and can be used in + // FFI or hashing applications. The "raw" ID is the numeric representation of + // the plain version of the type without exactness or any other attributes we + // might add in the future. It's useful in contexts where all heap types using + // the same type definition need to be treated identically. + constexpr TypeID getID() const { return id; } + constexpr TypeID getRawID() const { + return isBasic() ? id : with(Inexact).id; + } + // (In)equality must be defined for both HeapType and BasicHeapType because it // is otherwise ambiguous whether to convert both this and other to int or // convert other to HeapType. diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 5cdb76c19dd..8a36dee7472 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -228,7 +228,7 @@ namespace { HeapTypeInfo* getHeapTypeInfo(HeapType ht) { assert(!ht.isBasic()); - return (HeapTypeInfo*)ht.getID(); + return (HeapTypeInfo*)(ht.getRawID()); } HeapType asHeapType(std::unique_ptr& info) { @@ -1247,7 +1247,7 @@ RecGroup HeapType::getRecGroup() const { } else { // Mark the low bit to signify that this is a trivial recursion group and // points to a heap type info rather than a vector of heap types. - return RecGroup(id | 1); + return RecGroup(getRawID() | 1); } } @@ -1608,14 +1608,20 @@ bool SubTyper::isSubType(const Array& a, const Array& b) { } void TypePrinter::printHeapTypeName(HeapType type) { + if (type.isExact()) { + os << "(exact "; + } if (type.isBasic()) { print(type); - return; - } - generator(type).name.print(os); + } else { + generator(type).name.print(os); #if TRACE_CANONICALIZATION - os << "(;" << ((type.getID() >> 4) % 1000) << ";) "; + os << "(;" << ((type.getID() >> 4) % 1000) << ";) "; #endif + } + if (type.isExact()) { + os << ')'; + } } std::ostream& TypePrinter::print(Type type) { @@ -1942,8 +1948,10 @@ size_t RecGroupHasher::hash(HeapType type) const { wasm::rehash(digest, type.getID()); return digest; } + wasm::rehash(digest, type.isExact()); wasm::rehash(digest, type.getRecGroupIndex()); auto currGroup = type.getRecGroup(); + wasm::rehash(digest, currGroup != group); if (currGroup != group) { wasm::rehash(digest, currGroup.getID()); } @@ -2073,6 +2081,9 @@ bool RecGroupEquator::eq(HeapType a, HeapType b) const { if (a.isBasic() || b.isBasic()) { return a == b; } + if (a.getExactness() != b.getExactness()) { + return false; + } if (a.getRecGroupIndex() != b.getRecGroupIndex()) { return false; } @@ -2456,8 +2467,10 @@ void updateReferencedHeapTypes( isTopLevel = false; if (type->isRef()) { auto ht = type->getHeapType(); + auto exact = ht.getExactness(); + ht = ht.with(Inexact); if (auto it = canonicalized.find(ht); it != canonicalized.end()) { - *type = Type(it->second, type->getNullability()); + *type = Type(it->second.with(exact), type->getNullability()); } } else if (type->isTuple()) { TypeGraphWalkerBase::scanType(type); @@ -2465,6 +2478,7 @@ void updateReferencedHeapTypes( } void scanHeapType(HeapType* type) { + assert(!type->isExact() && "unexpected exact type in definition"); if (isTopLevel) { isTopLevel = false; TypeGraphWalkerBase::scanHeapType(type); @@ -2529,7 +2543,8 @@ buildRecGroup(std::unique_ptr&& groupInfo, for (size_t i = 0; i < typeInfos.size(); ++i) { auto type = asHeapType(typeInfos[i]); for (auto child : type.getHeapTypeChildren()) { - if (isTemp(child) && !seenTypes.count(child)) { + HeapType rawChild(child.getRawID()); + if (isTemp(rawChild) && !seenTypes.count(rawChild)) { return {TypeBuilder::Error{ i, TypeBuilder::ErrorReason::ForwardChildReference}}; } diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 1e676b7194a..305a0380ca3 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -428,6 +428,93 @@ TEST_F(TypeTest, CanonicalizeUses) { EXPECT_NE(built[4], built[6]); } +TEST_F(TypeTest, CanonicalizeExactHeapTypes) { + TypeBuilder builder(8); + + HeapType inexact = HeapType(builder[0]).with(Inexact); + HeapType exact = HeapType(builder[1]).with(Exact); + + Type inexactRef = builder.getTempRefType(inexact, Nullable); + Type exactRef = builder.getTempRefType(exact, Nullable); + + // Types that vary in exactness of the referenced heap type are different. + builder[0] = Struct({Field(inexactRef, Mutable)}); + builder[1] = Struct({Field(exactRef, Mutable)}); + builder[2] = Signature(Type({inexactRef, exactRef}), Type::none); + builder[3] = Signature(Type::none, Type({exactRef, inexactRef})); + + auto translate = [&](HeapType t) { + for (int i = 0; i < 4; ++i) { + if (t.with(Inexact) == builder[i]) { + return HeapType(builder[4 + i]).with(t.getExactness()); + } + } + WASM_UNREACHABLE("unexpected type"); + }; + + builder[4].copy(builder[0], translate); + builder[5].copy(builder[1], translate); + builder[6].copy(builder[2], translate); + builder[7].copy(builder[3], translate); + + auto result = builder.build(); + ASSERT_TRUE(result); + auto built = *result; + + // Different types should be different. + EXPECT_NE(built[0], built[1]); + EXPECT_NE(built[0], built[2]); + EXPECT_NE(built[0], built[3]); + EXPECT_NE(built[1], built[2]); + EXPECT_NE(built[1], built[3]); + EXPECT_NE(built[2], built[3]); + + // Copies of the types should match. + EXPECT_EQ(built[0], built[4]); + EXPECT_EQ(built[1], built[5]); + EXPECT_EQ(built[2], built[6]); + EXPECT_EQ(built[3], built[7]); + + // A type is inexact by default. + EXPECT_EQ(built[0], built[0].with(Inexact)); + EXPECT_EQ(built[1], built[1].with(Inexact)); + EXPECT_EQ(built[2], built[2].with(Inexact)); + EXPECT_EQ(built[3], built[3].with(Inexact)); + + // We can freely convert between exact and inexact. + EXPECT_EQ(built[0], built[0].with(Exact).with(Inexact)); + EXPECT_EQ(built[0].with(Exact), + built[0].with(Exact).with(Inexact).with(Exact)); + + // Conversions are idempotent. + EXPECT_EQ(built[0].with(Exact), built[0].with(Exact).with(Exact)); + EXPECT_EQ(built[0], built[0].with(Inexact)); + + // An exact version of a type is not the same as its inexact version. + EXPECT_NE(built[0].with(Exact), built[0].with(Inexact)); + + // But they have the same rec group. + EXPECT_EQ(built[0].with(Exact).getRecGroup(), + built[0].with(Inexact).getRecGroup()); + + // Looking up the inner structure works either way. + ASSERT_TRUE(built[0].with(Exact).isStruct()); + ASSERT_TRUE(built[0].with(Inexact).isStruct()); + EXPECT_EQ(built[0].with(Exact).getStruct(), + built[0].with(Inexact).getStruct()); + + // The exactness of children types is preserved. + EXPECT_EQ(built[0], built[0].getStruct().fields[0].type.getHeapType()); + EXPECT_EQ(built[1].with(Exact), + built[1].getStruct().fields[0].type.getHeapType()); + EXPECT_EQ(built[0], built[2].getSignature().params[0].getHeapType()); + EXPECT_EQ(built[1].with(Exact), + built[2].getSignature().params[1].getHeapType()); + EXPECT_EQ(built[0], built[3].getSignature().results[1].getHeapType()); + EXPECT_EQ(built[1].with(Exact), + built[3].getSignature().results[0].getHeapType()); +} + TEST_F(TypeTest, CanonicalizeSelfReferences) { TypeBuilder builder(5); // Single self-reference From 387552935a80913d24b9a41fa8b6b9e88bac597e Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Wed, 26 Mar 2025 17:35:37 -0700 Subject: [PATCH 381/622] [Outlining] Insert Unreachable (#7400) When the last instruction of the outlined sequence is unreachable, we need to insert an unreachable instruction immediately after the call to the outlined function. This maintains the unreachable type in the original scope of the outlined sequence. --- src/passes/Outlining.cpp | 23 ++++++++++++++++++++-- src/passes/stringify-walker.h | 11 ++++++++--- test/lit/passes/outlining.wast | 35 ++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index 0faa7653e15..8ec0e14b415 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -219,12 +219,27 @@ struct ReconstructStringifyWalker // call will replace the instructions moved to the outlined function. ASSERT_OK(existingBuilder.makeCall(outlinedFunc->name, false)); DBG(std::cerr << "\ncreated outlined fn: " << outlinedFunc->name << "\n"); + + // If the last instruction of the outlined sequence is unreachable, insert + // an unreachable instruction immediately after the call to the outlined + // function. This maintains the unreachable type in the original scope + // of the outlined sequence. + if (sequences[seqCounter].endsTypeUnreachable) { + ASSERT_OK(existingBuilder.makeUnreachable()); + } } void transitionToInSkipSeq() { Function* outlinedFunc = getModule()->getFunction(sequences[seqCounter].func); ASSERT_OK(existingBuilder.makeCall(outlinedFunc->name, false)); + // If the last instruction of the outlined sequence is unreachable, insert + // an unreachable instruction immediately after the call to the outlined + // function. This maintains the unreachable type in the original scope + // of the outlined sequence. + if (sequences[seqCounter].endsTypeUnreachable) { + ASSERT_OK(existingBuilder.makeUnreachable()); + } DBG(std::cerr << "\nstarting to skip instructions " << sequences[seqCounter].startIdx << " - " << sequences[seqCounter].endIdx - 1 << " to " @@ -351,8 +366,12 @@ struct Outlining : public Pass { // sequence relative to its function is better for outlining because we // walk functions. auto [relativeIdx, existingFunc] = stringify.makeRelative(seqIdx); - auto seq = - OutliningSequence(relativeIdx, relativeIdx + substring.Length, func); + auto seq = OutliningSequence( + relativeIdx, + relativeIdx + substring.Length, + func, + stringify.exprs[seqIdx + substring.Length - 1]->type == + Type::unreachable); seqByFunc[existingFunc].push_back(seq); } } diff --git a/src/passes/stringify-walker.h b/src/passes/stringify-walker.h index 5e7f6731d8c..0ba6c2fba91 100644 --- a/src/passes/stringify-walker.h +++ b/src/passes/stringify-walker.h @@ -254,9 +254,14 @@ struct OutliningSequence { unsigned startIdx; unsigned endIdx; Name func; - - OutliningSequence(unsigned startIdx, unsigned endIdx, Name func) - : startIdx(startIdx), endIdx(endIdx), func(func) {} + bool endsTypeUnreachable; + + OutliningSequence(unsigned startIdx, + unsigned endIdx, + Name func, + bool endsTypeUnreachable) + : startIdx(startIdx), endIdx(endIdx), func(func), + endsTypeUnreachable(endsTypeUnreachable) {} }; using Substrings = std::vector; diff --git a/test/lit/passes/outlining.wast b/test/lit/passes/outlining.wast index 83b772519ed..3eb476f60cf 100644 --- a/test/lit/passes/outlining.wast +++ b/test/lit/passes/outlining.wast @@ -1074,3 +1074,38 @@ drop ;; End substring 2 repeat ) ) + +;; Tests unreachable type handling +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (result f32))) + + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (func $outline$ (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + + ;; CHECK: (func $a (type $1) (result f32) + ;; CHECK-NEXT: (call $outline$) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $a (result f32) + i32.const 0 + drop + unreachable + ) + ;; CHECK: (func $b (type $2) (result i32) + ;; CHECK-NEXT: (call $outline$) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $b (result i32) + i32.const 0 + drop + unreachable + ) +) From 43d635c5441c2c88722f82e5245322c975bd1c2a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 27 Mar 2025 09:42:29 -0700 Subject: [PATCH 382/622] Subtyping and LUBs for exact heap types (#7412) Also fix `with(Inexact)` to no longer strip sharedness from basic heap types. Add exact heap types, defined subtypes, and exact subtypes to the gtest for heap type relations. --- src/wasm-type.h | 4 +- src/wasm/wasm-type.cpp | 11 ++- test/gtest/type-builder.cpp | 177 ++++++++++++++++++++++++++++++++++-- 3 files changed, 183 insertions(+), 9 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 47b08ef9cc6..1eb1f9fe86e 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -234,7 +234,9 @@ class HeapType { constexpr HeapType with(Exactness exactness) const { assert((!isBasic() || exactness == Inexact) && "abstract types cannot be exact"); - return HeapType(exactness == Exact ? (id | ExactMask) : (id & ~ExactMask)); + return isBasic() ? *this + : HeapType(exactness == Exact ? (id | ExactMask) + : (id & ~ExactMask)); } // The ID is the numeric representation of the heap type and can be used in diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 8a36dee7472..d198919882d 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -798,7 +798,6 @@ Type Type::getLeastUpperBound(Type a, Type b) { } } return Type::none; - WASM_UNREACHABLE("unexpected type"); } Type Type::getGreatestLowerBound(Type a, Type b) { @@ -1195,6 +1194,9 @@ std::optional HeapType::getLeastUpperBound(HeapType a, HeapType b) { return getBasicHeapTypeLUB(getBasicHeapSupertype(a), getBasicHeapSupertype(b)); } + if (a.with(Inexact) == b.with(Inexact)) { + return a.with(Inexact); + } auto* infoA = getHeapTypeInfo(a); auto* infoB = getHeapTypeInfo(b); @@ -1505,7 +1507,7 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { // See: // https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#subtyping // https://github.com/WebAssembly/gc/blob/master/proposals/gc/MVP.md#defined-types - if (a == b) { + if (a == b || a.with(Inexact) == b) { return true; } if (a.isShared() != b.isShared()) { @@ -1550,6 +1552,11 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { // bottom types. return a == b.getBottom(); } + if (b.isExact()) { + // The only subtypes of an exact type are itself and bottom, both of which + // we have ruled out. + return false; + } // Subtyping must be declared rather than derived from structure, so we will // not recurse. TODO: optimize this search with some form of caching. HeapTypeInfo* curr = getHeapTypeInfo(a); diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 305a0380ca3..8b85e7e97bf 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -701,9 +701,16 @@ TEST_F(TypeTest, TestHeapTypeRelations) { HeapType nofunc = HeapType::nofunc; HeapType nocont = HeapType::nocont; HeapType defFunc = Signature(); + HeapType exactDefFunc = defFunc.with(Exact); HeapType defCont = Continuation(defFunc); - HeapType defStruct = Struct(); + HeapType defStruct; + HeapType exactDefStruct; + HeapType subStruct; + HeapType exactSubStruct; + HeapType subStruct2; + HeapType exactSubStruct2; HeapType defArray = Array(Field(Type::i32, Immutable)); + HeapType exactDefArray = defArray.with(Exact); HeapType sharedAny = any.getBasic(Shared); HeapType sharedEq = eq.getBasic(Shared); HeapType sharedI31 = i31.getBasic(Shared); @@ -714,16 +721,25 @@ TEST_F(TypeTest, TestHeapTypeRelations) { HeapType sharedDefStruct; HeapType sharedDefFunc; { - TypeBuilder builder(2); - builder[0] = Struct{}; - builder[1] = Signature(); - builder[0].setShared(); - builder[1].setShared(); + TypeBuilder builder(5); + builder[0].setShared() = Struct{}; + builder[1].setShared() = Signature(); + builder[2].setOpen() = Struct{}; + builder[3].subTypeOf(builder[2]) = Struct{}; + builder[4].copy(builder[3]); + builder.createRecGroup(3, 2); auto results = builder.build(); ASSERT_TRUE(results); auto built = *results; sharedDefStruct = built[0]; sharedDefFunc = built[1]; + defStruct = built[2]; + subStruct = built[3]; + subStruct2 = built[4]; + ASSERT_NE(subStruct, subStruct2); + exactDefStruct = defStruct.with(Exact); + exactSubStruct = subStruct.with(Exact); + exactSubStruct2 = subStruct2.with(Exact); } auto assertLUB = [](HeapType a, HeapType b, std::optional lub) { @@ -773,8 +789,13 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(ext, nofunc, {}); assertLUB(ext, nocont, {}); assertLUB(ext, defFunc, {}); + assertLUB(ext, exactDefFunc, {}); assertLUB(ext, defStruct, {}); + assertLUB(ext, exactDefStruct, {}); + assertLUB(ext, subStruct, {}); + assertLUB(ext, exactSubStruct, {}); assertLUB(ext, defArray, {}); + assertLUB(ext, exactDefArray, {}); assertLUB(ext, sharedAny, {}); assertLUB(ext, sharedEq, {}); assertLUB(ext, sharedI31, {}); @@ -797,9 +818,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(func, nofunc, func); assertLUB(func, nocont, {}); assertLUB(func, defFunc, func); + assertLUB(func, exactDefFunc, func); assertLUB(func, defCont, {}); assertLUB(func, defStruct, {}); + assertLUB(func, exactDefStruct, {}); + assertLUB(func, subStruct, {}); + assertLUB(func, exactSubStruct, {}); assertLUB(func, defArray, {}); + assertLUB(func, exactDefArray, {}); assertLUB(func, sharedAny, {}); assertLUB(func, sharedEq, {}); assertLUB(func, sharedI31, {}); @@ -822,9 +848,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(cont, nofunc, {}); assertLUB(cont, nocont, cont); assertLUB(cont, defFunc, {}); + assertLUB(cont, exactDefFunc, {}); assertLUB(cont, defCont, cont); assertLUB(cont, defStruct, {}); + assertLUB(cont, exactDefStruct, {}); + assertLUB(cont, subStruct, {}); + assertLUB(cont, exactSubStruct, {}); assertLUB(cont, defArray, {}); + assertLUB(cont, exactDefArray, {}); assertLUB(cont, sharedAny, {}); assertLUB(cont, sharedEq, {}); assertLUB(cont, sharedI31, {}); @@ -846,9 +877,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(any, nofunc, {}); assertLUB(any, nocont, {}); assertLUB(any, defFunc, {}); + assertLUB(any, exactDefFunc, {}); assertLUB(any, defCont, {}); assertLUB(any, defStruct, any); + assertLUB(any, exactDefStruct, any); + assertLUB(any, subStruct, any); + assertLUB(any, exactSubStruct, any); assertLUB(any, defArray, any); + assertLUB(any, exactDefArray, any); assertLUB(any, sharedAny, {}); assertLUB(any, sharedEq, {}); assertLUB(any, sharedI31, {}); @@ -869,9 +905,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(eq, nofunc, {}); assertLUB(eq, nocont, {}); assertLUB(eq, defFunc, {}); + assertLUB(eq, exactDefFunc, {}); assertLUB(eq, defCont, {}); assertLUB(eq, defStruct, eq); + assertLUB(eq, exactDefStruct, eq); + assertLUB(eq, subStruct, eq); + assertLUB(eq, exactSubStruct, eq); assertLUB(eq, defArray, eq); + assertLUB(eq, exactDefArray, eq); assertLUB(eq, sharedAny, {}); assertLUB(eq, sharedEq, {}); assertLUB(eq, sharedI31, {}); @@ -891,9 +932,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(i31, nofunc, {}); assertLUB(i31, nocont, {}); assertLUB(i31, defFunc, {}); + assertLUB(i31, exactDefFunc, {}); assertLUB(i31, defCont, {}); assertLUB(i31, defStruct, eq); + assertLUB(i31, exactDefStruct, eq); + assertLUB(i31, subStruct, eq); + assertLUB(i31, exactSubStruct, eq); assertLUB(i31, defArray, eq); + assertLUB(i31, exactDefArray, eq); assertLUB(i31, sharedAny, {}); assertLUB(i31, sharedEq, {}); assertLUB(i31, sharedI31, {}); @@ -912,9 +958,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(struct_, nofunc, {}); assertLUB(struct_, nocont, {}); assertLUB(struct_, defFunc, {}); + assertLUB(struct_, exactDefFunc, {}); assertLUB(struct_, defCont, {}); assertLUB(struct_, defStruct, struct_); + assertLUB(struct_, exactDefStruct, struct_); + assertLUB(struct_, subStruct, struct_); + assertLUB(struct_, exactSubStruct, struct_); assertLUB(struct_, defArray, eq); + assertLUB(struct_, exactDefArray, eq); assertLUB(struct_, sharedAny, {}); assertLUB(struct_, sharedEq, {}); assertLUB(struct_, sharedI31, {}); @@ -932,9 +983,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(array, nofunc, {}); assertLUB(array, nocont, {}); assertLUB(array, defFunc, {}); + assertLUB(array, exactDefFunc, {}); assertLUB(array, defCont, {}); assertLUB(array, defStruct, eq); + assertLUB(array, exactDefStruct, eq); + assertLUB(array, subStruct, eq); + assertLUB(array, exactSubStruct, eq); assertLUB(array, defArray, array); + assertLUB(array, exactDefArray, array); assertLUB(array, sharedAny, {}); assertLUB(array, sharedEq, {}); assertLUB(array, sharedI31, {}); @@ -951,9 +1007,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(string, nofunc, {}); assertLUB(string, nocont, {}); assertLUB(string, defFunc, {}); + assertLUB(string, exactDefFunc, {}); assertLUB(string, defCont, {}); assertLUB(string, defStruct, {}); + assertLUB(string, exactDefStruct, {}); + assertLUB(string, subStruct, {}); + assertLUB(string, exactSubStruct, {}); assertLUB(string, defArray, {}); + assertLUB(string, exactDefArray, {}); assertLUB(string, sharedAny, {}); assertLUB(string, sharedEq, {}); assertLUB(string, sharedI31, {}); @@ -968,9 +1029,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(none, nofunc, {}); assertLUB(none, nocont, {}); assertLUB(none, defFunc, {}); + assertLUB(none, exactDefFunc, {}); assertLUB(none, defCont, {}); assertLUB(none, defStruct, defStruct); + assertLUB(none, exactDefStruct, exactDefStruct); + assertLUB(none, subStruct, subStruct); + assertLUB(none, exactSubStruct, exactSubStruct); assertLUB(none, defArray, defArray); + assertLUB(none, exactDefArray, exactDefArray); assertLUB(none, sharedAny, {}); assertLUB(none, sharedEq, {}); assertLUB(none, sharedI31, {}); @@ -984,9 +1050,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(noext, nofunc, {}); assertLUB(noext, nocont, {}); assertLUB(noext, defFunc, {}); + assertLUB(noext, exactDefFunc, {}); assertLUB(noext, defCont, {}); assertLUB(noext, defStruct, {}); + assertLUB(noext, exactDefStruct, {}); + assertLUB(noext, subStruct, {}); + assertLUB(noext, exactSubStruct, {}); assertLUB(noext, defArray, {}); + assertLUB(noext, exactDefArray, {}); assertLUB(noext, sharedAny, {}); assertLUB(noext, sharedEq, {}); assertLUB(noext, sharedI31, {}); @@ -999,9 +1070,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nofunc, nofunc, nofunc); assertLUB(nofunc, nocont, {}); assertLUB(nofunc, defFunc, defFunc); + assertLUB(nofunc, exactDefFunc, exactDefFunc); assertLUB(nofunc, defCont, {}); assertLUB(nofunc, defStruct, {}); + assertLUB(nofunc, exactDefStruct, {}); + assertLUB(nofunc, subStruct, {}); + assertLUB(nofunc, exactSubStruct, {}); assertLUB(nofunc, defArray, {}); + assertLUB(nofunc, exactDefArray, {}); assertLUB(nofunc, sharedAny, {}); assertLUB(nofunc, sharedEq, {}); assertLUB(nofunc, sharedI31, {}); @@ -1016,9 +1092,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nocont, cont, cont); assertLUB(nocont, nofunc, {}); assertLUB(nocont, defFunc, {}); + assertLUB(nocont, exactDefFunc, {}); assertLUB(nocont, defCont, defCont); assertLUB(nocont, defStruct, {}); + assertLUB(nocont, exactDefStruct, {}); + assertLUB(nocont, subStruct, {}); + assertLUB(nocont, exactSubStruct, {}); assertLUB(nocont, defArray, {}); + assertLUB(nocont, exactDefArray, {}); assertLUB(nocont, sharedAny, {}); assertLUB(nocont, sharedEq, {}); assertLUB(nocont, sharedI31, {}); @@ -1029,9 +1110,14 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nocont, sharedDefFunc, {}); assertLUB(defFunc, defFunc, defFunc); + assertLUB(defFunc, exactDefFunc, defFunc); assertLUB(defFunc, defCont, {}); assertLUB(defFunc, defStruct, {}); + assertLUB(defFunc, exactDefStruct, {}); + assertLUB(defFunc, subStruct, {}); + assertLUB(defFunc, exactSubStruct, {}); assertLUB(defFunc, defArray, {}); + assertLUB(defFunc, exactDefArray, {}); assertLUB(defFunc, sharedAny, {}); assertLUB(defFunc, sharedEq, {}); assertLUB(defFunc, sharedI31, {}); @@ -1041,10 +1127,31 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defFunc, sharedDefStruct, {}); assertLUB(defFunc, sharedDefFunc, {}); + assertLUB(exactDefFunc, exactDefFunc, exactDefFunc); + assertLUB(exactDefFunc, defCont, {}); + assertLUB(exactDefFunc, defStruct, {}); + assertLUB(exactDefFunc, exactDefStruct, {}); + assertLUB(exactDefFunc, subStruct, {}); + assertLUB(exactDefFunc, exactSubStruct, {}); + assertLUB(exactDefFunc, defArray, {}); + assertLUB(exactDefFunc, exactDefArray, {}); + assertLUB(exactDefFunc, sharedAny, {}); + assertLUB(exactDefFunc, sharedEq, {}); + assertLUB(exactDefFunc, sharedI31, {}); + assertLUB(exactDefFunc, sharedStruct, {}); + assertLUB(exactDefFunc, sharedNone, {}); + assertLUB(exactDefFunc, sharedFunc, {}); + assertLUB(exactDefFunc, sharedDefStruct, {}); + assertLUB(exactDefFunc, sharedDefFunc, {}); + assertLUB(defCont, defCont, defCont); assertLUB(defCont, defFunc, {}); assertLUB(defCont, defStruct, {}); + assertLUB(defCont, exactDefStruct, {}); + assertLUB(defCont, subStruct, {}); + assertLUB(defCont, exactSubStruct, {}); assertLUB(defCont, defArray, {}); + assertLUB(defCont, exactDefArray, {}); assertLUB(defCont, sharedAny, {}); assertLUB(defCont, sharedEq, {}); assertLUB(defCont, sharedI31, {}); @@ -1055,7 +1162,11 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defCont, sharedDefFunc, {}); assertLUB(defStruct, defStruct, defStruct); + assertLUB(defStruct, exactDefStruct, defStruct); + assertLUB(defStruct, subStruct, defStruct); + assertLUB(defStruct, exactSubStruct, defStruct); assertLUB(defStruct, defArray, eq); + assertLUB(defStruct, exactDefArray, eq); assertLUB(defStruct, sharedAny, {}); assertLUB(defStruct, sharedEq, {}); assertLUB(defStruct, sharedI31, {}); @@ -1065,7 +1176,51 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defStruct, sharedDefStruct, {}); assertLUB(defStruct, sharedDefFunc, {}); + assertLUB(exactDefStruct, exactDefStruct, exactDefStruct); + assertLUB(exactDefStruct, subStruct, defStruct); + assertLUB(exactDefStruct, exactSubStruct, defStruct); + assertLUB(exactDefStruct, defArray, eq); + assertLUB(exactDefStruct, exactDefArray, eq); + assertLUB(exactDefStruct, sharedAny, {}); + assertLUB(exactDefStruct, sharedEq, {}); + assertLUB(exactDefStruct, sharedI31, {}); + assertLUB(exactDefStruct, sharedStruct, {}); + assertLUB(exactDefStruct, sharedNone, {}); + assertLUB(exactDefStruct, sharedFunc, {}); + assertLUB(exactDefStruct, sharedDefStruct, {}); + assertLUB(exactDefStruct, sharedDefFunc, {}); + + assertLUB(subStruct, subStruct, subStruct); + assertLUB(subStruct, exactSubStruct, subStruct); + assertLUB(subStruct, subStruct2, defStruct); + assertLUB(subStruct, exactSubStruct2, defStruct); + assertLUB(subStruct, defArray, eq); + assertLUB(subStruct, exactDefArray, eq); + assertLUB(subStruct, sharedAny, {}); + assertLUB(subStruct, sharedEq, {}); + assertLUB(subStruct, sharedI31, {}); + assertLUB(subStruct, sharedStruct, {}); + assertLUB(subStruct, sharedNone, {}); + assertLUB(subStruct, sharedFunc, {}); + assertLUB(subStruct, sharedDefStruct, {}); + assertLUB(subStruct, sharedDefFunc, {}); + + assertLUB(exactSubStruct, exactSubStruct, exactSubStruct); + assertLUB(exactSubStruct, subStruct2, defStruct); + assertLUB(exactSubStruct, exactSubStruct2, defStruct); + assertLUB(exactSubStruct, defArray, eq); + assertLUB(exactSubStruct, exactDefArray, eq); + assertLUB(exactSubStruct, sharedAny, {}); + assertLUB(exactSubStruct, sharedEq, {}); + assertLUB(exactSubStruct, sharedI31, {}); + assertLUB(exactSubStruct, sharedStruct, {}); + assertLUB(exactSubStruct, sharedNone, {}); + assertLUB(exactSubStruct, sharedFunc, {}); + assertLUB(exactSubStruct, sharedDefStruct, {}); + assertLUB(exactSubStruct, sharedDefFunc, {}); + assertLUB(defArray, defArray, defArray); + assertLUB(defArray, exactDefArray, defArray); assertLUB(defArray, sharedAny, {}); assertLUB(defArray, sharedEq, {}); assertLUB(defArray, sharedI31, {}); @@ -1075,6 +1230,16 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defArray, sharedDefStruct, {}); assertLUB(defArray, sharedDefFunc, {}); + assertLUB(exactDefArray, exactDefArray, exactDefArray); + assertLUB(exactDefArray, sharedAny, {}); + assertLUB(exactDefArray, sharedEq, {}); + assertLUB(exactDefArray, sharedI31, {}); + assertLUB(exactDefArray, sharedStruct, {}); + assertLUB(exactDefArray, sharedNone, {}); + assertLUB(exactDefArray, sharedFunc, {}); + assertLUB(exactDefArray, sharedDefStruct, {}); + assertLUB(exactDefArray, sharedDefFunc, {}); + assertLUB(sharedAny, sharedAny, sharedAny); assertLUB(sharedAny, sharedEq, sharedAny); assertLUB(sharedAny, sharedI31, sharedAny); From a12de94e272d5cf9ebfdb232d6d3ca303008f6b2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Mar 2025 13:12:36 -0700 Subject: [PATCH 383/622] [Strings] Unescape in JSON parsing, so StringLifting can read escaped strings (#7410) --- src/support/json.h | 30 +++++++++- src/support/string.cpp | 61 +++++++++++++++++++++ src/support/string.h | 3 + test/lit/passes/string-lifting-section.wast | 26 +++++++-- 4 files changed, 111 insertions(+), 9 deletions(-) diff --git a/src/support/json.h b/src/support/json.h index c005543f60e..92674cf78db 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -39,6 +39,7 @@ #include "support/istring.h" #include "support/safe_integer.h" +#include "support/string.h" namespace json { @@ -260,11 +261,17 @@ struct Value { skip(); if (*curr == '"') { // String - curr++; - char* close = strchr(curr, '"'); + // Start |close| at the opening ", and in the loop below we will always + // begin looking at the first character after. + char* close = curr; + // Skip escaped " + do { + close = strchr(close + 1, '"'); + } while (*(close - 1) == '\\'); assert(close); *close = 0; // end this string, and reuse it straight from the input - setString(curr); + char* raw = curr + 1; + unescapeAndSetString(raw); curr = close + 1; } else if (*curr == '[') { // Array @@ -403,6 +410,23 @@ struct Value { assert(isObject()); return obj->count(x) > 0; } + +private: + // If the string has no escaped characters, setString() the char* directly. If + // it does require escaping, do that and intern a new string with those + // contents. + void unescapeAndSetString(char* str) { + if (!strchr(str, '\\')) { + // No escaping slash. + setString(str); + return; + } + + auto unescaped = wasm::String::unescapeJSONToWTF8(str); + + setString( + IString(std::string_view(unescaped.data(), unescaped.size()), false)); + } }; using Ref = Value::Ref; diff --git a/src/support/string.cpp b/src/support/string.cpp index 96056d90a0e..0dd6c49f5c4 100644 --- a/src/support/string.cpp +++ b/src/support/string.cpp @@ -432,4 +432,65 @@ bool isUTF8(std::string_view str) { return true; } +std::vector unescapeJSONToWTF8(const char* str) { + std::vector unescaped; + size_t i = 0; + while (str[i]) { + if (str[i] != '\\') { + // Normal character. + unescaped.push_back(str[i]); + i++; + continue; + } + + // Escaped character. + char c = str[i + 1]; + if (c != 'u') { + switch (c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 0: + Fatal() << "Invalid escaped JSON ends in slash"; + } + unescaped.push_back(c); + i += 2; + continue; + } + + // \uXXXX, 4-digit hex number. First, read the hex. + unsigned int x; + std::stringstream unhex; + if (!str[i + 2] || !str[i + 3] || !str[i + 4] || !str[i + 5]) { + Fatal() << "Invalid escaped JSON \\uXXXX"; + } + unhex << std::hex << std::string_view(str + i + 2, 4); + unhex >> x; + + // Write out the results. + unescaped.push_back(x & 0xff); + x >>= 8; + if (x) { + unescaped.push_back(x); + } + // TODO UTF stuff + + i += 6; + } + + return unescaped; +} + } // namespace wasm::String diff --git a/src/support/string.h b/src/support/string.h index 24eb570c2c3..c0d0d5e8c79 100644 --- a/src/support/string.h +++ b/src/support/string.h @@ -102,6 +102,9 @@ bool convertUTF16ToUTF8(std::ostream& os, std::string_view str); // Whether the string is valid UTF-8. bool isUTF8(std::string_view str); +// Given a string of properly-escaped JSON, unescape it. +std::vector unescapeJSONToWTF8(const char* str); + } // namespace wasm::String #endif // wasm_support_string_h diff --git a/test/lit/passes/string-lifting-section.wast b/test/lit/passes/string-lifting-section.wast index 88165c208f6..49b0d646c6f 100644 --- a/test/lit/passes/string-lifting-section.wast +++ b/test/lit/passes/string-lifting-section.wast @@ -7,9 +7,9 @@ (module ;; CHECK: (type $0 (array (mut i16))) - ;; CHECK: (type $1 (func (param externref externref) (result i32))) + ;; CHECK: (type $1 (func)) - ;; CHECK: (type $2 (func)) + ;; CHECK: (type $2 (func (param externref externref) (result i32))) ;; CHECK: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) @@ -29,6 +29,8 @@ ;; CHECK: (import "string.const" "1" (global $"string.const_\"foo\"" (ref extern))) + ;; CHECK: (import "string.const" "2" (global $"string.const_\"needs\\tescaping\\00.\\\'#%\\\"\"" (ref extern))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) @@ -37,9 +39,9 @@ ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) ;; CHECK: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) @@ -47,7 +49,7 @@ ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (func $consts (type $2) + ;; CHECK: (func $consts (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (string.const "foo") ;; CHECK-NEXT: ) @@ -71,7 +73,19 @@ (drop (string.const "foo") ) - ;; TODO: test utf-8 etc. + ) + + ;; CHECK: (func $tricky-consts (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "needs\tescaping\00.\'#%\"") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tricky-consts + ;; This tricky string should remain exactly the same after lowering and + ;; lifting. + (drop + (string.const "needs\tescaping\00.'#%\"") + ) ) ) From d8b4c569c7ab6abe9c06f953c0eff01731d93d32 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 27 Mar 2025 17:20:24 -0700 Subject: [PATCH 384/622] Test StringLowering with a surrogate pair (#7415) We tested isolated surrogates, but we did not test with a valid surrogate pair. --- test/lit/passes/string-lowering.wast | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/string-lowering.wast b/test/lit/passes/string-lowering.wast index 0182082f4e5..3917a40e059 100644 --- a/test/lit/passes/string-lowering.wast +++ b/test/lit/passes/string-lowering.wast @@ -16,6 +16,10 @@ (drop (string.const "needs\tescaping\00.'#%\"- .\r\n\\08\0C\0A\0D\09.ꙮ") ) + (drop + ;; GOTHIC LETTER HWAIR: 𐍈 has code point 0x10348 and is \uD800 \uDF48 in UTF-16. + (string.const "surrogate pair \F0\90\8D\88 ") + ) (drop (string.const "unpaired high surrogate \ED\A0\80 ") ) @@ -40,7 +44,7 @@ ;; RUN: not wasm-opt %s --string-lowering-magic-imports-assert -all -S -o - \ ;; RUN: 2>&1 | filecheck %s --check-prefix=ASSERT ;; -;; CHECK: custom section "string.consts", size 136, contents: "[\"bar\",\"foo\",\"needs\\tescaping\\u0000.'#%\\\"- .\\r\\n\\\\08\\f\\n\\r\\t.\\ua66e\",\"unpaired high surrogate \\ud800 \",\"unpaired low surrogate \\udf48 \"]" +;; CHECK: custom section "string.consts", size 167, contents: "[\"bar\",\"foo\",\"needs\\tescaping\\u0000.'#%\\\"- .\\r\\n\\\\08\\f\\n\\r\\t.\\ua66e\",\"surrogate pair \\ud800\\udf48 \",\"unpaired high surrogate \\ud800 \",\"unpaired low surrogate \\udf48 \"]" ;; ;; MAGIC: custom section "string.consts", size 68, contents: "[\"unpaired high surrogate \\ud800 \",\"unpaired low surrogate \\udf48 \"]" ;; @@ -53,6 +57,6 @@ ;; RUN: wasm-opt %s --string-lowering --remove-unused-module-elements -all -o %t.wasm ;; RUN: node %S/string-lowering.js %t.wasm | filecheck %s --check-prefix=CHECK-JS ;; -;; CHECK-JS: string: ["bar","foo","needs\tescaping\x00.'#%\"- .\r\n\\08\f\n\r\t.\ua66e","unpaired high surrogate \ud800 ","unpaired low surrogate \udf48 "] +;; CHECK-JS: string: ["bar","foo","needs\tescaping\x00.'#%\"- .\r\n\\08\f\n\r\t.\ua66e","surrogate pair \ud800\udf48 ","unpaired high surrogate \ud800 ","unpaired low surrogate \udf48 "] ;; -;; CHECK-JS: JSON: ["bar","foo","needs\tescaping\x00.'#%\"- .\r\n\\08\f\n\r\t.ꙮ","unpaired high surrogate \ud800 ","unpaired low surrogate \udf48 "] +;; CHECK-JS: JSON: ["bar","foo","needs\tescaping\x00.'#%\"- .\r\n\\08\f\n\r\t.ꙮ","surrogate pair 𐍈 ","unpaired high surrogate \ud800 ","unpaired low surrogate \udf48 "] From 1fd008538113d25b4224eb1a67ea3642bedd30d4 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 28 Mar 2025 18:01:11 -0700 Subject: [PATCH 385/622] Allow using mimalloc with dynamic linking (#7391) With dynamic linking, build and link mimalloc's dynamic library, and include it in the installation (this also brings along the headers and CMake files, but it seemed like more trouble than it was worth to try to manually install just the library or remove the extras). The static build remains the same. Remove the restriction that mimalloc can only be linked into a static-lib build. --- .github/workflows/ci.yml | 2 +- .github/workflows/create_release.yml | 2 +- CMakeLists.txt | 19 ++++++++++++------- third_party/CMakeLists.txt | 18 +++++++++++++----- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c4a4f27248..e54a7e494ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -250,7 +250,7 @@ jobs: - name: cmake run: | - ./alpine.sh cmake . -G Ninja -DCMAKE_INSTALL_PREFIX=out/install -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIB=ON -DMIMALLOC_STATIC=ON -DCMAKE_INSTALL_PREFIX=install + ./alpine.sh cmake . -G Ninja -DCMAKE_INSTALL_PREFIX=out/install -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIB=ON -DBUILD_MIMALLOC=ON -DCMAKE_INSTALL_PREFIX=install - name: build run: | diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index f6b8f7802ad..143f0300451 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -140,7 +140,7 @@ jobs: - name: cmake run: | - ./alpine.sh cmake . -G Ninja -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIB=ON -DMIMALLOC_STATIC=ON -DCMAKE_INSTALL_PREFIX=install + ./alpine.sh cmake . -G Ninja -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIB=ON -DBUILD_MIMALLOC=ON -DCMAKE_INSTALL_PREFIX=install - name: build run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 90edcb2c858..16e2add800f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,9 +56,10 @@ endif() # Advised to turn on when statically linking against musl libc (e.g., in the # Alpine Linux build we use for producing official Linux binaries), because # musl libc's allocator has very bad performance on heavily multi-threaded -# workloads / high core count machines. +# workloads / high core count machines. But it also works with dynamic linking +# with a small performance advantage in some cases over the glibc allocator. # See https://github.com/WebAssembly/binaryen/issues/5561. -option(MIMALLOC_STATIC "Build with statically linked mimalloc allocator" OFF) +option(BUILD_MIMALLOC "Build with mimalloc allocator" OFF) # Turn this off to install only tools and not static/dynamic libs. option(INSTALL_LIBS "Install libraries" ON) @@ -467,12 +468,16 @@ if(BUILD_LLVM_DWARF) target_link_libraries(binaryen llvm_dwarf) endif() -if(MIMALLOC_STATIC) - if(NOT(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND BUILD_STATIC_LIB) OR EMSCRIPTEN) - message(FATAL_ERROR "Statically linking mimalloc is only supported when building as a native, statically linked library on Linux.") +if(BUILD_MIMALLOC) + if(NOT(CMAKE_SYSTEM_NAME STREQUAL "Linux") OR EMSCRIPTEN) + message(FATAL_ERROR "Linking mimalloc is only supported on Linux.") + endif() + message(STATUS "Building with mimalloc allocator.") + if(BUILD_STATIC_LIB) + target_link_libraries(binaryen mimalloc-static) + else() + target_link_libraries(binaryen mimalloc) endif() - message(STATUS "Building with statically linked mimalloc allocator.") - target_link_libraries(binaryen mimalloc-static) endif() add_subdirectory(src/ir) diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index ca0c2bc39a9..e4f614edf56 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -16,15 +16,23 @@ if(BUILD_LLVM_DWARF) add_subdirectory(llvm-project) endif() -if(MIMALLOC_STATIC) - # We only need the static library, nothing else. - set(MI_BUILD_STATIC ON) - set(MI_BUILD_SHARED OFF) +if(BUILD_MIMALLOC) + # Match static/dynamic linking between libbinaryen and mimalloc + set(MI_BUILD_STATIC ${BUILD_STATIC_LIB}) + if (BUILD_STATIC_LIB) + set(MI_BUILD_SHARED OFF) + endif() set(MI_BUILD_OBJECT OFF) set(MI_BUILD_TESTS OFF) + set(MI_INSTALL_TOPLEVEL ON) # Do not show debug and warning messages of the allocator by default. # (They can still be enabled via MIMALLOC_VERBOSE=1 wasm-opt ...) add_compile_definitions(MI_DEBUG=0) - add_subdirectory(mimalloc EXCLUDE_FROM_ALL) + if(BUILD_STATIC_LIB) + # No need to install libmimalloc.a when it's linked statically into the tools. + add_subdirectory(mimalloc EXCLUDE_FROM_ALL) + else() + add_subdirectory(mimalloc) + endif() endif() From f77a69d7f64dffdcdda65c478ee2a93c2821349c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 31 Mar 2025 11:55:22 -0700 Subject: [PATCH 386/622] OptimizeInstructions: Handle all binary and unary expressions emitting zero bits (#7413) We have many hardcoded rules, but also have general logic that computes the max bits. If the max bits are 0, we can replace with a 0. Fixes #7406 --- src/passes/OptimizeInstructions.cpp | 22 +++++++ .../lit/passes/optimize-instructions-mvp.wast | 60 +++++++++++-------- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index c8c192685a2..d02f7f17e51 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -304,6 +304,20 @@ struct OptimizeInstructions return EffectAnalyzer::canReorder(getPassOptions(), *getModule(), a, b); } + // If an expression can only have zero bits, return a constant 0 (or null if + // we cannot optimize). + Expression* replaceZeroBitsWithZero(Expression* curr) { + // We should never be called with a constant. + assert(!curr->is()); + + if (!curr->type.isInteger() || Bits::getMaxBits(curr, this) != 0) { + return nullptr; + } + + auto zero = Builder(*getModule()).makeConst(Literal::makeZero(curr->type)); + return getDroppedChildrenAndAppend(curr, zero); + } + void visitBinary(Binary* curr) { // If this contains dead code, don't bother trying to optimize it, the type // might change (if might not be unreachable if just one arm is, for @@ -838,6 +852,10 @@ struct OptimizeInstructions return replaceCurrent(ret); } } + // see if we can infer this is a zero + if (auto* ret = replaceZeroBitsWithZero(curr)) { + return replaceCurrent(ret); + } // finally, try more expensive operations on the curr in // the case that they have no side effects if (!effects(curr->left).hasSideEffects()) { @@ -1101,6 +1119,10 @@ struct OptimizeInstructions if (auto* ret = simplifyRoundingsAndConversions(curr)) { return replaceCurrent(ret); } + + if (auto* ret = replaceZeroBitsWithZero(curr)) { + return replaceCurrent(ret); + } } void visitSelect(Select* curr) { diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 077ecf49502..8336568d15d 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -2833,23 +2833,38 @@ ) ) ;; CHECK: (func $sext-24-div (result i32) - ;; CHECK-NEXT: (i32.shr_u - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $sext-24-div (result i32) (i32.shr_s (i32.shl - (i32.div_s ;; this could be optimizable in theory, but currently we don't look into adds etc. - (i32.const 1) - (i32.const 2) + (i32.div_s ;; we don't precompute this, but we do know the limit on + (i32.const 1) ;; max bits, and the sign bit cannot be 1, so this all + (i32.const 2) ;; ends up as zero. ) (i32.const 24) ) (i32.const 24) ) ) + ;; CHECK: (func $sext-24-param (param $x i32) (result i32) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $sext-24-param (param $x i32) (result i32) + (i32.shr_s + (i32.shl + (local.get $x) ;; we don't know what this is, and so optimize nothing + (i32.const 24) + ) + (i32.const 24) + ) + ) ;; CHECK: (func $sext-24-and-127-128 (result i32) ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (i32.const 127) @@ -3178,13 +3193,7 @@ ) ) ;; CHECK: (func $sext-24-shr_s-and-masked-sign (result i32) - ;; CHECK-NEXT: (i32.shr_u - ;; CHECK-NEXT: (i32.and - ;; CHECK-NEXT: (i32.const -1) - ;; CHECK-NEXT: (i32.const 2147483647) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 31) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $sext-24-shr_s-and-masked-sign (result i32) (i32.shr_s @@ -3194,8 +3203,8 @@ (i32.const -1) (i32.const 2147483647) ) - (i32.const 31) ;; adjusted after we fixed shift computation to just look at lower 5 bits - ) + (i32.const 31) ;; no sign bit, so the shift zeroes us out, and + ) ;; later shifts cannot add bits, so the result is 0 (i32.const 24) ) (i32.const 24) @@ -6281,7 +6290,7 @@ ;; CHECK: (func $mix-shifts (result i32) ;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (i32.shl - ;; CHECK-NEXT: (i32.const 23) + ;; CHECK-NEXT: (i32.const 65535) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 8) @@ -6290,7 +6299,7 @@ (func $mix-shifts (result i32) (i32.shr_s (i32.shl - (i32.const 23) + (i32.const 65535) (i32.const -61) ) (i32.const 168) @@ -8407,9 +8416,11 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.and - ;; CHECK-NEXT: (call $andZero - ;; CHECK-NEXT: (i32.const 1234) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $andZero + ;; CHECK-NEXT: (i32.const 1234) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -8425,7 +8436,8 @@ ) (drop (i32.and - (call $andZero (i32.const 1234)) ;; side effects + (call $andZero (i32.const 1234)) ;; side effects, we must keep this, but + ;; can drop it. (i32.const 0) ) ) @@ -11114,9 +11126,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i64.extend_i32_s - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) From a7d93efd82d3c04ff835dbd4d3b619f8f6a4e4ce Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 1 Apr 2025 16:07:14 -0700 Subject: [PATCH 387/622] [Strings] Handle encoding in JSON parsing so StringLifting can handle arbitrary custom section content (#7414) Rather than encode to WTF8 and re-encode, instead make the unescaping logic go from UTF8 straight to WTF16. That makes it simpler and more efficient. Make the JSON parser get a parameter for which encoding to use for strings, so we can use ascii in old places. --- src/passes/StringLifting.cpp | 22 +++++----- src/support/json.h | 45 ++++++++++++--------- src/support/string.cpp | 16 +++----- src/support/string.h | 4 +- src/tools/wasm-metadce.cpp | 2 +- test/gtest/json.cpp | 2 +- test/lit/passes/string-lifting-section.wast | 32 +++++++++++++-- 7 files changed, 75 insertions(+), 48 deletions(-) diff --git a/src/passes/StringLifting.cpp b/src/passes/StringLifting.cpp index ec09a599916..58e2d28882e 100644 --- a/src/passes/StringLifting.cpp +++ b/src/passes/StringLifting.cpp @@ -66,7 +66,14 @@ struct StringLifting : public Pass { continue; } if (global->module == stringConstsModule) { - importedStrings[global->name] = global->base; + // Encode from WTF-8 to WTF-16. + auto wtf8 = global->base; + std::stringstream wtf16; + bool valid = String::convertWTF8ToWTF16(wtf16, wtf8.str); + if (!valid) { + Fatal() << "Bad string to lift: " << wtf8; + } + importedStrings[global->name] = wtf16.str(); found = true; } } @@ -77,7 +84,7 @@ struct StringLifting : public Pass { // We found the string consts section. Parse it. auto copy = section.data; json::Value array; - array.parse(copy.data()); + array.parse(copy.data(), json::Value::WTF16); if (!array.isArray()) { Fatal() << "StringLifting: string.const section should be a JSON array"; @@ -203,15 +210,8 @@ struct StringLifting : public Pass { // Replace global.gets of imported strings with string.const. auto iter = parent.importedStrings.find(curr->name); if (iter != parent.importedStrings.end()) { - // Encode from WTF-8 to WTF-16. - auto wtf8 = iter->second; - std::stringstream wtf16; - bool valid = String::convertWTF8ToWTF16(wtf16, wtf8.str); - if (!valid) { - Fatal() << "Bad string to lift: " << wtf8; - } - - replaceCurrent(Builder(*getModule()).makeStringConst(wtf16.str())); + auto wtf16 = iter->second; + replaceCurrent(Builder(*getModule()).makeStringConst(wtf16.str)); modified = true; } } diff --git a/src/support/json.h b/src/support/json.h index 92674cf78db..1c8f2bf180f 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -249,7 +249,16 @@ struct Value { return true; } - char* parse(char* curr) { + // The encoding into which we parse strings. The input encoding is always + // UTF8, but we can parse into ASCII (very quickly, and without many small + // allocations), or we can parse into WTF16 (which is the format used by + // StringConst). + enum StringEncoding { + ASCII, + WTF16, + }; + + char* parse(char* curr, StringEncoding stringEncoding) { #define is_json_space(x) \ (x == 32 || x == 9 || x == 10 || \ x == 13) /* space, tab, linefeed/newline, or return */ @@ -271,7 +280,13 @@ struct Value { assert(close); *close = 0; // end this string, and reuse it straight from the input char* raw = curr + 1; - unescapeAndSetString(raw); + if (stringEncoding == ASCII) { + // Just use the current string. + setString(raw); + } else { + assert(stringEncoding == WTF16); + unescapeIntoWTF16(raw); + } curr = close + 1; } else if (*curr == '[') { // Array @@ -281,7 +296,7 @@ struct Value { while (*curr != ']') { Ref temp = Ref(new Value()); arr->push_back(temp); - curr = temp->parse(curr); + curr = temp->parse(curr, stringEncoding); skip(); if (*curr == ']') { break; @@ -324,7 +339,7 @@ struct Value { curr++; skip(); Ref value = Ref(new Value()); - curr = value->parse(curr); + curr = value->parse(curr, stringEncoding); (*obj)[key] = value; skip(); if (*curr == '}') { @@ -412,20 +427,14 @@ struct Value { } private: - // If the string has no escaped characters, setString() the char* directly. If - // it does require escaping, do that and intern a new string with those - // contents. - void unescapeAndSetString(char* str) { - if (!strchr(str, '\\')) { - // No escaping slash. - setString(str); - return; - } - - auto unescaped = wasm::String::unescapeJSONToWTF8(str); - - setString( - IString(std::string_view(unescaped.data(), unescaped.size()), false)); + // Unescape the input (UTF8) string into one of our internal strings (WTF16). + void unescapeIntoWTF16(char* str) { + // TODO: Optimize the unescaped path? But it is impossible to avoid an + // allocation here. + std::stringstream ss; + wasm::String::unescapeUTF8JSONtoWTF16(ss, str); + // TODO: Use ss.view() once we have C++20. + setString(ss.str()); } }; diff --git a/src/support/string.cpp b/src/support/string.cpp index 0dd6c49f5c4..f8f06a71804 100644 --- a/src/support/string.cpp +++ b/src/support/string.cpp @@ -432,13 +432,12 @@ bool isUTF8(std::string_view str) { return true; } -std::vector unescapeJSONToWTF8(const char* str) { - std::vector unescaped; +std::ostream& unescapeUTF8JSONtoWTF16(std::ostream& os, const char* str) { size_t i = 0; while (str[i]) { if (str[i] != '\\') { // Normal character. - unescaped.push_back(str[i]); + writeWTF16CodePoint(os, str[i]); i++; continue; } @@ -465,7 +464,7 @@ std::vector unescapeJSONToWTF8(const char* str) { case 0: Fatal() << "Invalid escaped JSON ends in slash"; } - unescaped.push_back(c); + writeWTF16CodePoint(os, c); i += 2; continue; } @@ -480,17 +479,12 @@ std::vector unescapeJSONToWTF8(const char* str) { unhex >> x; // Write out the results. - unescaped.push_back(x & 0xff); - x >>= 8; - if (x) { - unescaped.push_back(x); - } - // TODO UTF stuff + writeWTF16CodePoint(os, x); i += 6; } - return unescaped; + return os; } } // namespace wasm::String diff --git a/src/support/string.h b/src/support/string.h index c0d0d5e8c79..56a7a81ab80 100644 --- a/src/support/string.h +++ b/src/support/string.h @@ -102,8 +102,8 @@ bool convertUTF16ToUTF8(std::ostream& os, std::string_view str); // Whether the string is valid UTF-8. bool isUTF8(std::string_view str); -// Given a string of properly-escaped JSON, unescape it. -std::vector unescapeJSONToWTF8(const char* str); +// Given a string of properly-escaped JSON in UTF8, unescape it into WTF16. +std::ostream& unescapeUTF8JSONtoWTF16(std::ostream& os, const char* str); } // namespace wasm::String diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index bddf0c28ed6..3572cd5f201 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -518,7 +518,7 @@ int main(int argc, const char* argv[]) { auto graphInput(read_file(graphFile, Flags::Text)); auto* copy = strdup(graphInput.c_str()); json::Value outside; - outside.parse(copy); + outside.parse(copy, json::Value::ASCII); // parse the JSON into our graph, doing all the JSON parsing here, leaving // the abstract computation for the class itself diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index 10417cdb947..626861a626a 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -8,7 +8,7 @@ TEST_F(JSONTest, Stringify) { auto input = "[\"hello\",\"world\"]"; auto* copy = strdup(input); json::Value value; - value.parse(copy); + value.parse(copy, json::Value::ASCII); std::stringstream ss; value.stringify(ss); EXPECT_EQ(ss.str(), input); diff --git a/test/lit/passes/string-lifting-section.wast b/test/lit/passes/string-lifting-section.wast index 49b0d646c6f..247fc39a48e 100644 --- a/test/lit/passes/string-lifting-section.wast +++ b/test/lit/passes/string-lifting-section.wast @@ -29,7 +29,13 @@ ;; CHECK: (import "string.const" "1" (global $"string.const_\"foo\"" (ref extern))) - ;; CHECK: (import "string.const" "2" (global $"string.const_\"needs\\tescaping\\00.\\\'#%\\\"\"" (ref extern))) + ;; CHECK: (import "string.const" "2" (global $"string.const_\"needs\\tescaping\\00.\\\'#%\\\"- .\\r\\n\\\\08\\0c\\n\\r\\t.\\ea\\99\\ae\"" (ref extern))) + + ;; CHECK: (import "string.const" "3" (global $"string.const_\"surrogate pair \\f0\\90\\8d\\88 \"" (ref extern))) + + ;; CHECK: (import "string.const" "4" (global $"string.const_\"unpaired high surrogate \\ed\\a0\\80 \"" (ref extern))) + + ;; CHECK: (import "string.const" "5" (global $"string.const_\"unpaired low surrogate \\ed\\bd\\88 \"" (ref extern))) ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) @@ -77,14 +83,32 @@ ;; CHECK: (func $tricky-consts (type $1) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (string.const "needs\tescaping\00.\'#%\"") + ;; CHECK-NEXT: (string.const "needs\tescaping\00.\'#%\"- .\r\n\\08\0c\n\r\t.\ea\99\ae") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "surrogate pair \f0\90\8d\88 ") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "unpaired high surrogate \ed\a0\80 ") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "unpaired low surrogate \ed\bd\88 ") ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $tricky-consts - ;; This tricky string should remain exactly the same after lowering and + ;; These tricky strings should remain exactly the same after lowering and ;; lifting. (drop - (string.const "needs\tescaping\00.'#%\"") + (string.const "needs\tescaping\00.'#%\"- .\r\n\\08\0C\0A\0D\09.ꙮ") + ) + (drop + (string.const "surrogate pair \F0\90\8D\88 ") + ) + (drop + (string.const "unpaired high surrogate \ED\A0\80 ") + ) + (drop + (string.const "unpaired low surrogate \ED\BD\88 ") ) ) ) From 05a8c8d0daafa39fba5c012a0ba413411edd8b4c Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Tue, 1 Apr 2025 16:21:55 -0700 Subject: [PATCH 388/622] [Outlining] Filter in nested control flow (#7411) Fixes a bug where stringify walker was not removing outlining candidates with restricted expressions because they were in nested control flow. --- src/passes/hash-stringify-walker.cpp | 1 + src/passes/stringify-walker-impl.h | 4 +- src/passes/stringify-walker.h | 6 ++- test/lit/passes/outlining.wast | 55 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/passes/hash-stringify-walker.cpp b/src/passes/hash-stringify-walker.cpp index 69163391f3c..0f65ccc3f46 100644 --- a/src/passes/hash-stringify-walker.cpp +++ b/src/passes/hash-stringify-walker.cpp @@ -204,6 +204,7 @@ std::vector StringifyProcessor::filter( void walk(Expression* curr) { hasFilterValue = false; Super::walk(curr); + flushControlFlowQueue(); } void addUniqueSymbol(SeparatorReason reason) {} diff --git a/src/passes/stringify-walker-impl.h b/src/passes/stringify-walker-impl.h index 040770fdf2f..018d851dd25 100644 --- a/src/passes/stringify-walker-impl.h +++ b/src/passes/stringify-walker-impl.h @@ -42,9 +42,7 @@ inline void StringifyWalker::doWalkFunction(Function* func) { addUniqueSymbol(SeparatorReason::makeFuncStart(func)); Super::walk(func->body); addUniqueSymbol(SeparatorReason::makeEnd()); - while (!controlFlowQueue.empty()) { - dequeueControlFlow(); - } + flushControlFlowQueue(); } template diff --git a/src/passes/stringify-walker.h b/src/passes/stringify-walker.h index 0ba6c2fba91..bb7a09924f9 100644 --- a/src/passes/stringify-walker.h +++ b/src/passes/stringify-walker.h @@ -191,7 +191,11 @@ struct StringifyWalker static void scan(SubType* self, Expression** currp); static void doVisitExpression(SubType* self, Expression** currp); -private: + void flushControlFlowQueue() { + while (!controlFlowQueue.empty()) { + dequeueControlFlow(); + } + } void dequeueControlFlow(); }; diff --git a/test/lit/passes/outlining.wast b/test/lit/passes/outlining.wast index 3eb476f60cf..c608ec7d7d9 100644 --- a/test/lit/passes/outlining.wast +++ b/test/lit/passes/outlining.wast @@ -1109,3 +1109,58 @@ unreachable ) ) + +;; Tests that restricted expressions (local.set) are filtered from outlining +;; even when nested within control flow. +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (func $a (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a + (local $x i32) + (block + (if + (i32.const 0) + (then + (local.set $x + (i32.const 1) + ) + ) + ) + ) + ) + ;; CHECK: (func $b (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $b + (local $x i32) + (block + (if + (i32.const 0) + (then + (local.set $x + (i32.const 1) + ) + ) + ) + ) + ) +) From 52ac4c11e24b002a9e001d26f8503a580437a0a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Apr 2025 08:58:11 -0700 Subject: [PATCH 389/622] [GC] RemoveUnusedBrs must not un-refine sent types (#7421) The pass refines cast types of br_on, but that will un-refine the sent type of a br_on_cast_fail - a more refined cast type means more things fail it, so more things are sent. We need to avoid that. --- src/passes/RemoveUnusedBrs.cpp | 12 +++++ test/lit/passes/remove-unused-brs-gc.wast | 65 ++++++++++++++++++++--- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 47a1f1c6553..6bf9114d604 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -906,6 +906,18 @@ struct RemoveUnusedBrs : public WalkerPass> { // further optimizations after this, and those optimizations might even // benefit from this improvement. auto glb = Type::getGreatestLowerBound(curr->castType, refType); + if (curr->op == BrOnCastFail) { + // BrOnCastFail sends the input type, with adjusted nullability. The + // input heap type makes sense for the branch target, and we will not + // change it anyhow, but we need to be careful with nullability: if + // the cast type was nullable, then we were sending a non-nullable + // value to the branch, and if we refined the cast type to non- + // nullable, we would no longer be doing that. In other words, we must + // not refine the nullability, as that would *un*refine the send type. + if (curr->castType.isNullable() && glb.isNonNullable()) { + glb = glb.with(Nullable); + } + } if (glb != Type::unreachable && glb != curr->castType) { curr->castType = glb; auto oldType = curr->type; diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 2ee6171cbcb..268bbde2092 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -494,8 +494,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br $block - ;; CHECK-NEXT: (local.tee $any - ;; CHECK-NEXT: (struct.new_default $struct2) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.tee $any + ;; CHECK-NEXT: (struct.new_default $struct2) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -531,7 +533,10 @@ ) ) (drop - ;; Ditto. + ;; Ditto, but also add a ref.as_non_null, as we must keep sending a non- + ;; null value to the block (the block would still validate either way, but + ;; we do not want to un-refine the sent value). See the next function for a + ;; test with a non-nullable block. (br_on_cast_fail $block anyref (ref null $struct) (local.tee $any (struct.new $struct2)) ) @@ -552,6 +557,54 @@ ) ) + ;; CHECK: (func $br_on_cast_fail_unrelated-fallthrough-non-null (type $11) (result (ref any)) + ;; CHECK-NEXT: (local $any anyref) + ;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2)) + ;; CHECK-NEXT: (block $block (result (ref any)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.tee $any + ;; CHECK-NEXT: (struct.new_default $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (br_on_non_null $block + ;; CHECK-NEXT: (local.tee $any + ;; CHECK-NEXT: (local.get $nullable-struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail_unrelated-fallthrough-non-null (result (ref any)) + ;; Same as above, but the block is now non-nullable. Only the branches that + ;; work with that are tested. + (local $any anyref) + (local $nullable-struct2 (ref null $struct2)) + (block $block (result (ref any)) ;; this changed, and the function's results + (drop + ;; Will definitely take the branch. + (br_on_cast_fail $block anyref (ref null $struct) + (local.tee $any (struct.new $struct2)) + ) + ) + (drop + ;; Still has to do a null check. + (br_on_cast_fail $block anyref (ref null $struct) + (local.tee $any (local.get $nullable-struct2)) + ) + ) + (unreachable) + ) + ) + ;; CHECK: (func $br_on_cast-unreachable (type $7) (param $i31ref i31ref) (result anyref) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop @@ -840,7 +893,7 @@ ) ) - ;; CHECK: (func $threading (type $11) (param $x anyref) + ;; CHECK: (func $threading (type $12) (param $x anyref) ;; CHECK-NEXT: (block $outer ;; CHECK-NEXT: (block $inner ;; CHECK-NEXT: (drop @@ -864,7 +917,7 @@ ) ) - ;; CHECK: (func $test (type $12) (param $x (ref any)) + ;; CHECK: (func $test (type $13) (param $x (ref any)) ;; CHECK-NEXT: (local $temp anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref $struct-nn)) @@ -916,7 +969,7 @@ ) ) - ;; CHECK: (func $select-refinalize (type $13) (param $param (ref $struct)) (result (ref struct)) + ;; CHECK: (func $select-refinalize (type $14) (param $param (ref $struct)) (result (ref struct)) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (global.get $struct) From df26f96333c8fe3f90638e2f022b804c67396eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 2 Apr 2025 20:19:44 +0200 Subject: [PATCH 390/622] Stack switching: fix some optimization passes (#7271) This continues #7041 by adapting the optimizations passes to work with the stack switching instructions. --- scripts/test/fuzzing.py | 6 + src/cfg/cfg-traversal.h | 27 +++ src/ir/ReFinalize.cpp | 14 +- src/ir/branch-utils.h | 8 +- src/ir/subtypes.h | 3 +- src/ir/type-updating.cpp | 8 +- src/ir/type-updating.h | 20 +- src/wasm-interpreter.h | 26 ++- src/wasm/wasm.cpp | 6 +- test/lit/basic/stack_switching_switch_2.wast | 29 +++ test/lit/passes/O3_stack-switching.wast | 167 +++++++++++++++ .../coalesce-locals-stack-switching.wast | 198 ++++++++++++++++++ test/lit/passes/dce-stack-switching.wast | 65 ++++++ .../passes/precompute-stack-switching.wast | 95 +++++++++ test/lit/passes/vacuum-stack-switching.wast | 65 ++++++ 15 files changed, 715 insertions(+), 22 deletions(-) create mode 100644 test/lit/basic/stack_switching_switch_2.wast create mode 100644 test/lit/passes/O3_stack-switching.wast create mode 100644 test/lit/passes/coalesce-locals-stack-switching.wast create mode 100644 test/lit/passes/dce-stack-switching.wast create mode 100644 test/lit/passes/precompute-stack-switching.wast create mode 100644 test/lit/passes/vacuum-stack-switching.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index e426dbbd181..bceb6ae1c55 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -105,6 +105,12 @@ 'stack_switching_resume.wast', 'stack_switching_resume_throw.wast', 'stack_switching_switch.wast', + 'stack_switching_switch_2.wast', + 'O3_stack-switching.wast', + 'coalesce-locals-stack-switching.wast', + 'dce-stack-switching.wast', + 'precompute-stack-switching.wast', + 'vacuum-stack-switching.wast' # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/cfg/cfg-traversal.h b/src/cfg/cfg-traversal.h index 64877c58cfc..46ecc4b2a30 100644 --- a/src/cfg/cfg-traversal.h +++ b/src/cfg/cfg-traversal.h @@ -444,6 +444,19 @@ struct CFGWalker : public PostWalker { self->tryStack.pop_back(); } + static void doEndResume(SubType* self, Expression** currp) { + auto* module = self->getModule(); + if (!module || module->features.hasExceptionHandling()) { + // This resume might throw, so run the code to handle that. + doEndThrowingInst(self, currp); + } + auto handlerBlocks = BranchUtils::getUniqueTargets(*currp); + // Add branches to the targets. + for (auto target : handlerBlocks) { + self->branches[target].push_back(self->currBasicBlock); + } + } + static bool isReturnCall(Expression* curr) { switch (curr->_id) { case Expression::Id::CallId: @@ -521,6 +534,20 @@ struct CFGWalker : public PostWalker { self->pushTask(SubType::doEndThrow, currp); break; } + case Expression::Id::ResumeId: + case Expression::Id::ResumeThrowId: { + self->pushTask(SubType::doEndResume, currp); + break; + } + case Expression::Id::SuspendId: + case Expression::Id::StackSwitchId: { + auto* module = self->getModule(); + if (!module || module->features.hasExceptionHandling()) { + // This might throw, so run the code to handle that. + self->pushTask(SubType::doEndCall, currp); + } + break; + } default: { if (Properties::isBranch(curr)) { self->pushTask(SubType::doEndBranch, currp); diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 42b13919726..df41546ed14 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -183,8 +183,18 @@ void ReFinalize::visitStringSliceWTF(StringSliceWTF* curr) { curr->finalize(); } void ReFinalize::visitContNew(ContNew* curr) { curr->finalize(); } void ReFinalize::visitContBind(ContBind* curr) { curr->finalize(); } void ReFinalize::visitSuspend(Suspend* curr) { curr->finalize(getModule()); } -void ReFinalize::visitResume(Resume* curr) { curr->finalize(); } -void ReFinalize::visitResumeThrow(ResumeThrow* curr) { curr->finalize(); } +void ReFinalize::visitResume(Resume* curr) { + curr->finalize(); + for (size_t i = 0; i < curr->handlerBlocks.size(); i++) { + updateBreakValueType(curr->handlerBlocks[i], curr->sentTypes[i]); + } +} +void ReFinalize::visitResumeThrow(ResumeThrow* curr) { + curr->finalize(); + for (size_t i = 0; i < curr->handlerBlocks.size(); i++) { + updateBreakValueType(curr->handlerBlocks[i], curr->sentTypes[i]); + } +} void ReFinalize::visitStackSwitch(StackSwitch* curr) { curr->finalize(); } void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); } diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index 1771f2e0e88..b76c601be3b 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -83,15 +83,15 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) { } } } else if (auto* r = expr->dynCast()) { - for (Index i = 0; i < r->handlerTags.size(); i++) { - auto dest = r->handlerTags[i]; + for (Index i = 0; i < r->handlerBlocks.size(); i++) { + auto dest = r->handlerBlocks[i]; if (!dest.isNull() && dest == name) { func(name, r->sentTypes[i]); } } } else if (auto* r = expr->dynCast()) { - for (Index i = 0; i < r->handlerTags.size(); i++) { - auto dest = r->handlerTags[i]; + for (Index i = 0; i < r->handlerBlocks.size(); i++) { + auto dest = r->handlerBlocks[i]; if (!dest.isNull() && dest == name) { func(name, r->sentTypes[i]); } diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index c69250043b8..5c654ceb7be 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -126,7 +126,8 @@ struct SubTypes { basic = HeapTypes::array.getBasic(share); break; case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + basic = HeapTypes::cont.getBasic(share); + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 6dd91e0960e..6ea5b0d87cd 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -150,8 +150,12 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( typeBuilder[i] = newArray; break; } - case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Cont: { + auto newCont = HeapType(typeBuilder[i]).getContinuation(); + modifyContinuation(type, newCont); + typeBuilder[i] = newCont; + break; + } case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h index fe1cd2806aa..5e55c460880 100644 --- a/src/ir/type-updating.h +++ b/src/ir/type-updating.h @@ -382,6 +382,7 @@ class GlobalTypeRewriter { // used to define the new type in the TypeBuilder. virtual void modifyStruct(HeapType oldType, Struct& struct_) {} virtual void modifyArray(HeapType oldType, Array& array) {} + virtual void modifyContinuation(HeapType oldType, Continuation& sig) {} virtual void modifySignature(HeapType oldType, Signature& sig) {} // This additional hook is called after modify* and other operations, and @@ -490,16 +491,19 @@ class TypeMapper : public GlobalTypeRewriter { mapTypes(newMapping); } + HeapType getNewHeapType(HeapType type) { + auto iter = mapping.find(type); + if (iter != mapping.end()) { + return iter->second; + } + return type; + } + Type getNewType(Type type) { if (!type.isRef()) { return type; } - auto heapType = type.getHeapType(); - auto iter = mapping.find(heapType); - if (iter != mapping.end()) { - return getTempType(Type(iter->second, type.getNullability())); - } - return getTempType(type); + return getTempType(type.with(getNewHeapType(type.getHeapType()))); } void modifyStruct(HeapType oldType, Struct& struct_) override { @@ -513,6 +517,10 @@ class TypeMapper : public GlobalTypeRewriter { void modifyArray(HeapType oldType, Array& array) override { array.element.type = getNewType(oldType.getArray().element.type); } + void modifyContinuation(HeapType oldType, + Continuation& continuation) override { + continuation.type = getNewHeapType(oldType.getContinuation().type); + } void modifySignature(HeapType oldSignatureType, Signature& sig) override { auto getUpdatedTypeList = [&](Type type) { std::vector vec; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 479d246b2d6..1215c8eef6b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2617,15 +2617,29 @@ class ConstantExpressionRunner : public ExpressionRunner { } return ExpressionRunner::visitRefAs(curr); } - Flow visitContNew(ContNew* curr) { WASM_UNREACHABLE("unimplemented"); } - Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("unimplemented"); } - Flow visitSuspend(Suspend* curr) { WASM_UNREACHABLE("unimplemented"); } - Flow visitResume(Resume* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitContNew(ContNew* curr) { + NOTE_ENTER("ContNew"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitContBind(ContBind* curr) { + NOTE_ENTER("ContBind"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitSuspend(Suspend* curr) { + NOTE_ENTER("Suspend"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitResume(Resume* curr) { + NOTE_ENTER("Resume"); + return Flow(NONCONSTANT_FLOW); + } Flow visitResumeThrow(ResumeThrow* curr) { - WASM_UNREACHABLE("unimplemented"); + NOTE_ENTER("ResumeThrow"); + return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { - WASM_UNREACHABLE("unimplemented"); + NOTE_ENTER("StackSwitch"); + return Flow(NONCONSTANT_FLOW); } void trap(const char* why) override { throw NonconstantException(); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ad84449be61..b85de54f140 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1443,8 +1443,12 @@ void StackSwitch::finalize() { } assert(this->cont->type.isContinuation()); - type = + Type params = this->cont->type.getHeapType().getContinuation().type.getSignature().params; + assert(params.size() > 0); + Type cont = params[params.size() - 1]; + assert(cont.isContinuation()); + type = cont.getHeapType().getContinuation().type.getSignature().params; } size_t Function::getNumParams() { return getParams().size(); } diff --git a/test/lit/basic/stack_switching_switch_2.wast b/test/lit/basic/stack_switching_switch_2.wast new file mode 100644 index 00000000000..2e7eb91fb35 --- /dev/null +++ b/test/lit/basic/stack_switching_switch_2.wast @@ -0,0 +1,29 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt -all %s -S -o - | filecheck %s + + +(module + ;; CHECK: (type $function (func (param i64))) + (type $function (func (param i64))) + ;; CHECK: (type $cont (cont $function)) + (type $cont (cont $function)) + ;; CHECK: (type $function_2 (func (param i32 (ref $cont)))) + (type $function_2 (func (param i32 (ref $cont)))) + ;; CHECK: (type $cont_2 (cont $function_2)) + (type $cont_2 (cont $function_2)) + ;; CHECK: (tag $tag (type $4)) + (tag $tag) + + ;; CHECK: (func $switch (type $5) (param $c (ref $cont_2)) (result i64) + ;; CHECK-NEXT: (switch $cont_2 $tag + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch (param $c (ref $cont_2)) (result i64) + (switch $cont_2 $tag + (i32.const 0) + (local.get $c) + ) + ) +) diff --git a/test/lit/passes/O3_stack-switching.wast b/test/lit/passes/O3_stack-switching.wast new file mode 100644 index 00000000000..d928f6614b2 --- /dev/null +++ b/test/lit/passes/O3_stack-switching.wast @@ -0,0 +1,167 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt -all -O3 %s -S -o - | filecheck %s + +;; Fairly comprehensive test case + +(module + ;; CHECK: (type $function_1 (func (param (ref eq) (ref eq)) (result (ref eq)))) + (type $function_1 (func (param (ref eq) (ref eq)) (result (ref eq)))) + ;; CHECK: (type $closure (sub (struct (field (ref $function_1))))) + (type $closure (sub (struct (field (ref $function_1))))) + ;; CHECK: (type $cont (cont $function_1)) + + ;; CHECK: (type $function_2 (func (param (ref eq) (ref eq) (ref eq)) (result (ref eq)))) + (type $function_2 (func (param (ref eq) (ref eq) (ref eq)) (result (ref eq)))) + ;; CHECK: (type $closure_2 (struct (field (ref $function_2)))) + (type $closure_2 (struct (field (ref $function_2)))) + ;; CHECK: (type $handlers (struct (field $value (ref $closure)) (field $exn (ref $closure)) (field $effect (ref $closure_2)))) + (type $handlers (struct (field $value (ref $closure)) (field $exn (ref $closure)) (field $effect (ref $closure_2)))) + (type $cont (cont $function_1)) + ;; CHECK: (type $fiber (struct (field $handlers (ref $handlers)) (field $cont (ref $cont)))) + (type $fiber (struct (field $handlers (ref $handlers)) (field $cont (ref $cont)))) + ;; CHECK: (tag $exception (type $8) (param (ref eq))) + (tag $exception (param (ref eq))) + ;; CHECK: (tag $effect (type $9) (param (ref eq)) (result (ref eq) (ref eq))) + (tag $effect (param (ref eq)) (result (ref eq) (ref eq))) + + ;; CHECK: (func $resume (type $10) (param $0 (ref $fiber)) (param $1 (ref $closure)) (param $2 (ref eq)) (result (ref eq)) + ;; CHECK-NEXT: (local $3 (tuple (ref eq) (ref $cont))) + ;; CHECK-NEXT: (local $4 (ref $handlers)) + ;; CHECK-NEXT: (local $5 (ref $closure_2)) + ;; CHECK-NEXT: (return_call_ref $function_1 + ;; CHECK-NEXT: (block $handle_exception (result (ref eq)) + ;; CHECK-NEXT: (return_call_ref $function_2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $3 + ;; CHECK-NEXT: (block $handle_effect (type $7) (result (ref eq) (ref $cont)) + ;; CHECK-NEXT: (return_call_ref $function_1 + ;; CHECK-NEXT: (try_table (result (ref eq)) (catch $exception $handle_exception) + ;; CHECK-NEXT: (resume $cont (on $effect $handle_effect) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (struct.get $fiber $cont + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (struct.get $handlers $value + ;; CHECK-NEXT: (struct.get $fiber $handlers + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $closure 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $fiber + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $fiber $handlers + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $5 + ;; CHECK-NEXT: (struct.get $handlers $effect + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $closure_2 0 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (struct.get $handlers $exn + ;; CHECK-NEXT: (struct.get $fiber $handlers + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $closure 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume (export "resume") (param $fiber (ref $fiber)) (param $f (ref $closure)) (param $v (ref eq)) (result (ref eq)) + (local $g (ref $closure_2)) + (local $res (ref eq)) + (local $exn (ref eq)) + (local $resume_res (tuple (ref eq) (ref $cont))) + (local.set $exn + (block $handle_exception (result (ref eq)) + (local.set $resume_res + (block $handle_effect (result (ref eq) (ref $cont)) + (local.set $res + (try_table (result (ref eq)) (catch $exception $handle_exception) + (resume $cont (on $effect $handle_effect) + (local.get $f) + (local.get $v) + (struct.get $fiber $cont + (local.get $fiber) + ) + ) + ) + ) + (return_call_ref $function_1 + (local.get $res) + (local.tee $f + (struct.get $handlers $value + (struct.get $fiber $handlers + (local.get $fiber) + ) + ) + ) + (struct.get $closure 0 + (local.get $f) + ) + ) + ) + ) + (return_call_ref $function_2 + (tuple.extract 2 0 + (local.get $resume_res) + ) + (struct.new $fiber + (struct.get $fiber $handlers + (local.get $fiber) + ) + (tuple.extract 2 1 + (local.get $resume_res) + ) + ) + (local.tee $g + (struct.get $handlers $effect + (struct.get $fiber $handlers + (local.get $fiber) + ) + ) + ) + (struct.get $closure_2 0 + (local.get $g) + ) + ) + ) + ) + (return_call_ref $function_1 + (local.get $exn) + (local.tee $f + (struct.get $handlers $exn + (struct.get $fiber $handlers + (local.get $fiber) + ) + ) + ) + (struct.get $closure 0 + (local.get $f) + ) + ) + ) +) diff --git a/test/lit/passes/coalesce-locals-stack-switching.wast b/test/lit/passes/coalesce-locals-stack-switching.wast new file mode 100644 index 00000000000..c0733c9c087 --- /dev/null +++ b/test/lit/passes/coalesce-locals-stack-switching.wast @@ -0,0 +1,198 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt -all --coalesce-locals %s -S -o - | filecheck %s + +(module + ;; CHECK: (type $function (func)) + (type $function (func)) + ;; CHECK: (type $cont (cont $function)) + (type $cont (cont $function)) + ;; CHECK: (type $function_2 (func (param (ref $cont)))) + (type $function_2 (func (param (ref $cont)))) + ;; CHECK: (type $cont_2 (cont $function_2)) + (type $cont_2 (cont $function_2)) + + ;; CHECK: (tag $tag (type $function)) + (tag $tag) + ;; CHECK: (tag $exn (type $5) (param i32)) + (tag $exn (param i32)) + + ;; CHECK: (func $resume (type $2) (param $0 (ref $cont)) (result i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block $handle_exn (result i32) + ;; CHECK-NEXT: (try_table (catch $exn $handle_exn) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (block $handle_effect (result (ref $cont)) + ;; CHECK-NEXT: (resume $cont (on $tag $handle_effect) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return_call $resume + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $resume (param $c (ref $cont)) (result i32) + (local $x i32) + (local $y (ref $cont)) + ;; resume can raise an exception, so this local.set should not be + ;; optimized away + (local.set $x + (block $handle_exn (result i32) + (try_table (catch $exn $handle_exn) + ;; this local.set is reachable, so it should not be optimized away + (local.set $y + (block $handle_effect (result (ref $cont)) + (resume $cont (on $tag $handle_effect) + (local.get $c) + ) + (return + (i32.const 0) + ) + ) + ) + (return_call $resume + (local.get $y) + ) + ) + (return + (i32.const 0) + ) + ) + ) + (local.get $x) + ) + + ;; CHECK: (func $resume_throw (type $2) (param $0 (ref $cont)) (result i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block $handle_exn (result i32) + ;; CHECK-NEXT: (try_table (catch $exn $handle_exn) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (block $handle_effect (result (ref $cont)) + ;; CHECK-NEXT: (resume_throw $cont $tag (on $tag $handle_effect) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return_call $resume + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $resume_throw (param $c (ref $cont)) (result i32) + (local $x i32) + (local $y (ref $cont)) + ;; resume can raise an exception, so this local.set should not be + ;; optimized away + (local.set $x + (block $handle_exn (result i32) + (try_table (catch $exn $handle_exn) + ;; this local.set is reachable, so it should not be optimized away + (local.set $y + (block $handle_effect (result (ref $cont)) + (resume_throw $cont $tag (on $tag $handle_effect) + (local.get $c) + ) + (return + (i32.const 0) + ) + ) + ) + (return_call $resume + (local.get $y) + ) + ) + (return + (i32.const 0) + ) + ) + ) + (local.get $x) + ) + + ;; CHECK: (func $suspend (type $6) (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (block $handle_exn (result i32) + ;; CHECK-NEXT: (try_table (catch $exn $handle_exn) + ;; CHECK-NEXT: (suspend $tag) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $suspend (result i32) + (local $x i32) + ;; suspend can raise an exception, so this local.set should not be + ;; optimized away + (local.set $x + (block $handle_exn (result i32) + (try_table (catch $exn $handle_exn) + (suspend $tag) + ) + (return + (i32.const 0) + ) + ) + ) + (local.get $x) + ) + + ;; CHECK: (func $switch (type $7) (param $0 (ref $cont_2)) (result i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block $handle_exn (result i32) + ;; CHECK-NEXT: (try_table (catch $exn $handle_exn) + ;; CHECK-NEXT: (switch $cont_2 $tag + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $switch (param $c (ref $cont_2)) (result i32) + (local $x i32) + ;; switch can raise an exception, so this local.set should not be + ;; optimized away + (local.set $x + (block $handle_exn (result i32) + (try_table (catch $exn $handle_exn) + (switch $cont_2 $tag + (local.get $c) + ) + ) + (return + (i32.const 0) + ) + ) + ) + (local.get $x) + ) +) diff --git a/test/lit/passes/dce-stack-switching.wast b/test/lit/passes/dce-stack-switching.wast new file mode 100644 index 00000000000..763184a9278 --- /dev/null +++ b/test/lit/passes/dce-stack-switching.wast @@ -0,0 +1,65 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt -all --dce %s -S -o - | filecheck %s + +(module + ;; CHECK: (type $function (func)) + (type $function (func)) + ;; CHECK: (type $cont (cont $function)) + (type $cont (cont $function)) + ;; CHECK: (tag $tag (type $function)) + (tag $tag) + + ;; CHECK: (func $resume (type $2) (param $c (ref $cont)) + ;; CHECK-NEXT: (block $exit + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $handle_effect (result (ref $cont)) + ;; CHECK-NEXT: (resume $cont (on $tag $handle_effect) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $exit) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume (param $c (ref $cont)) + (block $exit + (drop + ;; The return type of the block should not be dropped since we can + ;; jump there when handling $tag. + (block $handle_effect (result (ref $cont)) + (resume $cont (on $tag $handle_effect) + (local.get $c) + ) + (br $exit) + ) + ) + ) + ) + + ;; CHECK: (func $resume_throw (type $2) (param $c (ref $cont)) + ;; CHECK-NEXT: (block $exit + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $handle_effect (result (ref $cont)) + ;; CHECK-NEXT: (resume_throw $cont $tag (on $tag $handle_effect) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $exit) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume_throw (param $c (ref $cont)) + (block $exit + (drop + ;; The return type of the block should not be dropped since we can + ;; jump there when handling $tag. + (block $handle_effect (result (ref $cont)) + (resume_throw $cont $tag (on $tag $handle_effect) + (local.get $c) + ) + (br $exit) + ) + ) + ) + ) +) diff --git a/test/lit/passes/precompute-stack-switching.wast b/test/lit/passes/precompute-stack-switching.wast new file mode 100644 index 00000000000..0677fee0ccb --- /dev/null +++ b/test/lit/passes/precompute-stack-switching.wast @@ -0,0 +1,95 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt -all --precompute %s -S -o - | filecheck %s + +(module + ;; CHECK: (type $function (func)) + (type $function (func)) + ;; CHECK: (type $cont (cont $function)) + (type $cont (cont $function)) + ;; CHECK: (type $function_2 (func (param i32))) + (type $function_2 (func (param i32))) + ;; CHECK: (type $cont_2 (cont $function_2)) + (type $cont_2 (cont $function_2)) + ;; CHECK: (type $function_3 (func (param (ref $cont)))) + + ;; CHECK: (type $cont_3 (cont $function_3)) + + ;; CHECK: (tag $tag (type $function)) + (tag $tag) + (type $function_3 (func (param (ref $cont)))) + (type $cont_3 (cont $function_3)) + + ;; CHECK: (func $cont_new (type $7) (param $f (ref $function)) (result (ref $cont)) + ;; CHECK-NEXT: (cont.new $cont + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont_new (param $f (ref $function)) (result (ref $cont)) + (cont.new $cont (local.get $f)) + ) + + ;; CHECK: (func $cont_bind (type $8) (param $c (ref $cont_2)) (result (ref $cont)) + ;; CHECK-NEXT: (cont.bind $cont_2 $cont + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont_bind (param $c (ref $cont_2)) (result (ref $cont)) + (cont.bind $cont_2 $cont + (i32.const 0) + (local.get $c) + ) + ) + + ;; CHECK: (func $suspend (type $function) + ;; CHECK-NEXT: (suspend $tag) + ;; CHECK-NEXT: ) + (func $suspend + (suspend $tag) + ) + + ;; CHECK: (func $resume (type $2) (param $c (ref $cont)) (result (ref $cont)) + ;; CHECK-NEXT: (block $handle_effect (result (ref $cont)) + ;; CHECK-NEXT: (resume $cont (on $tag $handle_effect) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume (param $c (ref $cont)) (result (ref $cont)) + (block $handle_effect (result (ref $cont)) + (resume $cont (on $tag $handle_effect) + (local.get $c) + ) + (local.get $c) + ) + ) + + ;; CHECK: (func $resume_throw (type $2) (param $c (ref $cont)) (result (ref $cont)) + ;; CHECK-NEXT: (block $handle_effect (result (ref $cont)) + ;; CHECK-NEXT: (resume_throw $cont $tag (on $tag $handle_effect) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume_throw (param $c (ref $cont)) (result (ref $cont)) + (block $handle_effect (result (ref $cont)) + (resume_throw $cont $tag (on $tag $handle_effect) + (local.get $c) + ) + (local.get $c) + ) + ) + + ;; CHECK: (func $switch (type $9) (param $c (ref $cont_3)) + ;; CHECK-NEXT: (switch $cont_3 $tag + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch (param $c (ref $cont_3)) + (switch $cont_3 $tag + (local.get $c) + ) + ) +) diff --git a/test/lit/passes/vacuum-stack-switching.wast b/test/lit/passes/vacuum-stack-switching.wast new file mode 100644 index 00000000000..18250baddaa --- /dev/null +++ b/test/lit/passes/vacuum-stack-switching.wast @@ -0,0 +1,65 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt -all --vacuum %s -S -o - | filecheck %s + +(module + ;; CHECK: (type $function (func)) + (type $function (func)) + ;; CHECK: (type $cont (cont $function)) + (type $cont (cont $function)) + ;; CHECK: (tag $tag (type $function)) + (tag $tag) + + ;; CHECK: (func $resume (type $2) (param $c (ref $cont)) + ;; CHECK-NEXT: (block $exit + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $handle_effect (result (ref $cont)) + ;; CHECK-NEXT: (resume $cont (on $tag $handle_effect) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $exit) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume (param $c (ref $cont)) + (block $exit + (drop + ;; The return type of the block should not be dropped since we can + ;; jump there when handling $tag. + (block $handle_effect (result (ref $cont)) + (resume $cont (on $tag $handle_effect) + (local.get $c) + ) + (br $exit) + ) + ) + ) + ) + + ;; CHECK: (func $resume_throw (type $2) (param $c (ref $cont)) + ;; CHECK-NEXT: (block $exit + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $handle_effect (result (ref $cont)) + ;; CHECK-NEXT: (resume_throw $cont $tag (on $tag $handle_effect) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $exit) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume_throw (param $c (ref $cont)) + (block $exit + (drop + ;; The return type of the block should not be dropped since we can + ;; jump there when handling $tag. + (block $handle_effect (result (ref $cont)) + (resume_throw $cont $tag (on $tag $handle_effect) + (local.get $c) + ) + (br $exit) + ) + ) + ) + ) +) From 85fc8bbb017c87654bf382b566cb79b4ba923987 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Apr 2025 14:10:58 -0700 Subject: [PATCH 391/622] Fix typo in fuzz skipping of a test (#7430) --- scripts/test/fuzzing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index bceb6ae1c55..50ab56683fa 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -110,7 +110,7 @@ 'coalesce-locals-stack-switching.wast', 'dce-stack-switching.wast', 'precompute-stack-switching.wast', - 'vacuum-stack-switching.wast' + 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] From 570de7968d9866b24f3d64a2a63d6cb57a43c959 Mon Sep 17 00:00:00 2001 From: Gulg <65360617+GulgDev@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:41:41 +0500 Subject: [PATCH 392/622] [C/JS APIs] Allow JS and C to read the start function of a module (#7424) This PR adds `BinaryenGetStart` to the C api and the corresponding `module.getStart` JS wrapper. --- src/binaryen-c.cpp | 4 ++++ src/binaryen-c.h | 2 ++ src/js/binaryen.js-post.js | 3 +++ test/binaryen.js/kitchen-sink.js | 1 + test/example/c-api-kitchen-sink.c | 1 + 5 files changed, 11 insertions(+) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 82261c0dc63..8e8c295f2ca 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -5289,6 +5289,10 @@ void BinaryenSetStart(BinaryenModuleRef module, BinaryenFunctionRef start) { ((Module*)module)->addStart(((Function*)start)->name); } +BinaryenFunctionRef BinaryenGetStart(BinaryenModuleRef module) { + return ((Module*)module)->getFunctionOrNull(((Module*)module)->start); +} + // Features BinaryenFeatures BinaryenModuleGetFeatures(BinaryenModuleRef module) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 06604a510a8..65a2f12f451 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -2869,6 +2869,8 @@ BINARYEN_API void BinaryenAddDataSegment(BinaryenModuleRef module, BINARYEN_API void BinaryenSetStart(BinaryenModuleRef module, BinaryenFunctionRef start); +BINARYEN_API BinaryenFunctionRef BinaryenGetStart(BinaryenModuleRef module); + // Features // These control what features are allowed when validation and in passes. diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 5d6022d2550..6d886d26b1d 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -2594,6 +2594,9 @@ function wrapModule(module, self = {}) { self['setStart'] = function(start) { return Module['_BinaryenSetStart'](module, start); }; + self['getStart'] = function() { + return Module['_BinaryenGetStart'](module); + }; self['getFeatures'] = function() { return Module['_BinaryenModuleGetFeatures'](module); }; diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index 04cf4ef8e4a..556749059b7 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -760,6 +760,7 @@ function test_core() { // Start function. One per module var starter = module.addFunction("starter", binaryen.none, binaryen.none, [], module.nop()); module.setStart(starter); + assert(module.getStart() == starter); var features = binaryen.Features.All; module.setFeatures(features); diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index fcf6c78d1fb..c97de45a366 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -1376,6 +1376,7 @@ void test_core() { 0, BinaryenNop(module)); BinaryenSetStart(module, starter); + assert(BinaryenGetStart(module) == starter); BinaryenFeatures features = BinaryenFeatureAll(); BinaryenModuleSetFeatures(module, features); From 862aeb9ecce074915945a4329537341966b80a09 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 2 Apr 2025 16:55:09 -0700 Subject: [PATCH 393/622] Parse and emit exact heap types (#7432) Implement text and binary parsing for exact heap types as well as binary emitting. --- scripts/test/fuzzing.py | 2 ++ src/parser/contexts.h | 4 +++ src/parser/parsers.h | 10 ++++++++ src/wasm-binary.h | 2 ++ src/wasm/wasm-binary.cpp | 41 +++++++++++++++++++++--------- src/wasm/wasm-type.cpp | 4 +-- test/lit/basic/exact.wast | 52 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 test/lit/basic/exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 50ab56683fa..28b03294939 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -113,6 +113,8 @@ 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', + # TODO: fuzzer support for exact heap types + 'exact.wast', ] diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 4c56780323b..030d404adfe 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -167,6 +167,8 @@ struct NullTypeParserCtx { Result getTypeIndex(Name) { return 1; } Result getHeapTypeFromIdx(Index) { return Ok{}; } + HeapTypeT makeExact(HeapTypeT) { return Ok{}; } + DataStringT makeDataString() { return Ok{}; } void appendDataString(DataStringT&, std::string_view) {} @@ -251,6 +253,8 @@ template struct TypeParserCtx { return HeapTypes::nocont.getBasic(share); } + HeapTypeT makeExact(HeapTypeT type) { return type.with(Exact); } + TypeT makeI32() { return Type::i32; } TypeT makeI64() { return Type::i64; } TypeT makeF32() { return Type::f32; } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 33e9d20fdc9..2e2a6da7e39 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -434,6 +434,7 @@ Result absheaptype(Ctx& ctx, Shareability share) { } // heaptype ::= x:typeidx => types[x] +// | '(' 'exact' x:typeidx ')' => exact types[x] // | t:absheaptype => unshared t // | '(' 'shared' t:absheaptype ')' => shared t template Result heaptype(Ctx& ctx) { @@ -442,6 +443,15 @@ template Result heaptype(Ctx& ctx) { return *t; } + if (ctx.in.takeSExprStart("exact"sv)) { + auto t = typeidx(ctx); + CHECK_ERR(t); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of exact heap type"); + } + return ctx.makeExact(*t); + } + auto share = ctx.in.takeSExprStart("shared"sv) ? Shared : Unshared; auto t = absheaptype(ctx, share); CHECK_ERR(t); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index c20e8e5ab0e..fda3f1e741f 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -343,6 +343,8 @@ enum EncodedType { SubFinal = 0x4f, Shared = 0x65, SharedLEB = -0x1b, // Also 0x65 as an SLEB128 + Exact = 0x62, + ExactLEB = -0x1e, // Also 0x62 as an SLEB128 Rec = 0x4e, Descriptor = 0x4d, Describes = 0x4c, diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index c18416a727f..53b736fcf4c 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -719,7 +719,7 @@ uint32_t WasmBinaryWriter::getElementSegmentIndex(Name name) const { } uint32_t WasmBinaryWriter::getTypeIndex(HeapType type) const { - auto it = indexedTypes.indices.find(type); + auto it = indexedTypes.indices.find(type.with(Inexact)); #ifndef NDEBUG if (it == indexedTypes.indices.end()) { std::cout << "Missing type: " << type << '\n'; @@ -1668,8 +1668,10 @@ void WasmBinaryWriter::writeHeapType(HeapType type) { if (!wasm->features.hasGC()) { type = type.getTop(); } - if (!type.isBasic()) { + if (type.isExact()) { + o << uint8_t(BinaryConsts::EncodedType::Exact); + } o << S64LEB(getTypeIndex(type)); // TODO: Actually s33 return; } @@ -2183,12 +2185,20 @@ Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { auto type = getS64LEB(); // TODO: Actually s33 + auto exactness = Inexact; + if (type == BinaryConsts::EncodedType::ExactLEB) { + exactness = Exact; + type = getS64LEB(); // TODO: Actually s33 + } // Single heap types are negative; heap type indices are non-negative if (type >= 0) { if (size_t(type) >= types.size()) { - throwError("invalid signature index: " + std::to_string(type)); + throwError("invalid type index: " + std::to_string(type)); } - return types[type]; + return types[type].with(exactness); + } + if (exactness == Exact) { + throwError("invalid type index: " + std::to_string(type)); } auto share = Unshared; if (type == BinaryConsts::EncodedType::SharedLEB) { @@ -2198,10 +2208,8 @@ HeapType WasmBinaryReader::getHeapType() { HeapType ht; if (getBasicHeapType(type, ht)) { return ht.getBasic(share); - } else { - throwError("invalid wasm heap type: " + std::to_string(type)); } - WASM_UNREACHABLE("unexpected type"); + throwError("invalid wasm heap type: " + std::to_string(type)); } HeapType WasmBinaryReader::getIndexedHeapType() { @@ -2325,6 +2333,20 @@ void WasmBinaryReader::readTypes() { auto readHeapType = [&]() -> HeapType { int64_t htCode = getS64LEB(); // TODO: Actually s33 + auto exactness = Inexact; + if (htCode == BinaryConsts::EncodedType::ExactLEB) { + exactness = Exact; + htCode = getS64LEB(); // TODO: Actually s33 + } + if (htCode >= 0) { + if (size_t(htCode) >= builder.size()) { + throwError("invalid type index: " + std::to_string(htCode)); + } + return builder.getTempHeapType(size_t(htCode)).with(exactness); + } + if (exactness == Exact) { + throwError("invalid type index: " + std::to_string(htCode)); + } auto share = Unshared; if (htCode == BinaryConsts::EncodedType::SharedLEB) { share = Shared; @@ -2334,10 +2356,7 @@ void WasmBinaryReader::readTypes() { if (getBasicHeapType(htCode, ht)) { return ht.getBasic(share); } - if (size_t(htCode) >= builder.size()) { - throwError("invalid type index: " + std::to_string(htCode)); - } - return builder.getTempHeapType(size_t(htCode)); + throwError("invalid wasm heap type: " + std::to_string(htCode)); }; auto makeType = [&](int32_t typeCode) { Type type; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index d198919882d..6fc43c2e091 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1621,9 +1621,9 @@ void TypePrinter::printHeapTypeName(HeapType type) { if (type.isBasic()) { print(type); } else { - generator(type).name.print(os); + generator(type.with(Inexact)).name.print(os); #if TRACE_CANONICALIZATION - os << "(;" << ((type.getID() >> 4) % 1000) << ";) "; + os << "(;" << ((type.with(Inexact).getID() >> 4) % 1000) << ";) "; #endif } if (type.isExact()) { diff --git a/test/lit/basic/exact.wast b/test/lit/basic/exact.wast new file mode 100644 index 00000000000..13adca540ee --- /dev/null +++ b/test/lit/basic/exact.wast @@ -0,0 +1,52 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + (rec + ;; CHECK-TEXT: (rec + ;; CHECK-TEXT-NEXT: (type $a (struct (field (ref null (exact $a))) (field (ref (exact $b))))) + ;; CHECK-BIN: (rec + ;; CHECK-BIN-NEXT: (type $a (struct (field (ref null (exact $a))) (field (ref (exact $b))))) + (type $a (struct (field (ref null (exact 0)) (ref (exact 1))))) + ;; CHECK-TEXT: (type $b (struct (field (ref (exact $a))) (field (ref null (exact $b))))) + ;; CHECK-BIN: (type $b (struct (field (ref (exact $a))) (field (ref null (exact $b))))) + (type $b (struct (field (ref (exact $a)) (ref null (exact $b))))) + ) + + ;; CHECK-TEXT: (type $2 (func (param (ref null (exact $a)) (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))))) + + ;; CHECK-TEXT: (func $foo (type $2) (param $0 (ref null (exact $a))) (param $1 (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))) + ;; CHECK-TEXT-NEXT: (local $2 (ref null (exact $a))) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (type $2 (func (param (ref null (exact $a)) (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))))) + + ;; CHECK-BIN: (func $foo (type $2) (param $0 (ref null (exact $a))) (param $1 (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))) + ;; CHECK-BIN-NEXT: (local $2 (ref null (exact $a))) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $foo (param (ref null (exact $a)) (ref (exact $b))) + (result (ref (exact $a)) (ref null (exact $b))) + (local (ref null (exact $a))) + (unreachable) + ) +) +;; CHECK-BIN-NODEBUG: (rec +;; CHECK-BIN-NODEBUG-NEXT: (type $0 (struct (field (ref null (exact $0))) (field (ref (exact $1))))) + +;; CHECK-BIN-NODEBUG: (type $1 (struct (field (ref (exact $0))) (field (ref null (exact $1))))) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref null (exact $0)) (ref (exact $1))) (result (ref (exact $0)) (ref null (exact $1))))) + +;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (ref null (exact $0))) (param $1 (ref (exact $1))) (result (ref (exact $0)) (ref null (exact $1))) +;; CHECK-BIN-NODEBUG-NEXT: (local $2 (ref null (exact $0))) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) From 01e0eea42fc8429206f0264e6ec4c51fe015b62b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Apr 2025 09:30:20 -0700 Subject: [PATCH 394/622] [GC] Add a TypeRefiningGUFA pass (#7433) This variation of TypeRefining uses GUFA to determine what types to refine struct fields to. GUFA does a (slow) whole-program analysis which can infer things the normal pass cannot, e.g. refinements that contain cycles through things like locals or globals. This is mainly a proof of concept, as it is pretty slow to compute GUFA just for this, and while I see improvements on real-world code, they are minor. If we find that the benefits here are worth it, a larger refactoring could do this optimization in the existing GUFA pass (which already does the computation of the graph anyhow). --- scripts/fuzz_opt.py | 2 + src/passes/TypeRefining.cpp | 59 ++++- src/passes/pass.cpp | 4 + src/passes/passes.h | 1 + test/lit/help/wasm-metadce.test | 4 + test/lit/help/wasm-opt.test | 4 + test/lit/help/wasm2js.test | 4 + test/lit/passes/type-refining-gufa.wast | 298 ++++++++++++++++++++++++ 8 files changed, 374 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/type-refining-gufa.wast diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index e77b755e2e1..1a56e9c5ae7 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2005,6 +2005,7 @@ def write_commands(commands, filename): ("--tuple-optimization",), ("--type-finalizing",), ("--type-refining",), + ("--type-refining-gufa",), ("--type-merging",), ("--type-ssa",), ("--type-unfinalizing",), @@ -2014,6 +2015,7 @@ def write_commands(commands, filename): # TODO: Fix these passes so that they still work without --closed-world! requires_closed_world = {("--type-refining",), + ("--type-refining-gufa",), ("--signature-pruning",), ("--signature-refining",), ("--gto",), diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 401275fcba1..585abefb948 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -18,8 +18,15 @@ // Apply more specific subtypes to type fields where possible, where all the // writes to that field in the entire program allow doing so. // +// TODO: handle arrays and not just structs. +// +// The GUFA variant of this uses GUFA to infer types, which performs a (slow) +// whole-program inference, rather than just scan struct/array operations by +// themselves. +// #include "ir/lubs.h" +#include "ir/possible-contents.h" #include "ir/struct-utils.h" #include "ir/type-updating.h" #include "ir/utils.h" @@ -104,8 +111,16 @@ struct TypeRefining : public Pass { // Only affects GC type declarations and struct.gets. bool requiresNonNullableLocalFixups() override { return false; } + bool gufa; + + TypeRefining(bool gufa) : gufa(gufa) {} + + // The final information we inferred about struct usage, that we then use to + // optimize. StructUtils::StructValuesMap finalInfos; + using Propagator = StructUtils::TypeHierarchyPropagator; + void run(Module* module) override { if (!module->features.hasGC()) { return; @@ -115,6 +130,20 @@ struct TypeRefining : public Pass { Fatal() << "TypeRefining requires --closed-world"; } + Propagator propagator(*module); + + // Compute our main data structure, finalInfos, either normally or using + // GUFA. + if (!gufa) { + computeFinalInfos(module, propagator); + } else { + computeFinalInfosGUFA(module, propagator); + } + + useFinalInfos(module, propagator); + } + + void computeFinalInfos(Module* module, Propagator& propagator) { // Find and analyze struct operations inside each function. StructUtils::FunctionStructValuesMap functionNewInfos(*module), functionSetGetInfos(*module); @@ -132,14 +161,39 @@ struct TypeRefining : public Pass { // able to contain that type. Propagate things written using set to subtypes // as well, as the reference might be to a supertype if the field is present // there. - StructUtils::TypeHierarchyPropagator propagator(*module); propagator.propagateToSuperTypes(combinedNewInfos); propagator.propagateToSuperAndSubTypes(combinedSetGetInfos); // Combine everything together. combinedNewInfos.combineInto(finalInfos); combinedSetGetInfos.combineInto(finalInfos); + } + + void computeFinalInfosGUFA(Module* module, Propagator& propagator) { + // Compute the oracle, then simply apply it. + // TODO: Consider doing this in GUFA.cpp, where we already computed the + // oracle. That would require refactoring out the rest of this pass to + // a shared location. Alternatively, perhaps we can reuse the computed + // oracle, but any pass that changes anything would need to invalidate + // it... + ContentOracle oracle(*module, getPassOptions()); + auto allTypes = ModuleUtils::collectHeapTypes(*module); + for (auto type : allTypes) { + if (type.isStruct()) { + auto& fields = type.getStruct().fields; + auto& infos = finalInfos[type]; + for (Index i = 0; i < fields.size(); i++) { + auto gufaType = oracle.getContents(DataLocation{type, i}).getType(); + infos[i] = LUBFinder(gufaType); + } + } + } + + // Propagate to supertypes, so no field is less refined than its super. + propagator.propagateToSuperTypes(finalInfos); + } + void useFinalInfos(Module* module, Propagator& propagator) { // While we do the following work, see if we have anything to optimize, so // that we can avoid wasteful work later if not. bool canOptimize = false; @@ -427,6 +481,7 @@ struct TypeRefining : public Pass { } // anonymous namespace -Pass* createTypeRefiningPass() { return new TypeRefining(); } +Pass* createTypeRefiningPass() { return new TypeRefining(false); } +Pass* createTypeRefiningGUFAPass() { return new TypeRefining(true); } } // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index b665b022062..2042bc71d3a 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -214,6 +214,10 @@ void PassRegistry::registerPasses() { registerPass("type-refining", "apply more specific subtypes to type fields where possible", createTypeRefiningPass); + registerPass("type-refining-gufa", + "apply more specific subtypes to type fields where possible " + "(using GUFA)", + createTypeRefiningGUFAPass); registerPass( "heap2local", "replace GC allocations with locals", createHeap2LocalPass); registerPass("heap-store-optimization", diff --git a/src/passes/passes.h b/src/passes/passes.h index f179732a356..e051e466e72 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -183,6 +183,7 @@ Pass* createTrapModeJS(); Pass* createTupleOptimizationPass(); Pass* createTypeGeneralizingPass(); Pass* createTypeRefiningPass(); +Pass* createTypeRefiningGUFAPass(); Pass* createTypeFinalizingPass(); Pass* createTypeMergingPass(); Pass* createTypeSSAPass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 53daee199c2..5870b5c9d45 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -554,6 +554,10 @@ ;; CHECK-NEXT: --type-refining apply more specific subtypes to ;; CHECK-NEXT: type fields where possible ;; CHECK-NEXT: +;; CHECK-NEXT: --type-refining-gufa apply more specific subtypes to +;; CHECK-NEXT: type fields where possible +;; CHECK-NEXT: (using GUFA) +;; CHECK-NEXT: ;; CHECK-NEXT: --type-ssa create new nominal types to help ;; CHECK-NEXT: other optimizations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index f97e55ef61e..ac98198d2dd 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -566,6 +566,10 @@ ;; CHECK-NEXT: --type-refining apply more specific subtypes to ;; CHECK-NEXT: type fields where possible ;; CHECK-NEXT: +;; CHECK-NEXT: --type-refining-gufa apply more specific subtypes to +;; CHECK-NEXT: type fields where possible +;; CHECK-NEXT: (using GUFA) +;; CHECK-NEXT: ;; CHECK-NEXT: --type-ssa create new nominal types to help ;; CHECK-NEXT: other optimizations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index e74bb416857..ac68667554b 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -518,6 +518,10 @@ ;; CHECK-NEXT: --type-refining apply more specific subtypes to ;; CHECK-NEXT: type fields where possible ;; CHECK-NEXT: +;; CHECK-NEXT: --type-refining-gufa apply more specific subtypes to +;; CHECK-NEXT: type fields where possible +;; CHECK-NEXT: (using GUFA) +;; CHECK-NEXT: ;; CHECK-NEXT: --type-ssa create new nominal types to help ;; CHECK-NEXT: other optimizations ;; CHECK-NEXT: diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast new file mode 100644 index 00000000000..5ab1aea7eb2 --- /dev/null +++ b/test/lit/passes/type-refining-gufa.wast @@ -0,0 +1,298 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Compare the normal type refining pass to the GUFA variant. Also compare to +;; -O3 -O3, that is, all the refining passes run twice, showing that GUFA can +;; outdo them all. + +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-refining -S -o - | filecheck %s --check-prefix=NRML +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-refining-gufa -S -o - | filecheck %s --check-prefix=GUFA +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: -O3 -O3 -S -o - | filecheck %s --check-prefix=O3O3 + +;; A module that requires GUFA to fully optimize, as we must track type +;; information through locals etc. +;; +;; In NRML mode (normal type-refining), we can improve $A's field to nullref, +;; but can do nothing for $B. +;; +;; In GUFA mode we can also turn $B's field to (ref null $A). +;; +;; -O3 -O3 can remove the field from $A, and we can make $B's field immutable, +;; but because of the cyclic nature of the dataflow graph, iteratively running +;; separate refinement passes for globals, types, locals, etc. is not able to +;; refine the type of $B's field. +(module + (rec + ;; NRML: (rec + ;; NRML-NEXT: (type $A (sub (struct (field (mut nullref))))) + ;; GUFA: (rec + ;; GUFA-NEXT: (type $A (sub (struct (field (mut nullref))))) + ;; O3O3: (rec + ;; O3O3-NEXT: (type $A (sub (struct))) + (type $A (sub (struct (field (mut anyref))))) + ;; NRML: (type $B (sub (struct (field (mut anyref))))) + ;; GUFA: (type $B (sub (struct (field (mut (ref null $A)))))) + ;; O3O3: (type $B (sub (struct (field anyref)))) + (type $B (sub (struct (field (mut anyref))))) + ) + + ;; NRML: (type $2 (func (param i32) (result anyref))) + + ;; NRML: (type $3 (func (param i32))) + + ;; NRML: (global $any (mut anyref) (ref.null none)) + ;; GUFA: (type $2 (func (param i32) (result anyref))) + + ;; GUFA: (type $3 (func (param i32))) + + ;; GUFA: (global $any (mut anyref) (ref.null none)) + ;; O3O3: (type $2 (func (param i32) (result anyref))) + + ;; O3O3: (type $3 (func (param i32))) + + ;; O3O3: (global $any (mut structref) (ref.null none)) + (global $any (mut anyref) (ref.null any)) + + ;; NRML: (export "get_from_global" (func $get_from_global)) + + ;; NRML: (export "work" (func $work)) + + ;; NRML: (func $get_from_global (type $2) (param $x i32) (result anyref) + ;; NRML-NEXT: (if (result anyref) + ;; NRML-NEXT: (local.get $x) + ;; NRML-NEXT: (then + ;; NRML-NEXT: (struct.get $A 0 + ;; NRML-NEXT: (ref.cast (ref $A) + ;; NRML-NEXT: (global.get $any) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (else + ;; NRML-NEXT: (struct.get $B 0 + ;; NRML-NEXT: (ref.cast (ref $B) + ;; NRML-NEXT: (global.get $any) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (export "get_from_global" (func $get_from_global)) + + ;; GUFA: (export "work" (func $work)) + + ;; GUFA: (func $get_from_global (type $2) (param $x i32) (result anyref) + ;; GUFA-NEXT: (if (result (ref null $A)) + ;; GUFA-NEXT: (local.get $x) + ;; GUFA-NEXT: (then + ;; GUFA-NEXT: (struct.get $A 0 + ;; GUFA-NEXT: (ref.cast (ref $A) + ;; GUFA-NEXT: (global.get $any) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (else + ;; GUFA-NEXT: (struct.get $B 0 + ;; GUFA-NEXT: (ref.cast (ref $B) + ;; GUFA-NEXT: (global.get $any) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; O3O3: (export "get_from_global" (func $get_from_global)) + + ;; O3O3: (export "work" (func $work)) + + ;; O3O3: (func $get_from_global (type $2) (param $0 i32) (result anyref) + ;; O3O3-NEXT: (if (result anyref) + ;; O3O3-NEXT: (local.get $0) + ;; O3O3-NEXT: (then + ;; O3O3-NEXT: (drop + ;; O3O3-NEXT: (ref.cast (ref $A) + ;; O3O3-NEXT: (global.get $any) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: (ref.null none) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: (else + ;; O3O3-NEXT: (struct.get $B 0 + ;; O3O3-NEXT: (ref.cast (ref $B) + ;; O3O3-NEXT: (global.get $any) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + (func $get_from_global (export "get_from_global") (param $x i32) (result anyref) + ;; Read from the global, casting in a way that depends on the parameter. + ;; By storing objects in the global + having this function, we prevent -O3 + ;; from being able to trivially clear out the entire module. + (if (result anyref) + (local.get $x) + (then + (struct.get $A 0 + (ref.cast (ref $A) + (global.get $any) + ) + ) + ) + (else + (struct.get $B 0 + (ref.cast (ref $B) + (global.get $any) + ) + ) + ) + ) + ) + + ;; NRML: (func $work (type $3) (param $x i32) + ;; NRML-NEXT: (local $a anyref) + ;; NRML-NEXT: (local $b (ref null $B)) + ;; NRML-NEXT: (local.set $a + ;; NRML-NEXT: (struct.new_default $A) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (local.set $b + ;; NRML-NEXT: (struct.new $B + ;; NRML-NEXT: (local.get $a) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (local.set $b + ;; NRML-NEXT: (struct.new $B + ;; NRML-NEXT: (call $get_from_global + ;; NRML-NEXT: (local.get $x) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (if + ;; NRML-NEXT: (local.get $x) + ;; NRML-NEXT: (then + ;; NRML-NEXT: (global.set $any + ;; NRML-NEXT: (local.get $a) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (else + ;; NRML-NEXT: (global.set $any + ;; NRML-NEXT: (local.get $b) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (func $work (type $3) (param $x i32) + ;; GUFA-NEXT: (local $a anyref) + ;; GUFA-NEXT: (local $b (ref null $B)) + ;; GUFA-NEXT: (local.set $a + ;; GUFA-NEXT: (struct.new_default $A) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (local.set $b + ;; GUFA-NEXT: (struct.new $B + ;; GUFA-NEXT: (ref.cast (ref null $A) + ;; GUFA-NEXT: (local.get $a) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (local.set $b + ;; GUFA-NEXT: (struct.new $B + ;; GUFA-NEXT: (ref.cast (ref null $A) + ;; GUFA-NEXT: (call $get_from_global + ;; GUFA-NEXT: (local.get $x) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (if + ;; GUFA-NEXT: (local.get $x) + ;; GUFA-NEXT: (then + ;; GUFA-NEXT: (global.set $any + ;; GUFA-NEXT: (local.get $a) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (else + ;; GUFA-NEXT: (global.set $any + ;; GUFA-NEXT: (local.get $b) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; O3O3: (func $work (type $3) (param $0 i32) + ;; O3O3-NEXT: (local $1 (ref $B)) + ;; O3O3-NEXT: (local.set $1 + ;; O3O3-NEXT: (struct.new $B + ;; O3O3-NEXT: (if (result anyref) + ;; O3O3-NEXT: (local.get $0) + ;; O3O3-NEXT: (then + ;; O3O3-NEXT: (drop + ;; O3O3-NEXT: (ref.cast (ref $A) + ;; O3O3-NEXT: (global.get $any) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: (ref.null none) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: (else + ;; O3O3-NEXT: (struct.get $B 0 + ;; O3O3-NEXT: (ref.cast (ref $B) + ;; O3O3-NEXT: (global.get $any) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: (if + ;; O3O3-NEXT: (local.get $0) + ;; O3O3-NEXT: (then + ;; O3O3-NEXT: (global.set $any + ;; O3O3-NEXT: (struct.new_default $A) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: (else + ;; O3O3-NEXT: (global.set $any + ;; O3O3-NEXT: (local.get $1) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + ;; O3O3-NEXT: ) + (func $work (export "work") (param $x i32) + (local $a anyref) + (local $b (ref null $B)) + ;; $A's field contains null. + (local.set $a + (struct.new_default $A) + ) + ;; $B's field contains a reference to $A, even though the local's type is + ;; anyref. When we refine the field in GUFA, a cast will be added here. + (local.set $b + (struct.new $B + (local.get $a) + ) + ) + ;; Another write to $B's field, using $get_from_global, which reads from + ;; either an $A (which always contains null) or a $B (cyclic dependency, but + ;; in the end only an $A is written there). + (local.set $b + (struct.new $B + (call $get_from_global + (local.get $x) + ) + ) + ) + ;; Store something in the global, so the entire program is not trivial in + ;; the eyes of -O3. + (if + (local.get $x) + (then + (global.set $any + (local.get $a) + ) + ) + (else + (global.set $any + (local.get $b) + ) + ) + ) + ) +) + From 9cef51c10ea51ec2e3ec807715a779bd1b813331 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 3 Apr 2025 11:47:53 -0700 Subject: [PATCH 395/622] [NFC] Take a HeapType instead of Type in RefFunc::finalize (#7442) This removes from the callers the burden of constructing the type with the correct nullability and other future attributes. --- src/passes/Precompute.cpp | 2 +- src/wasm-builder.h | 2 +- src/wasm.h | 2 +- src/wasm/wasm.cpp | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 2df23f2ae96..c396a7d8dda 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -313,7 +313,7 @@ struct Precompute if (auto* r = curr->value->template dynCast()) { r->func = singleValue.getFunc(); auto heapType = getModule()->getFunction(r->func)->type; - r->finalize(Type(heapType, NonNullable)); + r->finalize(heapType); curr->finalize(); return; } diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 8344355b64a..a8a76f4740c 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -688,7 +688,7 @@ class Builder { RefFunc* makeRefFunc(Name func, HeapType heapType) { auto* ret = wasm.allocator.alloc(); ret->func = func; - ret->finalize(Type(heapType, NonNullable)); + ret->finalize(heapType); return ret; } RefEq* makeRefEq(Expression* left, Expression* right) { diff --git a/src/wasm.h b/src/wasm.h index e3f53379a9d..e4429c699ff 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1364,7 +1364,7 @@ class RefFunc : public SpecificExpression { Name func; void finalize(); - void finalize(Type type_); + void finalize(HeapType heapType); }; class RefEq : public SpecificExpression { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index b85de54f140..0bc888646cc 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -821,7 +821,9 @@ void RefFunc::finalize() { assert(type.isSignature()); } -void RefFunc::finalize(Type type_) { type = type_; } +void RefFunc::finalize(HeapType heapType) { + type = Type(heapType, NonNullable); +} void RefEq::finalize() { if (left->type == Type::unreachable || right->type == Type::unreachable) { From 3ce02b645f45af049d2ab2b8f3f87396f9b84a34 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 3 Apr 2025 13:22:17 -0700 Subject: [PATCH 396/622] [NFC] printHeapType => printHeapTypeName in Print.cpp (#7443) Rename the function in anticipation of exact heap types appearing in the IR. When an expression like `StructNew` has an exact heap type, the `exact` does not appear in `struct.new $foo`. In this case `$foo` is not the full heap type, but rather than name of the heap type definition. --- src/passes/Print.cpp | 62 ++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 90eecef2eef..cc302a3ace5 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -232,7 +232,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { std::ostream& printType(Type type) { return o << typePrinter(type); } - std::ostream& printHeapType(HeapType type) { + std::ostream& printHeapTypeName(HeapType type) { if (type.isBasic()) { return o << type; } @@ -257,7 +257,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { if (sig.results.isTuple()) { if (auto it = signatureTypes.find(sig); it != signatureTypes.end()) { o << "(type "; - printHeapType(it->second); + printHeapTypeName(it->second); o << ") "; } } @@ -471,8 +471,8 @@ struct PrintExpressionContents std::ostream& printType(Type type) { return parent.printType(type); } - std::ostream& printHeapType(HeapType type) { - return parent.printHeapType(type); + std::ostream& printHeapTypeName(HeapType type) { + return parent.printHeapTypeName(type); } std::ostream& printResultType(Type type) { @@ -563,7 +563,7 @@ struct PrintExpressionContents o << '('; printMinor(o, "type "); - printHeapType(curr->heapType); + printHeapTypeName(curr->heapType); o << ')'; } @@ -2144,7 +2144,7 @@ struct PrintExpressionContents } void visitRefNull(RefNull* curr) { printMedium(o, "ref.null "); - printHeapType(curr->type.getHeapType()); + printHeapTypeName(curr->type.getHeapType()); } void visitRefIsNull(RefIsNull* curr) { printMedium(o, "ref.is_null"); } void visitRefFunc(RefFunc* curr) { @@ -2253,7 +2253,7 @@ struct PrintExpressionContents void visitCallRef(CallRef* curr) { printMedium(o, curr->isReturn ? "return_call_ref " : "call_ref "); - printHeapType(curr->target->type.getHeapType()); + printHeapTypeName(curr->target->type.getHeapType()); } void visitRefTest(RefTest* curr) { printMedium(o, "ref.test "); @@ -2310,7 +2310,7 @@ struct PrintExpressionContents printMedium(o, "_default"); } o << ' '; - printHeapType(curr->type.getHeapType()); + printHeapTypeName(curr->type.getHeapType()); } void printFieldName(HeapType type, Index index) { auto names = parent.typePrinter.getNames(type).fieldNames; @@ -2350,7 +2350,7 @@ struct PrintExpressionContents printMedium(o, ".get "); } printMemoryOrder(curr->order); - printHeapType(heapType); + printHeapTypeName(heapType); o << ' '; printFieldName(heapType, curr->index); } @@ -2362,7 +2362,7 @@ struct PrintExpressionContents } printMemoryOrder(curr->order); auto heapType = curr->ref->type.getHeapType(); - printHeapType(heapType); + printHeapTypeName(heapType); o << ' '; printFieldName(heapType, curr->index); } @@ -2375,7 +2375,7 @@ struct PrintExpressionContents printMemoryOrder(curr->order); printMemoryOrder(curr->order); auto heapType = curr->ref->type.getHeapType(); - printHeapType(heapType); + printHeapTypeName(heapType); o << ' '; printFieldName(heapType, curr->index); } @@ -2386,7 +2386,7 @@ struct PrintExpressionContents printMemoryOrder(curr->order); printMemoryOrder(curr->order); auto heapType = curr->ref->type.getHeapType(); - printHeapType(heapType); + printHeapTypeName(heapType); o << ' '; printFieldName(heapType, curr->index); } @@ -2396,26 +2396,26 @@ struct PrintExpressionContents printMedium(o, "_default"); } o << ' '; - printHeapType(curr->type.getHeapType()); + printHeapTypeName(curr->type.getHeapType()); } void visitArrayNewData(ArrayNewData* curr) { printMedium(o, "array.new_data"); o << ' '; - printHeapType(curr->type.getHeapType()); + printHeapTypeName(curr->type.getHeapType()); o << ' '; curr->segment.print(o); } void visitArrayNewElem(ArrayNewElem* curr) { printMedium(o, "array.new_elem"); o << ' '; - printHeapType(curr->type.getHeapType()); + printHeapTypeName(curr->type.getHeapType()); o << ' '; curr->segment.print(o); } void visitArrayNewFixed(ArrayNewFixed* curr) { printMedium(o, "array.new_fixed"); o << ' '; - printHeapType(curr->type.getHeapType()); + printHeapTypeName(curr->type.getHeapType()); o << ' '; o << curr->values.size(); } @@ -2430,32 +2430,32 @@ struct PrintExpressionContents } else { printMedium(o, "array.get "); } - printHeapType(curr->ref->type.getHeapType()); + printHeapTypeName(curr->ref->type.getHeapType()); } void visitArraySet(ArraySet* curr) { printMedium(o, "array.set "); - printHeapType(curr->ref->type.getHeapType()); + printHeapTypeName(curr->ref->type.getHeapType()); } void visitArrayLen(ArrayLen* curr) { printMedium(o, "array.len"); } void visitArrayCopy(ArrayCopy* curr) { printMedium(o, "array.copy "); - printHeapType(curr->destRef->type.getHeapType()); + printHeapTypeName(curr->destRef->type.getHeapType()); o << ' '; - printHeapType(curr->srcRef->type.getHeapType()); + printHeapTypeName(curr->srcRef->type.getHeapType()); } void visitArrayFill(ArrayFill* curr) { printMedium(o, "array.fill "); - printHeapType(curr->ref->type.getHeapType()); + printHeapTypeName(curr->ref->type.getHeapType()); } void visitArrayInitData(ArrayInitData* curr) { printMedium(o, "array.init_data "); - printHeapType(curr->ref->type.getHeapType()); + printHeapTypeName(curr->ref->type.getHeapType()); o << ' '; curr->segment.print(o); } void visitArrayInitElem(ArrayInitElem* curr) { printMedium(o, "array.init_elem "); - printHeapType(curr->ref->type.getHeapType()); + printHeapTypeName(curr->ref->type.getHeapType()); o << ' '; curr->segment.print(o); } @@ -2547,14 +2547,14 @@ struct PrintExpressionContents void visitContNew(ContNew* curr) { assert(curr->type.isContinuation()); printMedium(o, "cont.new "); - printHeapType(curr->type.getHeapType()); + printHeapTypeName(curr->type.getHeapType()); } void visitContBind(ContBind* curr) { assert(curr->cont->type.isContinuation() && curr->type.isContinuation()); printMedium(o, "cont.bind "); - printHeapType(curr->cont->type.getHeapType()); + printHeapTypeName(curr->cont->type.getHeapType()); o << ' '; - printHeapType(curr->type.getHeapType()); + printHeapTypeName(curr->type.getHeapType()); } void visitSuspend(Suspend* curr) { printMedium(o, "suspend "); @@ -2582,7 +2582,7 @@ struct PrintExpressionContents printMedium(o, "resume"); o << ' '; - printHeapType(curr->cont->type.getHeapType()); + printHeapTypeName(curr->cont->type.getHeapType()); handleResumeTable(o, curr); } @@ -2591,7 +2591,7 @@ struct PrintExpressionContents printMedium(o, "resume_throw"); o << ' '; - printHeapType(curr->cont->type.getHeapType()); + printHeapTypeName(curr->cont->type.getHeapType()); o << ' '; curr->tag.print(o); @@ -2602,7 +2602,7 @@ struct PrintExpressionContents printMedium(o, "switch"); o << ' '; - printHeapType(curr->cont->type.getHeapType()); + printHeapTypeName(curr->cont->type.getHeapType()); o << ' '; curr->tag.print(o); } @@ -3022,7 +3022,7 @@ void PrintSExpression::handleSignature(Function* curr, if ((currModule && currModule->features.hasGC()) || requiresExplicitFuncType(curr->type)) { o << " (type "; - printHeapType(curr->type) << ')'; + printHeapTypeName(curr->type) << ')'; } bool inParam = false; Index i = 0; @@ -3247,7 +3247,7 @@ void PrintSExpression::visitDefinedTag(Tag* curr) { void PrintSExpression::printTagType(HeapType type) { o << "(type "; - printHeapType(type); + printHeapTypeName(type); o << ')'; if (auto params = type.getSignature().params; params != Type::none) { o << maybeSpace << "(param"; From fbf08efb9bcbb782b06a45430b4134d46ee6a22a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 3 Apr 2025 15:33:07 -0700 Subject: [PATCH 397/622] [NFC] Introduce HeapTypeDef and use it for printing (#7444) Now that we have exact heap types, non-abstract heap types no longer correspond 1:1 with heap type definitions. We previously used a single type, `HeapType`, to represent both heap types and heap type definitions. Now that `HeapType` can represent multiple heap types corresponding to the same definition, there is a new class of potential bugs in which code that expects `HeapType` values to map 1:1 with heap type definitions observes both an exact and inexact heap type for the same definition. To eliminate this class of bugs, introduce a new type, `HeapTypeDef`, whose values do correspond 1:1 with heap type definitions. `HeapTypeDef` is a subclass of `HeapType`, so it supports all the same functionality, but it cannot represent exact heap types because its constructor clears the exact bit. As an initial proof-of-concept, use HeapTypeDef in the type printing machinery, which only cares about heap type names corresponding to heap type definitions. Future PRs will use HeapTypeDef in more places. --- src/passes/Print.cpp | 2 +- src/tools/wasm-fuzz-types.cpp | 2 +- src/wasm-type-printing.h | 39 +++++++++++++++++++++++------------ src/wasm-type.h | 17 ++++++++++++++- src/wasm.h | 4 ++-- src/wasm/wasm-type.cpp | 6 +++++- 6 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index cc302a3ace5..982b9e6e37c 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -198,7 +198,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { } } - TypeNames getNames(HeapType type) { + TypeNames getNames(HeapTypeDef type) { if (parent.currModule) { if (auto it = parent.currModule->typeNames.find(type); it != parent.currModule->typeNames.end()) { diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index 7ba341e09df..b53d5c0b5b8 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -97,7 +97,7 @@ void Fuzzer::printTypes(const std::vector& types) { std::cout << "Built " << types.size() << " types:\n"; struct FatalTypeNameGenerator : TypeNameGeneratorBase { - TypeNames getNames(HeapType type) { + TypeNames getNames(HeapTypeDef type) { Fatal() << "trying to print unknown heap type"; } } fatalGenerator; diff --git a/src/wasm-type-printing.h b/src/wasm-type-printing.h index 11afde88212..e977a07188b 100644 --- a/src/wasm-type-printing.h +++ b/src/wasm-type-printing.h @@ -32,19 +32,32 @@ namespace wasm { // ability to use the generator as a function to print Types and HeapTypes to // streams. template struct TypeNameGeneratorBase { - TypeNames getNames(HeapType type) { - static_assert(&TypeNameGeneratorBase::getNames != - &Subclass::getNames, - "Derived class must implement getNames"); + TypeNameGeneratorBase() { assertValidUsage(); } + + TypeNames getNames(HeapTypeDef type) { WASM_UNREACHABLE("Derived class must implement getNames"); } - HeapType::Printed operator()(HeapType type) { - return type.print( - [&](HeapType ht) { return static_cast(this)->getNames(ht); }); + HeapType::Printed operator()(HeapTypeDef type) { + return type.print([&](HeapTypeDef ht) { + return static_cast(this)->getNames(ht); + }); } Type::Printed operator()(Type type) { - return type.print( - [&](HeapType ht) { return static_cast(this)->getNames(ht); }); + return type.print([&](HeapTypeDef ht) { + return static_cast(this)->getNames(ht); + }); + } + +private: + constexpr void assertValidUsage() { +#if !defined(__GNUC__) || __GNUC__ >= 14 + // Check that the subclass provides `getNames` with the correct type. + using Self = TypeNameGeneratorBase; + static_assert( + static_cast(&Self::getNames) != + static_cast(&Subclass::getNames), + "Derived class must implement getNames"); +#endif } }; @@ -60,7 +73,7 @@ struct DefaultTypeNameGenerator // Cached names for types that have already been seen. std::unordered_map nameCache; - TypeNames getNames(HeapType type); + TypeNames getNames(HeapTypeDef type); }; // Generates names based on the indices of types in some collection, falling @@ -71,7 +84,7 @@ struct IndexedTypeNameGenerator : TypeNameGeneratorBase> { DefaultTypeNameGenerator defaultGenerator; FallbackGenerator& fallback; - std::unordered_map names; + std::unordered_map names; template IndexedTypeNameGenerator(T& types, @@ -86,7 +99,7 @@ struct IndexedTypeNameGenerator IndexedTypeNameGenerator(T& types, const std::string& prefix = "") : IndexedTypeNameGenerator(types, defaultGenerator, prefix) {} - TypeNames getNames(HeapType type) { + TypeNames getNames(HeapTypeDef type) { if (auto it = names.find(type); it != names.end()) { return it->second; } else { @@ -117,7 +130,7 @@ struct ModuleTypeNameGenerator std::enable_if_t>* = nullptr) : ModuleTypeNameGenerator(wasm, defaultGenerator) {} - TypeNames getNames(HeapType type) { + TypeNames getNames(HeapTypeDef type) { if (auto it = wasm.typeNames.find(type); it != wasm.typeNames.end()) { return it->second; } diff --git a/src/wasm-type.h b/src/wasm-type.h index 1eb1f9fe86e..b3fdf08f31a 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -50,6 +50,7 @@ void destroyAllTypesForTestingPurposesOnly(); // data. class Type; class HeapType; +class HeapTypeDef; class RecGroup; struct Signature; struct Continuation; @@ -73,7 +74,7 @@ struct TypeNames { }; // Used to generate HeapType names. -using HeapTypeNameGenerator = std::function; +using HeapTypeNameGenerator = std::function; // The type used for interning IDs in the public interfaces of Type and // HeapType. @@ -294,6 +295,16 @@ class HeapType { std::string toString() const; }; +// Like `HeapType`, but used to represent heap type definitions and abstract +// heap types rather than arbitrary heap types. Use this whenever it would be a +// category error to use an exact heap type. +class HeapTypeDef : public HeapType { +public: + // Allow implicit conversions from HeapType. + constexpr HeapTypeDef(HeapType type) : HeapType(type.with(Inexact)) {} + constexpr HeapTypeDef() = default; +}; + class Type { // The `id` uniquely represents each type, so type equality is just a // comparison of the ids. The basic types are packed at the bottom of the @@ -1007,6 +1018,10 @@ template<> class hash { public: size_t operator()(const wasm::HeapType&) const; }; +template<> class hash { +public: + size_t operator()(const wasm::HeapTypeDef&) const; +}; template<> class hash { public: size_t operator()(const wasm::RecGroup&) const; diff --git a/src/wasm.h b/src/wasm.h index e4429c699ff..e3979e21975 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2415,8 +2415,8 @@ class Module { // Module name, if specified. Serves a documentary role only. Name name; - std::unordered_map typeNames; - std::unordered_map typeIndices; + std::unordered_map typeNames; + std::unordered_map typeIndices; MixedArena allocator; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 6fc43c2e091..4bb988fad2e 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1369,7 +1369,7 @@ size_t RecGroup::size() const { } } -TypeNames DefaultTypeNameGenerator::getNames(HeapType type) { +TypeNames DefaultTypeNameGenerator::getNames(HeapTypeDef type) { auto [it, inserted] = nameCache.insert({type, {}}); if (inserted) { // Generate a new name for this type we have not previously seen. @@ -2768,6 +2768,10 @@ size_t hash::operator()(const wasm::HeapType& heapType) const { return wasm::hash(heapType.getID()); } +size_t hash::operator()(const wasm::HeapTypeDef& def) const { + return wasm::hash(def.getID()); +} + size_t hash::operator()(const wasm::RecGroup& group) const { return wasm::hash(group.getID()); } From e0c20f332064302fa91e52393674cda5dd90508f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Apr 2025 16:43:05 -0700 Subject: [PATCH 398/622] Add missing SIMD fuzzing (#7445) This fixes almost all the current TODOs. --- src/tools/fuzzing/fuzzing.cpp | 176 ++++++++++++------ ...e-to-fuzz_all-features_metrics_noprint.txt | 51 ++--- 2 files changed, 149 insertions(+), 78 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 2b958590384..431b6255d7c 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3831,12 +3831,17 @@ Expression* TranslateToFuzzReader::makeUnary(Type type) { } case Type::v128: { assert(wasm.features.hasSIMD()); - // TODO: Add the other SIMD unary ops - return buildUnary({pick(AnyTrueVec128, - AllTrueVecI8x16, - AllTrueVecI16x8, - AllTrueVecI32x4), - make(Type::v128)}); + auto op = pick(FeatureOptions().add(FeatureSet::SIMD, + AnyTrueVec128, + AllTrueVecI8x16, + AllTrueVecI16x8, + AllTrueVecI32x4, + AllTrueVecI64x2, + BitmaskVecI8x16, + BitmaskVecI16x8, + BitmaskVecI32x4, + BitmaskVecI64x2)); + return buildUnary({op, make(Type::v128)}); } case Type::none: case Type::unreachable: @@ -3945,46 +3950,76 @@ Expression* TranslateToFuzzReader::makeUnary(Type type) { case 3: return buildUnary({SplatVecF64x2, make(Type::f64)}); case 4: - return buildUnary( - {pick(FeatureOptions() - .add(FeatureSet::SIMD, - NotVec128, - // TODO: add additional SIMD instructions - NegVecI8x16, - NegVecI16x8, - NegVecI32x4, - NegVecI64x2, - AbsVecF32x4, - NegVecF32x4, - SqrtVecF32x4, - AbsVecF64x2, - NegVecF64x2, - SqrtVecF64x2, - TruncSatSVecF32x4ToVecI32x4, - TruncSatUVecF32x4ToVecI32x4, - ConvertSVecI32x4ToVecF32x4, - ConvertUVecI32x4ToVecF32x4, - ExtendLowSVecI8x16ToVecI16x8, - ExtendHighSVecI8x16ToVecI16x8, - ExtendLowUVecI8x16ToVecI16x8, - ExtendHighUVecI8x16ToVecI16x8, - ExtendLowSVecI16x8ToVecI32x4, - ExtendHighSVecI16x8ToVecI32x4, - ExtendLowUVecI16x8ToVecI32x4, - ExtendHighUVecI16x8ToVecI32x4) - .add(FeatureSet::FP16, - AbsVecF16x8, - NegVecF16x8, - SqrtVecF16x8, - CeilVecF16x8, - FloorVecF16x8, - TruncVecF16x8, - NearestVecF16x8, - TruncSatSVecF16x8ToVecI16x8, - TruncSatUVecF16x8ToVecI16x8, - ConvertSVecI16x8ToVecF16x8, - ConvertUVecI16x8ToVecF16x8)), - make(Type::v128)}); + return buildUnary({pick(FeatureOptions() + .add(FeatureSet::SIMD, + NotVec128, + AbsVecI8x16, + AbsVecI16x8, + AbsVecI32x4, + AbsVecI64x2, + PopcntVecI8x16, + NegVecI8x16, + NegVecI16x8, + NegVecI32x4, + NegVecI64x2, + AbsVecF32x4, + NegVecF32x4, + SqrtVecF32x4, + CeilVecF32x4, + FloorVecF32x4, + TruncVecF32x4, + NearestVecF32x4, + AbsVecF64x2, + NegVecF64x2, + SqrtVecF64x2, + CeilVecF64x2, + FloorVecF64x2, + TruncVecF64x2, + NearestVecF64x2, + ExtAddPairwiseSVecI8x16ToI16x8, + ExtAddPairwiseUVecI8x16ToI16x8, + ExtAddPairwiseSVecI16x8ToI32x4, + ExtAddPairwiseUVecI16x8ToI32x4, + TruncSatSVecF32x4ToVecI32x4, + TruncSatUVecF32x4ToVecI32x4, + ConvertSVecI32x4ToVecF32x4, + ConvertUVecI32x4ToVecF32x4, + ExtendLowSVecI8x16ToVecI16x8, + ExtendHighSVecI8x16ToVecI16x8, + ExtendLowUVecI8x16ToVecI16x8, + ExtendHighUVecI8x16ToVecI16x8, + ExtendLowSVecI16x8ToVecI32x4, + ExtendHighSVecI16x8ToVecI32x4, + ExtendLowUVecI16x8ToVecI32x4, + ExtendHighUVecI16x8ToVecI32x4, + ExtendLowSVecI32x4ToVecI64x2, + ExtendHighSVecI32x4ToVecI64x2, + ExtendLowUVecI32x4ToVecI64x2, + ExtendHighUVecI32x4ToVecI64x2, + ConvertLowSVecI32x4ToVecF64x2, + ConvertLowUVecI32x4ToVecF64x2, + TruncSatZeroSVecF64x2ToVecI32x4, + TruncSatZeroUVecF64x2ToVecI32x4, + DemoteZeroVecF64x2ToVecF32x4, + PromoteLowVecF32x4ToVecF64x2) + .add(FeatureSet::RelaxedSIMD, + RelaxedTruncSVecF32x4ToVecI32x4, + RelaxedTruncUVecF32x4ToVecI32x4, + RelaxedTruncZeroSVecF64x2ToVecI32x4, + RelaxedTruncZeroUVecF64x2ToVecI32x4) + .add(FeatureSet::FP16, + AbsVecF16x8, + NegVecF16x8, + SqrtVecF16x8, + CeilVecF16x8, + FloorVecF16x8, + TruncVecF16x8, + NearestVecF16x8, + TruncSatSVecF16x8ToVecI16x8, + TruncSatUVecF16x8ToVecI16x8, + ConvertSVecI16x8ToVecF16x8, + ConvertUVecI16x8ToVecF16x8)), + make(Type::v128)}); } WASM_UNREACHABLE("invalid value"); } @@ -4146,6 +4181,12 @@ Expression* TranslateToFuzzReader::makeBinary(Type type) { LeUVecI32x4, GeSVecI32x4, GeUVecI32x4, + EqVecI64x2, + NeVecI64x2, + LtSVecI64x2, + GtSVecI64x2, + LeSVecI64x2, + GeSVecI64x2, EqVecF32x4, NeVecF32x4, LtVecF32x4, @@ -4158,6 +4199,8 @@ Expression* TranslateToFuzzReader::makeBinary(Type type) { GtVecF64x2, LeVecF64x2, GeVecF64x2, + + // SIMD arithmetic AndVec128, OrVec128, XorVec128, @@ -4172,9 +4215,7 @@ Expression* TranslateToFuzzReader::makeBinary(Type type) { MinUVecI8x16, MaxSVecI8x16, MaxUVecI8x16, - // TODO: avgr_u - // TODO: q15mulr_sat_s - // TODO: extmul + AvgrUVecI8x16, AddVecI16x8, AddSatSVecI16x8, AddSatUVecI16x8, @@ -4186,6 +4227,12 @@ Expression* TranslateToFuzzReader::makeBinary(Type type) { MinUVecI16x8, MaxSVecI16x8, MaxUVecI16x8, + AvgrUVecI16x8, + Q15MulrSatSVecI16x8, + ExtMulLowSVecI16x8, + ExtMulHighSVecI16x8, + ExtMulLowUVecI16x8, + ExtMulHighUVecI16x8, AddVecI32x4, SubVecI32x4, MulVecI32x4, @@ -4194,24 +4241,41 @@ Expression* TranslateToFuzzReader::makeBinary(Type type) { MaxSVecI32x4, MaxUVecI32x4, DotSVecI16x8ToVecI32x4, + ExtMulLowSVecI32x4, + ExtMulHighSVecI32x4, + ExtMulLowUVecI32x4, + ExtMulHighUVecI32x4, AddVecI64x2, SubVecI64x2, + MulVecI64x2, + ExtMulLowSVecI64x2, + ExtMulHighSVecI64x2, + ExtMulLowUVecI64x2, + ExtMulHighUVecI64x2, AddVecF32x4, SubVecF32x4, MulVecF32x4, DivVecF32x4, MinVecF32x4, MaxVecF32x4, + PMinVecF32x4, + PMaxVecF32x4, AddVecF64x2, SubVecF64x2, MulVecF64x2, DivVecF64x2, MinVecF64x2, MaxVecF64x2, + PMinVecF64x2, + PMaxVecF64x2, + + // SIMD Conversion NarrowSVecI16x8ToVecI8x16, NarrowUVecI16x8ToVecI8x16, NarrowSVecI32x4ToVecI16x8, NarrowUVecI32x4ToVecI16x8, + + // SIMD Swizzle SwizzleVecI8x16) .add(FeatureSet::FP16, EqVecF16x8, @@ -4226,7 +4290,9 @@ Expression* TranslateToFuzzReader::makeBinary(Type type) { MulVecF16x8, DivVecF16x8, MinVecF16x8, - MaxVecF16x8)), + MaxVecF16x8, + PMinVecF16x8, + PMaxVecF16x8)), make(Type::v128), make(Type::v128)}); } @@ -4559,7 +4625,6 @@ Expression* TranslateToFuzzReader::makeSIMDShift() { } Expression* TranslateToFuzzReader::makeSIMDLoad() { - // TODO: add Load{32,64}Zero if merged to proposal SIMDLoadOp op = pick(Load8SplatVec128, Load16SplatVec128, Load32SplatVec128, @@ -4569,7 +4634,9 @@ Expression* TranslateToFuzzReader::makeSIMDLoad() { Load16x4SVec128, Load16x4UVec128, Load32x2SVec128, - Load32x2UVec128); + Load32x2UVec128, + Load32ZeroVec128, + Load64ZeroVec128); Address offset = logify(get()); Address align; switch (op) { @@ -4592,8 +4659,11 @@ Expression* TranslateToFuzzReader::makeSIMDLoad() { align = pick(1, 2, 4, 8); break; case Load32ZeroVec128: + align = 4; + break; case Load64ZeroVec128: - WASM_UNREACHABLE("Unexpected SIMD loads"); + align = 8; + break; } Expression* ptr = makePointer(); return builder.makeSIMDLoad(op, offset, align, ptr, wasm.memories[0]->name); diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 57f9609abc3..6aa6117a0f8 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -9,48 +9,49 @@ total [table-data] : 5 [tables] : 2 [tags] : 3 - [total] : 917 - [vars] : 42 + [total] : 921 + [vars] : 46 ArrayNewFixed : 1 AtomicCmpxchg : 1 + AtomicNotify : 1 AtomicRMW : 1 - Binary : 94 - Block : 127 - Break : 4 - Call : 40 + Binary : 97 + Block : 134 + BrOn : 1 + Break : 6 + Call : 37 CallIndirect : 1 CallRef : 1 - Const : 170 + Const : 177 DataDrop : 2 - Drop : 16 - GlobalGet : 61 + Drop : 18 + GlobalGet : 62 GlobalSet : 54 I31Get : 2 - If : 33 - Load : 23 - LocalGet : 70 - LocalSet : 42 + If : 35 + Load : 25 + LocalGet : 58 + LocalSet : 43 Loop : 9 MemoryCopy : 3 MemoryFill : 1 - Nop : 7 - Pop : 4 - RefAs : 2 + Nop : 12 + Pop : 5 RefEq : 5 - RefFunc : 6 + RefFunc : 7 RefI31 : 8 - RefNull : 2 + RefNull : 1 Return : 12 - SIMDExtract : 3 + SIMDExtract : 2 Select : 7 - Store : 2 + Store : 1 StringConst : 7 StringEq : 3 - StructNew : 5 + StructNew : 6 + TableSet : 1 Throw : 1 Try : 4 - TryTable : 4 - TupleExtract : 13 - TupleMake : 3 - Unary : 36 + TryTable : 3 + TupleMake : 1 + Unary : 38 Unreachable : 27 From 5ad90cbcb8c03e710b17b274c807f3e293d9d2e1 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 4 Apr 2025 08:13:26 -0700 Subject: [PATCH 399/622] Use standard type printing for BINARYEN_PRINT_FULL (#7447) `printTypeOrName`, used to print types for BINARYEN_PRINT_FULL and a few other niche use cases, previously had bespoke printing for references that did not follow the standard format. To improve the output and reduce the number of ways we print types, change it to use the standard printing utility. --- src/passes/Print.cpp | 24 ++++++++++++------------ test/lit/passes/gsi-debug.wast | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 982b9e6e37c..df337bfbdb5 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -82,20 +82,20 @@ std::ostream& printLocal(Index index, Function* func, std::ostream& o) { // Print a name from the type section, if available. Otherwise print the type // normally. void printTypeOrName(Type type, std::ostream& o, Module* wasm) { - if (type.isRef() && wasm) { - auto heapType = type.getHeapType(); - auto iter = wasm->typeNames.find(heapType); - if (iter != wasm->typeNames.end()) { - o << iter->second.name; - if (type.isNullable()) { - o << " null"; + struct Printer : TypeNameGeneratorBase { + Module* wasm; + DefaultTypeNameGenerator fallback; + Printer(Module* wasm) : wasm(wasm) {} + TypeNames getNames(HeapTypeDef type) { + if (wasm) { + if (auto it = wasm->typeNames.find(type); it != wasm->typeNames.end()) { + return it->second; + } } - return; + return fallback.getNames(type); } - } - - // No luck with a name, just print the test as best we can. - o << type; + } print{wasm}; + o << print(type); } } // anonymous namespace diff --git a/test/lit/passes/gsi-debug.wast b/test/lit/passes/gsi-debug.wast index a55ea3e8ea9..4c690652543 100644 --- a/test/lit/passes/gsi-debug.wast +++ b/test/lit/passes/gsi-debug.wast @@ -47,10 +47,10 @@ ;; CHECK-NEXT: ;;@ ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: ;;@ local.c:30:3 - ;; CHECK-NEXT: (local.get $struct) (; struct null ;) - ;; CHECK-NEXT: ) (; struct ;) + ;; CHECK-NEXT: (local.get $struct) (; (ref null $struct) ;) + ;; CHECK-NEXT: ) (; (ref $struct) ;) ;; CHECK-NEXT: ;;@ - ;; CHECK-NEXT: (global.get $global1) (; struct ;) + ;; CHECK-NEXT: (global.get $global1) (; (ref $struct) ;) ;; CHECK-NEXT: ) (; i32 ;) ;; CHECK-NEXT: ) (; i32 ;) ;; CHECK-NEXT: ) (; none ;) From 5dd2d41aa61eab0e4fd198b055d3532048332795 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 4 Apr 2025 08:30:18 -0700 Subject: [PATCH 400/622] [NFC] Use HeapTypeDef more widely (#7446) Update `TypeBuilder::build` to return a vector of `HeapTypeDef`. Although `HeapTypeDef` is implicitly convertible to `HeapType`, containers of `HeapTypeDef` are not implicitly convertible to containers of `HeapType`, so this change requires several other utilities and passes to start using `HeapTypeDef` directly and transitively. --- src/ir/module-utils.cpp | 38 +++++++------- src/ir/module-utils.h | 12 ++--- src/ir/subtypes.h | 6 +-- src/parser/contexts.h | 41 +++++++-------- src/parser/parse-2-typedefs.cpp | 4 +- src/parser/parse-3-implicit-types.cpp | 4 +- src/parser/parse-4-module-types.cpp | 11 ++-- src/parser/parse-5-defs.cpp | 6 +-- src/parser/wat-parser-internal.h | 25 +++++----- src/parser/wat-parser.cpp | 6 +-- src/passes/NameTypes.cpp | 2 +- src/passes/Print.cpp | 4 +- src/passes/TypeMerging.cpp | 72 +++++++++++++-------------- src/passes/TypeSSA.cpp | 10 ++-- src/tools/fuzzing.h | 5 +- src/tools/fuzzing/fuzzing.cpp | 10 ++-- src/tools/fuzzing/heap-types.cpp | 50 +++++++++---------- src/tools/fuzzing/heap-types.h | 8 +-- src/tools/wasm-fuzz-types.cpp | 11 ++-- src/wasm-binary.h | 2 +- src/wasm-type-ordering.h | 8 +-- src/wasm-type.h | 10 ++-- src/wasm/wasm-type.cpp | 6 +-- test/gtest/possible-contents.cpp | 4 +- test/gtest/type-builder.cpp | 2 +- test/gtest/type-domains.cpp | 21 ++++++-- test/gtest/type-domains.h | 2 +- 27 files changed, 199 insertions(+), 181 deletions(-) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 7f2dfcc089c..7cdb5913f0c 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -347,7 +347,7 @@ namespace { // Helper for collecting HeapTypes and their frequencies. struct TypeInfos { - InsertOrderedMap info; + InsertOrderedMap info; // Multivalue control flow structures need a function type, but the identity // of the function type (i.e. what recursion group it is in or whether it is @@ -355,7 +355,7 @@ struct TypeInfos { // existing function type with the necessary signature. InsertOrderedMap controlFlowSignatures; - void note(HeapType type) { + void note(HeapTypeDef type) { if (!type.isBasic()) { ++info[type].useCount; } @@ -366,7 +366,7 @@ struct TypeInfos { } } // Ensure a type is included without increasing its count. - void include(HeapType type) { + void include(HeapTypeDef type) { if (!type.isBasic()) { info[type]; } @@ -388,7 +388,7 @@ struct TypeInfos { note(sig.results); } } - bool contains(HeapType type) { return info.count(type); } + bool contains(HeapTypeDef type) { return info.count(type); } }; struct CodeScanner @@ -468,11 +468,11 @@ struct CodeScanner }; void classifyTypeVisibility(Module& wasm, - InsertOrderedMap& types); + InsertOrderedMap& types); } // anonymous namespace -InsertOrderedMap collectHeapTypeInfo( +InsertOrderedMap collectHeapTypeInfo( Module& wasm, TypeInclusion inclusion, VisibilityHandling visibility) { // Collect module-level info. TypeInfos info; @@ -523,9 +523,9 @@ InsertOrderedMap collectHeapTypeInfo( // track which recursion groups we've already processed to avoid quadratic // behavior when there is a single large group. // TODO: Use a vector here, since we never try to add the same type twice. - UniqueNonrepeatingDeferredQueue newTypes; - std::unordered_map seenSigs; - auto noteNewType = [&](HeapType type) { + UniqueNonrepeatingDeferredQueue newTypes; + std::unordered_map seenSigs; + auto noteNewType = [&](HeapTypeDef type) { newTypes.push(type); if (type.isSignature()) { seenSigs.insert({type.getSignature(), type}); @@ -588,14 +588,14 @@ InsertOrderedMap collectHeapTypeInfo( namespace { -void classifyTypeVisibility(Module& wasm, - InsertOrderedMap& types) { +void classifyTypeVisibility( + Module& wasm, InsertOrderedMap& types) { // We will need to traverse the types used by public types and mark them // public as well. - std::vector workList; + std::vector workList; std::unordered_set publicGroups; - auto notePublic = [&](HeapType type) { + auto notePublic = [&](HeapTypeDef type) { if (type.isBasic()) { return; } @@ -694,9 +694,9 @@ void setIndices(IndexedHeapTypes& indexedTypes) { } // anonymous namespace -std::vector collectHeapTypes(Module& wasm) { +std::vector collectHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo(wasm); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, _] : info) { types.push_back(type); @@ -704,10 +704,10 @@ std::vector collectHeapTypes(Module& wasm) { return types; } -std::vector getPublicHeapTypes(Module& wasm) { +std::vector getPublicHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo( wasm, TypeInclusion::BinaryTypes, VisibilityHandling::FindVisibility); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, typeInfo] : info) { if (typeInfo.visibility == Visibility::Public) { @@ -717,10 +717,10 @@ std::vector getPublicHeapTypes(Module& wasm) { return types; } -std::vector getPrivateHeapTypes(Module& wasm) { +std::vector getPrivateHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo( wasm, TypeInclusion::UsedIRTypes, VisibilityHandling::FindVisibility); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, typeInfo] : info) { if (typeInfo.visibility == Visibility::Private) { diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index bb8b6ae439d..593e4d5a20f 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -470,26 +470,26 @@ struct HeapTypeInfo { Visibility visibility = Visibility::Unknown; }; -InsertOrderedMap collectHeapTypeInfo( +InsertOrderedMap collectHeapTypeInfo( Module& wasm, TypeInclusion inclusion = TypeInclusion::AllTypes, VisibilityHandling visibility = VisibilityHandling::NoVisibility); // Helper function for collecting all the non-basic heap types used in the // module, i.e. the types that would appear in the type section. -std::vector collectHeapTypes(Module& wasm); +std::vector collectHeapTypes(Module& wasm); // Collect all the heap types visible on the module boundary that cannot be // changed. TODO: For open world use cases, this needs to include all subtypes // of public types as well. -std::vector getPublicHeapTypes(Module& wasm); +std::vector getPublicHeapTypes(Module& wasm); // getHeapTypes - getPublicHeapTypes -std::vector getPrivateHeapTypes(Module& wasm); +std::vector getPrivateHeapTypes(Module& wasm); struct IndexedHeapTypes { - std::vector types; - std::unordered_map indices; + std::vector types; + std::unordered_map indices; }; // Similar to `collectHeapTypes`, but provides fast lookup of the index for each diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index 5c654ceb7be..a58617868e2 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -28,7 +28,7 @@ namespace wasm { // // This only scans user types, and not basic types like HeapType::eq. struct SubTypes { - SubTypes(const std::vector& types) : types(types) { + SubTypes(const std::vector& types) : types(types) { for (auto type : types) { note(type); } @@ -198,11 +198,11 @@ struct SubTypes { // All the types in the program. This is computed here anyhow, and can be // useful for callers to iterate on, so it is public. - std::vector types; + std::vector types; private: // Add a type to the graph. - void note(HeapType type) { + void note(HeapTypeDef type) { if (auto super = type.getDeclaredSuperType()) { typeSubTypes[*super].push_back(type); } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 030d404adfe..c6b0481123e 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1179,18 +1179,19 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { Lexer in; // Types parsed so far. - std::vector& types; + std::vector& types; // Map typeuse positions without an explicit type to the correct type. - std::unordered_map& implicitTypes; + std::unordered_map& implicitTypes; // Map signatures to the first defined heap type they match. - std::unordered_map sigTypes; + std::unordered_map sigTypes; - ParseImplicitTypeDefsCtx(Lexer& in, - std::vector& types, - std::unordered_map& implicitTypes, - const IndexMap& typeIndices) + ParseImplicitTypeDefsCtx( + Lexer& in, + std::vector& types, + std::unordered_map& implicitTypes, + const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), types(types), implicitTypes(implicitTypes) { for (auto type : types) { @@ -1231,7 +1232,7 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { } auto sig = Signature(Type(paramTypes), Type(resultTypes)); - auto [it, inserted] = sigTypes.insert({sig, HeapType::func}); + auto [it, inserted] = sigTypes.insert({sig, HeapType(HeapType::func)}); if (inserted) { auto type = HeapType(sig); it->second = type; @@ -1259,8 +1260,8 @@ struct ParseModuleTypesCtx : TypeParserCtx, Module& wasm; - const std::vector& types; - const std::unordered_map& implicitTypes; + const std::vector& types; + const std::unordered_map& implicitTypes; const std::unordered_map& implicitElemIndices; // The index of the current type. @@ -1269,8 +1270,8 @@ struct ParseModuleTypesCtx : TypeParserCtx, ParseModuleTypesCtx( Lexer& in, Module& wasm, - const std::vector& types, - const std::unordered_map& implicitTypes, + const std::vector& types, + const std::unordered_map& implicitTypes, const std::unordered_map& implicitElemIndices, const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), wasm(wasm), @@ -1308,7 +1309,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, return TypeUse{it->second, ids}; } - Result getBlockTypeFromTypeUse(Index pos, TypeUse use) { + Result getBlockTypeFromTypeUse(Index pos, TypeUse use) { return use.type; } @@ -1448,9 +1449,9 @@ struct ParseDefsCtx : TypeParserCtx { Module& wasm; Builder builder; - const std::vector& types; - const std::unordered_map& implicitTypes; - const std::unordered_map>& + const std::vector& types; + const std::unordered_map& implicitTypes; + const std::unordered_map>& typeNames; const std::unordered_map& implicitElemIndices; @@ -1475,9 +1476,9 @@ struct ParseDefsCtx : TypeParserCtx { ParseDefsCtx( Lexer& in, Module& wasm, - const std::vector& types, - const std::unordered_map& implicitTypes, - const std::unordered_map>& + const std::vector& types, + const std::unordered_map& implicitTypes, + const std::unordered_map>& typeNames, const std::unordered_map& implicitElemIndices, const IndexMap& typeIndices) @@ -1501,7 +1502,7 @@ struct ParseDefsCtx : TypeParserCtx { return HeapType(Signature(Type::none, results[0])); } - Result getBlockTypeFromTypeUse(Index pos, HeapType type) { + Result getBlockTypeFromTypeUse(Index pos, HeapType type) { assert(type.isSignature()); // TODO: Error if block parameters are named return type; diff --git a/src/parser/parse-2-typedefs.cpp b/src/parser/parse-2-typedefs.cpp index 83e10ec5b8a..e5c55bf9065 100644 --- a/src/parser/parse-2-typedefs.cpp +++ b/src/parser/parse-2-typedefs.cpp @@ -22,8 +22,8 @@ Result<> parseTypeDefs( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map>& typeNames) { + std::vector& types, + std::unordered_map>& typeNames) { TypeBuilder builder(decls.typeDefs.size()); ParseTypeDefsCtx ctx(input, builder, typeIndices); for (auto& recType : decls.recTypeDefs) { diff --git a/src/parser/parse-3-implicit-types.cpp b/src/parser/parse-3-implicit-types.cpp index cf13ae0f7e2..02caaf2d6a5 100644 --- a/src/parser/parse-3-implicit-types.cpp +++ b/src/parser/parse-3-implicit-types.cpp @@ -22,8 +22,8 @@ Result<> parseImplicitTypeDefs(ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes) { + std::vector& types, + std::unordered_map& implicitTypes) { ParseImplicitTypeDefsCtx ctx(input, types, implicitTypes, typeIndices); for (Index pos : decls.implicitTypeDefs) { WithPosition with(ctx, pos); diff --git a/src/parser/parse-4-module-types.cpp b/src/parser/parse-4-module-types.cpp index 04d8292d0bf..07d88d0c0a3 100644 --- a/src/parser/parse-4-module-types.cpp +++ b/src/parser/parse-4-module-types.cpp @@ -18,11 +18,12 @@ namespace wasm::WATParser { -Result<> parseModuleTypes(ParseDeclsCtx& decls, - Lexer& input, - IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes) { +Result<> +parseModuleTypes(ParseDeclsCtx& decls, + Lexer& input, + IndexMap& typeIndices, + std::vector& types, + std::unordered_map& implicitTypes) { ParseModuleTypesCtx ctx(input, decls.wasm, types, diff --git a/src/parser/parse-5-defs.cpp b/src/parser/parse-5-defs.cpp index acc81bb75a4..bc619dd748e 100644 --- a/src/parser/parse-5-defs.cpp +++ b/src/parser/parse-5-defs.cpp @@ -22,9 +22,9 @@ Result<> parseDefinitions( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes, - std::unordered_map>& typeNames) { + std::vector& types, + std::unordered_map& implicitTypes, + std::unordered_map>& typeNames) { // Parse definitions. // TODO: Parallelize this. ParseDefsCtx ctx(input, diff --git a/src/parser/wat-parser-internal.h b/src/parser/wat-parser-internal.h index 00c96abd42e..5b78d7e9632 100644 --- a/src/parser/wat-parser-internal.h +++ b/src/parser/wat-parser-internal.h @@ -28,29 +28,30 @@ Result<> parseTypeDefs( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map>& typeNames); + std::vector& types, + std::unordered_map>& typeNames); Result<> parseImplicitTypeDefs(ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes); + std::vector& types, + std::unordered_map& implicitTypes); -Result<> parseModuleTypes(ParseDeclsCtx& decls, - Lexer& input, - IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes); +Result<> +parseModuleTypes(ParseDeclsCtx& decls, + Lexer& input, + IndexMap& typeIndices, + std::vector& types, + std::unordered_map& implicitTypes); Result<> parseDefinitions( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes, - std::unordered_map>& typeNames); + std::vector& types, + std::unordered_map& implicitTypes, + std::unordered_map>& typeNames); // RAII utility for temporarily changing the parsing position of a parsing // context. diff --git a/src/parser/wat-parser.cpp b/src/parser/wat-parser.cpp index 26b2bbad06f..a64e62b227d 100644 --- a/src/parser/wat-parser.cpp +++ b/src/parser/wat-parser.cpp @@ -100,11 +100,11 @@ Result<> doParseModule(Module& wasm, Lexer& input, bool allowExtra) { auto typeIndices = createIndexMap(decls.in, decls.typeDefs); CHECK_ERR(typeIndices); - std::vector types; - std::unordered_map> typeNames; + std::vector types; + std::unordered_map> typeNames; CHECK_ERR(parseTypeDefs(decls, input, *typeIndices, types, typeNames)); - std::unordered_map implicitTypes; + std::unordered_map implicitTypes; CHECK_ERR( parseImplicitTypeDefs(decls, input, *typeIndices, types, implicitTypes)); diff --git a/src/passes/NameTypes.cpp b/src/passes/NameTypes.cpp index 0e18f30945a..1a6961efaec 100644 --- a/src/passes/NameTypes.cpp +++ b/src/passes/NameTypes.cpp @@ -32,7 +32,7 @@ struct NameTypes : public Pass { void run(Module* module) override { // Find all the types. - std::vector types = ModuleUtils::collectHeapTypes(*module); + std::vector types = ModuleUtils::collectHeapTypes(*module); std::unordered_set used; diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index df337bfbdb5..41bbe3bbc58 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -141,7 +141,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { // Used to print delegate's depth argument when it throws to the caller int controlFlowDepth = 0; - std::vector heapTypes; + std::vector heapTypes; std::unordered_map signatureTypes; // Track the print indent so that we can see when it changes. That affects how @@ -173,7 +173,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { DefaultTypeNameGenerator fallback; std::unordered_map fallbackNames; - TypePrinter(PrintSExpression& parent, const std::vector& types) + TypePrinter(PrintSExpression& parent, const std::vector& types) : parent(parent) { if (!parent.currModule) { return; diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index e7a25cf372c..79be9ebabe4 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -66,7 +66,7 @@ constexpr int MAX_ITERATIONS = 20; // casts are never distinguished from their supertypes. // Most functions do no casts, or perhaps cast |this| and perhaps a few others. -using CastTypes = SmallUnorderedSet; +using CastTypes = SmallUnorderedSet; struct CastFinder : public PostWalker { CastTypes castTypes; @@ -115,7 +115,7 @@ struct CastFinder : public PostWalker { // split out into separate partitions. struct TypeMerging : public Pass { // A list of partitions with stable iterators. - using Partition = std::vector>; + using Partition = std::vector>; using Partitions = std::list; // Only modifies types. @@ -124,18 +124,18 @@ struct TypeMerging : public Pass { Module* module; // All private original types. - std::unordered_set privateTypes; + std::unordered_set privateTypes; // Types that are distinguished by cast instructions. CastTypes castTypes; // The list of remaining types that have not been merged into other types. // Candidates for further merging. - std::vector mergeable; + std::vector mergeable; // Map the original types to the types they will be merged into, if any. TypeMapper::TypeUpdates merges; - HeapType getMerged(HeapType type) { + HeapType getMerged(HeapTypeDef type) { for (auto it = merges.find(type); it != merges.end(); it = merges.find(type)) { type = it->second; @@ -143,10 +143,10 @@ struct TypeMerging : public Pass { return type; } - std::vector - mergeableSupertypesFirst(const std::vector& types) { + std::vector + mergeableSupertypesFirst(const std::vector& types) { return HeapTypeOrdering::supertypesFirst( - types, [&](HeapType type) -> std::optional { + types, [&](HeapTypeDef type) -> std::optional { if (auto super = type.getDeclaredSuperType()) { return getMerged(*super); } @@ -166,19 +166,19 @@ struct TypeMerging : public Pass { // Split a partition into potentially multiple partitions for each // disconnected group of types it contains. - std::vector> - splitSupertypePartition(const std::vector&); + std::vector> + splitSupertypePartition(const std::vector&); CastTypes findCastTypes(); - std::vector getPublicChildren(HeapType type); - DFA::State makeDFAState(HeapType type); + std::vector getPublicChildren(HeapTypeDef type); + DFA::State makeDFAState(HeapTypeDef type); void applyMerges(); }; // Hash and equality-compare HeapTypes based on their top-level structure (i.e. // "shape"), ignoring nontrivial heap type children that will not be // differentiated between until we run the DFA partition refinement. -bool shapeEq(HeapType a, HeapType b); +bool shapeEq(HeapTypeDef a, HeapTypeDef b); bool shapeEq(const Struct& a, const Struct& b); bool shapeEq(Array a, Array b); bool shapeEq(Signature a, Signature b); @@ -186,7 +186,7 @@ bool shapeEq(Field a, Field b); bool shapeEq(Type a, Type b); bool shapeEq(const Tuple& a, const Tuple& b); -size_t shapeHash(HeapType a); +size_t shapeHash(HeapTypeDef a); size_t shapeHash(const Struct& a); size_t shapeHash(Array a); size_t shapeHash(Signature a); @@ -195,13 +195,13 @@ size_t shapeHash(Type a); size_t shapeHash(const Tuple& a); struct ShapeEq { - bool operator()(const HeapType& a, const HeapType& b) const { + bool operator()(const HeapTypeDef& a, const HeapTypeDef& b) const { return shapeEq(a, b); } }; struct ShapeHash { - size_t operator()(const HeapType& type) const { return shapeHash(type); } + size_t operator()(const HeapTypeDef& type) const { return shapeHash(type); } }; void TypeMerging::run(Module* module_) { @@ -219,7 +219,7 @@ void TypeMerging::run(Module* module_) { // determine whether types are eligible to be merged. mergeable = ModuleUtils::getPrivateHeapTypes(*module); privateTypes = - std::unordered_set(mergeable.begin(), mergeable.end()); + std::unordered_set(mergeable.begin(), mergeable.end()); castTypes = findCastTypes(); // Merging supertypes or siblings can unlock more sibling merging @@ -274,21 +274,21 @@ bool TypeMerging::merge(MergeKind kind) { #endif // TYPE_MERGING_DEBUG // Map each type to its partition in the list. - std::unordered_map typePartitions; + std::unordered_map typePartitions; // Map the supertypes and top-level structures of each type to partitions so // that siblings that refine the supertype in the same way can be assigned to // the same partition and potentially merged. std::unordered_map< - std::optional, - std::unordered_map> + std::optional, + std::unordered_map> shapePartitions; // Ensure the type has a partition and return a reference to it. Since we // merge up the type tree and visit supertypes first, the partition usually // already exists. The exception is when the supertype is public, in which // case we might not have created a partition for it yet. - auto ensurePartition = [&](HeapType type) -> Partitions::iterator { + auto ensurePartition = [&](HeapTypeDef type) -> Partitions::iterator { auto [it, inserted] = typePartitions.insert({type, partitions.end()}); if (inserted) { it->second = partitions.insert(partitions.end(), {makeDFAState(type)}); @@ -298,7 +298,7 @@ bool TypeMerging::merge(MergeKind kind) { // Similar to the above, but look up or create a partition associated with the // type's supertype and top-level shape rather than its identity. - auto ensureShapePartition = [&](HeapType type) -> Partitions::iterator { + auto ensureShapePartition = [&](HeapTypeDef type) -> Partitions::iterator { auto super = type.getDeclaredSuperType(); if (super) { super = getMerged(*super); @@ -401,7 +401,7 @@ bool TypeMerging::merge(MergeKind kind) { // differentiatable. A type and its subtype cannot differ by referring to // different, unrelated types in the same position because then they would // not be in a valid subtype relationship. - std::vector> newPartitions; + std::vector> newPartitions; for (const auto& partitionTypes : refinedPartitions) { auto split = splitSupertypePartition(partitionTypes); newPartitions.insert(newPartitions.end(), split.begin(), split.end()); @@ -418,7 +418,7 @@ bool TypeMerging::merge(MergeKind kind) { // supertypes or siblings because if we try to merge into a subtype then we // will accidentally set that subtype to be its own supertype. Also keep track // of the remaining types. - std::vector newMergeable; + std::vector newMergeable; bool merged = false; for (const auto& partition : refinedPartitions) { auto target = mergeableSupertypesFirst(partition).front(); @@ -434,7 +434,7 @@ bool TypeMerging::merge(MergeKind kind) { #if TYPE_MERGING_DEBUG std::cerr << "Merges:\n"; - std::unordered_map> mergees; + std::unordered_map> mergees; for (auto& [mergee, target] : merges) { mergees[target].push_back(mergee); } @@ -450,15 +450,15 @@ bool TypeMerging::merge(MergeKind kind) { return merged; } -std::vector> -TypeMerging::splitSupertypePartition(const std::vector& types) { +std::vector> +TypeMerging::splitSupertypePartition(const std::vector& types) { if (types.size() == 1) { // Cannot split a partition containing just one type. return {types}; } - std::unordered_set includedTypes(types.begin(), types.end()); - std::vector> partitions; - std::unordered_map partitionIndices; + std::unordered_set includedTypes(types.begin(), types.end()); + std::vector> partitions; + std::unordered_map partitionIndices; for (auto type : mergeableSupertypesFirst(types)) { auto super = type.getDeclaredSuperType(); if (super && includedTypes.count(*super)) { @@ -503,8 +503,8 @@ CastTypes TypeMerging::findCastTypes() { return allCastTypes; } -std::vector TypeMerging::getPublicChildren(HeapType type) { - std::vector publicChildren; +std::vector TypeMerging::getPublicChildren(HeapTypeDef type) { + std::vector publicChildren; for (auto child : type.getHeapTypeChildren()) { if (!child.isBasic() && !privateTypes.count(child)) { publicChildren.push_back(child); @@ -513,8 +513,8 @@ std::vector TypeMerging::getPublicChildren(HeapType type) { return publicChildren; } -DFA::State TypeMerging::makeDFAState(HeapType type) { - std::vector succs; +DFA::State TypeMerging::makeDFAState(HeapTypeDef type) { + std::vector succs; // Both private and public heap type children participate in the DFA and are // eligible to be successors, except that public types are terminal states // that do not have successors. This is sufficient because public types are @@ -548,7 +548,7 @@ void TypeMerging::applyMerges() { TypeMapper(*module, merges).map(); } -bool shapeEq(HeapType a, HeapType b) { +bool shapeEq(HeapTypeDef a, HeapTypeDef b) { // Check whether `a` and `b` have the same top-level structure, including the // position and identity of any children that are not included as transitions // in the DFA, i.e. any children that are not nontrivial references. @@ -578,7 +578,7 @@ bool shapeEq(HeapType a, HeapType b) { return false; } -size_t shapeHash(HeapType a) { +size_t shapeHash(HeapTypeDef a) { size_t digest = hash(a.isOpen()); rehash(digest, a.isShared()); auto kind = a.getKind(); diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 3d68c991396..679f6a34d82 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -69,11 +69,11 @@ namespace { // way to ensure that the new types are in fact in a new rec group. // // TODO: Move this outside if we find more uses. -std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, - Module& wasm) { +std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, + Module& wasm) { auto num = recGroup.size(); - std::vector types; + std::vector types; types.reserve(num); for (auto type : recGroup) { types.push_back(type); @@ -81,8 +81,8 @@ std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, // Find all the heap types present before we create the new ones. The new // types must not appear in |existingSet|. - std::vector existing = ModuleUtils::collectHeapTypes(wasm); - std::unordered_set existingSet(existing.begin(), existing.end()); + std::vector existing = ModuleUtils::collectHeapTypes(wasm); + std::unordered_set existingSet(existing.begin(), existing.end()); // Check for a collision with an existing rec group. Note that it is enough to // check one of the types: either the entire rec group gets merged, so they diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 383e5af70c1..0fcd45c00d1 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -191,11 +191,12 @@ class TranslateToFuzzReader { std::vector loggableTypes; // The heap types we can pick from to generate instructions. - std::vector interestingHeapTypes; + std::vector interestingHeapTypes; // A mapping of a heap type to the subset of interestingHeapTypes that are // subtypes of it. - std::unordered_map> interestingHeapSubTypes; + std::unordered_map> + interestingHeapSubTypes; // Type => list of struct fields that have that type. std::unordered_map> typeStructFields; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 431b6255d7c..b40c28159e1 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -484,11 +484,11 @@ void TranslateToFuzzReader::setupHeapTypes() { // Basic types must be handled directly, since subTypes doesn't look at // those. auto share = type.getShared(); - auto struct_ = HeapTypes::struct_.getBasic(share); - auto array = HeapTypes::array.getBasic(share); - auto eq = HeapTypes::eq.getBasic(share); - auto any = HeapTypes::any.getBasic(share); - auto func = HeapTypes::func.getBasic(share); + HeapType struct_ = HeapTypes::struct_.getBasic(share); + HeapType array = HeapTypes::array.getBasic(share); + HeapType eq = HeapTypes::eq.getBasic(share); + HeapType any = HeapTypes::any.getBasic(share); + HeapType func = HeapTypes::func.getBasic(share); switch (type.getKind()) { case HeapTypeKind::Func: interestingHeapSubTypes[func].push_back(type); diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index f5a6717ceb3..56c0b29a4bd 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -669,7 +669,7 @@ namespace { // supertypes in which they appear. struct Inhabitator { // Uniquely identify fields as an index into a type. - using FieldPos = std::pair; + using FieldPos = std::pair; // When we make a reference nullable, we typically need to make the same // reference in other types nullable to maintain valid subtyping. Which types @@ -682,14 +682,14 @@ struct Inhabitator { enum Variance { Invariant, Covariant }; // The input types. - const std::vector& types; + const std::vector& types; // The fields we will make nullable. std::unordered_set nullables; SubTypes subtypes; - Inhabitator(const std::vector& types) + Inhabitator(const std::vector& types) : types(types), subtypes(types) {} Variance getVariance(FieldPos fieldPos); @@ -698,7 +698,7 @@ struct Inhabitator { void markExternRefsNullable(); void breakNonNullableCycles(); - std::vector build(); + std::vector build(); }; Inhabitator::Variance Inhabitator::getVariance(FieldPos fieldPos) { @@ -749,7 +749,7 @@ void Inhabitator::markNullable(FieldPos field) { // this extra `index` variable once we have C++20. It's a workaround for // lambdas being unable to capture structured bindings. const size_t index = idx; - subtypes.iterSubTypes(curr, [&](HeapType type, Index) { + subtypes.iterSubTypes(curr, [&](HeapTypeDef type, Index) { nullables.insert({type, index}); }); break; @@ -800,13 +800,13 @@ void Inhabitator::markExternRefsNullable() { // the cycle to be made non-nullable. void Inhabitator::breakNonNullableCycles() { // Types we've finished visiting. We don't need to visit them again. - std::unordered_set visited; + std::unordered_set visited; // The path of types we are currently visiting. If one of them comes back up, // we've found a cycle. Map the types to the other types they reference and // our current index into that list so we can track where we are in each level // of the search. - InsertOrderedMap, Index>> visiting; + InsertOrderedMap, Index>> visiting; for (auto root : types) { if (visited.count(root)) { @@ -881,8 +881,8 @@ void Inhabitator::breakNonNullableCycles() { } } -std::vector Inhabitator::build() { - std::unordered_map typeIndices; +std::vector Inhabitator::build() { + std::unordered_map typeIndices; for (size_t i = 0; i < types.size(); ++i) { typeIndices.insert({types[i], i}); } @@ -975,16 +975,16 @@ std::vector Inhabitator::build() { } // anonymous namespace -std::vector -HeapTypeGenerator::makeInhabitable(const std::vector& types) { +std::vector +HeapTypeGenerator::makeInhabitable(const std::vector& types) { if (types.empty()) { return {}; } // Remove duplicate and basic types. We will insert them back at the end. - std::unordered_map typeIndices; + std::unordered_map typeIndices; std::vector deduplicatedIndices; - std::vector deduplicated; + std::vector deduplicated; for (auto type : types) { if (type.isBasic()) { deduplicatedIndices.push_back(-1); @@ -1006,7 +1006,7 @@ HeapTypeGenerator::makeInhabitable(const std::vector& types) { deduplicated = inhabitator.build(); // Re-duplicate and re-insert basic types as necessary. - std::vector result; + std::vector result; for (size_t i = 0; i < types.size(); ++i) { if (deduplicatedIndices[i] == (size_t)-1) { assert(types[i].isBasic()); @@ -1021,14 +1021,14 @@ HeapTypeGenerator::makeInhabitable(const std::vector& types) { namespace { bool isUninhabitable(Type type, - std::unordered_set& visited, - std::unordered_set& visiting); + std::unordered_set& visited, + std::unordered_set& visiting); // Simple recursive DFS through non-nullable references to see if we find any // cycles. -bool isUninhabitable(HeapType type, - std::unordered_set& visited, - std::unordered_set& visiting) { +bool isUninhabitable(HeapTypeDef type, + std::unordered_set& visited, + std::unordered_set& visiting) { switch (type.getKind()) { case HeapTypeKind::Basic: return false; @@ -1071,8 +1071,8 @@ bool isUninhabitable(HeapType type, } bool isUninhabitable(Type type, - std::unordered_set& visited, - std::unordered_set& visiting) { + std::unordered_set& visited, + std::unordered_set& visiting) { if (type.isRef() && type.isNonNullable()) { if (type.getHeapType().isBottom() || type.getHeapType().isMaybeShared(HeapType::ext)) { @@ -1085,10 +1085,10 @@ bool isUninhabitable(Type type, } // anonymous namespace -std::vector -HeapTypeGenerator::getInhabitable(const std::vector& types) { - std::unordered_set visited, visiting; - std::vector inhabitable; +std::vector +HeapTypeGenerator::getInhabitable(const std::vector& types) { + std::unordered_set visited, visiting; + std::vector inhabitable; for (auto type : types) { if (!isUninhabitable(type, visited, visiting)) { inhabitable.push_back(type); diff --git a/src/tools/fuzzing/heap-types.h b/src/tools/fuzzing/heap-types.h index bd30178f2b4..119b865b540 100644 --- a/src/tools/fuzzing/heap-types.h +++ b/src/tools/fuzzing/heap-types.h @@ -42,12 +42,12 @@ struct HeapTypeGenerator { // Given a sequence of newly-built heap types, produce a sequence of similar // or identical types that are all inhabitable, i.e. that are possible to // create values for. - static std::vector - makeInhabitable(const std::vector& types); + static std::vector + makeInhabitable(const std::vector& types); // Returns the types in the input that are inhabitable. - static std::vector - getInhabitable(const std::vector& types); + static std::vector + getInhabitable(const std::vector& types); }; } // namespace wasm diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index b53d5c0b5b8..8bf9fa340a3 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -39,7 +39,7 @@ struct Fuzzer { bool verbose; // Initialized by `run` for checkers and possible later inspection - std::vector types; + std::vector types; std::vector> subtypeIndices; Random rand; @@ -48,7 +48,7 @@ struct Fuzzer { // Generate types and run checkers on them. void run(uint64_t seed); - static void printTypes(const std::vector&); + static void printTypes(const std::vector&); // Checkers for various properties. void checkSubtypes() const; @@ -93,7 +93,7 @@ void Fuzzer::run(uint64_t seed) { checkRecGroupShapes(); } -void Fuzzer::printTypes(const std::vector& types) { +void Fuzzer::printTypes(const std::vector& types) { std::cout << "Built " << types.size() << " types:\n"; struct FatalTypeNameGenerator : TypeNameGeneratorBase { @@ -233,7 +233,7 @@ void Fuzzer::checkCanonicalization() { // between canonical and temporary components. struct Copier { Random& rand; - const std::vector& types; + const std::vector& types; TypeBuilder& builder; // For each type, the indices in `types` at which it appears. @@ -479,7 +479,8 @@ void Fuzzer::checkCanonicalization() { } void Fuzzer::checkInhabitable() { - std::vector inhabitable = HeapTypeGenerator::makeInhabitable(types); + std::vector inhabitable = + HeapTypeGenerator::makeInhabitable(types); if (verbose) { std::cout << "\nInhabitable types:\n\n"; printTypes(inhabitable); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index fda3f1e741f..bd9a521e4a4 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1458,7 +1458,7 @@ class WasmBinaryReader { SourceMapReader sourceMapReader; // All types defined in the type section - std::vector types; + std::vector types; public: WasmBinaryReader(Module& wasm, diff --git a/src/wasm-type-ordering.h b/src/wasm-type-ordering.h index 0f23b495308..f8248bdc91f 100644 --- a/src/wasm-type-ordering.h +++ b/src/wasm-type-ordering.h @@ -29,12 +29,12 @@ namespace wasm::HeapTypeOrdering { // type in the sequence comes only after its immediate supertype in the // collection is visited. template -std::vector supertypesFirst( +std::vector supertypesFirst( const T& types, - std::function(HeapType)> getSuper = - [](HeapType type) { return type.getDeclaredSuperType(); }) { + std::function(HeapTypeDef)> getSuper = + [](HeapTypeDef type) { return type.getDeclaredSuperType(); }) { - InsertOrderedMap> subtypes; + InsertOrderedMap> subtypes; for (auto type : types) { subtypes.insert({type, {}}); } diff --git a/src/wasm-type.h b/src/wasm-type.h index b3fdf08f31a..fb21976dc04 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -855,14 +855,14 @@ struct TypeBuilder { ErrorReason reason; }; - struct BuildResult : std::variant, Error> { + struct BuildResult : std::variant, Error> { operator bool() const { - return bool(std::get_if>(this)); + return bool(std::get_if>(this)); } - const std::vector& operator*() const { - return std::get>(*this); + const std::vector& operator*() const { + return std::get>(*this); } - const std::vector* operator->() const { return &*(*this); } + const std::vector* operator->() const { return &*(*this); } const Error* getError() const { return std::get_if(this); } }; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 4bb988fad2e..be732fdad41 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -2574,7 +2574,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, canonicalized.insert({group[i], canonical[i]}); } // Return the canonical types. - return {std::vector(canonical.begin(), canonical.end())}; + return {std::vector(canonical.begin(), canonical.end())}; } // The group was successfully moved to the global rec group store, so it is @@ -2588,7 +2588,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, } } - std::vector results(group.begin(), group.end()); + std::vector results(group.begin(), group.end()); // We need to make the tuples canonical as well, but right now there is no way // to move them to their global store, so we have to create new tuples and @@ -2622,7 +2622,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, TypeBuilder::BuildResult TypeBuilder::build() { size_t entryCount = impl->entries.size(); - std::vector results; + std::vector results; results.reserve(entryCount); // Map temporary HeapTypes to their canonicalized versions so they can be diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index f267742a71b..9d0767595f7 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -349,7 +349,7 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { std::vector vec(set.begin(), set.end()); // Find the maximum depths for the normalized cone tests later down. - std::unordered_set heapTypes; + std::unordered_set heapTypes; for (auto& contents : set) { auto type = contents.getType(); if (type.isRef()) { @@ -359,7 +359,7 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { } } } - std::vector heapTypesVec(heapTypes.begin(), heapTypes.end()); + std::vector heapTypesVec(heapTypes.begin(), heapTypes.end()); SubTypes subTypes(heapTypesVec); auto maxDepths = subTypes.getMaxDepths(); diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 8b85e7e97bf..66a7526f095 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -185,7 +185,7 @@ TEST_F(TypeTest, Basics) { auto result = builder.build(); ASSERT_TRUE(result); - std::vector built = *result; + std::vector built = *result; ASSERT_EQ(built.size(), size_t{3}); // The built types should have the correct kinds. diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index deb9d0a1dad..b4f178a759e 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -940,7 +940,7 @@ fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan) { WASM_UNREACHABLE("unexpected kind"); } -std::vector BuildHeapTypes(TypeBuilderPlan plan) { +std::vector BuildHeapTypes(TypeBuilderPlan plan) { // Continuation types without reachable function types need a fallback. TypeBuilder fallbackBuilder(2); fallbackBuilder[0] = Signature(); @@ -1062,7 +1062,7 @@ auto ArbitraryDefinedHeapTypesAndPlan() { ArbitraryTypeBuilderPlan()); } -void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { +void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { auto types = std::move(pair.first); auto plan = std::move(pair.second); @@ -1138,7 +1138,7 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { } }; - auto checkDef = [&](TypeDefPlan& plan, HeapType type) { + auto checkDef = [&](TypeDefPlan& plan, HeapTypeDef type) { if (auto* f = plan.getFunc()) { checkFunc(*f, type); } else if (auto* s = plan.getStruct()) { @@ -1168,6 +1168,19 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { FUZZ_TEST(TypeBuilderDomainsTest, TestBuiltTypes) .WithDomains(ArbitraryDefinedHeapTypesAndPlan()); +fuzztest::Domain> ArbitraryDefinedHeapTypeDefs() { + return fuzztest::Map(BuildHeapTypes, ArbitraryTypeBuilderPlan()); +} + +std::vector convertToHeapTypes(std::vector defs) { + std::vector types; + types.reserve(defs.size()); + for (auto def : defs) { + types.push_back(HeapType(def)); + } + return types; +} + } // anonymous namespace fuzztest::Domain ArbitraryTypeBuilderPlan() { @@ -1177,7 +1190,7 @@ fuzztest::Domain ArbitraryTypeBuilderPlan() { } fuzztest::Domain> ArbitraryDefinedHeapTypes() { - return fuzztest::Map(BuildHeapTypes, ArbitraryTypeBuilderPlan()); + return fuzztest::Map(convertToHeapTypes, ArbitraryDefinedHeapTypeDefs()); } fuzztest::Domain> ArbitraryHeapTypePair() { diff --git a/test/gtest/type-domains.h b/test/gtest/type-domains.h index 17ad7fe9530..b2fc268cc75 100644 --- a/test/gtest/type-domains.h +++ b/test/gtest/type-domains.h @@ -134,7 +134,7 @@ struct TypeBuilderPlan { std::vector defs; // Built types. - std::vector types; + std::vector types; friend std::ostream& operator<<(std::ostream& o, const TypeBuilderPlan& plan); }; From 62b55de0e619491b0c3e97fee90d01dc9ba35249 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 4 Apr 2025 08:58:44 -0700 Subject: [PATCH 401/622] Avoid overflow UB in random.cpp (#7449) The random number generator adds into `xorFactor` in various places. This variable was previously signed, so overflows from these adds were UB. Make it unsigned to avoid UB. --- src/tools/fuzzing/random.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/random.h b/src/tools/fuzzing/random.h index 21182c97e0b..664386939f2 100644 --- a/src/tools/fuzzing/random.h +++ b/src/tools/fuzzing/random.h @@ -35,7 +35,7 @@ class Random { bool finishedInput = false; // After we finish the input, we start going through it again, but xoring // so it's not identical. - int xorFactor = 0; + unsigned int xorFactor = 0; // Features used for picking among FeatureOptions. FeatureSet features; From 63186dcf7023c108d16f790efdc281979b2f4fc9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Apr 2025 12:48:01 -0700 Subject: [PATCH 402/622] [GC] Fix global handling in TypeRefiningGUFA (#7451) TypeRefiningGUFA refines struct types based on the types that flow into fields, and looks past the immediate types in the IR. As a result, it can require casts when we end up storing something that appears not-sufficiently- refined to wasm validation rules, in ways that cannot occur with normal TypeRefining. The specific problem that can happen is that casts are disallowed in globals, so we must be careful to not refine too much there. --- src/passes/TypeRefining.cpp | 34 ++++++++++ test/lit/passes/type-refining-gufa.wast | 87 +++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 585abefb948..962621a6d34 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -25,6 +25,7 @@ // themselves. // +#include "ir/find_all.h" #include "ir/lubs.h" #include "ir/possible-contents.h" #include "ir/struct-utils.h" @@ -191,6 +192,39 @@ struct TypeRefining : public Pass { // Propagate to supertypes, so no field is less refined than its super. propagator.propagateToSuperTypes(finalInfos); + + // Take into account possible problems. This pass only refines struct + // fields, and when we refine in a way that exceeds the wasm type system + // then we fix that up with a cast (see below). However, we cannot use casts + // in all places, specifically in globals, so we must account for that. + for (auto& global : module->globals) { + if (global->imported()) { + continue; + } + + // Find StructNews, which are the one thing that can appear in a global + // init that is affected by our optimizations. + for (auto* structNew : FindAll(global->init).list) { + if (structNew->isWithDefault()) { + continue; + } + + auto type = structNew->type.getHeapType(); + auto& infos = finalInfos[type]; + auto& fields = type.getStruct().fields; + for (Index i = 0; i < fields.size(); i++) { + // We are in a situation like this: + // + // (struct.new $A + // (global.get or such + // + // To avoid ending up requiring a cast later, the type of our child + // must fit perfectly in the field it is written to. + auto childType = structNew->operands[i]->type; + infos[i].note(childType); + } + } + } } void useFinalInfos(Module* module, Propagator& propagator) { diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index 5ab1aea7eb2..208c1fbd9a6 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -296,3 +296,90 @@ ) ) +;; GUFA can infer the first global contains a $func, so the struct type's field +;; can be refined. However, doing so would require adding a cast from the +;; global's declared type (ref func) to the refined type, so that the struct.new +;; validates. (Alternatively, we would need to refine the global's type at the +;; same time we refine the struct, but this pass only refines structs.) The type +;; of the struct's field should only refine as much as is valid, which is the +;; type of the global, (ref func), and not the declared type $func. Both GUFA +;; and normal type refining succeed here (-O3 removes the entire module, and is +;; not interesting here). +(module + ;; NRML: (rec + ;; NRML-NEXT: (type $struct (struct (field (ref func)))) + ;; GUFA: (rec + ;; GUFA-NEXT: (type $struct (struct (field (ref func)))) + (type $struct (struct (field funcref))) + + ;; NRML: (type $func (func)) + ;; GUFA: (type $func (func)) + (type $func (func)) + + ;; NRML: (global $A (ref func) (ref.func $func)) + ;; GUFA: (global $A (ref func) (ref.func $func)) + (global $A (ref func) (ref.func $func)) + + ;; NRML: (global $B (ref $struct) (struct.new $struct + ;; NRML-NEXT: (global.get $A) + ;; NRML-NEXT: )) + ;; GUFA: (global $B (ref $struct) (struct.new $struct + ;; GUFA-NEXT: (global.get $A) + ;; GUFA-NEXT: )) + (global $B (ref $struct) (struct.new $struct + (global.get $A) + )) + + ;; NRML: (func $func (type $func) + ;; NRML-NEXT: ) + ;; GUFA: (func $func (type $func) + ;; GUFA-NEXT: ) + (func $func (type $func) + ) +) + +;; As above, but now the global has a refined type, so we can refine fully. +(module + ;; NRML: (rec + ;; NRML-NEXT: (type $struct (struct (field (ref $func)))) + ;; GUFA: (rec + ;; GUFA-NEXT: (type $struct (struct (field (ref $func)))) + (type $struct (struct (field funcref))) + + ;; NRML: (type $func (func)) + ;; GUFA: (type $func (func)) + (type $func (func)) + + ;; NRML: (global $A (ref $func) (ref.func $func)) + ;; GUFA: (global $A (ref $func) (ref.func $func)) + (global $A (ref $func) (ref.func $func)) ;; the type here changed + + ;; NRML: (global $B (ref $struct) (struct.new $struct + ;; NRML-NEXT: (global.get $A) + ;; NRML-NEXT: )) + ;; GUFA: (global $B (ref $struct) (struct.new $struct + ;; GUFA-NEXT: (global.get $A) + ;; GUFA-NEXT: )) + (global $B (ref $struct) (struct.new $struct + (global.get $A) + )) + + ;; NRML: (func $func (type $func) + ;; NRML-NEXT: ) + ;; GUFA: (func $func (type $func) + ;; GUFA-NEXT: ) + (func $func (type $func) + ) +) + +;; Check we do not error on struct.new_default in a global. Here we can refine +;; the field to nullref. +(module + ;; NRML: (type $struct (struct (field nullfuncref))) + ;; GUFA: (type $struct (struct (field nullfuncref))) + (type $struct (struct (field funcref))) + + ;; NRML: (global $C (ref $struct) (struct.new_default $struct)) + ;; GUFA: (global $C (ref $struct) (struct.new_default $struct)) + (global $C (ref $struct) (struct.new_default $struct)) +) From 4734c0e7423b8088f48a612be0fc36e0f00866d7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Apr 2025 13:43:02 -0700 Subject: [PATCH 403/622] ClusterFuzz: Bundle mimalloc (#7450) The emsdk's linux builds now include mimalloc, so we must bundle it for ClusterFuzz, same as we already do for libc++. --- scripts/bundle_clusterfuzz.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/bundle_clusterfuzz.py b/scripts/bundle_clusterfuzz.py index 0dee8c95157..fb16f74d952 100755 --- a/scripts/bundle_clusterfuzz.py +++ b/scripts/bundle_clusterfuzz.py @@ -70,6 +70,7 @@ up). Note that these may take longer to show up than 1 and 2. ''' +import glob import os import subprocess import sys @@ -135,11 +136,15 @@ tar.add(libbinaryen, arcname=f'lib/libbinaryen{suffix}') # The emsdk build also includes some more necessary files. - for name in [f'libc++{suffix}', f'libc++{suffix}.2', f'libc++{suffix}.2.0']: - path = os.path.join(binaryen_lib, name) - if os.path.exists(path): - print(f' ......... : {path}') - tar.add(path, arcname=f'lib/{name}') + for lib in ['libc++', 'libmimalloc']: + # Include the main name plus any NAME.2.0 and such. + # TODO: Using ldd/otool would be better, to find the actual + # dependencies of libbinaryen. Using glob like this will + # pick up stale contents in the directory. + full_lib = os.path.join(binaryen_lib, lib) + suffix + for path in glob.glob(f'{full_lib}*'): + print(f' ............. : {path}') + tar.add(path, arcname=f'lib/{os.path.basename(path)}') # Add tests we will use as initial content under initial/. We put all the # tests from the test suite there. From 7e9e9dd55c79f427ae17bbc40c4e22f2bee18399 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Sat, 5 Apr 2025 04:43:48 +0800 Subject: [PATCH 404/622] OptimizeInstructions: Optimize (unsigned)x <= -1 ==> i32(1) even with side effects (#7436) Fixes #7434 --- src/passes/OptimizeInstructions.cpp | 4 +-- .../lit/passes/optimize-instructions-mvp.wast | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index d02f7f17e51..a367bab255e 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -4025,10 +4025,10 @@ struct OptimizeInstructions return right; } // (unsigned)x <= -1 ==> i32(1) - if (matches(curr, binary(LeU, pure(&left), ival(-1)))) { + if (matches(curr, binary(LeU, any(&left), ival(-1)))) { right->value = Literal::makeOne(Type::i32); right->type = Type::i32; - return right; + return getDroppedChildrenAndAppend(curr, right); } // (unsigned)x > -1 ==> i32(0) if (matches(curr, binary(GtU, pure(&left), ival(-1)))) { diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 8336568d15d..55a8746abea 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -11132,9 +11132,29 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.lt_s ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 0) @@ -11246,10 +11266,22 @@ (local.get $x) (i32.const -1) )) + (drop (i32.le_u + (i32.load + (i32.const 0) + ) + (i32.const -1) + )) (drop (i64.le_u (local.get $y) (i64.const -1) )) + (drop (i64.le_u + (i64.load + (i32.const 0) + ) + (i64.const -1) + )) (drop (i32.le_s (local.get $x) (i32.const -1) From 8e4d3c1e3f375d82ced41b99912bcd917b94fbe6 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Sat, 5 Apr 2025 06:32:22 +0800 Subject: [PATCH 405/622] OptimizeInstructions: Optimize (unsigned)x > -1 ==> i32(0) even with side effects (#7437) Fixes: #7435 --- src/passes/OptimizeInstructions.cpp | 4 +-- .../lit/passes/optimize-instructions-mvp.wast | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index a367bab255e..0d5745ca0cd 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -4031,10 +4031,10 @@ struct OptimizeInstructions return getDroppedChildrenAndAppend(curr, right); } // (unsigned)x > -1 ==> i32(0) - if (matches(curr, binary(GtU, pure(&left), ival(-1)))) { + if (matches(curr, binary(GtU, any(&left), ival(-1)))) { right->value = Literal::makeZero(Type::i32); right->type = Type::i32; - return right; + return getDroppedChildrenAndAppend(curr, right); } // (unsigned)x >= 0 ==> i32(1) if (matches(curr, binary(GeU, pure(&left), ival(0)))) { diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 55a8746abea..e4dd53f76c0 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -11111,9 +11111,29 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ge_s ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 0) @@ -11243,10 +11263,22 @@ (local.get $x) (i32.const -1) )) + (drop (i32.gt_u + (i32.load + (i32.const 0) + ) + (i32.const -1) + )) (drop (i64.gt_u (local.get $y) (i64.const -1) )) + (drop (i64.gt_u + (i64.load + (i32.const 0) + ) + (i64.const -1) + )) (drop (i32.gt_s (local.get $x) (i32.const -1) From 16dbac101ba3b7a930ad1481a71fe39bd5966a35 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Sat, 5 Apr 2025 07:32:11 +0800 Subject: [PATCH 406/622] OptimizeInstructions: Optimize x | -1 ==> -1 even with side effects (#7439) Fixes: #7438 --- src/passes/OptimizeInstructions.cpp | 4 +-- .../lit/passes/optimize-instructions-mvp.wast | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 0d5745ca0cd..ac15d5a490a 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -3993,8 +3993,8 @@ struct OptimizeInstructions return left; } // x | -1 ==> -1 - if (matches(curr, binary(Or, pure(&left), ival(-1)))) { - return right; + if (matches(curr, binary(Or, any(&left), ival(-1)))) { + return getDroppedChildrenAndAppend(curr, right); } // (signed)x % -1 ==> 0 if (matches(curr, binary(RemS, pure(&left), ival(-1)))) { diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index e4dd53f76c0..5b137afa989 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -9438,9 +9438,11 @@ ;; CHECK-NEXT: (i32.const -1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.or - ;; CHECK-NEXT: (local.tee $x - ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const -1) ;; CHECK-NEXT: ) @@ -9451,6 +9453,16 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.const -1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $y + ;; CHECK-NEXT: (i64.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $all_ones (param $x i32) (param $y i64) (drop @@ -9485,6 +9497,14 @@ (i64.const -1) ) ) + (drop + (i64.or + (local.tee $y + (i64.const 1337) + ) + (i64.const -1) + ) + ) ) ;; CHECK: (func $xor (param $x i32) (param $y i64) ;; CHECK-NEXT: (drop From b0d51f5af6d84617700c3f700bb11a72dce89ea8 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Tue, 8 Apr 2025 02:18:59 +0800 Subject: [PATCH 407/622] OptimizeInstructions: Optimize `unsigned(x) >= 0 => i32(1)` even with side effects (#7429) Fixes #7425 --- src/passes/OptimizeInstructions.cpp | 6 +-- .../lit/passes/optimize-instructions-mvp.wast | 40 +++++++++++++++++-- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index ac15d5a490a..6a1ae1d6726 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -553,15 +553,13 @@ struct OptimizeInstructions } { // unsigned(x) >= 0 => i32(1) - // TODO: Use getDroppedChildrenAndAppend() here, so we can optimize even - // if pure. Const* c; Expression* x; - if (matches(curr, binary(GeU, pure(&x), ival(&c))) && + if (matches(curr, binary(GeU, any(&x), ival(&c))) && c->value.isZero()) { c->value = Literal::makeOne(Type::i32); c->type = Type::i32; - return replaceCurrent(c); + return replaceCurrent(getDroppedChildrenAndAppend(curr, c)); } // unsigned(x) < 0 => i32(0) if (matches(curr, binary(LtU, pure(&x), ival(&c))) && diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 5b137afa989..91aab4c6354 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -11450,9 +11450,29 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -11659,10 +11679,22 @@ (local.get $x) (i32.const 0) )) + (drop (i32.ge_u + (i32.load + (i32.const 0) + ) + (i32.const 0) + )) (drop (i64.ge_u (local.get $y) (i64.const 0) )) + (drop (i64.ge_u + (i64.load + (i32.const 0) + ) + (i64.const 0) + )) ;; (unsigned)x < 0 => i32(0) (drop (i32.lt_u @@ -17389,7 +17421,7 @@ ) ;; CHECK: (func $skip-added-constants-zero-b (result i32) - ;; CHECK-NEXT: (i32.ge_u + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (i32.load @@ -17399,12 +17431,12 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) (func $skip-added-constants-zero-b (result i32) - ;; Parallel case to the above, with a zero in the added constant. We do not - ;; optimize. + ;; Parallel case to the above, with a zero in the added constant, but we + ;; do optimize the outer i32.ge_u away. (i32.ge_u (i32.add (i32.shr_u From 69e665eda383b967878773b4581ffac73e9b9eda Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Apr 2025 12:49:26 -0700 Subject: [PATCH 408/622] [GC] Fix order of operations in TypeRefiningGUFA: Restrictions must propagate (#7462) We must first find global restrictions, then propagate, so that the restrictions propagate. --- src/passes/TypeRefining.cpp | 6 ++-- test/lit/passes/type-refining-gufa.wast | 42 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 962621a6d34..d934709a256 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -190,9 +190,6 @@ struct TypeRefining : public Pass { } } - // Propagate to supertypes, so no field is less refined than its super. - propagator.propagateToSuperTypes(finalInfos); - // Take into account possible problems. This pass only refines struct // fields, and when we refine in a way that exceeds the wasm type system // then we fix that up with a cast (see below). However, we cannot use casts @@ -225,6 +222,9 @@ struct TypeRefining : public Pass { } } } + + // Propagate to supertypes, so no field is less refined than its super. + propagator.propagateToSuperTypes(finalInfos); } void useFinalInfos(Module* module, Propagator& propagator) { diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index 208c1fbd9a6..c866d80dca2 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -383,3 +383,45 @@ ;; GUFA: (global $C (ref $struct) (struct.new_default $struct)) (global $C (ref $struct) (struct.new_default $struct)) ) + +;; As above, but $struct has a supertype. We must propagate restrictions on it +;; to its supertype. After doing so, we can refine the field to (ref func) in +;; both, but no further, because of the restriction of global.get $A, which has +;; that type. +(module + (rec + ;; NRML: (rec + ;; NRML-NEXT: (type $super (sub (struct (field (ref func))))) + ;; GUFA: (rec + ;; GUFA-NEXT: (type $super (sub (struct (field (ref func))))) + (type $super (sub (struct (field funcref)))) + ;; NRML: (type $struct (sub $super (struct (field (ref func))))) + ;; GUFA: (type $struct (sub $super (struct (field (ref func))))) + (type $struct (sub $super (struct (field funcref)))) + ) + + ;; NRML: (type $func (func)) + ;; GUFA: (type $func (func)) + (type $func (func)) + + ;; NRML: (global $A (ref func) (ref.func $func)) + ;; GUFA: (global $A (ref func) (ref.func $func)) + (global $A (ref func) (ref.func $func)) + + ;; NRML: (global $B (ref $struct) (struct.new $struct + ;; NRML-NEXT: (global.get $A) + ;; NRML-NEXT: )) + ;; GUFA: (global $B (ref $struct) (struct.new $struct + ;; GUFA-NEXT: (global.get $A) + ;; GUFA-NEXT: )) + (global $B (ref $struct) (struct.new $struct + (global.get $A) + )) + + ;; NRML: (func $func (type $func) + ;; NRML-NEXT: ) + ;; GUFA: (func $func (type $func) + ;; GUFA-NEXT: ) + (func $func (type $func) + ) +) From 3ca38d82317e9a3ee0a22d8f99070e9a40d9166d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Apr 2025 16:07:15 -0700 Subject: [PATCH 409/622] [NFC] Add an explicit ModuleRunner.start() (#7463) Previously the constructor ran the start method and other initialization. Doing that in an explicit function that the user calls makes it possible to do things in the middle. The specific thing I would like to do in the middle is have a function to change the interpreter instance's mode to "reject as nonconstant relaxed SIMD", but I've wanted this in the past too, and worked around it - seems best to just fix it. Adding more flags to the constructor is another option, but there are already several, and more in the parent classes, and several levels of inheritance through which all such options must be forwarded, which is annoying. --- src/binaryen-c.cpp | 1 + src/tools/execution-results.h | 2 ++ src/tools/wasm-ctor-eval.cpp | 1 + src/tools/wasm-shell.cpp | 1 + src/wasm-interpreter.h | 7 ++++++- 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 8e8c295f2ca..95b7846ae1a 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -5651,6 +5651,7 @@ BinaryenModuleRef BinaryenModuleRead(char* input, size_t inputSize) { void BinaryenModuleInterpret(BinaryenModuleRef module) { ShellExternalInterface interface; ModuleRunner instance(*(Module*)module, &interface, {}); + instance.instantiate(); } BinaryenIndex BinaryenModuleAddDebugInfoFileName(BinaryenModuleRef module, diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index fe96fc55771..1e20fd1e5b5 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -271,6 +271,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + instance.instantiate(); interface.setModuleRunner(&instance); // execute all exported methods (that are therefore preserved through // opts) @@ -430,6 +431,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + instance.instantiate(); interface.setModuleRunner(&instance); return run(func, wasm, instance); } catch (const TrapException&) { diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 4b819b73988..bb4050003df 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -1287,6 +1287,7 @@ void evalCtors(Module& wasm, try { // create an instance for evalling EvallingModuleRunner instance(wasm, &interface, linkedInstances); + instance.instantiate(); interface.instanceInitialized = true; // go one by one, in order, until we fail // TODO: if we knew priorities, we could reorder? diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index eda4facc945..8899a097202 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -139,6 +139,7 @@ struct Shell { std::make_shared(linkedInstances); auto instance = std::make_shared(wasm, interface.get(), linkedInstances); + instance->instantiate(); return {{std::move(interface), std::move(instance)}}; } catch (...) { return Err{"failed to instantiate module"}; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 1215c8eef6b..b2739a16d9e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2900,7 +2900,12 @@ class ModuleRunnerBase : public ExpressionRunner { ExternalInterface* externalInterface, std::map> linkedInstances_ = {}) : ExpressionRunner(&wasm), wasm(wasm), - externalInterface(externalInterface), linkedInstances(linkedInstances_) { + externalInterface(externalInterface), linkedInstances(linkedInstances_) {} + + // Start up this instance. This must be called before doing anything else. + // (This is separate from the constructor so that it does not occur + // synchronously, which makes some code patterns harder to write.) + void instantiate() { // import globals from the outside externalInterface->importGlobals(globals, wasm); // generate internal (non-imported) globals From 948d4a68f123e8eae3c8c3e7f0c5b4293827473f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Apr 2025 11:09:05 -0700 Subject: [PATCH 410/622] [SIMD] Avoid comparing V8 to other VMs when relaxed SIMD is enabled (#7461) That spec allows for differences between VMs, so we must disable most comparisons. --- scripts/fuzz_opt.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 1a56e9c5ae7..a1e2d084aa5 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -152,11 +152,13 @@ def randomize_feature_opts(): # The shared-everything feature is new and we want to fuzz it, but it # also currently disables fuzzing V8, so disable it most of the time. # Same with custom descriptors and strings - all these cannot be run in - # V8 for now. + # V8 for now. Relaxed SIMD's nondeterminism disables much but not all of + # our V8 fuzzing, so avoid it too. if random.random() < 0.9: FEATURE_OPTS.append('--disable-shared-everything') FEATURE_OPTS.append('--disable-custom-descriptors') FEATURE_OPTS.append('--disable-strings') + FEATURE_OPTS.append('--disable-relaxed-simd') print('randomized feature opts:', '\n ' + '\n '.join(FEATURE_OPTS)) @@ -828,8 +830,10 @@ def can_compare_to_self(self): def can_compare_to_others(self): # If not legalized, the JS will fail immediately, so no point to - # compare to others. - return LEGALIZE and not NANS + # compare to others. Relaxed SIMD allows different behavior + # between VMs (in principle we could compare to other D8 + # variants, though TODO). + return self.can_compare_to_self() and LEGALIZE and all_disallowed(['relaxed-simd']) class D8Liftoff(D8): name = 'd8_liftoff' From 38a712f2061cc9fab08f7194951d53f0e2a363fe Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Apr 2025 13:37:57 -0700 Subject: [PATCH 411/622] [Relaxed SIMD] Do not optimize relaxed SIMD (#7465) Add an interpreter mode to return "nonconstant" on relaxed SIMD, which is what we need to do when optimizing, as we don't know what machine the code will run on (and relaxed SIMD instructions must match it). --- src/tools/execution-results.h | 4 ++ src/tools/wasm-shell.cpp | 3 + src/wasm-interpreter.h | 96 ++++++++++++++++++++++--- test/lit/passes/precompute-relaxed.wast | 36 ++++++++++ 4 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 test/lit/passes/precompute-relaxed.wast diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 1e20fd1e5b5..0dea839c839 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -271,6 +271,9 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + // This is not an optimization: we want to execute anything, even relaxed + // SIMD instructions. + instance.setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute); instance.instantiate(); interface.setModuleRunner(&instance); // execute all exported methods (that are therefore preserved through @@ -431,6 +434,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + instance.setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute); instance.instantiate(); interface.setModuleRunner(&instance); return run(func, wasm, instance); diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 8899a097202..9139983c947 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -139,6 +139,9 @@ struct Shell { std::make_shared(linkedInstances); auto instance = std::make_shared(wasm, interface.get(), linkedInstances); + // This is not an optimization: we want to execute anything, even relaxed + // SIMD instructions. + instance->setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute); instance->instantiate(); return {{std::move(interface), std::move(instance)}}; } catch (...) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index b2739a16d9e..eb9b40d491e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -219,6 +219,20 @@ class ExpressionRunner : public OverriddenVisitor { // Indicates no limit of maxDepth or maxLoopIterations. static const Index NO_LIMIT = 0; + enum RelaxedBehavior { + // Consider relaxed SIMD instructions non-constant. This is suitable for + // optimizations, as we bake the results of optimizations into the output, + // but relaxed operations must behave according to the host semantics, not + // ours, so we do not want to optimize such expressions. + NonConstant, + // Execute relaxed SIMD instructions. + Execute, + }; + +protected: + RelaxedBehavior relaxedBehavior = RelaxedBehavior::NonConstant; + +public: ExpressionRunner(Module* module = nullptr, Index maxDepth = NO_LIMIT, Index maxLoopIterations = NO_LIMIT) @@ -226,6 +240,8 @@ class ExpressionRunner : public OverriddenVisitor { } virtual ~ExpressionRunner() = default; + void setRelaxedBehavior(RelaxedBehavior value) { relaxedBehavior = value; } + Flow visit(Expression* curr) { depth++; if (maxDepth != NO_LIMIT && depth > maxDepth) { @@ -584,11 +600,21 @@ class ExpressionRunner : public OverriddenVisitor { return value.extAddPairwiseToSI32x4(); case ExtAddPairwiseUVecI16x8ToI32x4: return value.extAddPairwiseToUI32x4(); - case TruncSatSVecF32x4ToVecI32x4: case RelaxedTruncSVecF32x4ToVecI32x4: + // TODO: We could do this only if the actual values are in the relaxed + // range. + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case TruncSatSVecF32x4ToVecI32x4: return value.truncSatToSI32x4(); - case TruncSatUVecF32x4ToVecI32x4: case RelaxedTruncUVecF32x4ToVecI32x4: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case TruncSatUVecF32x4ToVecI32x4: return value.truncSatToUI32x4(); case ConvertSVecI32x4ToVecF32x4: return value.convertSToF32x4(); @@ -622,11 +648,19 @@ class ExpressionRunner : public OverriddenVisitor { return value.convertLowSToF64x2(); case ConvertLowUVecI32x4ToVecF64x2: return value.convertLowUToF64x2(); - case TruncSatZeroSVecF64x2ToVecI32x4: case RelaxedTruncZeroSVecF64x2ToVecI32x4: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case TruncSatZeroSVecF64x2ToVecI32x4: return value.truncSatZeroSToI32x4(); - case TruncSatZeroUVecF64x2ToVecI32x4: case RelaxedTruncZeroUVecF64x2ToVecI32x4: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case TruncSatZeroUVecF64x2ToVecI32x4: return value.truncSatZeroUToI32x4(); case DemoteZeroVecF64x2ToVecF32x4: return value.demoteZeroToF32x4(); @@ -989,8 +1023,12 @@ class ExpressionRunner : public OverriddenVisitor { return left.maxUI16x8(right); case AvgrUVecI16x8: return left.avgrUI16x8(right); - case Q15MulrSatSVecI16x8: case RelaxedQ15MulrSVecI16x8: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case Q15MulrSatSVecI16x8: return left.q15MulrSatSI16x8(right); case ExtMulLowSVecI16x8: return left.extMulLowSI16x8(right); @@ -1064,11 +1102,19 @@ class ExpressionRunner : public OverriddenVisitor { return left.mulF32x4(right); case DivVecF32x4: return left.divF32x4(right); - case MinVecF32x4: case RelaxedMinVecF32x4: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case MinVecF32x4: return left.minF32x4(right); - case MaxVecF32x4: case RelaxedMaxVecF32x4: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case MaxVecF32x4: return left.maxF32x4(right); case PMinVecF32x4: return left.pminF32x4(right); @@ -1082,11 +1128,19 @@ class ExpressionRunner : public OverriddenVisitor { return left.mulF64x2(right); case DivVecF64x2: return left.divF64x2(right); - case MinVecF64x2: case RelaxedMinVecF64x2: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case MinVecF64x2: return left.minF64x2(right); - case MaxVecF64x2: case RelaxedMaxVecF64x2: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case MaxVecF64x2: return left.maxF64x2(right); case PMinVecF64x2: return left.pminF64x2(right); @@ -1102,8 +1156,12 @@ class ExpressionRunner : public OverriddenVisitor { case NarrowUVecI32x4ToVecI16x8: return left.narrowUToI16x8(right); - case SwizzleVecI8x16: case RelaxedSwizzleVecI8x16: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + [[fallthrough]]; + case SwizzleVecI8x16: return left.swizzleI8x16(right); case DotI8x16I7x16SToVecI16x8: @@ -1213,16 +1271,34 @@ class ExpressionRunner : public OverriddenVisitor { return c.bitselectV128(a, b); case RelaxedMaddVecF16x8: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } return a.relaxedMaddF16x8(b, c); case RelaxedNmaddVecF16x8: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } return a.relaxedNmaddF16x8(b, c); case RelaxedMaddVecF32x4: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } return a.relaxedMaddF32x4(b, c); case RelaxedNmaddVecF32x4: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } return a.relaxedNmaddF32x4(b, c); case RelaxedMaddVecF64x2: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } return a.relaxedMaddF64x2(b, c); case RelaxedNmaddVecF64x2: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } return a.relaxedNmaddF64x2(b, c); default: // TODO: implement signselect and dot_add diff --git a/test/lit/passes/precompute-relaxed.wast b/test/lit/passes/precompute-relaxed.wast new file mode 100644 index 00000000000..84682318cf4 --- /dev/null +++ b/test/lit/passes/precompute-relaxed.wast @@ -0,0 +1,36 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s --precompute -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func (result v128))) + + ;; CHECK: (func $relaxed-max (type $0) (result v128) + ;; CHECK-NEXT: (f32x4.relaxed_max + ;; CHECK-NEXT: (v128.const i32x4 0x3f800000 0x40000000 0x40400000 0x40800000) + ;; CHECK-NEXT: (v128.const i32x4 0x40a00000 0x40c00000 0x40e00000 0x41000000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $relaxed-max (result v128) + ;; Though this is all constant and precomputable, we do not optimize + ;; relaxed SIMD operations. + ;; TODO if we optimize some cases of relaxed operations (ones without + ;; nondeterminism) we should pick proper nondeterministic values here. + (f32x4.relaxed_max + (v128.const f32x4 1 2 3 4) + (v128.const f32x4 5 6 7 8) + ) + ) + + ;; CHECK: (func $normal-max (type $0) (result v128) + ;; CHECK-NEXT: (v128.const i32x4 0x41100000 0x40c00000 0x40e00000 0x41000000) + ;; CHECK-NEXT: ) + (func $normal-max (result v128) + ;; For comparison, we do optimize non-relaxed SIMD, even one with a + ;; corresponding relaxed variant. + (f32x4.max + (v128.const f32x4 5 6 7 8) + (v128.const f32x4 9 3 1 0) + ) + ) +) From aa08927f4d7b81c9376449c28ed3742fd0231f0a Mon Sep 17 00:00:00 2001 From: Gulg <65360617+GulgDev@users.noreply.github.com> Date: Wed, 9 Apr 2025 01:41:02 +0500 Subject: [PATCH 412/622] Fix operator precedence in Binaryen.js tests [NFC] (#7459) The equality operator (`==`) has higher operator precedence than bitwise OR (`|`), meaning that `a == b | c` is interpreted like `(a == b) | c` and not `a == (b | c)`, how it was intended. --- test/binaryen.js/sideffects.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/binaryen.js/sideffects.js b/test/binaryen.js/sideffects.js index be5414aed4e..a2ff4778a33 100644 --- a/test/binaryen.js/sideffects.js +++ b/test/binaryen.js/sideffects.js @@ -88,7 +88,7 @@ assert( module ) == - binaryen.SideEffects.ReadsMemory | binaryen.SideEffects.ImplicitTrap + (binaryen.SideEffects.ReadsMemory | binaryen.SideEffects.ImplicitTrap) ); assert( binaryen.getSideEffects( @@ -99,7 +99,7 @@ assert( module ) == - binaryen.SideEffects.WritesMemory | binaryen.SideEffects.ImplicitTrap + (binaryen.SideEffects.WritesMemory | binaryen.SideEffects.ImplicitTrap) ); assert( binaryen.getSideEffects( @@ -121,7 +121,7 @@ assert( module ) == - binaryen.SideEffects.Calls | binaryen.SideEffects.Throws + (binaryen.SideEffects.Calls | binaryen.SideEffects.Throws) ); assert( From 8d85f4ee6d5e1eba198535dada0330a49fb073bf Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Wed, 9 Apr 2025 06:20:30 +0800 Subject: [PATCH 413/622] OptimizeInstructions: Optimize unsigned(x) < 0 => i32(0) even with side effects (#7428) Fixes #7418 --- src/passes/OptimizeInstructions.cpp | 4 +-- .../lit/passes/optimize-instructions-mvp.wast | 32 +++++++++++++++++++ test/wasm2js/bulk-memory.2asm.js.opt | 9 +----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 6a1ae1d6726..2fdd138f85e 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -562,11 +562,11 @@ struct OptimizeInstructions return replaceCurrent(getDroppedChildrenAndAppend(curr, c)); } // unsigned(x) < 0 => i32(0) - if (matches(curr, binary(LtU, pure(&x), ival(&c))) && + if (matches(curr, binary(LtU, any(&x), ival(&c))) && c->value.isZero()) { c->value = Literal::makeZero(Type::i32); c->type = Type::i32; - return replaceCurrent(c); + return replaceCurrent(getDroppedChildrenAndAppend(curr, c)); } } } diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 91aab4c6354..f8652cdf15f 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -11476,9 +11476,29 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ne ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 0) @@ -11701,10 +11721,22 @@ (local.get $x) (i32.const 0) )) + (drop (i32.lt_u + (i32.load + (i32.const 0) + ) + (i32.const 0) + )) (drop (i64.lt_u (local.get $y) (i64.const 0) )) + (drop (i64.lt_u + (i64.load + (i32.const 0) + ) + (i64.const 0) + )) ;; (unsigned)x > 0 => x != 0 (drop (i32.gt_u diff --git a/test/wasm2js/bulk-memory.2asm.js.opt b/test/wasm2js/bulk-memory.2asm.js.opt index 620f181fcd0..50ddecee8b2 100644 --- a/test/wasm2js/bulk-memory.2asm.js.opt +++ b/test/wasm2js/bulk-memory.2asm.js.opt @@ -275,9 +275,6 @@ var retasmFunc = asmFunc({ export var init = retasmFunc.init; export var load8_u = retasmFunc.load8_u; - var bufferView; -function wasm2js_trap() { throw new Error('abort'); } - function asmFunc(imports) { var buffer = new ArrayBuffer(65536); var HEAP8 = new Int8Array(buffer); @@ -303,12 +300,9 @@ function asmFunc(imports) { } function $1() { - if (__wasm_memory_size() << 16 >>> 0 < 0) { - wasm2js_trap() - } + __wasm_memory_size(); } - bufferView = HEAPU8; function __wasm_memory_size() { return buffer.byteLength / 65536 | 0; } @@ -330,7 +324,6 @@ function asmFunc(imports) { HEAPF32 = new Float32Array(newBuffer); HEAPF64 = new Float64Array(newBuffer); buffer = newBuffer; - bufferView = HEAPU8; } return oldPages; } From d0d970cb5f70de233106d38686f8e49f69654193 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Tue, 8 Apr 2025 16:09:39 -0700 Subject: [PATCH 414/622] Switch source map parsing to use the JSON parser (#7467) This is in preparation for fixing #6805. Aside from cleaning up and reusing more code, it allows optional fields in the source map and random access to those fields, rather than walking through the whole JSON file just once. (It does keep the current behavior of walking through the actual mappings in a single pass synchronized with reading the binary). It also adds some unit tests. --- src/source-map.h | 30 +++------ src/wasm-binary.h | 2 +- src/wasm.h | 5 ++ src/wasm/source-map.cpp | 127 ++++++++++++------------------------- src/wasm/wasm-binary.cpp | 4 +- test/gtest/source-map.cpp | 130 ++++++++++++++++++++++++++++++++++---- 6 files changed, 174 insertions(+), 124 deletions(-) diff --git a/src/source-map.h b/src/source-map.h index d8c50b5e1fa..88058d29da9 100644 --- a/src/source-map.h +++ b/src/source-map.h @@ -18,7 +18,6 @@ #define wasm_source_map_h #include -#include #include "wasm.h" @@ -32,7 +31,8 @@ struct MapParseException { }; class SourceMapReader { - const std::vector& buffer; + std::vector& buffer; + std::string_view mappings; // Current position in the source map buffer. size_t pos = 0; @@ -53,9 +53,9 @@ class SourceMapReader { bool hasSymbol = false; public: - SourceMapReader(const std::vector& buffer) : buffer(buffer) {} + SourceMapReader(std::vector& buffer) : buffer(buffer) {} - void readHeader(Module& wasm); + void parse(Module& wasm); std::optional readDebugLocationAt(size_t currLocation); @@ -65,10 +65,12 @@ class SourceMapReader { private: char peek() { - if (pos >= buffer.size()) { + if (pos == mappings.size()) { + return '"'; + } else if (pos > mappings.size()) { throw MapParseException("unexpected end of source map"); } - return buffer[pos]; + return mappings[pos]; } char get() { @@ -77,22 +79,6 @@ class SourceMapReader { return c; } - bool maybeGet(char c) { - if (pos < buffer.size() && peek() == c) { - ++pos; - return true; - } - return false; - } - - void expect(char c) { - using namespace std::string_literals; - char got = get(); - if (got != c) { - throw MapParseException("expected '"s + c + "', got '" + got + "'"); - } - } - int32_t readBase64VLQ(); }; diff --git a/src/wasm-binary.h b/src/wasm-binary.h index bd9a521e4a4..eb83f3dae59 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1464,7 +1464,7 @@ class WasmBinaryReader { WasmBinaryReader(Module& wasm, FeatureSet features, const std::vector& input, - const std::vector& sourceMap = defaultEmptySourceMap); + std::vector& sourceMap = defaultEmptySourceMap); void setDebugInfo(bool value) { debugInfo = value; } void setDWARF(bool value) { DWARF = value; } diff --git a/src/wasm.h b/src/wasm.h index e3979e21975..97098fd749a 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2173,6 +2173,11 @@ class Function : public Importable { ? columnNumber < other.columnNumber : symbolNameIndex < other.symbolNameIndex; } + void dump() { + std::cerr << (symbolNameIndex ? symbolNameIndex.value() : -1) << " @ " + << fileIndex << ":" << lineNumber << ":" << columnNumber + << "\n"; + } }; // One can explicitly set the debug location of an expression to // nullopt to stop the propagation of debug locations. diff --git a/src/wasm/source-map.cpp b/src/wasm/source-map.cpp index 7ad26e89861..adba2504350 100644 --- a/src/wasm/source-map.cpp +++ b/src/wasm/source-map.cpp @@ -16,6 +16,7 @@ #include "source-map.h" #include "support/colors.h" +#include "support/json.h" namespace wasm { @@ -33,96 +34,55 @@ void MapParseException::dump(std::ostream& o) const { Colors::normal(o); } -void SourceMapReader::readHeader(Module& wasm) { - assert(pos == 0); +void SourceMapReader::parse(Module& wasm) { if (buffer.empty()) { return; } - - auto skipWhitespace = [&]() { - while (pos < buffer.size() && (buffer[pos] == ' ' || buffer[pos] == '\n')) { - ++pos; + json::Value json; + json.parse(buffer.data(), json::Value::ASCII); + if (!json.isObject()) { + throw MapParseException("Source map is not valid JSON"); + } + if (!(json.has("version") && json["version"]->isNumber() && + json["version"]->getInteger() == 3)) { + throw MapParseException("Source map version missing or is not 3"); + } + if (!(json.has("sources") && json["sources"]->isArray())) { + throw MapParseException("Source map sources missing or not an array"); + } + json::Ref s = json["sources"]; + for (size_t i = 0; i < s->size(); i++) { + json::Ref v = s[i]; + if (!(s[i]->isString())) { + throw MapParseException("Source map sources contains non-string"); } - }; - - auto findField = [&](const char* name) { - bool matching = false; - size_t len = strlen(name); - size_t index = 0; - while (1) { - char ch = get(); - if (ch == '\"') { - if (matching) { - if (index == len) { - // We matched a terminating quote. - break; - } - matching = false; - } else { - // Beginning of a new potential match. - matching = true; - index = 0; - } - } else if (matching && name[index] == ch) { - ++index; - } else if (matching) { - matching = false; - } + wasm.debugInfoFileNames.push_back(v->getCString()); + } + + if (json.has("names")) { + json::Ref n = json["names"]; + if (!n->isArray()) { + throw MapParseException("Source map names is not an array"); } - skipWhitespace(); - expect(':'); - skipWhitespace(); - return true; - }; - - auto readString = [&](std::string& str) { - std::vector vec; - skipWhitespace(); - expect('\"'); - while (1) { - if (maybeGet('\"')) { - break; + for (size_t i = 0; i < n->size(); i++) { + json::Ref v = n[i]; + if (!v->isString()) { + throw MapParseException("Source map names contains non-string"); } - vec.push_back(get()); + wasm.debugInfoSymbolNames.push_back(v->getCString()); } - skipWhitespace(); - str = std::string(vec.begin(), vec.end()); - }; - - if (!findField("sources")) { - throw MapParseException("cannot find the 'sources' field in map"); } - skipWhitespace(); - expect('['); - if (!maybeGet(']')) { - do { - std::string file; - readString(file); - wasm.debugInfoFileNames.push_back(file); - } while (maybeGet(',')); - expect(']'); + if (!json.has("mappings")) { + throw MapParseException("Source map mappings missing"); } - - if (findField("names")) { - skipWhitespace(); - expect('['); - if (!maybeGet(']')) { - do { - std::string symbol; - readString(symbol); - wasm.debugInfoSymbolNames.push_back(symbol); - } while (maybeGet(',')); - expect(']'); - } + json::Ref m = json["mappings"]; + if (!m->isString()) { + throw MapParseException("Source map mappings is not a string"); } - if (!findField("mappings")) { - throw MapParseException("cannot find the 'mappings' field in map"); - } - - expect('\"'); - if (maybeGet('\"')) { + mappings = m->getCString(); + if (mappings.empty()) { // There are no mappings. location = 0; return; @@ -134,10 +94,6 @@ void SourceMapReader::readHeader(Module& wasm) { std::optional SourceMapReader::readDebugLocationAt(size_t currLocation) { - if (pos >= buffer.size()) { - return std::nullopt; - } - while (location && location <= currLocation) { do { char next = peek(); @@ -164,13 +120,13 @@ SourceMapReader::readDebugLocationAt(size_t currLocation) { } while (false); // Check whether there is another record to read the position for. - char next = get(); - if (next == '\"') { + + if (peek() == '\"') { // End of records. location = 0; break; } - if (next != ',') { + if (get() != ',') { throw MapParseException("Expected delimiter"); } @@ -181,7 +137,6 @@ SourceMapReader::readDebugLocationAt(size_t currLocation) { if (!hasInfo) { return std::nullopt; } - auto sym = hasSymbol ? symbol : std::optional{}; return Function::DebugLocation{file, line, col, sym}; } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 53b736fcf4c..415fc9a8730 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1774,7 +1774,7 @@ void WasmBinaryWriter::writeMemoryOrder(MemoryOrder order, bool isRMW) { WasmBinaryReader::WasmBinaryReader(Module& wasm, FeatureSet features, const std::vector& input, - const std::vector& sourceMap) + std::vector& sourceMap) : wasm(wasm), allocator(wasm.allocator), input(input), builder(wasm), sourceMapReader(sourceMap) { wasm.features = features; @@ -1824,7 +1824,7 @@ void WasmBinaryReader::read() { } readHeader(); - sourceMapReader.readHeader(wasm); + sourceMapReader.parse(wasm); // Read sections until the end while (more()) { diff --git a/test/gtest/source-map.cpp b/test/gtest/source-map.cpp index c943be17239..5076fb8ded1 100644 --- a/test/gtest/source-map.cpp +++ b/test/gtest/source-map.cpp @@ -20,32 +20,136 @@ using namespace wasm; -using SourceMapTest = PrintTest; +class SourceMapTest : public PrintTest { + std::vector buffer; -// Check that debug location parsers can handle single-segment mappings. -TEST_F(SourceMapTest, SourceMappingSingleSegment) { - auto text = "(module)"; +protected: Module wasm; - parseWast(wasm, text); + std::unique_ptr reader; + + void SetUp() override { + PrintTest::SetUp(); + reader.reset(); + parseWast(wasm, "(module)"); + } + void parseMap(std::string& sourceMap) { + buffer = {sourceMap.begin(), sourceMap.end()}; + reader.reset(new SourceMapReader(buffer)); + reader->parse(wasm); + } + + void ExpectDbgLocEq(size_t location, + BinaryLocation file, + BinaryLocation line, + BinaryLocation col, + std::optional sym) { + auto loc_str = std::stringstream() << "location: " << location; + SCOPED_TRACE(loc_str.str()); + auto loc = reader->readDebugLocationAt(location); + ASSERT_TRUE(loc.has_value()); + EXPECT_EQ(loc->fileIndex, file); + EXPECT_EQ(loc->lineNumber, line); + EXPECT_EQ(loc->columnNumber, col); + EXPECT_EQ(loc->symbolNameIndex, sym); + } +}; + +// Check that debug location parsers can handle single-segment mappings. +TEST_F(SourceMapTest, SourceMappingSingleSegment) { // A single-segment mapping starting at offset 0. std::string sourceMap = R"( { "version": 3, "sources": [], + "sourcesContent": [], "names": [], "mappings": "A" } )"; - std::vector buffer(sourceMap.begin(), sourceMap.end()); + parseMap(sourceMap); + + auto loc = reader->readDebugLocationAt(0); + EXPECT_FALSE(loc.has_value()); +} + +TEST_F(SourceMapTest, BadSourceMap) { + // This source map is missing the version field. + std::string sourceMap = R"( + { + "sources": [], + "names": [], + "mappings": "A" + } + )"; + EXPECT_THROW(parseMap(sourceMap), MapParseException); +} + +TEST_F(SourceMapTest, SourcesAndNames) { + std::string sourceMap = R"( + { + "version": 3, + "sources": ["foo.c", "bar.c"], + "names": ["foo", "bar"], + "mappings": "" + } + )"; + parseMap(sourceMap); + + EXPECT_EQ(wasm.debugInfoFileNames.size(), 2); + EXPECT_EQ(wasm.debugInfoFileNames[0], "foo.c"); + EXPECT_EQ(wasm.debugInfoFileNames[1], "bar.c"); + EXPECT_EQ(wasm.debugInfoSymbolNames.size(), 2); + EXPECT_EQ(wasm.debugInfoSymbolNames[0], "foo"); + EXPECT_EQ(wasm.debugInfoSymbolNames[1], "bar"); +} - SourceMapReader reader(buffer); +TEST_F(SourceMapTest, OptionalFields) { + // The "names" field is optional. + std::string sourceMap = R"( + { + "version": 3, + "sources": [], + "mappings": "A" + } + )"; + parseMap(sourceMap); +} + +// This map is taken from test/fib-dbg.wasm +TEST_F(SourceMapTest, Fibonacci) { + // Test mapping parsing and debug locs + std::string sourceMap = R"( + { + "version":3, + "sources":["fib.c"], + "names":[], + "mappings": "moBAEA,4BAKA,QAJA,OADA,OAAA,uCAKA" + } + )"; + parseMap(sourceMap); - // Test `readSourceMapHeader` (only check for errors, as there is no mapping - // to print). - reader.readHeader(wasm); + // Location before the first record has no value + auto loc = reader->readDebugLocationAt(642); + EXPECT_FALSE(loc.has_value()); - // Test `readNextDebugLocation`. - // TODO: Actually check the result. - reader.readDebugLocationAt(1); + // TODO: These column numbers show as 0 but emsymbolizer.py prints them as 1. + // Figure out which is right. + // First location + ExpectDbgLocEq(643, 0, 3, 0, std::nullopt); + // locations in between records have the same value as the previous one. + for (size_t l = 644; l < 671; l++) { + ExpectDbgLocEq(l, 0, 3, 0, std::nullopt); + } + // Subsequent ones are on record boundaries + ExpectDbgLocEq(671, 0, 8, 0, std::nullopt); + ExpectDbgLocEq(679, 0, 4, 0, std::nullopt); + ExpectDbgLocEq(686, 0, 3, 0, std::nullopt); + ExpectDbgLocEq(693, 0, 3, 0, std::nullopt); + ExpectDbgLocEq(732, 0, 8, 0, std::nullopt); + // Entries after the last record have the same value as the last one. + ExpectDbgLocEq(733, 0, 8, 0, std::nullopt); + // Should we return empty values for locations that are past the end of the + // program? + ExpectDbgLocEq(9999, 0, 8, 0, std::nullopt); } From ea7593b22a63eda68fe2e3229ab63657d8586fa1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Apr 2025 08:38:50 -0700 Subject: [PATCH 415/622] [Fuzzer] Compare V8 variants (#7469) Relaxed SIMD prevents comparisons between VMs, but v8-liftoff can be compared to v8-turboshaft for example, as it is the same VM in another mode. To do this, replace the `can_compare_to_others` with a function that considers a specific other VM. --- scripts/fuzz_opt.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index a1e2d084aa5..5ae3a522c16 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -807,7 +807,7 @@ def can_run(self, wasm): def can_compare_to_self(self): return True - def can_compare_to_others(self): + def can_compare_to_other(self, other): return True class D8: @@ -828,12 +828,15 @@ def can_compare_to_self(self): # can compare to themselves after opts in that case. return not NANS - def can_compare_to_others(self): + def can_compare_to_other(self, other): + # Relaxed SIMD allows different behavior between VMs, so only + # allow comparisons to other d8 variants if it is enabled. + if not all_disallowed(['relaxed-simd']) and not other.name.startswith('d8'): + return False + # If not legalized, the JS will fail immediately, so no point to - # compare to others. Relaxed SIMD allows different behavior - # between VMs (in principle we could compare to other D8 - # variants, though TODO). - return self.can_compare_to_self() and LEGALIZE and all_disallowed(['relaxed-simd']) + # compare to others. + return self.can_compare_to_self() and LEGALIZE class D8Liftoff(D8): name = 'd8_liftoff' @@ -887,7 +890,7 @@ def can_compare_to_self(self): # expects, but that's not quite what C has return not NANS - def can_compare_to_others(self): + def can_compare_to_other(self, other): # C won't trap on OOB, and NaNs can differ from wasm VMs return not OOB and not NANS @@ -934,7 +937,7 @@ def can_run(self, wasm): return super(Wasm2C2Wasm, self).can_run(wasm) and self.has_emcc and \ os.path.getsize(wasm) <= INPUT_SIZE_MEAN - def can_compare_to_others(self): + def can_compare_to_other(self, other): # NaNs can differ from wasm VMs return not NANS @@ -962,10 +965,12 @@ def run_vms(self, wasm): ignored_before = ignored_vm_runs # vm_results will map vms to their results + relevant_vms = [] vm_results = {} for vm in self.vms: if vm.can_run(wasm): print(f'[CompareVMs] running {vm.name}') + relevant_vms.append(vm) vm_results[vm] = fix_output(vm.run(wasm)) # If the binaryen interpreter hit a host limitation then do not @@ -986,13 +991,13 @@ def run_vms(self, wasm): return vm_results # compare between the vms on this specific input - first_vm = None - for vm in vm_results.keys(): - if vm.can_compare_to_others(): - if first_vm is None: - first_vm = vm - else: - compare_between_vms(vm_results[first_vm], vm_results[vm], 'CompareVMs between VMs: ' + first_vm.name + ' and ' + vm.name) + num_vms = len(relevant_vms) + for i in range(0, num_vms): + for j in range(i + 1, num_vms): + vm1 = relevant_vms[i] + vm2 = relevant_vms[j] + if vm1.can_compare_to_other(vm2) and vm2.can_compare_to_other(vm1): + compare_between_vms(vm_results[vm1], vm_results[vm2], 'CompareVMs between VMs: ' + vm1.name + ' and ' + vm2.name) return vm_results From 217240282f95b668ad585d5411153857866a9b83 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Wed, 9 Apr 2025 11:25:53 -0700 Subject: [PATCH 416/622] Add support for more source map fields (#7473) Read the "sourcesContent", "file", and "sourceRoot" fields from incoming source maps, attach them to the wasm IR module, and write them back to the output source map. These fields are unchanged by Binaryen's updates to the mappings, so they do not need to be decoded or interpreted. Fixes #6805 --- src/wasm.h | 8 +++- src/wasm/source-map.cpp | 26 ++++++++++++ src/wasm/wasm-binary.cpp | 43 +++++++++++++------- test/gtest/source-map.cpp | 35 ++++++++++++++++ test/lit/sourcemap-sourceroot-file.wat | 14 +++++++ test/lit/sourcemap-sourceroot-file.wat.map | 9 ++++ test/lit/sourcemap-sourceroot-file.wat.wasm | Bin 0 -> 8 bytes 7 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 test/lit/sourcemap-sourceroot-file.wat create mode 100644 test/lit/sourcemap-sourceroot-file.wat.map create mode 100644 test/lit/sourcemap-sourceroot-file.wat.wasm diff --git a/src/wasm.h b/src/wasm.h index 97098fd749a..3d4ce837b94 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2406,9 +2406,15 @@ class Module { // Optional user section IR representation. std::unique_ptr dylinkSection; - // Source maps debug info. + // Source maps debug info. All of these fields are read directly in from the + // source map and are encoded as in the original JSON (UTF-8 encoded with + // with escaped quotes and slashes). The string values are uninterpreted in + // Binaryen, and they are written directly back out without re-encoding. std::vector debugInfoFileNames; std::vector debugInfoSymbolNames; + std::string debugInfoSourceRoot; + std::string debugInfoFile; + std::vector debugInfoSourcesContent; // `features` are the features allowed to be used in this module and should be // respected regardless of the value of`hasFeaturesSection`. diff --git a/src/wasm/source-map.cpp b/src/wasm/source-map.cpp index adba2504350..cce8a5967f1 100644 --- a/src/wasm/source-map.cpp +++ b/src/wasm/source-map.cpp @@ -59,6 +59,16 @@ void SourceMapReader::parse(Module& wasm) { wasm.debugInfoFileNames.push_back(v->getCString()); } + if (json.has("sourcesContent")) { + json::Ref sc = json["sourcesContent"]; + if (!sc->isArray()) { + throw MapParseException("Source map sourcesContent is not an array"); + } + for (size_t i = 0; i < sc->size(); i++) { + wasm.debugInfoSourcesContent.push_back(sc[i]->getCString()); + } + } + if (json.has("names")) { json::Ref n = json["names"]; if (!n->isArray()) { @@ -73,6 +83,22 @@ void SourceMapReader::parse(Module& wasm) { } } + if (json.has("sourceRoot")) { + json::Ref sr = json["sourceRoot"]; + if (!sr->isString()) { + throw MapParseException("Source map sourceRoot is not a string"); + } + wasm.debugInfoSourceRoot = sr->getCString(); + } + + if (json.has("file")) { + json::Ref f = json["file"]; + if (!f->isString()) { + throw MapParseException("Source map file is not a string"); + } + wasm.debugInfoFile = f->getCString(); + } + if (!json.has("mappings")) { throw MapParseException("Source map mappings missing"); } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 415fc9a8730..b2c5842f4a4 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1226,25 +1226,40 @@ void WasmBinaryWriter::writeSourceMapProlog() { } } - *sourceMap << "\"sources\":["; - for (size_t i = 0; i < wasm->debugInfoFileNames.size(); i++) { - if (i > 0) { - *sourceMap << ","; + auto writeOptionalString = [&](const char* name, const std::string& str) { + if (!str.empty()) { + *sourceMap << "\"" << name << "\":\"" << str << "\","; } - // TODO respect JSON string encoding, e.g. quotes and control chars. - *sourceMap << "\"" << wasm->debugInfoFileNames[i] << "\""; - } - *sourceMap << "],\"names\":["; + }; - for (size_t i = 0; i < wasm->debugInfoSymbolNames.size(); i++) { - if (i > 0) { - *sourceMap << ","; + writeOptionalString("file", wasm->debugInfoFile); + writeOptionalString("sourceRoot", wasm->debugInfoSourceRoot); + + auto writeStringVector = [&](const char* name, + const std::vector& vec) { + *sourceMap << "\"" << name << "\":["; + for (size_t i = 0; i < vec.size(); i++) { + if (i > 0) { + *sourceMap << ","; + } + *sourceMap << "\"" << vec[i] << "\""; } - // TODO respect JSON string encoding, e.g. quotes and control chars. - *sourceMap << "\"" << wasm->debugInfoSymbolNames[i] << "\""; + *sourceMap << "],"; + }; + + writeStringVector("sources", wasm->debugInfoFileNames); + + if (!wasm->debugInfoSourcesContent.empty()) { + writeStringVector("sourcesContent", wasm->debugInfoSourcesContent); } - *sourceMap << "],\"mappings\":\""; + // TODO: This field is optional; maybe we should omit if it's empty. + // TODO: Binaryen actually does not correctly preserve symbol names when it + // rewrites the mappings. We should maybe just drop them, or else handle + // them correctly. + writeStringVector("names", wasm->debugInfoSymbolNames); + + *sourceMap << "\"mappings\":\""; } static void writeBase64VLQ(std::ostream& out, int32_t n) { diff --git a/test/gtest/source-map.cpp b/test/gtest/source-map.cpp index 5076fb8ded1..657034befcb 100644 --- a/test/gtest/source-map.cpp +++ b/test/gtest/source-map.cpp @@ -153,3 +153,38 @@ TEST_F(SourceMapTest, Fibonacci) { // program? ExpectDbgLocEq(9999, 0, 8, 0, std::nullopt); } + +TEST_F(SourceMapTest, SourceMapSourceRootFile) { + std::string sourceMap = R"( + { + "version":3, + "file": "foo.wasm", + "sources":[], + "names":[], + "mappings": "", + "sourceRoot": "/foo/bar" + } + )"; + parseMap(sourceMap); + EXPECT_EQ(wasm.debugInfoSourceRoot, "/foo/bar"); + EXPECT_EQ(wasm.debugInfoFile, "foo.wasm"); +} + +TEST_F(SourceMapTest, SourcesContent) { + // The backslash escapes appear in the JSON encoding, and are preserved in + // the internal representation. The string values are uninterpreted in + // Binaryen, and they are written directly back out without re-encoding. + std::string sourceMap = R"( + { + "version": 3, + "sources": ["foo.c"], + "sourcesContent": ["#include int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"], + "mappings" : "" + } + )"; + parseMap(sourceMap); + ASSERT_EQ(wasm.debugInfoSourcesContent.size(), 1); + EXPECT_EQ(wasm.debugInfoSourcesContent[0], + "#include int main()\\n{ printf(\\\"Gr\\u00fc\\u00df " + "Gott, Welt!\\\"); return 0;}"); +} diff --git a/test/lit/sourcemap-sourceroot-file.wat b/test/lit/sourcemap-sourceroot-file.wat new file mode 100644 index 00000000000..12bcc693d50 --- /dev/null +++ b/test/lit/sourcemap-sourceroot-file.wat @@ -0,0 +1,14 @@ +;; RUN: wasm-opt %s.wasm -ism %s.map -osm %t -o %t2 +;; Running multiple times is needed here because the output is all on one line. +;; RUN: cat %t | filecheck %s --check-prefix=FILE +;; RUN: cat %t | filecheck %s --check-prefix=SOURCEROOT +;; RUN: cat %t | filecheck %s --check-prefix=CONTENT + + +;; This wat file is not actually part of the test (the binary file is used), +;; but no comments are allowed in JSON so the RUN and CHECK lines are here. + +;; FILE: "file":"foo.wasm", +;; SOURCEROOT: "sourceRoot":"/foo/bar", +;; CONTENT: "sourcesContent":["#include int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"] +(module) diff --git a/test/lit/sourcemap-sourceroot-file.wat.map b/test/lit/sourcemap-sourceroot-file.wat.map new file mode 100644 index 00000000000..3f98ccd28a2 --- /dev/null +++ b/test/lit/sourcemap-sourceroot-file.wat.map @@ -0,0 +1,9 @@ +{ + "version":3, + "file": "foo.wasm", + "sources":[], + "names":[], + "mappings": "", + "sourceRoot": "/foo/bar", + "sourcesContent": ["#include int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"] +} diff --git a/test/lit/sourcemap-sourceroot-file.wat.wasm b/test/lit/sourcemap-sourceroot-file.wat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 From deb404ca69eb5a5c10a0db4f13a9714bd1334f9c Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Wed, 9 Apr 2025 11:53:13 -0700 Subject: [PATCH 417/622] [Outlining] Improve logging (#7464) Improve the quality of debug logs during outlining by: - reorder logs to occur before corresponding asserts - rename the debug logging macro used in Outlining.cpp so its not defined twice - add a parameter to function outline to thread the data needed for logging - add a parameters to struct OutliningSequence to connect the sequence back to its location in the stringified binary --- src/passes/Outlining.cpp | 153 ++++++++++++++++++++++++++-------- src/passes/stringify-walker.h | 14 ---- 2 files changed, 118 insertions(+), 49 deletions(-) diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index 8ec0e14b415..85bc12e5869 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -24,9 +24,9 @@ #define OUTLINING_DEBUG 0 #if OUTLINING_DEBUG -#define DBG(statement) statement +#define ODBG(statement) statement #else -#define DBG(statement) +#define ODBG(statement) #endif // Check a Result or MaybeResult for error and call Fatal() if the error exists. @@ -37,6 +37,34 @@ namespace wasm { +struct OutliningSequence { + unsigned startIdx; + unsigned endIdx; + Name func; + bool endsTypeUnreachable; +#if OUTLINING_DEBUG + unsigned programIdx; +#endif + + OutliningSequence(unsigned startIdx, + unsigned endIdx, + Name func, + bool endsTypeUnreachable +#if OUTLINING_DEBUG + , + unsigned programIdx +#endif + ) + : startIdx(startIdx), endIdx(endIdx), func(func), + endsTypeUnreachable(endsTypeUnreachable) +#if OUTLINING_DEBUG + , + programIdx(programIdx) +#endif + { + } +}; + // Instances of this walker are intended to walk a function at a time, at the // behest of the owner of the instance. struct ReconstructStringifyWalker @@ -45,8 +73,8 @@ struct ReconstructStringifyWalker ReconstructStringifyWalker(Module* wasm, Function* func) : existingBuilder(*wasm), outlinedBuilder(*wasm), func(func) { this->setModule(wasm); - DBG(std::cerr << "\nexistingBuilder: " << &existingBuilder - << " outlinedBuilder: " << &outlinedBuilder << "\n"); + ODBG(std::cerr << "\nexistingBuilder: " << &existingBuilder + << " outlinedBuilder: " << &outlinedBuilder << "\n"); } // As we reconstruct the IR during outlining, we need to know what @@ -91,25 +119,26 @@ struct ReconstructStringifyWalker // starting a new function, as that resets the counters back to 0. instrCounter++; - DBG(std::string desc); + ODBG(std::string desc); if (auto curr = reason.getBlockStart()) { + ODBG(desc = "Block Start at "); ASSERT_OK(existingBuilder.visitBlockStart(curr->block)); - DBG(desc = "Block Start at "); } else if (auto curr = reason.getIfStart()) { // IR builder needs the condition of the If pushed onto the builder before // visitIfStart(), which will expect to be able to pop the condition. // This is always okay to do because the correct condition was installed // onto the If when the outer scope was visited. existingBuilder.push(curr->iff->condition); + ODBG(desc = "If Start at "); ASSERT_OK(existingBuilder.visitIfStart(curr->iff)); - DBG(desc = "If Start at "); } else if (reason.getElseStart()) { + ODBG(desc = "Else Start at "); ASSERT_OK(existingBuilder.visitElse()); - DBG(desc = "Else Start at "); } else if (auto curr = reason.getLoopStart()) { + ODBG(desc = "Loop Start at "); ASSERT_OK(existingBuilder.visitLoopStart(curr->loop)); - DBG(desc = "Loop Start at "); } else if (reason.getEnd()) { + ODBG(desc = "End at "); ASSERT_OK(existingBuilder.visitEnd()); // Reset the function in case we just ended the function scope. existingBuilder.setFunction(func); @@ -122,12 +151,11 @@ struct ReconstructStringifyWalker // its expressions off the stack, so we must call build() after visitEnd() // to clear the internal stack IRBuilder manages. ASSERT_OK(existingBuilder.build()); - DBG(desc = "End at "); } else { - DBG(desc = "addUniqueSymbol for unimplemented control flow "); + ODBG(desc = "addUniqueSymbol for unimplemented control flow "); WASM_UNREACHABLE("unimplemented control flow"); } - DBG(printAddUniqueSymbol(desc)); + ODBG(printAddUniqueSymbol(desc)); } void visitExpression(Expression* curr) { @@ -151,7 +179,7 @@ struct ReconstructStringifyWalker ASSERT_OK(builder->visit(curr)); } } - DBG(printVisitExpression(curr)); + ODBG(printVisitExpression(curr)); if (state == InSeq || state == InSkipSeq) { maybeEndSeq(); @@ -165,9 +193,9 @@ struct ReconstructStringifyWalker instrCounter = 0; seqCounter = 0; state = NotInSeq; - DBG(std::cerr << "\n" - << "Func Start to $" << func->name << " at " - << &existingBuilder << "\n"); + ODBG(std::cerr << "\n" + << "Func Start to $" << func->name << " at " + << &existingBuilder << "\n"); } ReconstructState getCurrState() { @@ -209,29 +237,41 @@ struct ReconstructStringifyWalker getModule()->getFunction(sequences[seqCounter].func); ASSERT_OK(outlinedBuilder.visitFunctionStart(outlinedFunc)); - // Add a local.get instruction for every parameter of the outlined function. - Signature sig = outlinedFunc->type.getSignature(); - for (Index i = 0; i < sig.params.size(); i++) { - ASSERT_OK(outlinedBuilder.makeLocalGet(i)); - } - // Make a call from the existing function to the outlined function. This // call will replace the instructions moved to the outlined function. + ODBG(std::cerr << "\nadding call " << outlinedFunc->name << " to " + << &existingBuilder << "\n"); ASSERT_OK(existingBuilder.makeCall(outlinedFunc->name, false)); - DBG(std::cerr << "\ncreated outlined fn: " << outlinedFunc->name << "\n"); // If the last instruction of the outlined sequence is unreachable, insert // an unreachable instruction immediately after the call to the outlined // function. This maintains the unreachable type in the original scope // of the outlined sequence. if (sequences[seqCounter].endsTypeUnreachable) { + ODBG(std::cerr << "\nadding endsUnreachable to " << &existingBuilder + << "\n"); ASSERT_OK(existingBuilder.makeUnreachable()); } + + // Add a local.get instruction for every parameter of the outlined function. + Signature sig = outlinedFunc->type.getSignature(); + ODBG(std::cerr << outlinedFunc->name << " takes " << sig.params.size() + << " parameters\n"); + for (Index i = 0; i < sig.params.size(); i++) { + ODBG(std::cerr << "adding local.get $" << i << " to " << &outlinedBuilder + << "\n"); + ASSERT_OK(outlinedBuilder.makeLocalGet(i)); + } } void transitionToInSkipSeq() { Function* outlinedFunc = getModule()->getFunction(sequences[seqCounter].func); + ODBG(std::cerr << "\nstarting to skip instructions " + << sequences[seqCounter].startIdx << " - " + << sequences[seqCounter].endIdx - 1 << " to " + << sequences[seqCounter].func + << " and adding call() instead\n"); ASSERT_OK(existingBuilder.makeCall(outlinedFunc->name, false)); // If the last instruction of the outlined sequence is unreachable, insert // an unreachable instruction immediately after the call to the outlined @@ -240,11 +280,6 @@ struct ReconstructStringifyWalker if (sequences[seqCounter].endsTypeUnreachable) { ASSERT_OK(existingBuilder.makeUnreachable()); } - DBG(std::cerr << "\nstarting to skip instructions " - << sequences[seqCounter].startIdx << " - " - << sequences[seqCounter].endIdx - 1 << " to " - << sequences[seqCounter].func - << " and adding call() instead\n"); } void maybeEndSeq() { @@ -255,12 +290,12 @@ struct ReconstructStringifyWalker } void transitionToNotInSeq() { - DBG(std::cerr << "End of sequence "); + ODBG(std::cerr << "End of sequence "); if (state == InSeq) { + ODBG(std::cerr << "to " << &outlinedBuilder); ASSERT_OK(outlinedBuilder.visitEnd()); - DBG(std::cerr << "to " << &outlinedBuilder); } - DBG(std::cerr << "\n\n"); + ODBG(std::cerr << "\n\n"); // Completed a sequence so increase the seqCounter and reset the state. seqCounter++; } @@ -288,11 +323,11 @@ struct Outlining : public Pass { HashStringifyWalker stringify; // Walk the module and create a "string representation" of the program. stringify.walkModule(module); + ODBG(printHashString(stringify.hashString, stringify.exprs)); // Collect all of the substrings of the string representation that appear // more than once in the program. auto substrings = StringifyProcessor::repeatSubstrings(stringify.hashString); - DBG(printHashString(stringify.hashString, stringify.exprs)); // Remove substrings that are substrings of longer repeat substrings. substrings = StringifyProcessor::dedupe(substrings); // Remove substrings with overlapping indices. @@ -317,7 +352,13 @@ struct Outlining : public Pass { // are relative to the enclosing function while substrings have indices // relative to the entire program. auto sequences = makeSequences(module, substrings, stringify); - outline(module, sequences); + outline(module, + sequences +#if OUTLINING_DEBUG + , + stringify +#endif + ); // Position the outlined functions first in the functions vector to make // the outlining lit tests far more readable. moveOutlinedFunctions(module, substrings.size()); @@ -371,14 +412,25 @@ struct Outlining : public Pass { relativeIdx + substring.Length, func, stringify.exprs[seqIdx + substring.Length - 1]->type == - Type::unreachable); + Type::unreachable +#if OUTLINING_DEBUG + , + seqIdx +#endif + ); seqByFunc[existingFunc].push_back(seq); } } return seqByFunc; } - void outline(Module* module, Sequences seqByFunc) { + void outline(Module* module, + Sequences seqByFunc +#if OUTLINING_DEBUG + , + const HashStringifyWalker& stringify +#endif + ) { // TODO: Make this a function-parallel sub-pass. std::vector keys(seqByFunc.size()); std::transform(seqByFunc.begin(), @@ -398,6 +450,11 @@ struct Outlining : public Pass { }); ReconstructStringifyWalker reconstruct(module, module->getFunction(func)); reconstruct.sequences = std::move(seqByFunc[func]); + ODBG(printReconstruct(module, + stringify.hashString, + stringify.exprs, + func, + reconstruct.sequences)); reconstruct.doWalkFunction(module->getFunction(func)); } } @@ -433,6 +490,32 @@ struct Outlining : public Pass { } } } + void printReconstruct(Module* module, + const std::vector& hashString, + const std::vector& exprs, + Name existingFunc, + const std::vector& seqs) { + std::cerr << "\n\nReconstructing existing fn: " << existingFunc << "\n"; + std::cerr << "moving sequences: " + << "\n"; + for (auto& seq : seqs) { + for (Index idx = seq.programIdx; + idx < seq.programIdx + (seq.endIdx - seq.startIdx); + idx++) { + Expression* expr = exprs[idx]; + if (expr == nullptr) { + std::cerr << "unique symbol\n"; + } else { + std::cerr << idx << " - " << hashString[idx] << " - " << seq.startIdx + << " : " << ShallowExpression{expr} << "\n"; + } + } + std::cerr << "to outlined function: " << seq.func << "\n"; + auto outlinedFunction = module->getFunction(seq.func); + std::cerr << "with signature: " << outlinedFunction->type.toString() + << "\n"; + } + } #endif }; diff --git a/src/passes/stringify-walker.h b/src/passes/stringify-walker.h index bb7a09924f9..d46334d3b84 100644 --- a/src/passes/stringify-walker.h +++ b/src/passes/stringify-walker.h @@ -254,20 +254,6 @@ struct HashStringifyWalker : public StringifyWalker { std::map idxToFuncName; }; -struct OutliningSequence { - unsigned startIdx; - unsigned endIdx; - Name func; - bool endsTypeUnreachable; - - OutliningSequence(unsigned startIdx, - unsigned endIdx, - Name func, - bool endsTypeUnreachable) - : startIdx(startIdx), endIdx(endIdx), func(func), - endsTypeUnreachable(endsTypeUnreachable) {} -}; - using Substrings = std::vector; // Functions that filter vectors of SuffixTree::RepeatedSubstring From c528c7e0ee209321888304a7e3b1dde2457ac5bd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Apr 2025 16:59:12 -0700 Subject: [PATCH 418/622] [NFC] Clean up pick() calls in fuzzer (v2) (#7475) * Add some assertions on features being properly enabled. * Simplify code to avoid FeatureOptions when unnecessary (feature known to be enabled). * Add comments on the definition of upTo/oneIn of zero. * Add a FeatureOptions overload to handle an option without a feature. --- src/tools/fuzzing/fuzzing.cpp | 35 +++++++++++++++----------------- src/tools/fuzzing/heap-types.cpp | 3 +++ src/tools/fuzzing/random.h | 21 ++++++++++++++++++- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b40c28159e1..e1b6cf31192 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3426,13 +3426,9 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { // Choose a subtype we can materialize a constant for. We cannot // materialize non-nullable refs to func or i31 in global contexts. Nullability nullability = getSubType(type.getNullability()); - auto subtypeOpts = FeatureOptions().add( - FeatureSet::ReferenceTypes | FeatureSet::GC, - HeapType::i31, - HeapType::struct_, - HeapType::array); - auto subtype = pick(subtypeOpts).getBasic(share); - return makeConst(Type(subtype, nullability)); + assert(wasm.features.hasGC()); + auto subtype = pick(HeapTypes::i31, HeapTypes::struct_, HeapTypes::array); + return makeConst(Type(subtype.getBasic(share), nullability)); } case HeapType::eq: { if (!wasm.features.hasGC()) { @@ -5406,16 +5402,18 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { switch (type.getBasic(Unshared)) { case HeapType::func: // TODO: Typed function references. + assert(wasm.features.hasReferenceTypes()); return pick(FeatureOptions() - .add(FeatureSet::ReferenceTypes, HeapType::func) - .add(FeatureSet::GC, HeapType::nofunc)) + .add(HeapTypes::func) + .add(FeatureSet::GC, HeapTypes::nofunc)) .getBasic(share); case HeapType::cont: return pick(HeapTypes::cont, HeapTypes::nocont).getBasic(share); case HeapType::ext: { + assert(wasm.features.hasReferenceTypes()); auto options = FeatureOptions() - .add(FeatureSet::ReferenceTypes, HeapType::ext) - .add(FeatureSet::GC, HeapType::noext); + .add(HeapTypes::ext) + .add(FeatureSet::GC, HeapTypes::noext); if (share == Unshared) { // Shared strings not yet supported. options.add(FeatureSet::Strings, HeapType::string); @@ -5425,14 +5423,13 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { case HeapType::any: { assert(wasm.features.hasReferenceTypes()); assert(wasm.features.hasGC()); - auto options = FeatureOptions().add(FeatureSet::GC, - HeapType::any, - HeapType::eq, - HeapType::i31, - HeapType::struct_, - HeapType::array, - HeapType::none); - return pick(options).getBasic(share); + return pick(HeapTypes::any, + HeapTypes::eq, + HeapTypes::i31, + HeapTypes::struct_, + HeapTypes::array, + HeapTypes::none) + .getBasic(share); } case HeapType::eq: assert(wasm.features.hasReferenceTypes()); diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 56c0b29a4bd..991b1db9c45 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -424,6 +424,9 @@ struct HeapTypeGeneratorImpl { return HeapTypes::none.getBasic(share); } } + // If we had no candidates then the oneIn() above us should have returned + // true, since oneIn(0) => true. + assert(!candidates.empty()); return rand.pick(candidates); } else { // This is not a constructed type, so it must be a basic type. diff --git a/src/tools/fuzzing/random.h b/src/tools/fuzzing/random.h index 664386939f2..f4ff8782cc2 100644 --- a/src/tools/fuzzing/random.h +++ b/src/tools/fuzzing/random.h @@ -22,6 +22,7 @@ #include #include "wasm-features.h" +#include "wasm-type.h" namespace wasm { @@ -53,8 +54,9 @@ class Random { double getDouble(); // Choose an integer value in [0, x). This doesn't use a perfectly uniform - // distribution, but it's fast and reasonable. + // distribution, but it's fast and reasonable. upTo(0) is defined as 0. uint32_t upTo(uint32_t x); + // Returns true with probability 1 in x. oneIn(0) is defined as true. bool oneIn(uint32_t x) { return upTo(x) == 0; } // Apply upTo twice, generating a skewed distribution towards @@ -114,6 +116,23 @@ class Random { #endif template struct FeatureOptions { + FeatureOptions& add(HeapType::BasicHeapType option) { + // Using FeatureOptions with BasicHeapTypes is risky as BasicHeapType + // is an enum, which can convert into FeatureSet implicitly (which would + // then be ambiguous with add(FeatureSet) below). Use HeapType instead. + // + // Use a weird static assert on something other than |false| because of + // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2593r1.html + // (older compilers may error without this workaround). + static_assert(sizeof(T) == 0); + } + + // An option without a feature is applied in all cases. + FeatureOptions& add(T option) { + options[FeatureSet::MVP].push_back(option); + return *this; + } + template FeatureOptions& add(FeatureSet feature, T option, Ts... rest) { options[feature].push_back(option); From fa99f50f6fa2840933bdf2478c800d61de6ab961 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Apr 2025 08:53:14 -0700 Subject: [PATCH 419/622] Fix clang-tidy (#7476) Without this fix, error-on-exit makes us error without printing the diff. --- scripts/clang-tidy-diff.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/clang-tidy-diff.sh b/scripts/clang-tidy-diff.sh index 17a6a4687ea..84ddda9b30f 100755 --- a/scripts/clang-tidy-diff.sh +++ b/scripts/clang-tidy-diff.sh @@ -29,7 +29,7 @@ if [ ! -e "$CLANG_TIDY_DIFF" ]; then echo "Failed to find clang-tidy-diff.py ($CLANG_TIDY_DIFF)" exit 1 fi -TIDY_MSG=$(git diff -U0 $BRANCH... | $CLANG_TIDY_DIFF $ARG 2> /dev/null) +TIDY_MSG=$(git diff -U0 $BRANCH... | $CLANG_TIDY_DIFF $ARG 2> /dev/null || true) if [ -n "$TIDY_MSG" -a "$TIDY_MSG" != "No relevant changes found." ]; then echo "Please fix clang-tidy errors before committing" echo From 93c541d4be8dffa66b731cf3ad33e5d1f158643a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Apr 2025 10:28:30 -0700 Subject: [PATCH 420/622] Fuzzer: Improve V8 flags (#7482) `--liftoff/--no-liftoff` are enough to control which tier we get. Removing the extra flags avoids a warning. Test with `--no-wasm-generic-wrapper` sometimes, which was suggested. --- scripts/fuzz_opt.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 5ae3a522c16..cc5ca1b5d53 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -676,7 +676,7 @@ def get_v8_extra_flags(): return ['--future'] if random.random() < 0.5 else [] -V8_LIFTOFF_ARGS = ['--liftoff', '--no-wasm-tier-up'] +V8_LIFTOFF_ARGS = ['--liftoff'] # Default to running with liftoff enabled, because we need to pick either @@ -848,7 +848,10 @@ class D8Turboshaft(D8): name = 'd8_turboshaft' def run(self, wasm): - return super(D8Turboshaft, self).run(wasm, extra_d8_flags=['--no-liftoff', '--turboshaft-wasm', '--turboshaft-wasm-instruction-selection-staged']) + flags = ['--no-liftoff'] + if random.random() < 0.5: + flags += ['--no-wasm-generic-wrapper'] + return super(D8Turboshaft, self).run(wasm, extra_d8_flags=flags) class Wasm2C: name = 'wasm2c' From 755a8d0edd2ff5375cd6df2c318bf3e01c87247b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Apr 2025 16:58:01 -0700 Subject: [PATCH 421/622] [NFC] Add getFallthrough* helpers in OptimizeInstructions (#7485) Just to simplify code a little. --- src/passes/OptimizeInstructions.cpp | 48 ++++++++++++----------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 2fdd138f85e..4c07942d24e 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -295,6 +295,14 @@ struct OptimizeInstructions return EffectAnalyzer(getPassOptions(), *getModule(), expr); } + Expression* getFallthrough(Expression* curr) { + return Properties::getFallthrough(curr, getPassOptions(), *getModule()); + } + + Type getFallthroughType(Expression* curr) { + return Properties::getFallthroughType(curr, getPassOptions(), *getModule()); + } + decltype(auto) pure(Expression** binder) { using namespace Match::Internal; return Matcher>(binder, this); @@ -574,9 +582,7 @@ struct OptimizeInstructions Index extraLeftShifts; auto bits = Properties::getAlmostSignExtBits(curr, extraLeftShifts); if (extraLeftShifts == 0) { - if (auto* load = - Properties::getFallthrough(ext, getPassOptions(), *getModule()) - ->dynCast()) { + if (auto* load = getFallthrough(ext)->dynCast()) { // pattern match a load of 8 bits and a sign extend using a shl of // 24 then shr_s of 24 as well, etc. if (LoadUtils::canBeSigned(load) && @@ -1348,9 +1354,7 @@ struct OptimizeInstructions // the fallthrough value there. It takes more work to optimize this case, // but it is pretty important to allow a call_ref to become a fast direct // call, so make the effort. - if (auto* ref = Properties::getFallthrough( - curr->target, getPassOptions(), *getModule()) - ->dynCast()) { + if (auto* ref = getFallthrough(curr->target)->dynCast()) { // Check if the fallthrough make sense. We may have cast it to a different // type, which would be a problem - we'd be replacing a call_ref to one // type with a direct call to a function of another type. That would trap @@ -1748,8 +1752,7 @@ struct OptimizeInstructions } } - auto fallthrough = - Properties::getFallthrough(ref, getPassOptions(), *getModule()); + auto fallthrough = getFallthrough(ref); if (fallthrough->type.isNull()) { replaceCurrent( @@ -1821,7 +1824,6 @@ struct OptimizeInstructions return; } - auto& passOptions = getPassOptions(); const auto& fields = curr->type.getHeapType().getStruct().fields; assert(fields.size() == curr->operands.size()); @@ -1833,8 +1835,7 @@ struct OptimizeInstructions } // The field must be written the default value. - auto* value = Properties::getFallthrough( - curr->operands[i], passOptions, *getModule()); + auto* value = getFallthrough(curr->operands[i]); if (!Properties::isSingleConstantExpression(value) || Properties::getLiteral(value) != Literal::makeZero(type)) { return; @@ -1913,8 +1914,7 @@ struct OptimizeInstructions // this location in the total order of seqcst ops would have to be // considered to be reading from this RMW and therefore would synchronize // with it. - auto* value = - Properties::getFallthrough(curr->value, getPassOptions(), *getModule()); + auto* value = getFallthrough(curr->value); if (Properties::isSingleConstantExpression(value)) { auto val = Properties::getLiteral(value); bool canOptimize = false; @@ -2108,10 +2108,8 @@ struct OptimizeInstructions } // The value must be the default/zero. - auto& passOptions = getPassOptions(); auto zero = Literal::makeZero(type); - auto* value = - Properties::getFallthrough(curr->init, passOptions, *getModule()); + auto* value = getFallthrough(curr->init); if (!Properties::isSingleConstantExpression(value) || Properties::getLiteral(value) != zero) { return; @@ -2136,8 +2134,6 @@ struct OptimizeInstructions return; } - auto& passOptions = getPassOptions(); - // If all the values are equal then we can optimize, either to // array.new_default (if they are all equal to the default) or array.new (if // they are all equal to some other value). First, see if they are all @@ -2155,8 +2151,7 @@ struct OptimizeInstructions // See if they are equal to a constant, and if that constant is the default. auto type = curr->type.getHeapType().getArray().element.type; if (type.isDefaultable()) { - auto* value = - Properties::getFallthrough(curr->values[0], passOptions, *getModule()); + auto* value = getFallthrough(curr->values[0]); if (Properties::isSingleConstantExpression(value) && Properties::getLiteral(value) == Literal::makeZero(type)) { @@ -2254,8 +2249,7 @@ struct OptimizeInstructions // of the value we are casting. local.tee, br_if, and blocks can all "lose" // type information, so looking at all the fallthrough values can give us a // more precise type than is stored in the IR. - Type refType = - Properties::getFallthroughType(curr->ref, getPassOptions(), *getModule()); + Type refType = getFallthroughType(curr->ref); // As a first step, we can tighten up the cast type to be the greatest lower // bound of the original cast type and the type we know the cast value to @@ -2425,8 +2419,7 @@ struct OptimizeInstructions // Parallel to the code in visitRefCast: we look not just at the final type // we are given, but at fallthrough values as well. - Type refType = - Properties::getFallthroughType(curr->ref, getPassOptions(), *getModule()); + Type refType = getFallthroughType(curr->ref); // Improve the cast type as much as we can without changing the results. auto glb = Type::getGreatestLowerBound(curr->castType, refType); @@ -2610,10 +2603,9 @@ struct OptimizeInstructions // NoTeeBrIf as we do not want to look through the tee). We cannot do this // on the second, however, as there could be effects in the middle. // TODO: Use effects here perhaps. - auto& passOptions = getPassOptions(); left = Properties::getFallthrough(left, - passOptions, + getPassOptions(), *getModule(), Properties::FallthroughBehavior::NoTeeBrIf); if (areMatchingTeeAndGet(left, right)) { @@ -2622,9 +2614,9 @@ struct OptimizeInstructions // Ignore extraneous things and compare them syntactically. We can also // look at the full fallthrough for both sides now. - left = Properties::getFallthrough(left, passOptions, *getModule()); + left = getFallthrough(left); auto* originalRight = right; - right = Properties::getFallthrough(right, passOptions, *getModule()); + right = getFallthrough(right); if (!ExpressionAnalyzer::equal(left, right)) { return false; } From 5f2629cc80d5836556fa9370bd3768f55a41d268 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 14 Apr 2025 13:07:23 -0700 Subject: [PATCH 422/622] [EH] Use V8 flag to allow mixed old and new EH [NFC] (#7484) This is necessary with latest V8, since https://github.com/v8/v8/commit/af887e07511cce2831b96edf7e3d9a13bc8c9df8 This is not ideal, see comment in the source, but also seems hard to improve on. --- scripts/clusterfuzz/run.py | 4 +++- scripts/fuzz_opt.py | 26 +++++++++++++++++++------- test/unit/test_cluster_fuzz.py | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index 4e7bf6659f0..d44bde05cd4 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -33,7 +33,9 @@ # The V8 flags we put in the "fuzzer flags" files, which tell ClusterFuzz how to # run V8. By default we apply all staging flags. -FUZZER_FLAGS_FILE_CONTENTS = '--wasm-staging' +# +# We also allow mixed EH, see the comment on the same flag in fuzz_opt.py +FUZZER_FLAGS_FILE_CONTENTS = '--wasm-staging --wasm-allow-mixed-eh-for-testing' # Maximum size of the random data that we feed into wasm-opt -ttf. This is # smaller than fuzz_opt.py's INPUT_SIZE_MAX because that script is tuned for diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index cc5ca1b5d53..5d3c2756246 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -667,13 +667,25 @@ def run_bynterp(wasm, args): del os.environ['BINARYEN_MAX_INTERPRETER_DEPTH'] -# Enable even more staged things than V8_OPTS. V8_OPTS are the flags we want to -# use when testing, and enable all features we test against, while --future may -# also enable non-feature things like new JITs and such (which are never needed -# for our normal tests, but do make sense to fuzz for V8's sake). We do this -# randomly for more variety. +# Enable even more things than V8_OPTS. V8_OPTS are the flags we want to use +# when testing, on our fixed test suite, but when fuzzing we may want more. def get_v8_extra_flags(): - return ['--future'] if random.random() < 0.5 else [] + # Due to https://github.com/WebAssembly/exception-handling/issues/344 , VMs + # do not allow mixed old and new wasm EH. Our fuzzer will very frequently + # mix those instructions in a module, so we must use the flag to allow that. + # FIXME This is not great, as the majority of the wasm files we test on are + # not actually valid in VMs. But we get coverage this way for runtime + # linking of old and new EH (which VMs allow), that is, our compile- + # time combination of old and new simulates runtime linking to some + # extent. + flags = ['--wasm-allow-mixed-eh-for-testing'] + + # Sometimes add --future, which may enable new JITs and such, which is good + # to fuzz for V8's sake. + if random.random() < 0.5: + flags += ['--future'] + + return flags V8_LIFTOFF_ARGS = ['--liftoff'] @@ -1650,7 +1662,7 @@ def handle(self, wasm): # flags here! with open(flags_file, 'r') as f: flags = f.read() - cmd.append(flags) + cmd += flags.split(' ') # Run the fuzz file, which contains a modified fuzz_shell.js - we do # *not* run fuzz_shell.js normally. cmd.append(os.path.abspath(fuzz_file)) diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 72197939d76..6965e5f7c39 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -186,7 +186,7 @@ def test_file_contents(self): # The flags file must contain --wasm-staging with open(flags_file) as f: - self.assertEqual(f.read(), '--wasm-staging') + self.assertEqual(f.read(), '--wasm-staging --wasm-allow-mixed-eh-for-testing') # Extract the wasm file(s) from the JS. Make sure to not notice # stale files. From e6f1c53a2052d7c1e9e06ace64c7c2833aa82a7d Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Tue, 15 Apr 2025 04:15:35 +0800 Subject: [PATCH 423/622] OptimizeInstructions: Optimize bool(x) | 1 ==> 1 even with side effects (#7478) Fixes: #7477 --- src/passes/OptimizeInstructions.cpp | 4 +- .../lit/passes/optimize-instructions-mvp.wast | 66 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 4c07942d24e..7ade7ac3261 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -3972,9 +3972,9 @@ struct OptimizeInstructions return result; } // bool(x) | 1 ==> 1 - if (matches(curr, binary(Or, pure(&left), ival(1))) && + if (matches(curr, binary(Or, any(&left), ival(1))) && Bits::getMaxBits(left, this) == 1) { - return right; + return getDroppedChildrenAndAppend(curr, right); } // Operations on all 1s diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index f8652cdf15f..fa1060edfcc 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -11452,6 +11452,38 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (i64.eqz + ;; CHECK-NEXT: (i64.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.load ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -11694,6 +11726,40 @@ (i64.const -9223372036854775808) )) + ;; bool(x) | 1 ==> 1 + (drop (i32.or + (i32.eqz + (local.get $x) + ) + (i32.const 1) + )) + (drop (i32.or + (i32.eqz + (i32.load + (i32.const 0) + ) + ) + (i32.const 1) + )) + (drop (i64.or + (i64.extend_i32_u + (i64.eqz + (local.get $y) + ) + ) + (i64.const 1) + )) + (drop (i64.or + (i64.extend_i32_u + (i64.eqz + (i64.load + (i32.const 0) + ) + ) + ) + (i64.const 1) + )) + ;; (unsigned)x >= 0 => i32(1) (drop (i32.ge_u (local.get $x) From 551d8750727e50d911b0a32e0defb33adb9bbf04 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 15 Apr 2025 09:25:36 -0700 Subject: [PATCH 424/622] [SIMD] Fix relaxed SIMD in wasm-ctor-eval (#7495) Relaxed SIMD is a new class of "nonconstant" (too variable to execute at compile time) code. Throw a non-constant exception in the right place in the interpreter. This requires moving the NonconstantException class to a higher place. --- src/binaryen-c.cpp | 2 +- src/passes/Precompute.cpp | 2 +- src/tools/wasm-ctor-eval.cpp | 5 ++++ src/wasm-interpreter.h | 13 +++++++-- test/lit/ctor-eval/relaxed.wast | 51 +++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 test/lit/ctor-eval/relaxed.wast diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 95b7846ae1a..cd5bb737a4d 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -6144,7 +6144,7 @@ ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, if (!flow.breaking() && !flow.values.empty()) { ret = flow.getConstExpression(*R->getModule()); } - } catch (CExpressionRunner::NonconstantException&) { + } catch (NonconstantException&) { } delete R; return ret; diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index c396a7d8dda..f101cc525b6 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -728,7 +728,7 @@ struct Precompute flow = PrecomputingExpressionRunner( getModule(), getValues, heapValues, replaceExpression) .visit(curr); - } catch (PrecomputingExpressionRunner::NonconstantException&) { + } catch (NonconstantException&) { return Flow(NONCONSTANT_FLOW); } // If we are replacing the expression, then the resulting value must be of diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index bb4050003df..c4dcb6d4532 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -1113,6 +1113,11 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, } } break; + } catch (NonconstantException& fail) { + if (!quiet) { + std::cout << " ...stopping due to non-constant code\n"; + } + break; } if (flow.breakTo == RETURN_CALL_FLOW) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index eb9b40d491e..8cfae4e5009 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -51,6 +51,12 @@ struct WasmException { }; std::ostream& operator<<(std::ostream& o, const WasmException& exn); +// An exception thrown when we try to execute non-constant code, that is, code +// that we cannot properly evaluate at compile time (e.g. if it refers to an +// import, or we are optimizing and it uses relaxed SIMD). +// TODO: use a flow with a special name, as this is likely very slow +struct NonconstantException {}; + // Utilities extern Name WASM, RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW; @@ -2457,9 +2463,6 @@ class ConstantExpressionRunner : public ExpressionRunner { std::unordered_map globalValues; public: - struct NonconstantException { - }; // TODO: use a flow with a special name, as this is likely very slow - ConstantExpressionRunner(Module* module, Flags flags, Index maxDepth, @@ -4540,6 +4543,10 @@ class ModuleRunnerBase : public ExpressionRunner { arguments = flow.values; } + if (flow.breaking() && flow.breakTo == NONCONSTANT_FLOW) { + throw NonconstantException(); + } + // cannot still be breaking, it means we missed our stop assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); auto type = flow.getType(); diff --git a/test/lit/ctor-eval/relaxed.wast b/test/lit/ctor-eval/relaxed.wast new file mode 100644 index 00000000000..fd1dc46e216 --- /dev/null +++ b/test/lit/ctor-eval/relaxed.wast @@ -0,0 +1,51 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-ctor-eval %s --ctors=return,drop,passthrough --quiet -all -S -o - | filecheck %s + +;; The relaxed SIMD operation here cannot be optimized by wasm-ctor-eval, as we +;; do not know how the VM that the code will run on should execute it. We can +;; optimize nothing here, and should not error. + +(module + ;; CHECK: (type $0 (func (result v128))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (export "drop" (func $drop)) + + ;; CHECK: (export "passthrough" (func $passthrough)) + + ;; CHECK: (func $return (type $0) (result v128) + ;; CHECK-NEXT: (f32x4.relaxed_max + ;; CHECK-NEXT: (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000) + ;; CHECK-NEXT: (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $return (export "return") (result v128) + ;; This function returns a nonconstant value directly. + (f32x4.relaxed_max + (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000) + (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000) + ) + ) + + ;; CHECK: (func $drop (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop (export "drop") + ;; This function swallows a nonconstant value. + (drop + (call $return) + ) + ) + + ;; CHECK: (func $passthrough (type $0) (result v128) + ;; CHECK-NEXT: (call $return) + ;; CHECK-NEXT: ) + (func $passthrough (export "passthrough") (result v128) + ;; This function passes through a nonconstant value. + (call $return) + ) +) + From 76ca8334ea8b240d5c4302df774f1a6d74635d47 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Apr 2025 10:48:17 -0700 Subject: [PATCH 425/622] Revert exact HeapTypes (#7496) Revert the following PRs that introduced exactness on HeapType: - #7446 - #7444 - #7432 - #7412 - #7396 Keep only the changes to wasm-type-printing.cpp to fix the assertions that subclasses of TypeNameGeneratorBase correctly override getNames. Although putting exactness on heap types makes the most sense in the Custom Descriptors spec, it is not a great fit for how HeapType is used in Binaryen. In almost all cases, HeapType is used to represent heap type definitions rather than the dynamic types of values. Letting HeapType represent exact heap types is not useful in those cases and opens up a new class of possible bugs. To avoid these bugs, we had been introducing a new HeapTypeDef type to represent heap type definitions, but nearly all uses of HeapType would have had to have been replaced with HeapTypeDef. Rather than introduce HeapTypeDef as a third type alongside Type and HeapType, it will be simpler to continue using HeapType to represent heap type definitions and Type to represent the dynamic types of values, including their exactness. While exactness will syntactically be part of the heap type, it will be part of the `Type` in Binaryen IR. A follow-on PR will restore the original work on exact references, and PRs following that one will update the encoding, parsing, and printing of exact references to match the current spec. --- scripts/test/fuzzing.py | 2 - src/ir/module-utils.cpp | 38 ++-- src/ir/module-utils.h | 12 +- src/ir/subtypes.h | 6 +- src/parser/contexts.h | 45 ++--- src/parser/parse-2-typedefs.cpp | 4 +- src/parser/parse-3-implicit-types.cpp | 4 +- src/parser/parse-4-module-types.cpp | 11 +- src/parser/parse-5-defs.cpp | 6 +- src/parser/parsers.h | 10 - src/parser/wat-parser-internal.h | 25 ++- src/parser/wat-parser.cpp | 6 +- src/passes/NameTypes.cpp | 2 +- src/passes/Print.cpp | 8 +- src/passes/TypeMerging.cpp | 72 +++---- src/passes/TypeSSA.cpp | 10 +- src/tools/fuzzing.h | 5 +- src/tools/fuzzing/fuzzing.cpp | 10 +- src/tools/fuzzing/heap-types.cpp | 50 ++--- src/tools/fuzzing/heap-types.h | 8 +- src/tools/wasm-fuzz-types.cpp | 13 +- src/wasm-binary.h | 4 +- src/wasm-type-ordering.h | 8 +- src/wasm-type-printing.h | 26 ++- src/wasm-type.h | 60 ++---- src/wasm.h | 4 +- src/wasm/wasm-binary.cpp | 41 ++-- src/wasm/wasm-type.cpp | 54 ++---- test/gtest/possible-contents.cpp | 4 +- test/gtest/type-builder.cpp | 266 +------------------------- test/gtest/type-domains.cpp | 21 +- test/gtest/type-domains.h | 2 +- test/lit/basic/exact.wast | 52 ----- 33 files changed, 232 insertions(+), 657 deletions(-) delete mode 100644 test/lit/basic/exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 28b03294939..50ab56683fa 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -113,8 +113,6 @@ 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', - # TODO: fuzzer support for exact heap types - 'exact.wast', ] diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 7cdb5913f0c..7f2dfcc089c 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -347,7 +347,7 @@ namespace { // Helper for collecting HeapTypes and their frequencies. struct TypeInfos { - InsertOrderedMap info; + InsertOrderedMap info; // Multivalue control flow structures need a function type, but the identity // of the function type (i.e. what recursion group it is in or whether it is @@ -355,7 +355,7 @@ struct TypeInfos { // existing function type with the necessary signature. InsertOrderedMap controlFlowSignatures; - void note(HeapTypeDef type) { + void note(HeapType type) { if (!type.isBasic()) { ++info[type].useCount; } @@ -366,7 +366,7 @@ struct TypeInfos { } } // Ensure a type is included without increasing its count. - void include(HeapTypeDef type) { + void include(HeapType type) { if (!type.isBasic()) { info[type]; } @@ -388,7 +388,7 @@ struct TypeInfos { note(sig.results); } } - bool contains(HeapTypeDef type) { return info.count(type); } + bool contains(HeapType type) { return info.count(type); } }; struct CodeScanner @@ -468,11 +468,11 @@ struct CodeScanner }; void classifyTypeVisibility(Module& wasm, - InsertOrderedMap& types); + InsertOrderedMap& types); } // anonymous namespace -InsertOrderedMap collectHeapTypeInfo( +InsertOrderedMap collectHeapTypeInfo( Module& wasm, TypeInclusion inclusion, VisibilityHandling visibility) { // Collect module-level info. TypeInfos info; @@ -523,9 +523,9 @@ InsertOrderedMap collectHeapTypeInfo( // track which recursion groups we've already processed to avoid quadratic // behavior when there is a single large group. // TODO: Use a vector here, since we never try to add the same type twice. - UniqueNonrepeatingDeferredQueue newTypes; - std::unordered_map seenSigs; - auto noteNewType = [&](HeapTypeDef type) { + UniqueNonrepeatingDeferredQueue newTypes; + std::unordered_map seenSigs; + auto noteNewType = [&](HeapType type) { newTypes.push(type); if (type.isSignature()) { seenSigs.insert({type.getSignature(), type}); @@ -588,14 +588,14 @@ InsertOrderedMap collectHeapTypeInfo( namespace { -void classifyTypeVisibility( - Module& wasm, InsertOrderedMap& types) { +void classifyTypeVisibility(Module& wasm, + InsertOrderedMap& types) { // We will need to traverse the types used by public types and mark them // public as well. - std::vector workList; + std::vector workList; std::unordered_set publicGroups; - auto notePublic = [&](HeapTypeDef type) { + auto notePublic = [&](HeapType type) { if (type.isBasic()) { return; } @@ -694,9 +694,9 @@ void setIndices(IndexedHeapTypes& indexedTypes) { } // anonymous namespace -std::vector collectHeapTypes(Module& wasm) { +std::vector collectHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo(wasm); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, _] : info) { types.push_back(type); @@ -704,10 +704,10 @@ std::vector collectHeapTypes(Module& wasm) { return types; } -std::vector getPublicHeapTypes(Module& wasm) { +std::vector getPublicHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo( wasm, TypeInclusion::BinaryTypes, VisibilityHandling::FindVisibility); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, typeInfo] : info) { if (typeInfo.visibility == Visibility::Public) { @@ -717,10 +717,10 @@ std::vector getPublicHeapTypes(Module& wasm) { return types; } -std::vector getPrivateHeapTypes(Module& wasm) { +std::vector getPrivateHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo( wasm, TypeInclusion::UsedIRTypes, VisibilityHandling::FindVisibility); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, typeInfo] : info) { if (typeInfo.visibility == Visibility::Private) { diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 593e4d5a20f..bb8b6ae439d 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -470,26 +470,26 @@ struct HeapTypeInfo { Visibility visibility = Visibility::Unknown; }; -InsertOrderedMap collectHeapTypeInfo( +InsertOrderedMap collectHeapTypeInfo( Module& wasm, TypeInclusion inclusion = TypeInclusion::AllTypes, VisibilityHandling visibility = VisibilityHandling::NoVisibility); // Helper function for collecting all the non-basic heap types used in the // module, i.e. the types that would appear in the type section. -std::vector collectHeapTypes(Module& wasm); +std::vector collectHeapTypes(Module& wasm); // Collect all the heap types visible on the module boundary that cannot be // changed. TODO: For open world use cases, this needs to include all subtypes // of public types as well. -std::vector getPublicHeapTypes(Module& wasm); +std::vector getPublicHeapTypes(Module& wasm); // getHeapTypes - getPublicHeapTypes -std::vector getPrivateHeapTypes(Module& wasm); +std::vector getPrivateHeapTypes(Module& wasm); struct IndexedHeapTypes { - std::vector types; - std::unordered_map indices; + std::vector types; + std::unordered_map indices; }; // Similar to `collectHeapTypes`, but provides fast lookup of the index for each diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index a58617868e2..5c654ceb7be 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -28,7 +28,7 @@ namespace wasm { // // This only scans user types, and not basic types like HeapType::eq. struct SubTypes { - SubTypes(const std::vector& types) : types(types) { + SubTypes(const std::vector& types) : types(types) { for (auto type : types) { note(type); } @@ -198,11 +198,11 @@ struct SubTypes { // All the types in the program. This is computed here anyhow, and can be // useful for callers to iterate on, so it is public. - std::vector types; + std::vector types; private: // Add a type to the graph. - void note(HeapTypeDef type) { + void note(HeapType type) { if (auto super = type.getDeclaredSuperType()) { typeSubTypes[*super].push_back(type); } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index c6b0481123e..4c56780323b 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -167,8 +167,6 @@ struct NullTypeParserCtx { Result getTypeIndex(Name) { return 1; } Result getHeapTypeFromIdx(Index) { return Ok{}; } - HeapTypeT makeExact(HeapTypeT) { return Ok{}; } - DataStringT makeDataString() { return Ok{}; } void appendDataString(DataStringT&, std::string_view) {} @@ -253,8 +251,6 @@ template struct TypeParserCtx { return HeapTypes::nocont.getBasic(share); } - HeapTypeT makeExact(HeapTypeT type) { return type.with(Exact); } - TypeT makeI32() { return Type::i32; } TypeT makeI64() { return Type::i64; } TypeT makeF32() { return Type::f32; } @@ -1179,19 +1175,18 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { Lexer in; // Types parsed so far. - std::vector& types; + std::vector& types; // Map typeuse positions without an explicit type to the correct type. - std::unordered_map& implicitTypes; + std::unordered_map& implicitTypes; // Map signatures to the first defined heap type they match. - std::unordered_map sigTypes; + std::unordered_map sigTypes; - ParseImplicitTypeDefsCtx( - Lexer& in, - std::vector& types, - std::unordered_map& implicitTypes, - const IndexMap& typeIndices) + ParseImplicitTypeDefsCtx(Lexer& in, + std::vector& types, + std::unordered_map& implicitTypes, + const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), types(types), implicitTypes(implicitTypes) { for (auto type : types) { @@ -1232,7 +1227,7 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { } auto sig = Signature(Type(paramTypes), Type(resultTypes)); - auto [it, inserted] = sigTypes.insert({sig, HeapType(HeapType::func)}); + auto [it, inserted] = sigTypes.insert({sig, HeapType::func}); if (inserted) { auto type = HeapType(sig); it->second = type; @@ -1260,8 +1255,8 @@ struct ParseModuleTypesCtx : TypeParserCtx, Module& wasm; - const std::vector& types; - const std::unordered_map& implicitTypes; + const std::vector& types; + const std::unordered_map& implicitTypes; const std::unordered_map& implicitElemIndices; // The index of the current type. @@ -1270,8 +1265,8 @@ struct ParseModuleTypesCtx : TypeParserCtx, ParseModuleTypesCtx( Lexer& in, Module& wasm, - const std::vector& types, - const std::unordered_map& implicitTypes, + const std::vector& types, + const std::unordered_map& implicitTypes, const std::unordered_map& implicitElemIndices, const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), wasm(wasm), @@ -1309,7 +1304,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, return TypeUse{it->second, ids}; } - Result getBlockTypeFromTypeUse(Index pos, TypeUse use) { + Result getBlockTypeFromTypeUse(Index pos, TypeUse use) { return use.type; } @@ -1449,9 +1444,9 @@ struct ParseDefsCtx : TypeParserCtx { Module& wasm; Builder builder; - const std::vector& types; - const std::unordered_map& implicitTypes; - const std::unordered_map>& + const std::vector& types; + const std::unordered_map& implicitTypes; + const std::unordered_map>& typeNames; const std::unordered_map& implicitElemIndices; @@ -1476,9 +1471,9 @@ struct ParseDefsCtx : TypeParserCtx { ParseDefsCtx( Lexer& in, Module& wasm, - const std::vector& types, - const std::unordered_map& implicitTypes, - const std::unordered_map>& + const std::vector& types, + const std::unordered_map& implicitTypes, + const std::unordered_map>& typeNames, const std::unordered_map& implicitElemIndices, const IndexMap& typeIndices) @@ -1502,7 +1497,7 @@ struct ParseDefsCtx : TypeParserCtx { return HeapType(Signature(Type::none, results[0])); } - Result getBlockTypeFromTypeUse(Index pos, HeapType type) { + Result getBlockTypeFromTypeUse(Index pos, HeapType type) { assert(type.isSignature()); // TODO: Error if block parameters are named return type; diff --git a/src/parser/parse-2-typedefs.cpp b/src/parser/parse-2-typedefs.cpp index e5c55bf9065..83e10ec5b8a 100644 --- a/src/parser/parse-2-typedefs.cpp +++ b/src/parser/parse-2-typedefs.cpp @@ -22,8 +22,8 @@ Result<> parseTypeDefs( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map>& typeNames) { + std::vector& types, + std::unordered_map>& typeNames) { TypeBuilder builder(decls.typeDefs.size()); ParseTypeDefsCtx ctx(input, builder, typeIndices); for (auto& recType : decls.recTypeDefs) { diff --git a/src/parser/parse-3-implicit-types.cpp b/src/parser/parse-3-implicit-types.cpp index 02caaf2d6a5..cf13ae0f7e2 100644 --- a/src/parser/parse-3-implicit-types.cpp +++ b/src/parser/parse-3-implicit-types.cpp @@ -22,8 +22,8 @@ Result<> parseImplicitTypeDefs(ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes) { + std::vector& types, + std::unordered_map& implicitTypes) { ParseImplicitTypeDefsCtx ctx(input, types, implicitTypes, typeIndices); for (Index pos : decls.implicitTypeDefs) { WithPosition with(ctx, pos); diff --git a/src/parser/parse-4-module-types.cpp b/src/parser/parse-4-module-types.cpp index 07d88d0c0a3..04d8292d0bf 100644 --- a/src/parser/parse-4-module-types.cpp +++ b/src/parser/parse-4-module-types.cpp @@ -18,12 +18,11 @@ namespace wasm::WATParser { -Result<> -parseModuleTypes(ParseDeclsCtx& decls, - Lexer& input, - IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes) { +Result<> parseModuleTypes(ParseDeclsCtx& decls, + Lexer& input, + IndexMap& typeIndices, + std::vector& types, + std::unordered_map& implicitTypes) { ParseModuleTypesCtx ctx(input, decls.wasm, types, diff --git a/src/parser/parse-5-defs.cpp b/src/parser/parse-5-defs.cpp index bc619dd748e..acc81bb75a4 100644 --- a/src/parser/parse-5-defs.cpp +++ b/src/parser/parse-5-defs.cpp @@ -22,9 +22,9 @@ Result<> parseDefinitions( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes, - std::unordered_map>& typeNames) { + std::vector& types, + std::unordered_map& implicitTypes, + std::unordered_map>& typeNames) { // Parse definitions. // TODO: Parallelize this. ParseDefsCtx ctx(input, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 2e2a6da7e39..33e9d20fdc9 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -434,7 +434,6 @@ Result absheaptype(Ctx& ctx, Shareability share) { } // heaptype ::= x:typeidx => types[x] -// | '(' 'exact' x:typeidx ')' => exact types[x] // | t:absheaptype => unshared t // | '(' 'shared' t:absheaptype ')' => shared t template Result heaptype(Ctx& ctx) { @@ -443,15 +442,6 @@ template Result heaptype(Ctx& ctx) { return *t; } - if (ctx.in.takeSExprStart("exact"sv)) { - auto t = typeidx(ctx); - CHECK_ERR(t); - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of exact heap type"); - } - return ctx.makeExact(*t); - } - auto share = ctx.in.takeSExprStart("shared"sv) ? Shared : Unshared; auto t = absheaptype(ctx, share); CHECK_ERR(t); diff --git a/src/parser/wat-parser-internal.h b/src/parser/wat-parser-internal.h index 5b78d7e9632..00c96abd42e 100644 --- a/src/parser/wat-parser-internal.h +++ b/src/parser/wat-parser-internal.h @@ -28,30 +28,29 @@ Result<> parseTypeDefs( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map>& typeNames); + std::vector& types, + std::unordered_map>& typeNames); Result<> parseImplicitTypeDefs(ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes); + std::vector& types, + std::unordered_map& implicitTypes); -Result<> -parseModuleTypes(ParseDeclsCtx& decls, - Lexer& input, - IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes); +Result<> parseModuleTypes(ParseDeclsCtx& decls, + Lexer& input, + IndexMap& typeIndices, + std::vector& types, + std::unordered_map& implicitTypes); Result<> parseDefinitions( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes, - std::unordered_map>& typeNames); + std::vector& types, + std::unordered_map& implicitTypes, + std::unordered_map>& typeNames); // RAII utility for temporarily changing the parsing position of a parsing // context. diff --git a/src/parser/wat-parser.cpp b/src/parser/wat-parser.cpp index a64e62b227d..26b2bbad06f 100644 --- a/src/parser/wat-parser.cpp +++ b/src/parser/wat-parser.cpp @@ -100,11 +100,11 @@ Result<> doParseModule(Module& wasm, Lexer& input, bool allowExtra) { auto typeIndices = createIndexMap(decls.in, decls.typeDefs); CHECK_ERR(typeIndices); - std::vector types; - std::unordered_map> typeNames; + std::vector types; + std::unordered_map> typeNames; CHECK_ERR(parseTypeDefs(decls, input, *typeIndices, types, typeNames)); - std::unordered_map implicitTypes; + std::unordered_map implicitTypes; CHECK_ERR( parseImplicitTypeDefs(decls, input, *typeIndices, types, implicitTypes)); diff --git a/src/passes/NameTypes.cpp b/src/passes/NameTypes.cpp index 1a6961efaec..0e18f30945a 100644 --- a/src/passes/NameTypes.cpp +++ b/src/passes/NameTypes.cpp @@ -32,7 +32,7 @@ struct NameTypes : public Pass { void run(Module* module) override { // Find all the types. - std::vector types = ModuleUtils::collectHeapTypes(*module); + std::vector types = ModuleUtils::collectHeapTypes(*module); std::unordered_set used; diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 41bbe3bbc58..35fa57ea51a 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -86,7 +86,7 @@ void printTypeOrName(Type type, std::ostream& o, Module* wasm) { Module* wasm; DefaultTypeNameGenerator fallback; Printer(Module* wasm) : wasm(wasm) {} - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { if (wasm) { if (auto it = wasm->typeNames.find(type); it != wasm->typeNames.end()) { return it->second; @@ -141,7 +141,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { // Used to print delegate's depth argument when it throws to the caller int controlFlowDepth = 0; - std::vector heapTypes; + std::vector heapTypes; std::unordered_map signatureTypes; // Track the print indent so that we can see when it changes. That affects how @@ -173,7 +173,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { DefaultTypeNameGenerator fallback; std::unordered_map fallbackNames; - TypePrinter(PrintSExpression& parent, const std::vector& types) + TypePrinter(PrintSExpression& parent, const std::vector& types) : parent(parent) { if (!parent.currModule) { return; @@ -198,7 +198,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { } } - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { if (parent.currModule) { if (auto it = parent.currModule->typeNames.find(type); it != parent.currModule->typeNames.end()) { diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 79be9ebabe4..e7a25cf372c 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -66,7 +66,7 @@ constexpr int MAX_ITERATIONS = 20; // casts are never distinguished from their supertypes. // Most functions do no casts, or perhaps cast |this| and perhaps a few others. -using CastTypes = SmallUnorderedSet; +using CastTypes = SmallUnorderedSet; struct CastFinder : public PostWalker { CastTypes castTypes; @@ -115,7 +115,7 @@ struct CastFinder : public PostWalker { // split out into separate partitions. struct TypeMerging : public Pass { // A list of partitions with stable iterators. - using Partition = std::vector>; + using Partition = std::vector>; using Partitions = std::list; // Only modifies types. @@ -124,18 +124,18 @@ struct TypeMerging : public Pass { Module* module; // All private original types. - std::unordered_set privateTypes; + std::unordered_set privateTypes; // Types that are distinguished by cast instructions. CastTypes castTypes; // The list of remaining types that have not been merged into other types. // Candidates for further merging. - std::vector mergeable; + std::vector mergeable; // Map the original types to the types they will be merged into, if any. TypeMapper::TypeUpdates merges; - HeapType getMerged(HeapTypeDef type) { + HeapType getMerged(HeapType type) { for (auto it = merges.find(type); it != merges.end(); it = merges.find(type)) { type = it->second; @@ -143,10 +143,10 @@ struct TypeMerging : public Pass { return type; } - std::vector - mergeableSupertypesFirst(const std::vector& types) { + std::vector + mergeableSupertypesFirst(const std::vector& types) { return HeapTypeOrdering::supertypesFirst( - types, [&](HeapTypeDef type) -> std::optional { + types, [&](HeapType type) -> std::optional { if (auto super = type.getDeclaredSuperType()) { return getMerged(*super); } @@ -166,19 +166,19 @@ struct TypeMerging : public Pass { // Split a partition into potentially multiple partitions for each // disconnected group of types it contains. - std::vector> - splitSupertypePartition(const std::vector&); + std::vector> + splitSupertypePartition(const std::vector&); CastTypes findCastTypes(); - std::vector getPublicChildren(HeapTypeDef type); - DFA::State makeDFAState(HeapTypeDef type); + std::vector getPublicChildren(HeapType type); + DFA::State makeDFAState(HeapType type); void applyMerges(); }; // Hash and equality-compare HeapTypes based on their top-level structure (i.e. // "shape"), ignoring nontrivial heap type children that will not be // differentiated between until we run the DFA partition refinement. -bool shapeEq(HeapTypeDef a, HeapTypeDef b); +bool shapeEq(HeapType a, HeapType b); bool shapeEq(const Struct& a, const Struct& b); bool shapeEq(Array a, Array b); bool shapeEq(Signature a, Signature b); @@ -186,7 +186,7 @@ bool shapeEq(Field a, Field b); bool shapeEq(Type a, Type b); bool shapeEq(const Tuple& a, const Tuple& b); -size_t shapeHash(HeapTypeDef a); +size_t shapeHash(HeapType a); size_t shapeHash(const Struct& a); size_t shapeHash(Array a); size_t shapeHash(Signature a); @@ -195,13 +195,13 @@ size_t shapeHash(Type a); size_t shapeHash(const Tuple& a); struct ShapeEq { - bool operator()(const HeapTypeDef& a, const HeapTypeDef& b) const { + bool operator()(const HeapType& a, const HeapType& b) const { return shapeEq(a, b); } }; struct ShapeHash { - size_t operator()(const HeapTypeDef& type) const { return shapeHash(type); } + size_t operator()(const HeapType& type) const { return shapeHash(type); } }; void TypeMerging::run(Module* module_) { @@ -219,7 +219,7 @@ void TypeMerging::run(Module* module_) { // determine whether types are eligible to be merged. mergeable = ModuleUtils::getPrivateHeapTypes(*module); privateTypes = - std::unordered_set(mergeable.begin(), mergeable.end()); + std::unordered_set(mergeable.begin(), mergeable.end()); castTypes = findCastTypes(); // Merging supertypes or siblings can unlock more sibling merging @@ -274,21 +274,21 @@ bool TypeMerging::merge(MergeKind kind) { #endif // TYPE_MERGING_DEBUG // Map each type to its partition in the list. - std::unordered_map typePartitions; + std::unordered_map typePartitions; // Map the supertypes and top-level structures of each type to partitions so // that siblings that refine the supertype in the same way can be assigned to // the same partition and potentially merged. std::unordered_map< - std::optional, - std::unordered_map> + std::optional, + std::unordered_map> shapePartitions; // Ensure the type has a partition and return a reference to it. Since we // merge up the type tree and visit supertypes first, the partition usually // already exists. The exception is when the supertype is public, in which // case we might not have created a partition for it yet. - auto ensurePartition = [&](HeapTypeDef type) -> Partitions::iterator { + auto ensurePartition = [&](HeapType type) -> Partitions::iterator { auto [it, inserted] = typePartitions.insert({type, partitions.end()}); if (inserted) { it->second = partitions.insert(partitions.end(), {makeDFAState(type)}); @@ -298,7 +298,7 @@ bool TypeMerging::merge(MergeKind kind) { // Similar to the above, but look up or create a partition associated with the // type's supertype and top-level shape rather than its identity. - auto ensureShapePartition = [&](HeapTypeDef type) -> Partitions::iterator { + auto ensureShapePartition = [&](HeapType type) -> Partitions::iterator { auto super = type.getDeclaredSuperType(); if (super) { super = getMerged(*super); @@ -401,7 +401,7 @@ bool TypeMerging::merge(MergeKind kind) { // differentiatable. A type and its subtype cannot differ by referring to // different, unrelated types in the same position because then they would // not be in a valid subtype relationship. - std::vector> newPartitions; + std::vector> newPartitions; for (const auto& partitionTypes : refinedPartitions) { auto split = splitSupertypePartition(partitionTypes); newPartitions.insert(newPartitions.end(), split.begin(), split.end()); @@ -418,7 +418,7 @@ bool TypeMerging::merge(MergeKind kind) { // supertypes or siblings because if we try to merge into a subtype then we // will accidentally set that subtype to be its own supertype. Also keep track // of the remaining types. - std::vector newMergeable; + std::vector newMergeable; bool merged = false; for (const auto& partition : refinedPartitions) { auto target = mergeableSupertypesFirst(partition).front(); @@ -434,7 +434,7 @@ bool TypeMerging::merge(MergeKind kind) { #if TYPE_MERGING_DEBUG std::cerr << "Merges:\n"; - std::unordered_map> mergees; + std::unordered_map> mergees; for (auto& [mergee, target] : merges) { mergees[target].push_back(mergee); } @@ -450,15 +450,15 @@ bool TypeMerging::merge(MergeKind kind) { return merged; } -std::vector> -TypeMerging::splitSupertypePartition(const std::vector& types) { +std::vector> +TypeMerging::splitSupertypePartition(const std::vector& types) { if (types.size() == 1) { // Cannot split a partition containing just one type. return {types}; } - std::unordered_set includedTypes(types.begin(), types.end()); - std::vector> partitions; - std::unordered_map partitionIndices; + std::unordered_set includedTypes(types.begin(), types.end()); + std::vector> partitions; + std::unordered_map partitionIndices; for (auto type : mergeableSupertypesFirst(types)) { auto super = type.getDeclaredSuperType(); if (super && includedTypes.count(*super)) { @@ -503,8 +503,8 @@ CastTypes TypeMerging::findCastTypes() { return allCastTypes; } -std::vector TypeMerging::getPublicChildren(HeapTypeDef type) { - std::vector publicChildren; +std::vector TypeMerging::getPublicChildren(HeapType type) { + std::vector publicChildren; for (auto child : type.getHeapTypeChildren()) { if (!child.isBasic() && !privateTypes.count(child)) { publicChildren.push_back(child); @@ -513,8 +513,8 @@ std::vector TypeMerging::getPublicChildren(HeapTypeDef type) { return publicChildren; } -DFA::State TypeMerging::makeDFAState(HeapTypeDef type) { - std::vector succs; +DFA::State TypeMerging::makeDFAState(HeapType type) { + std::vector succs; // Both private and public heap type children participate in the DFA and are // eligible to be successors, except that public types are terminal states // that do not have successors. This is sufficient because public types are @@ -548,7 +548,7 @@ void TypeMerging::applyMerges() { TypeMapper(*module, merges).map(); } -bool shapeEq(HeapTypeDef a, HeapTypeDef b) { +bool shapeEq(HeapType a, HeapType b) { // Check whether `a` and `b` have the same top-level structure, including the // position and identity of any children that are not included as transitions // in the DFA, i.e. any children that are not nontrivial references. @@ -578,7 +578,7 @@ bool shapeEq(HeapTypeDef a, HeapTypeDef b) { return false; } -size_t shapeHash(HeapTypeDef a) { +size_t shapeHash(HeapType a) { size_t digest = hash(a.isOpen()); rehash(digest, a.isShared()); auto kind = a.getKind(); diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 679f6a34d82..3d68c991396 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -69,11 +69,11 @@ namespace { // way to ensure that the new types are in fact in a new rec group. // // TODO: Move this outside if we find more uses. -std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, - Module& wasm) { +std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, + Module& wasm) { auto num = recGroup.size(); - std::vector types; + std::vector types; types.reserve(num); for (auto type : recGroup) { types.push_back(type); @@ -81,8 +81,8 @@ std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, // Find all the heap types present before we create the new ones. The new // types must not appear in |existingSet|. - std::vector existing = ModuleUtils::collectHeapTypes(wasm); - std::unordered_set existingSet(existing.begin(), existing.end()); + std::vector existing = ModuleUtils::collectHeapTypes(wasm); + std::unordered_set existingSet(existing.begin(), existing.end()); // Check for a collision with an existing rec group. Note that it is enough to // check one of the types: either the entire rec group gets merged, so they diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 0fcd45c00d1..383e5af70c1 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -191,12 +191,11 @@ class TranslateToFuzzReader { std::vector loggableTypes; // The heap types we can pick from to generate instructions. - std::vector interestingHeapTypes; + std::vector interestingHeapTypes; // A mapping of a heap type to the subset of interestingHeapTypes that are // subtypes of it. - std::unordered_map> - interestingHeapSubTypes; + std::unordered_map> interestingHeapSubTypes; // Type => list of struct fields that have that type. std::unordered_map> typeStructFields; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index e1b6cf31192..683ec6849bc 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -484,11 +484,11 @@ void TranslateToFuzzReader::setupHeapTypes() { // Basic types must be handled directly, since subTypes doesn't look at // those. auto share = type.getShared(); - HeapType struct_ = HeapTypes::struct_.getBasic(share); - HeapType array = HeapTypes::array.getBasic(share); - HeapType eq = HeapTypes::eq.getBasic(share); - HeapType any = HeapTypes::any.getBasic(share); - HeapType func = HeapTypes::func.getBasic(share); + auto struct_ = HeapTypes::struct_.getBasic(share); + auto array = HeapTypes::array.getBasic(share); + auto eq = HeapTypes::eq.getBasic(share); + auto any = HeapTypes::any.getBasic(share); + auto func = HeapTypes::func.getBasic(share); switch (type.getKind()) { case HeapTypeKind::Func: interestingHeapSubTypes[func].push_back(type); diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 991b1db9c45..65820e50d8e 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -672,7 +672,7 @@ namespace { // supertypes in which they appear. struct Inhabitator { // Uniquely identify fields as an index into a type. - using FieldPos = std::pair; + using FieldPos = std::pair; // When we make a reference nullable, we typically need to make the same // reference in other types nullable to maintain valid subtyping. Which types @@ -685,14 +685,14 @@ struct Inhabitator { enum Variance { Invariant, Covariant }; // The input types. - const std::vector& types; + const std::vector& types; // The fields we will make nullable. std::unordered_set nullables; SubTypes subtypes; - Inhabitator(const std::vector& types) + Inhabitator(const std::vector& types) : types(types), subtypes(types) {} Variance getVariance(FieldPos fieldPos); @@ -701,7 +701,7 @@ struct Inhabitator { void markExternRefsNullable(); void breakNonNullableCycles(); - std::vector build(); + std::vector build(); }; Inhabitator::Variance Inhabitator::getVariance(FieldPos fieldPos) { @@ -752,7 +752,7 @@ void Inhabitator::markNullable(FieldPos field) { // this extra `index` variable once we have C++20. It's a workaround for // lambdas being unable to capture structured bindings. const size_t index = idx; - subtypes.iterSubTypes(curr, [&](HeapTypeDef type, Index) { + subtypes.iterSubTypes(curr, [&](HeapType type, Index) { nullables.insert({type, index}); }); break; @@ -803,13 +803,13 @@ void Inhabitator::markExternRefsNullable() { // the cycle to be made non-nullable. void Inhabitator::breakNonNullableCycles() { // Types we've finished visiting. We don't need to visit them again. - std::unordered_set visited; + std::unordered_set visited; // The path of types we are currently visiting. If one of them comes back up, // we've found a cycle. Map the types to the other types they reference and // our current index into that list so we can track where we are in each level // of the search. - InsertOrderedMap, Index>> visiting; + InsertOrderedMap, Index>> visiting; for (auto root : types) { if (visited.count(root)) { @@ -884,8 +884,8 @@ void Inhabitator::breakNonNullableCycles() { } } -std::vector Inhabitator::build() { - std::unordered_map typeIndices; +std::vector Inhabitator::build() { + std::unordered_map typeIndices; for (size_t i = 0; i < types.size(); ++i) { typeIndices.insert({types[i], i}); } @@ -978,16 +978,16 @@ std::vector Inhabitator::build() { } // anonymous namespace -std::vector -HeapTypeGenerator::makeInhabitable(const std::vector& types) { +std::vector +HeapTypeGenerator::makeInhabitable(const std::vector& types) { if (types.empty()) { return {}; } // Remove duplicate and basic types. We will insert them back at the end. - std::unordered_map typeIndices; + std::unordered_map typeIndices; std::vector deduplicatedIndices; - std::vector deduplicated; + std::vector deduplicated; for (auto type : types) { if (type.isBasic()) { deduplicatedIndices.push_back(-1); @@ -1009,7 +1009,7 @@ HeapTypeGenerator::makeInhabitable(const std::vector& types) { deduplicated = inhabitator.build(); // Re-duplicate and re-insert basic types as necessary. - std::vector result; + std::vector result; for (size_t i = 0; i < types.size(); ++i) { if (deduplicatedIndices[i] == (size_t)-1) { assert(types[i].isBasic()); @@ -1024,14 +1024,14 @@ HeapTypeGenerator::makeInhabitable(const std::vector& types) { namespace { bool isUninhabitable(Type type, - std::unordered_set& visited, - std::unordered_set& visiting); + std::unordered_set& visited, + std::unordered_set& visiting); // Simple recursive DFS through non-nullable references to see if we find any // cycles. -bool isUninhabitable(HeapTypeDef type, - std::unordered_set& visited, - std::unordered_set& visiting) { +bool isUninhabitable(HeapType type, + std::unordered_set& visited, + std::unordered_set& visiting) { switch (type.getKind()) { case HeapTypeKind::Basic: return false; @@ -1074,8 +1074,8 @@ bool isUninhabitable(HeapTypeDef type, } bool isUninhabitable(Type type, - std::unordered_set& visited, - std::unordered_set& visiting) { + std::unordered_set& visited, + std::unordered_set& visiting) { if (type.isRef() && type.isNonNullable()) { if (type.getHeapType().isBottom() || type.getHeapType().isMaybeShared(HeapType::ext)) { @@ -1088,10 +1088,10 @@ bool isUninhabitable(Type type, } // anonymous namespace -std::vector -HeapTypeGenerator::getInhabitable(const std::vector& types) { - std::unordered_set visited, visiting; - std::vector inhabitable; +std::vector +HeapTypeGenerator::getInhabitable(const std::vector& types) { + std::unordered_set visited, visiting; + std::vector inhabitable; for (auto type : types) { if (!isUninhabitable(type, visited, visiting)) { inhabitable.push_back(type); diff --git a/src/tools/fuzzing/heap-types.h b/src/tools/fuzzing/heap-types.h index 119b865b540..bd30178f2b4 100644 --- a/src/tools/fuzzing/heap-types.h +++ b/src/tools/fuzzing/heap-types.h @@ -42,12 +42,12 @@ struct HeapTypeGenerator { // Given a sequence of newly-built heap types, produce a sequence of similar // or identical types that are all inhabitable, i.e. that are possible to // create values for. - static std::vector - makeInhabitable(const std::vector& types); + static std::vector + makeInhabitable(const std::vector& types); // Returns the types in the input that are inhabitable. - static std::vector - getInhabitable(const std::vector& types); + static std::vector + getInhabitable(const std::vector& types); }; } // namespace wasm diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index 8bf9fa340a3..7ba341e09df 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -39,7 +39,7 @@ struct Fuzzer { bool verbose; // Initialized by `run` for checkers and possible later inspection - std::vector types; + std::vector types; std::vector> subtypeIndices; Random rand; @@ -48,7 +48,7 @@ struct Fuzzer { // Generate types and run checkers on them. void run(uint64_t seed); - static void printTypes(const std::vector&); + static void printTypes(const std::vector&); // Checkers for various properties. void checkSubtypes() const; @@ -93,11 +93,11 @@ void Fuzzer::run(uint64_t seed) { checkRecGroupShapes(); } -void Fuzzer::printTypes(const std::vector& types) { +void Fuzzer::printTypes(const std::vector& types) { std::cout << "Built " << types.size() << " types:\n"; struct FatalTypeNameGenerator : TypeNameGeneratorBase { - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { Fatal() << "trying to print unknown heap type"; } } fatalGenerator; @@ -233,7 +233,7 @@ void Fuzzer::checkCanonicalization() { // between canonical and temporary components. struct Copier { Random& rand; - const std::vector& types; + const std::vector& types; TypeBuilder& builder; // For each type, the indices in `types` at which it appears. @@ -479,8 +479,7 @@ void Fuzzer::checkCanonicalization() { } void Fuzzer::checkInhabitable() { - std::vector inhabitable = - HeapTypeGenerator::makeInhabitable(types); + std::vector inhabitable = HeapTypeGenerator::makeInhabitable(types); if (verbose) { std::cout << "\nInhabitable types:\n\n"; printTypes(inhabitable); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index eb83f3dae59..e83645837fc 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -343,8 +343,6 @@ enum EncodedType { SubFinal = 0x4f, Shared = 0x65, SharedLEB = -0x1b, // Also 0x65 as an SLEB128 - Exact = 0x62, - ExactLEB = -0x1e, // Also 0x62 as an SLEB128 Rec = 0x4e, Descriptor = 0x4d, Describes = 0x4c, @@ -1458,7 +1456,7 @@ class WasmBinaryReader { SourceMapReader sourceMapReader; // All types defined in the type section - std::vector types; + std::vector types; public: WasmBinaryReader(Module& wasm, diff --git a/src/wasm-type-ordering.h b/src/wasm-type-ordering.h index f8248bdc91f..0f23b495308 100644 --- a/src/wasm-type-ordering.h +++ b/src/wasm-type-ordering.h @@ -29,12 +29,12 @@ namespace wasm::HeapTypeOrdering { // type in the sequence comes only after its immediate supertype in the // collection is visited. template -std::vector supertypesFirst( +std::vector supertypesFirst( const T& types, - std::function(HeapTypeDef)> getSuper = - [](HeapTypeDef type) { return type.getDeclaredSuperType(); }) { + std::function(HeapType)> getSuper = + [](HeapType type) { return type.getDeclaredSuperType(); }) { - InsertOrderedMap> subtypes; + InsertOrderedMap> subtypes; for (auto type : types) { subtypes.insert({type, {}}); } diff --git a/src/wasm-type-printing.h b/src/wasm-type-printing.h index e977a07188b..a44e949dfc3 100644 --- a/src/wasm-type-printing.h +++ b/src/wasm-type-printing.h @@ -34,18 +34,16 @@ namespace wasm { template struct TypeNameGeneratorBase { TypeNameGeneratorBase() { assertValidUsage(); } - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { WASM_UNREACHABLE("Derived class must implement getNames"); } - HeapType::Printed operator()(HeapTypeDef type) { - return type.print([&](HeapTypeDef ht) { - return static_cast(this)->getNames(ht); - }); + HeapType::Printed operator()(HeapType type) { + return type.print( + [&](HeapType ht) { return static_cast(this)->getNames(ht); }); } Type::Printed operator()(Type type) { - return type.print([&](HeapTypeDef ht) { - return static_cast(this)->getNames(ht); - }); + return type.print( + [&](HeapType ht) { return static_cast(this)->getNames(ht); }); } private: @@ -54,8 +52,8 @@ template struct TypeNameGeneratorBase { // Check that the subclass provides `getNames` with the correct type. using Self = TypeNameGeneratorBase; static_assert( - static_cast(&Self::getNames) != - static_cast(&Subclass::getNames), + static_cast(&Self::getNames) != + static_cast(&Subclass::getNames), "Derived class must implement getNames"); #endif } @@ -73,7 +71,7 @@ struct DefaultTypeNameGenerator // Cached names for types that have already been seen. std::unordered_map nameCache; - TypeNames getNames(HeapTypeDef type); + TypeNames getNames(HeapType type); }; // Generates names based on the indices of types in some collection, falling @@ -84,7 +82,7 @@ struct IndexedTypeNameGenerator : TypeNameGeneratorBase> { DefaultTypeNameGenerator defaultGenerator; FallbackGenerator& fallback; - std::unordered_map names; + std::unordered_map names; template IndexedTypeNameGenerator(T& types, @@ -99,7 +97,7 @@ struct IndexedTypeNameGenerator IndexedTypeNameGenerator(T& types, const std::string& prefix = "") : IndexedTypeNameGenerator(types, defaultGenerator, prefix) {} - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { if (auto it = names.find(type); it != names.end()) { return it->second; } else { @@ -130,7 +128,7 @@ struct ModuleTypeNameGenerator std::enable_if_t>* = nullptr) : ModuleTypeNameGenerator(wasm, defaultGenerator) {} - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { if (auto it = wasm.typeNames.find(type); it != wasm.typeNames.end()) { return it->second; } diff --git a/src/wasm-type.h b/src/wasm-type.h index fb21976dc04..a72ba9d2cfa 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -50,7 +50,6 @@ void destroyAllTypesForTestingPurposesOnly(); // data. class Type; class HeapType; -class HeapTypeDef; class RecGroup; struct Signature; struct Continuation; @@ -63,7 +62,6 @@ using Tuple = TypeList; enum Nullability { NonNullable, Nullable }; enum Mutability { Immutable, Mutable }; -enum Exactness { Inexact, Exact }; // HeapType name information used for printing. struct TypeNames { @@ -74,7 +72,7 @@ struct TypeNames { }; // Used to generate HeapType names. -using HeapTypeNameGenerator = std::function; +using HeapTypeNameGenerator = std::function; // The type used for interning IDs in the public interfaces of Type and // HeapType. @@ -100,12 +98,10 @@ class HeapType { static constexpr int TypeBits = 2; static constexpr int UsedBits = TypeBits + 1; static constexpr int SharedMask = 1 << TypeBits; - static constexpr int ExactMask = SharedMask; public: - // Bits 0-1 are used by the Type representation, so need to be left free. Bit - // 2 determines whether a basic heap type is shared (1) or unshared (0). For - // non-basic heap types, bit 2 determines whether the type is exact instead. + // Bits 0-1 are used by the Type representation, so need to be left free. + // Bit 2 determines whether the basic heap type is shared (1) or unshared (0). enum BasicHeapType : uint32_t { ext = 1 << UsedBits, func = 2 << UsedBits, @@ -130,7 +126,7 @@ class HeapType { constexpr HeapType(BasicHeapType id) : id(id) {} // But converting raw TypeID is more dangerous, so make it explicit - explicit constexpr HeapType(TypeID id) : id(id) {} + explicit HeapType(TypeID id) : id(id) {} // Choose an arbitrary heap type as the default. constexpr HeapType() : HeapType(func) {} @@ -171,12 +167,8 @@ class HeapType { bool isBottom() const; bool isOpen() const; bool isShared() const { return getShared() == Shared; } - bool isExact() const { return getExactness() == Exact; } Shareability getShared() const; - Exactness getExactness() const { - return !isBasic() && (id & ExactMask) ? Exact : Inexact; - } // Check if the type is a given basic heap type, while ignoring whether it is // shared or not. @@ -225,6 +217,8 @@ class HeapType { // Get the index of this non-basic type within its recursion group. size_t getRecGroupIndex() const; + constexpr TypeID getID() const { return id; } + // Get the shared or unshared version of this basic heap type. constexpr BasicHeapType getBasic(Shareability share) const { assert(isBasic()); @@ -232,24 +226,6 @@ class HeapType { : (id & ~SharedMask)); } - constexpr HeapType with(Exactness exactness) const { - assert((!isBasic() || exactness == Inexact) && - "abstract types cannot be exact"); - return isBasic() ? *this - : HeapType(exactness == Exact ? (id | ExactMask) - : (id & ~ExactMask)); - } - - // The ID is the numeric representation of the heap type and can be used in - // FFI or hashing applications. The "raw" ID is the numeric representation of - // the plain version of the type without exactness or any other attributes we - // might add in the future. It's useful in contexts where all heap types using - // the same type definition need to be treated identically. - constexpr TypeID getID() const { return id; } - constexpr TypeID getRawID() const { - return isBasic() ? id : with(Inexact).id; - } - // (In)equality must be defined for both HeapType and BasicHeapType because it // is otherwise ambiguous whether to convert both this and other to int or // convert other to HeapType. @@ -295,16 +271,6 @@ class HeapType { std::string toString() const; }; -// Like `HeapType`, but used to represent heap type definitions and abstract -// heap types rather than arbitrary heap types. Use this whenever it would be a -// category error to use an exact heap type. -class HeapTypeDef : public HeapType { -public: - // Allow implicit conversions from HeapType. - constexpr HeapTypeDef(HeapType type) : HeapType(type.with(Inexact)) {} - constexpr HeapTypeDef() = default; -}; - class Type { // The `id` uniquely represents each type, so type equality is just a // comparison of the ids. The basic types are packed at the bottom of the @@ -855,14 +821,14 @@ struct TypeBuilder { ErrorReason reason; }; - struct BuildResult : std::variant, Error> { + struct BuildResult : std::variant, Error> { operator bool() const { - return bool(std::get_if>(this)); + return bool(std::get_if>(this)); } - const std::vector& operator*() const { - return std::get>(*this); + const std::vector& operator*() const { + return std::get>(*this); } - const std::vector* operator->() const { return &*(*this); } + const std::vector* operator->() const { return &*(*this); } const Error* getError() const { return std::get_if(this); } }; @@ -1018,10 +984,6 @@ template<> class hash { public: size_t operator()(const wasm::HeapType&) const; }; -template<> class hash { -public: - size_t operator()(const wasm::HeapTypeDef&) const; -}; template<> class hash { public: size_t operator()(const wasm::RecGroup&) const; diff --git a/src/wasm.h b/src/wasm.h index 3d4ce837b94..b6541fc6cf5 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2426,8 +2426,8 @@ class Module { // Module name, if specified. Serves a documentary role only. Name name; - std::unordered_map typeNames; - std::unordered_map typeIndices; + std::unordered_map typeNames; + std::unordered_map typeIndices; MixedArena allocator; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index b2c5842f4a4..a841d44638d 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -719,7 +719,7 @@ uint32_t WasmBinaryWriter::getElementSegmentIndex(Name name) const { } uint32_t WasmBinaryWriter::getTypeIndex(HeapType type) const { - auto it = indexedTypes.indices.find(type.with(Inexact)); + auto it = indexedTypes.indices.find(type); #ifndef NDEBUG if (it == indexedTypes.indices.end()) { std::cout << "Missing type: " << type << '\n'; @@ -1683,10 +1683,8 @@ void WasmBinaryWriter::writeHeapType(HeapType type) { if (!wasm->features.hasGC()) { type = type.getTop(); } + if (!type.isBasic()) { - if (type.isExact()) { - o << uint8_t(BinaryConsts::EncodedType::Exact); - } o << S64LEB(getTypeIndex(type)); // TODO: Actually s33 return; } @@ -2200,20 +2198,12 @@ Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { auto type = getS64LEB(); // TODO: Actually s33 - auto exactness = Inexact; - if (type == BinaryConsts::EncodedType::ExactLEB) { - exactness = Exact; - type = getS64LEB(); // TODO: Actually s33 - } // Single heap types are negative; heap type indices are non-negative if (type >= 0) { if (size_t(type) >= types.size()) { - throwError("invalid type index: " + std::to_string(type)); + throwError("invalid signature index: " + std::to_string(type)); } - return types[type].with(exactness); - } - if (exactness == Exact) { - throwError("invalid type index: " + std::to_string(type)); + return types[type]; } auto share = Unshared; if (type == BinaryConsts::EncodedType::SharedLEB) { @@ -2223,8 +2213,10 @@ HeapType WasmBinaryReader::getHeapType() { HeapType ht; if (getBasicHeapType(type, ht)) { return ht.getBasic(share); + } else { + throwError("invalid wasm heap type: " + std::to_string(type)); } - throwError("invalid wasm heap type: " + std::to_string(type)); + WASM_UNREACHABLE("unexpected type"); } HeapType WasmBinaryReader::getIndexedHeapType() { @@ -2348,20 +2340,6 @@ void WasmBinaryReader::readTypes() { auto readHeapType = [&]() -> HeapType { int64_t htCode = getS64LEB(); // TODO: Actually s33 - auto exactness = Inexact; - if (htCode == BinaryConsts::EncodedType::ExactLEB) { - exactness = Exact; - htCode = getS64LEB(); // TODO: Actually s33 - } - if (htCode >= 0) { - if (size_t(htCode) >= builder.size()) { - throwError("invalid type index: " + std::to_string(htCode)); - } - return builder.getTempHeapType(size_t(htCode)).with(exactness); - } - if (exactness == Exact) { - throwError("invalid type index: " + std::to_string(htCode)); - } auto share = Unshared; if (htCode == BinaryConsts::EncodedType::SharedLEB) { share = Shared; @@ -2371,7 +2349,10 @@ void WasmBinaryReader::readTypes() { if (getBasicHeapType(htCode, ht)) { return ht.getBasic(share); } - throwError("invalid wasm heap type: " + std::to_string(htCode)); + if (size_t(htCode) >= builder.size()) { + throwError("invalid type index: " + std::to_string(htCode)); + } + return builder.getTempHeapType(size_t(htCode)); }; auto makeType = [&](int32_t typeCode) { Type type; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index be732fdad41..5cdb76c19dd 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -228,7 +228,7 @@ namespace { HeapTypeInfo* getHeapTypeInfo(HeapType ht) { assert(!ht.isBasic()); - return (HeapTypeInfo*)(ht.getRawID()); + return (HeapTypeInfo*)ht.getID(); } HeapType asHeapType(std::unique_ptr& info) { @@ -798,6 +798,7 @@ Type Type::getLeastUpperBound(Type a, Type b) { } } return Type::none; + WASM_UNREACHABLE("unexpected type"); } Type Type::getGreatestLowerBound(Type a, Type b) { @@ -1194,9 +1195,6 @@ std::optional HeapType::getLeastUpperBound(HeapType a, HeapType b) { return getBasicHeapTypeLUB(getBasicHeapSupertype(a), getBasicHeapSupertype(b)); } - if (a.with(Inexact) == b.with(Inexact)) { - return a.with(Inexact); - } auto* infoA = getHeapTypeInfo(a); auto* infoB = getHeapTypeInfo(b); @@ -1249,7 +1247,7 @@ RecGroup HeapType::getRecGroup() const { } else { // Mark the low bit to signify that this is a trivial recursion group and // points to a heap type info rather than a vector of heap types. - return RecGroup(getRawID() | 1); + return RecGroup(id | 1); } } @@ -1369,7 +1367,7 @@ size_t RecGroup::size() const { } } -TypeNames DefaultTypeNameGenerator::getNames(HeapTypeDef type) { +TypeNames DefaultTypeNameGenerator::getNames(HeapType type) { auto [it, inserted] = nameCache.insert({type, {}}); if (inserted) { // Generate a new name for this type we have not previously seen. @@ -1507,7 +1505,7 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { // See: // https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#subtyping // https://github.com/WebAssembly/gc/blob/master/proposals/gc/MVP.md#defined-types - if (a == b || a.with(Inexact) == b) { + if (a == b) { return true; } if (a.isShared() != b.isShared()) { @@ -1552,11 +1550,6 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { // bottom types. return a == b.getBottom(); } - if (b.isExact()) { - // The only subtypes of an exact type are itself and bottom, both of which - // we have ruled out. - return false; - } // Subtyping must be declared rather than derived from structure, so we will // not recurse. TODO: optimize this search with some form of caching. HeapTypeInfo* curr = getHeapTypeInfo(a); @@ -1615,20 +1608,14 @@ bool SubTyper::isSubType(const Array& a, const Array& b) { } void TypePrinter::printHeapTypeName(HeapType type) { - if (type.isExact()) { - os << "(exact "; - } if (type.isBasic()) { print(type); - } else { - generator(type.with(Inexact)).name.print(os); + return; + } + generator(type).name.print(os); #if TRACE_CANONICALIZATION - os << "(;" << ((type.with(Inexact).getID() >> 4) % 1000) << ";) "; + os << "(;" << ((type.getID() >> 4) % 1000) << ";) "; #endif - } - if (type.isExact()) { - os << ')'; - } } std::ostream& TypePrinter::print(Type type) { @@ -1955,10 +1942,8 @@ size_t RecGroupHasher::hash(HeapType type) const { wasm::rehash(digest, type.getID()); return digest; } - wasm::rehash(digest, type.isExact()); wasm::rehash(digest, type.getRecGroupIndex()); auto currGroup = type.getRecGroup(); - wasm::rehash(digest, currGroup != group); if (currGroup != group) { wasm::rehash(digest, currGroup.getID()); } @@ -2088,9 +2073,6 @@ bool RecGroupEquator::eq(HeapType a, HeapType b) const { if (a.isBasic() || b.isBasic()) { return a == b; } - if (a.getExactness() != b.getExactness()) { - return false; - } if (a.getRecGroupIndex() != b.getRecGroupIndex()) { return false; } @@ -2474,10 +2456,8 @@ void updateReferencedHeapTypes( isTopLevel = false; if (type->isRef()) { auto ht = type->getHeapType(); - auto exact = ht.getExactness(); - ht = ht.with(Inexact); if (auto it = canonicalized.find(ht); it != canonicalized.end()) { - *type = Type(it->second.with(exact), type->getNullability()); + *type = Type(it->second, type->getNullability()); } } else if (type->isTuple()) { TypeGraphWalkerBase::scanType(type); @@ -2485,7 +2465,6 @@ void updateReferencedHeapTypes( } void scanHeapType(HeapType* type) { - assert(!type->isExact() && "unexpected exact type in definition"); if (isTopLevel) { isTopLevel = false; TypeGraphWalkerBase::scanHeapType(type); @@ -2550,8 +2529,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, for (size_t i = 0; i < typeInfos.size(); ++i) { auto type = asHeapType(typeInfos[i]); for (auto child : type.getHeapTypeChildren()) { - HeapType rawChild(child.getRawID()); - if (isTemp(rawChild) && !seenTypes.count(rawChild)) { + if (isTemp(child) && !seenTypes.count(child)) { return {TypeBuilder::Error{ i, TypeBuilder::ErrorReason::ForwardChildReference}}; } @@ -2574,7 +2552,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, canonicalized.insert({group[i], canonical[i]}); } // Return the canonical types. - return {std::vector(canonical.begin(), canonical.end())}; + return {std::vector(canonical.begin(), canonical.end())}; } // The group was successfully moved to the global rec group store, so it is @@ -2588,7 +2566,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, } } - std::vector results(group.begin(), group.end()); + std::vector results(group.begin(), group.end()); // We need to make the tuples canonical as well, but right now there is no way // to move them to their global store, so we have to create new tuples and @@ -2622,7 +2600,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, TypeBuilder::BuildResult TypeBuilder::build() { size_t entryCount = impl->entries.size(); - std::vector results; + std::vector results; results.reserve(entryCount); // Map temporary HeapTypes to their canonicalized versions so they can be @@ -2768,10 +2746,6 @@ size_t hash::operator()(const wasm::HeapType& heapType) const { return wasm::hash(heapType.getID()); } -size_t hash::operator()(const wasm::HeapTypeDef& def) const { - return wasm::hash(def.getID()); -} - size_t hash::operator()(const wasm::RecGroup& group) const { return wasm::hash(group.getID()); } diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 9d0767595f7..f267742a71b 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -349,7 +349,7 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { std::vector vec(set.begin(), set.end()); // Find the maximum depths for the normalized cone tests later down. - std::unordered_set heapTypes; + std::unordered_set heapTypes; for (auto& contents : set) { auto type = contents.getType(); if (type.isRef()) { @@ -359,7 +359,7 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { } } } - std::vector heapTypesVec(heapTypes.begin(), heapTypes.end()); + std::vector heapTypesVec(heapTypes.begin(), heapTypes.end()); SubTypes subTypes(heapTypesVec); auto maxDepths = subTypes.getMaxDepths(); diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 66a7526f095..1e676b7194a 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -185,7 +185,7 @@ TEST_F(TypeTest, Basics) { auto result = builder.build(); ASSERT_TRUE(result); - std::vector built = *result; + std::vector built = *result; ASSERT_EQ(built.size(), size_t{3}); // The built types should have the correct kinds. @@ -428,93 +428,6 @@ TEST_F(TypeTest, CanonicalizeUses) { EXPECT_NE(built[4], built[6]); } -TEST_F(TypeTest, CanonicalizeExactHeapTypes) { - TypeBuilder builder(8); - - HeapType inexact = HeapType(builder[0]).with(Inexact); - HeapType exact = HeapType(builder[1]).with(Exact); - - Type inexactRef = builder.getTempRefType(inexact, Nullable); - Type exactRef = builder.getTempRefType(exact, Nullable); - - // Types that vary in exactness of the referenced heap type are different. - builder[0] = Struct({Field(inexactRef, Mutable)}); - builder[1] = Struct({Field(exactRef, Mutable)}); - builder[2] = Signature(Type({inexactRef, exactRef}), Type::none); - builder[3] = Signature(Type::none, Type({exactRef, inexactRef})); - - auto translate = [&](HeapType t) { - for (int i = 0; i < 4; ++i) { - if (t.with(Inexact) == builder[i]) { - return HeapType(builder[4 + i]).with(t.getExactness()); - } - } - WASM_UNREACHABLE("unexpected type"); - }; - - builder[4].copy(builder[0], translate); - builder[5].copy(builder[1], translate); - builder[6].copy(builder[2], translate); - builder[7].copy(builder[3], translate); - - auto result = builder.build(); - ASSERT_TRUE(result); - auto built = *result; - - // Different types should be different. - EXPECT_NE(built[0], built[1]); - EXPECT_NE(built[0], built[2]); - EXPECT_NE(built[0], built[3]); - EXPECT_NE(built[1], built[2]); - EXPECT_NE(built[1], built[3]); - EXPECT_NE(built[2], built[3]); - - // Copies of the types should match. - EXPECT_EQ(built[0], built[4]); - EXPECT_EQ(built[1], built[5]); - EXPECT_EQ(built[2], built[6]); - EXPECT_EQ(built[3], built[7]); - - // A type is inexact by default. - EXPECT_EQ(built[0], built[0].with(Inexact)); - EXPECT_EQ(built[1], built[1].with(Inexact)); - EXPECT_EQ(built[2], built[2].with(Inexact)); - EXPECT_EQ(built[3], built[3].with(Inexact)); - - // We can freely convert between exact and inexact. - EXPECT_EQ(built[0], built[0].with(Exact).with(Inexact)); - EXPECT_EQ(built[0].with(Exact), - built[0].with(Exact).with(Inexact).with(Exact)); - - // Conversions are idempotent. - EXPECT_EQ(built[0].with(Exact), built[0].with(Exact).with(Exact)); - EXPECT_EQ(built[0], built[0].with(Inexact)); - - // An exact version of a type is not the same as its inexact version. - EXPECT_NE(built[0].with(Exact), built[0].with(Inexact)); - - // But they have the same rec group. - EXPECT_EQ(built[0].with(Exact).getRecGroup(), - built[0].with(Inexact).getRecGroup()); - - // Looking up the inner structure works either way. - ASSERT_TRUE(built[0].with(Exact).isStruct()); - ASSERT_TRUE(built[0].with(Inexact).isStruct()); - EXPECT_EQ(built[0].with(Exact).getStruct(), - built[0].with(Inexact).getStruct()); - - // The exactness of children types is preserved. - EXPECT_EQ(built[0], built[0].getStruct().fields[0].type.getHeapType()); - EXPECT_EQ(built[1].with(Exact), - built[1].getStruct().fields[0].type.getHeapType()); - EXPECT_EQ(built[0], built[2].getSignature().params[0].getHeapType()); - EXPECT_EQ(built[1].with(Exact), - built[2].getSignature().params[1].getHeapType()); - EXPECT_EQ(built[0], built[3].getSignature().results[1].getHeapType()); - EXPECT_EQ(built[1].with(Exact), - built[3].getSignature().results[0].getHeapType()); -} - TEST_F(TypeTest, CanonicalizeSelfReferences) { TypeBuilder builder(5); // Single self-reference @@ -701,16 +614,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { HeapType nofunc = HeapType::nofunc; HeapType nocont = HeapType::nocont; HeapType defFunc = Signature(); - HeapType exactDefFunc = defFunc.with(Exact); HeapType defCont = Continuation(defFunc); - HeapType defStruct; - HeapType exactDefStruct; - HeapType subStruct; - HeapType exactSubStruct; - HeapType subStruct2; - HeapType exactSubStruct2; + HeapType defStruct = Struct(); HeapType defArray = Array(Field(Type::i32, Immutable)); - HeapType exactDefArray = defArray.with(Exact); HeapType sharedAny = any.getBasic(Shared); HeapType sharedEq = eq.getBasic(Shared); HeapType sharedI31 = i31.getBasic(Shared); @@ -721,25 +627,16 @@ TEST_F(TypeTest, TestHeapTypeRelations) { HeapType sharedDefStruct; HeapType sharedDefFunc; { - TypeBuilder builder(5); - builder[0].setShared() = Struct{}; - builder[1].setShared() = Signature(); - builder[2].setOpen() = Struct{}; - builder[3].subTypeOf(builder[2]) = Struct{}; - builder[4].copy(builder[3]); - builder.createRecGroup(3, 2); + TypeBuilder builder(2); + builder[0] = Struct{}; + builder[1] = Signature(); + builder[0].setShared(); + builder[1].setShared(); auto results = builder.build(); ASSERT_TRUE(results); auto built = *results; sharedDefStruct = built[0]; sharedDefFunc = built[1]; - defStruct = built[2]; - subStruct = built[3]; - subStruct2 = built[4]; - ASSERT_NE(subStruct, subStruct2); - exactDefStruct = defStruct.with(Exact); - exactSubStruct = subStruct.with(Exact); - exactSubStruct2 = subStruct2.with(Exact); } auto assertLUB = [](HeapType a, HeapType b, std::optional lub) { @@ -789,13 +686,8 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(ext, nofunc, {}); assertLUB(ext, nocont, {}); assertLUB(ext, defFunc, {}); - assertLUB(ext, exactDefFunc, {}); assertLUB(ext, defStruct, {}); - assertLUB(ext, exactDefStruct, {}); - assertLUB(ext, subStruct, {}); - assertLUB(ext, exactSubStruct, {}); assertLUB(ext, defArray, {}); - assertLUB(ext, exactDefArray, {}); assertLUB(ext, sharedAny, {}); assertLUB(ext, sharedEq, {}); assertLUB(ext, sharedI31, {}); @@ -818,14 +710,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(func, nofunc, func); assertLUB(func, nocont, {}); assertLUB(func, defFunc, func); - assertLUB(func, exactDefFunc, func); assertLUB(func, defCont, {}); assertLUB(func, defStruct, {}); - assertLUB(func, exactDefStruct, {}); - assertLUB(func, subStruct, {}); - assertLUB(func, exactSubStruct, {}); assertLUB(func, defArray, {}); - assertLUB(func, exactDefArray, {}); assertLUB(func, sharedAny, {}); assertLUB(func, sharedEq, {}); assertLUB(func, sharedI31, {}); @@ -848,14 +735,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(cont, nofunc, {}); assertLUB(cont, nocont, cont); assertLUB(cont, defFunc, {}); - assertLUB(cont, exactDefFunc, {}); assertLUB(cont, defCont, cont); assertLUB(cont, defStruct, {}); - assertLUB(cont, exactDefStruct, {}); - assertLUB(cont, subStruct, {}); - assertLUB(cont, exactSubStruct, {}); assertLUB(cont, defArray, {}); - assertLUB(cont, exactDefArray, {}); assertLUB(cont, sharedAny, {}); assertLUB(cont, sharedEq, {}); assertLUB(cont, sharedI31, {}); @@ -877,14 +759,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(any, nofunc, {}); assertLUB(any, nocont, {}); assertLUB(any, defFunc, {}); - assertLUB(any, exactDefFunc, {}); assertLUB(any, defCont, {}); assertLUB(any, defStruct, any); - assertLUB(any, exactDefStruct, any); - assertLUB(any, subStruct, any); - assertLUB(any, exactSubStruct, any); assertLUB(any, defArray, any); - assertLUB(any, exactDefArray, any); assertLUB(any, sharedAny, {}); assertLUB(any, sharedEq, {}); assertLUB(any, sharedI31, {}); @@ -905,14 +782,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(eq, nofunc, {}); assertLUB(eq, nocont, {}); assertLUB(eq, defFunc, {}); - assertLUB(eq, exactDefFunc, {}); assertLUB(eq, defCont, {}); assertLUB(eq, defStruct, eq); - assertLUB(eq, exactDefStruct, eq); - assertLUB(eq, subStruct, eq); - assertLUB(eq, exactSubStruct, eq); assertLUB(eq, defArray, eq); - assertLUB(eq, exactDefArray, eq); assertLUB(eq, sharedAny, {}); assertLUB(eq, sharedEq, {}); assertLUB(eq, sharedI31, {}); @@ -932,14 +804,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(i31, nofunc, {}); assertLUB(i31, nocont, {}); assertLUB(i31, defFunc, {}); - assertLUB(i31, exactDefFunc, {}); assertLUB(i31, defCont, {}); assertLUB(i31, defStruct, eq); - assertLUB(i31, exactDefStruct, eq); - assertLUB(i31, subStruct, eq); - assertLUB(i31, exactSubStruct, eq); assertLUB(i31, defArray, eq); - assertLUB(i31, exactDefArray, eq); assertLUB(i31, sharedAny, {}); assertLUB(i31, sharedEq, {}); assertLUB(i31, sharedI31, {}); @@ -958,14 +825,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(struct_, nofunc, {}); assertLUB(struct_, nocont, {}); assertLUB(struct_, defFunc, {}); - assertLUB(struct_, exactDefFunc, {}); assertLUB(struct_, defCont, {}); assertLUB(struct_, defStruct, struct_); - assertLUB(struct_, exactDefStruct, struct_); - assertLUB(struct_, subStruct, struct_); - assertLUB(struct_, exactSubStruct, struct_); assertLUB(struct_, defArray, eq); - assertLUB(struct_, exactDefArray, eq); assertLUB(struct_, sharedAny, {}); assertLUB(struct_, sharedEq, {}); assertLUB(struct_, sharedI31, {}); @@ -983,14 +845,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(array, nofunc, {}); assertLUB(array, nocont, {}); assertLUB(array, defFunc, {}); - assertLUB(array, exactDefFunc, {}); assertLUB(array, defCont, {}); assertLUB(array, defStruct, eq); - assertLUB(array, exactDefStruct, eq); - assertLUB(array, subStruct, eq); - assertLUB(array, exactSubStruct, eq); assertLUB(array, defArray, array); - assertLUB(array, exactDefArray, array); assertLUB(array, sharedAny, {}); assertLUB(array, sharedEq, {}); assertLUB(array, sharedI31, {}); @@ -1007,14 +864,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(string, nofunc, {}); assertLUB(string, nocont, {}); assertLUB(string, defFunc, {}); - assertLUB(string, exactDefFunc, {}); assertLUB(string, defCont, {}); assertLUB(string, defStruct, {}); - assertLUB(string, exactDefStruct, {}); - assertLUB(string, subStruct, {}); - assertLUB(string, exactSubStruct, {}); assertLUB(string, defArray, {}); - assertLUB(string, exactDefArray, {}); assertLUB(string, sharedAny, {}); assertLUB(string, sharedEq, {}); assertLUB(string, sharedI31, {}); @@ -1029,14 +881,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(none, nofunc, {}); assertLUB(none, nocont, {}); assertLUB(none, defFunc, {}); - assertLUB(none, exactDefFunc, {}); assertLUB(none, defCont, {}); assertLUB(none, defStruct, defStruct); - assertLUB(none, exactDefStruct, exactDefStruct); - assertLUB(none, subStruct, subStruct); - assertLUB(none, exactSubStruct, exactSubStruct); assertLUB(none, defArray, defArray); - assertLUB(none, exactDefArray, exactDefArray); assertLUB(none, sharedAny, {}); assertLUB(none, sharedEq, {}); assertLUB(none, sharedI31, {}); @@ -1050,14 +897,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(noext, nofunc, {}); assertLUB(noext, nocont, {}); assertLUB(noext, defFunc, {}); - assertLUB(noext, exactDefFunc, {}); assertLUB(noext, defCont, {}); assertLUB(noext, defStruct, {}); - assertLUB(noext, exactDefStruct, {}); - assertLUB(noext, subStruct, {}); - assertLUB(noext, exactSubStruct, {}); assertLUB(noext, defArray, {}); - assertLUB(noext, exactDefArray, {}); assertLUB(noext, sharedAny, {}); assertLUB(noext, sharedEq, {}); assertLUB(noext, sharedI31, {}); @@ -1070,14 +912,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nofunc, nofunc, nofunc); assertLUB(nofunc, nocont, {}); assertLUB(nofunc, defFunc, defFunc); - assertLUB(nofunc, exactDefFunc, exactDefFunc); assertLUB(nofunc, defCont, {}); assertLUB(nofunc, defStruct, {}); - assertLUB(nofunc, exactDefStruct, {}); - assertLUB(nofunc, subStruct, {}); - assertLUB(nofunc, exactSubStruct, {}); assertLUB(nofunc, defArray, {}); - assertLUB(nofunc, exactDefArray, {}); assertLUB(nofunc, sharedAny, {}); assertLUB(nofunc, sharedEq, {}); assertLUB(nofunc, sharedI31, {}); @@ -1092,14 +929,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nocont, cont, cont); assertLUB(nocont, nofunc, {}); assertLUB(nocont, defFunc, {}); - assertLUB(nocont, exactDefFunc, {}); assertLUB(nocont, defCont, defCont); assertLUB(nocont, defStruct, {}); - assertLUB(nocont, exactDefStruct, {}); - assertLUB(nocont, subStruct, {}); - assertLUB(nocont, exactSubStruct, {}); assertLUB(nocont, defArray, {}); - assertLUB(nocont, exactDefArray, {}); assertLUB(nocont, sharedAny, {}); assertLUB(nocont, sharedEq, {}); assertLUB(nocont, sharedI31, {}); @@ -1110,14 +942,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nocont, sharedDefFunc, {}); assertLUB(defFunc, defFunc, defFunc); - assertLUB(defFunc, exactDefFunc, defFunc); assertLUB(defFunc, defCont, {}); assertLUB(defFunc, defStruct, {}); - assertLUB(defFunc, exactDefStruct, {}); - assertLUB(defFunc, subStruct, {}); - assertLUB(defFunc, exactSubStruct, {}); assertLUB(defFunc, defArray, {}); - assertLUB(defFunc, exactDefArray, {}); assertLUB(defFunc, sharedAny, {}); assertLUB(defFunc, sharedEq, {}); assertLUB(defFunc, sharedI31, {}); @@ -1127,31 +954,10 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defFunc, sharedDefStruct, {}); assertLUB(defFunc, sharedDefFunc, {}); - assertLUB(exactDefFunc, exactDefFunc, exactDefFunc); - assertLUB(exactDefFunc, defCont, {}); - assertLUB(exactDefFunc, defStruct, {}); - assertLUB(exactDefFunc, exactDefStruct, {}); - assertLUB(exactDefFunc, subStruct, {}); - assertLUB(exactDefFunc, exactSubStruct, {}); - assertLUB(exactDefFunc, defArray, {}); - assertLUB(exactDefFunc, exactDefArray, {}); - assertLUB(exactDefFunc, sharedAny, {}); - assertLUB(exactDefFunc, sharedEq, {}); - assertLUB(exactDefFunc, sharedI31, {}); - assertLUB(exactDefFunc, sharedStruct, {}); - assertLUB(exactDefFunc, sharedNone, {}); - assertLUB(exactDefFunc, sharedFunc, {}); - assertLUB(exactDefFunc, sharedDefStruct, {}); - assertLUB(exactDefFunc, sharedDefFunc, {}); - assertLUB(defCont, defCont, defCont); assertLUB(defCont, defFunc, {}); assertLUB(defCont, defStruct, {}); - assertLUB(defCont, exactDefStruct, {}); - assertLUB(defCont, subStruct, {}); - assertLUB(defCont, exactSubStruct, {}); assertLUB(defCont, defArray, {}); - assertLUB(defCont, exactDefArray, {}); assertLUB(defCont, sharedAny, {}); assertLUB(defCont, sharedEq, {}); assertLUB(defCont, sharedI31, {}); @@ -1162,11 +968,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defCont, sharedDefFunc, {}); assertLUB(defStruct, defStruct, defStruct); - assertLUB(defStruct, exactDefStruct, defStruct); - assertLUB(defStruct, subStruct, defStruct); - assertLUB(defStruct, exactSubStruct, defStruct); assertLUB(defStruct, defArray, eq); - assertLUB(defStruct, exactDefArray, eq); assertLUB(defStruct, sharedAny, {}); assertLUB(defStruct, sharedEq, {}); assertLUB(defStruct, sharedI31, {}); @@ -1176,51 +978,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defStruct, sharedDefStruct, {}); assertLUB(defStruct, sharedDefFunc, {}); - assertLUB(exactDefStruct, exactDefStruct, exactDefStruct); - assertLUB(exactDefStruct, subStruct, defStruct); - assertLUB(exactDefStruct, exactSubStruct, defStruct); - assertLUB(exactDefStruct, defArray, eq); - assertLUB(exactDefStruct, exactDefArray, eq); - assertLUB(exactDefStruct, sharedAny, {}); - assertLUB(exactDefStruct, sharedEq, {}); - assertLUB(exactDefStruct, sharedI31, {}); - assertLUB(exactDefStruct, sharedStruct, {}); - assertLUB(exactDefStruct, sharedNone, {}); - assertLUB(exactDefStruct, sharedFunc, {}); - assertLUB(exactDefStruct, sharedDefStruct, {}); - assertLUB(exactDefStruct, sharedDefFunc, {}); - - assertLUB(subStruct, subStruct, subStruct); - assertLUB(subStruct, exactSubStruct, subStruct); - assertLUB(subStruct, subStruct2, defStruct); - assertLUB(subStruct, exactSubStruct2, defStruct); - assertLUB(subStruct, defArray, eq); - assertLUB(subStruct, exactDefArray, eq); - assertLUB(subStruct, sharedAny, {}); - assertLUB(subStruct, sharedEq, {}); - assertLUB(subStruct, sharedI31, {}); - assertLUB(subStruct, sharedStruct, {}); - assertLUB(subStruct, sharedNone, {}); - assertLUB(subStruct, sharedFunc, {}); - assertLUB(subStruct, sharedDefStruct, {}); - assertLUB(subStruct, sharedDefFunc, {}); - - assertLUB(exactSubStruct, exactSubStruct, exactSubStruct); - assertLUB(exactSubStruct, subStruct2, defStruct); - assertLUB(exactSubStruct, exactSubStruct2, defStruct); - assertLUB(exactSubStruct, defArray, eq); - assertLUB(exactSubStruct, exactDefArray, eq); - assertLUB(exactSubStruct, sharedAny, {}); - assertLUB(exactSubStruct, sharedEq, {}); - assertLUB(exactSubStruct, sharedI31, {}); - assertLUB(exactSubStruct, sharedStruct, {}); - assertLUB(exactSubStruct, sharedNone, {}); - assertLUB(exactSubStruct, sharedFunc, {}); - assertLUB(exactSubStruct, sharedDefStruct, {}); - assertLUB(exactSubStruct, sharedDefFunc, {}); - assertLUB(defArray, defArray, defArray); - assertLUB(defArray, exactDefArray, defArray); assertLUB(defArray, sharedAny, {}); assertLUB(defArray, sharedEq, {}); assertLUB(defArray, sharedI31, {}); @@ -1230,16 +988,6 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defArray, sharedDefStruct, {}); assertLUB(defArray, sharedDefFunc, {}); - assertLUB(exactDefArray, exactDefArray, exactDefArray); - assertLUB(exactDefArray, sharedAny, {}); - assertLUB(exactDefArray, sharedEq, {}); - assertLUB(exactDefArray, sharedI31, {}); - assertLUB(exactDefArray, sharedStruct, {}); - assertLUB(exactDefArray, sharedNone, {}); - assertLUB(exactDefArray, sharedFunc, {}); - assertLUB(exactDefArray, sharedDefStruct, {}); - assertLUB(exactDefArray, sharedDefFunc, {}); - assertLUB(sharedAny, sharedAny, sharedAny); assertLUB(sharedAny, sharedEq, sharedAny); assertLUB(sharedAny, sharedI31, sharedAny); diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index b4f178a759e..deb9d0a1dad 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -940,7 +940,7 @@ fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan) { WASM_UNREACHABLE("unexpected kind"); } -std::vector BuildHeapTypes(TypeBuilderPlan plan) { +std::vector BuildHeapTypes(TypeBuilderPlan plan) { // Continuation types without reachable function types need a fallback. TypeBuilder fallbackBuilder(2); fallbackBuilder[0] = Signature(); @@ -1062,7 +1062,7 @@ auto ArbitraryDefinedHeapTypesAndPlan() { ArbitraryTypeBuilderPlan()); } -void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { +void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { auto types = std::move(pair.first); auto plan = std::move(pair.second); @@ -1138,7 +1138,7 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { } }; - auto checkDef = [&](TypeDefPlan& plan, HeapTypeDef type) { + auto checkDef = [&](TypeDefPlan& plan, HeapType type) { if (auto* f = plan.getFunc()) { checkFunc(*f, type); } else if (auto* s = plan.getStruct()) { @@ -1168,19 +1168,6 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { FUZZ_TEST(TypeBuilderDomainsTest, TestBuiltTypes) .WithDomains(ArbitraryDefinedHeapTypesAndPlan()); -fuzztest::Domain> ArbitraryDefinedHeapTypeDefs() { - return fuzztest::Map(BuildHeapTypes, ArbitraryTypeBuilderPlan()); -} - -std::vector convertToHeapTypes(std::vector defs) { - std::vector types; - types.reserve(defs.size()); - for (auto def : defs) { - types.push_back(HeapType(def)); - } - return types; -} - } // anonymous namespace fuzztest::Domain ArbitraryTypeBuilderPlan() { @@ -1190,7 +1177,7 @@ fuzztest::Domain ArbitraryTypeBuilderPlan() { } fuzztest::Domain> ArbitraryDefinedHeapTypes() { - return fuzztest::Map(convertToHeapTypes, ArbitraryDefinedHeapTypeDefs()); + return fuzztest::Map(BuildHeapTypes, ArbitraryTypeBuilderPlan()); } fuzztest::Domain> ArbitraryHeapTypePair() { diff --git a/test/gtest/type-domains.h b/test/gtest/type-domains.h index b2fc268cc75..17ad7fe9530 100644 --- a/test/gtest/type-domains.h +++ b/test/gtest/type-domains.h @@ -134,7 +134,7 @@ struct TypeBuilderPlan { std::vector defs; // Built types. - std::vector types; + std::vector types; friend std::ostream& operator<<(std::ostream& o, const TypeBuilderPlan& plan); }; diff --git a/test/lit/basic/exact.wast b/test/lit/basic/exact.wast deleted file mode 100644 index 13adca540ee..00000000000 --- a/test/lit/basic/exact.wast +++ /dev/null @@ -1,52 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: wasm-opt %s -all -o %t.text.wast -g -S -;; RUN: wasm-as %s -all -g -o %t.wasm -;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast -;; RUN: wasm-as %s -all -o %t.nodebug.wasm -;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast -;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT -;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN -;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG - -(module - (rec - ;; CHECK-TEXT: (rec - ;; CHECK-TEXT-NEXT: (type $a (struct (field (ref null (exact $a))) (field (ref (exact $b))))) - ;; CHECK-BIN: (rec - ;; CHECK-BIN-NEXT: (type $a (struct (field (ref null (exact $a))) (field (ref (exact $b))))) - (type $a (struct (field (ref null (exact 0)) (ref (exact 1))))) - ;; CHECK-TEXT: (type $b (struct (field (ref (exact $a))) (field (ref null (exact $b))))) - ;; CHECK-BIN: (type $b (struct (field (ref (exact $a))) (field (ref null (exact $b))))) - (type $b (struct (field (ref (exact $a)) (ref null (exact $b))))) - ) - - ;; CHECK-TEXT: (type $2 (func (param (ref null (exact $a)) (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))))) - - ;; CHECK-TEXT: (func $foo (type $2) (param $0 (ref null (exact $a))) (param $1 (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))) - ;; CHECK-TEXT-NEXT: (local $2 (ref null (exact $a))) - ;; CHECK-TEXT-NEXT: (unreachable) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (type $2 (func (param (ref null (exact $a)) (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))))) - - ;; CHECK-BIN: (func $foo (type $2) (param $0 (ref null (exact $a))) (param $1 (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))) - ;; CHECK-BIN-NEXT: (local $2 (ref null (exact $a))) - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) - (func $foo (param (ref null (exact $a)) (ref (exact $b))) - (result (ref (exact $a)) (ref null (exact $b))) - (local (ref null (exact $a))) - (unreachable) - ) -) -;; CHECK-BIN-NODEBUG: (rec -;; CHECK-BIN-NODEBUG-NEXT: (type $0 (struct (field (ref null (exact $0))) (field (ref (exact $1))))) - -;; CHECK-BIN-NODEBUG: (type $1 (struct (field (ref (exact $0))) (field (ref null (exact $1))))) - -;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref null (exact $0)) (ref (exact $1))) (result (ref (exact $0)) (ref null (exact $1))))) - -;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (ref null (exact $0))) (param $1 (ref (exact $1))) (result (ref (exact $0)) (ref null (exact $1))) -;; CHECK-BIN-NODEBUG-NEXT: (local $2 (ref null (exact $0))) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) From c9c62b1b6237e500fbe5bd425601b19fddba3ca4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Apr 2025 12:20:13 -0700 Subject: [PATCH 426/622] Restore exact references (#7497) Having decided that storing exactness on the Type rather than HeapType makes the most sense for our IR, revert the following PRs: - #7404 - #7402 This will restore the original work on exact references. Follow-on PRs will update it to match the current spec. --- scripts/test/fuzzing.py | 8 + src/ir/manipulation.h | 5 +- src/ir/type-updating.cpp | 1 + src/literal.h | 2 +- src/parser/contexts.h | 18 +- src/parser/parsers.h | 73 +- src/passes/OptimizeInstructions.cpp | 30 +- src/passes/RemoveUnusedBrs.cpp | 4 +- src/tools/fuzzing.h | 6 + src/tools/fuzzing/fuzzing.cpp | 75 +- src/wasm-binary.h | 11 + src/wasm-builder.h | 10 +- src/wasm-type.h | 41 +- src/wasm/literal.cpp | 4 +- src/wasm/wasm-binary.cpp | 56 +- src/wasm/wasm-stack.cpp | 52 +- src/wasm/wasm-type.cpp | 110 ++- src/wasm/wasm-validator.cpp | 4 + src/wasm/wasm.cpp | 22 +- test/example/c-api-kitchen-sink.txt | 22 +- test/gtest/type-builder.cpp | 161 ++++- test/lit/basic/exact-references.wast | 672 ++++++++++++++++++ test/lit/basic/reference-types.wast | 16 +- .../lit/ctor-eval/materialize-null-local.wast | 26 + test/lit/exec/exact.wast | 21 + test/lit/heap-types.wast | 4 +- test/lit/passes/cfp.wast | 4 +- test/lit/passes/coalesce-locals-exact.wast | 47 ++ test/lit/passes/code-pushing-gc.wast | 4 +- test/lit/passes/dae-gc-refine-params.wast | 2 +- test/lit/passes/dae-gc.wast | 2 +- test/lit/passes/flatten_all-features.wast | 6 +- test/lit/passes/global-refining.wast | 18 +- test/lit/passes/gufa-refs.wast | 52 +- test/lit/passes/gufa-vs-cfp.wast | 4 +- test/lit/passes/heap2local-rmw.wast | 38 +- test/lit/passes/heap2local.wast | 178 ++--- test/lit/passes/local-subtyping-exact.wast | 39 + test/lit/passes/local-subtyping-nn.wast | 4 +- test/lit/passes/local-subtyping.wast | 2 +- test/lit/passes/merge-blocks.wast | 2 +- test/lit/passes/monomorphize-context.wast | 4 +- .../passes/optimize-instructions-exact.wast | 33 + .../passes/optimize-instructions-gc-tnh.wast | 4 +- test/lit/passes/optimize-instructions-gc.wast | 4 +- test/lit/passes/precompute-gc.wast | 2 +- test/lit/passes/remove-unused-brs-exact.wast | 40 ++ test/lit/passes/remove-unused-brs-gc.wast | 24 +- .../lit/passes/remove-unused-types-exact.wast | 16 + test/lit/passes/signature-refining_gto.wat | 4 +- test/lit/passes/ssa.wast | 4 +- test/lit/passes/type-refining-gufa.wast | 4 +- .../passes/type-refining-isorecursive.wast | 6 +- test/lit/passes/type-refining-rmw.wast | 4 +- test/lit/passes/type-refining.wast | 14 +- test/passes/precompute_all-features.txt | 2 +- 56 files changed, 1666 insertions(+), 355 deletions(-) create mode 100644 test/lit/basic/exact-references.wast create mode 100644 test/lit/ctor-eval/materialize-null-local.wast create mode 100644 test/lit/exec/exact.wast create mode 100644 test/lit/passes/coalesce-locals-exact.wast create mode 100644 test/lit/passes/local-subtyping-exact.wast create mode 100644 test/lit/passes/optimize-instructions-exact.wast create mode 100644 test/lit/passes/remove-unused-brs-exact.wast create mode 100644 test/lit/passes/remove-unused-types-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 50ab56683fa..a65d3a92a21 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -111,6 +111,14 @@ 'dce-stack-switching.wast', 'precompute-stack-switching.wast', 'vacuum-stack-switching.wast', + # TODO: fuzzer support for exact references + 'exact-references.wast', + 'optimize-instructions-exact.wast', + 'local-subtyping-exact.wast', + 'remove-unused-types-exact.wast', + 'coalesce-locals-exact.wast', + 'remove-unused-brs-exact.wast', + 'exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/ir/manipulation.h b/src/ir/manipulation.h index e7816af9fce..c766fb8719e 100644 --- a/src/ir/manipulation.h +++ b/src/ir/manipulation.h @@ -40,10 +40,9 @@ template inline Nop* nop(InputType* target) { } template -inline RefNull* refNull(InputType* target, Type type) { - assert(type.isNullable() && type.getHeapType().isBottom()); +inline RefNull* refNull(InputType* target, HeapType type) { auto* ret = convert(target); - ret->finalize(type); + ret->finalize(Type(type.getBottom(), Nullable, Exact)); return ret; } diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 6ea5b0d87cd..59286734720 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -346,6 +346,7 @@ Type GlobalTypeRewriter::getTempType(Type type) { if (type.isRef()) { auto heapType = type.getHeapType(); if (auto it = typeIndices.find(heapType); it != typeIndices.end()) { + // TODO: Handle exactness. return typeBuilder.getTempRefType(typeBuilder[it->second], type.getNullability()); } diff --git a/src/literal.h b/src/literal.h index c3cf2c15f70..4f9e3d535ad 100644 --- a/src/literal.h +++ b/src/literal.h @@ -246,7 +246,7 @@ class Literal { } } static Literal makeNull(HeapType type) { - return Literal(Type(type.getBottom(), Nullable)); + return Literal(Type(type.getBottom(), Nullable, Exact)); } static Literal makeFunc(Name func, HeapType type) { return Literal(func, type); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 4c56780323b..fa96c270670 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -127,7 +127,9 @@ struct NullTypeParserCtx { TypeT makeF64() { return Ok{}; } TypeT makeV128() { return Ok{}; } - TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; } + TypeT makeRefType(HeapTypeT, Nullability, Exactness) { return Ok{}; } + + HeapTypeT getHeapTypeFromRefType(TypeT) { return Ok{}; } TupleElemListT makeTupleElemList() { return Ok{}; } void appendTupleElem(TupleElemListT&, TypeT) {} @@ -257,10 +259,13 @@ template struct TypeParserCtx { TypeT makeF64() { return Type::f64; } TypeT makeV128() { return Type::v128; } - TypeT makeRefType(HeapTypeT ht, Nullability nullability) { - return Type(ht, nullability); + TypeT + makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { + return Type(ht, nullability, exactness); } + HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } + std::vector makeTupleElemList() { return {}; } void appendTupleElem(std::vector& elems, Type elem) { elems.push_back(elem); @@ -1117,10 +1122,13 @@ struct ParseTypeDefsCtx : TypeParserCtx { : TypeParserCtx(typeIndices), in(in), builder(builder), names(builder.size()) {} - TypeT makeRefType(HeapTypeT ht, Nullability nullability) { - return builder.getTempRefType(ht, nullability); + TypeT + makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { + return builder.getTempRefType(ht, nullability, exactness); } + HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } + TypeT makeTupleType(const std::vector types) { return builder.getTempTupleType(types); } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 33e9d20fdc9..26c20767938 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -30,6 +30,8 @@ using namespace std::string_view_literals; template Result absheaptype(Ctx&, Shareability); template Result heaptype(Ctx&); +template +MaybeResult maybeReftypeAbbrev(Ctx&); template MaybeResult maybeReftype(Ctx&); template Result reftype(Ctx&); template MaybeResult tupletype(Ctx&); @@ -458,68 +460,85 @@ template Result heaptype(Ctx& ctx) { // | 'i31ref' => i31ref // | 'structref' => structref // | 'arrayref' => arrayref -// | '(' ref null? t:heaptype ')' => ref null? t -template MaybeResult maybeReftype(Ctx& ctx) { +// | ... +template +MaybeResult maybeReftypeAbbrev(Ctx& ctx) { if (ctx.in.takeKeyword("funcref"sv)) { - return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("externref"sv)) { - return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("anyref"sv)) { - return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("eqref"sv)) { - return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("i31ref"sv)) { - return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable); + return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("structref"sv)) { - return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("arrayref"sv)) { - return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("exnref"sv)) { - return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("stringref"sv)) { - return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("contref"sv)) { - return ctx.makeRefType(ctx.makeContType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullref"sv)) { - return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullexternref"sv)) { - return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullfuncref"sv)) { - return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullexnref"sv)) { - return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullcontref"sv)) { - return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact); } + return {}; +} - if (!ctx.in.takeSExprStart("ref"sv)) { - return {}; +// reftype ::= ... +// | '(' 'exact' (ref null ht):shorthand ')' => ref null exact ht +// | '(' ref null? exact? ht:heaptype ')' => ref null? t +template MaybeResult maybeReftype(Ctx& ctx) { + if (ctx.in.takeSExprStart("exact"sv)) { + auto rt = maybeReftypeAbbrev(ctx); + CHECK_ERR(rt); + if (!rt) { + return ctx.in.err("expected reftype shorthand"); + } + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); + } + return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact); } - auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; - - auto type = heaptype(ctx); - CHECK_ERR(type); - - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of reftype"); + if (ctx.in.takeSExprStart("ref"sv)) { + auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; + auto exactness = ctx.in.takeKeyword("exact"sv) ? Exact : Inexact; + auto type = heaptype(ctx); + CHECK_ERR(type); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); + } + return ctx.makeRefType(*type, nullability, exactness); } - return ctx.makeRefType(*type, nullability); + return maybeReftypeAbbrev(ctx); } template Result reftype(Ctx& ctx) { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 7ade7ac3261..8c13663deba 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2291,10 +2291,14 @@ struct OptimizeInstructions // emit a null check. bool needsNullCheck = ref->type.getNullability() == Nullable && curr->type.getNullability() == NonNullable; + // Same with exactness. + bool needsExactCast = ref->type.getExactness() == Inexact && + curr->type.getExactness() == Exact; // If the best value to propagate is the argument to the cast, we can // simply remove the cast (or downgrade it to a null check if - // necessary). - if (ref == curr->ref) { + // necessary). This does not work if we need a cast to prove + // exactness. + if (ref == curr->ref && !needsExactCast) { if (needsNullCheck) { replaceCurrent(builder.makeRefAs(RefAsNonNull, curr->ref)); } else { @@ -2303,9 +2307,9 @@ struct OptimizeInstructions return; } // Otherwise we can't just remove the cast and replace it with `ref` - // because the intermediate expressions might have had side effects. - // We can replace the cast with a drop followed by a direct return of - // the value, though. + // because the intermediate expressions might have had side effects or + // we need to check exactness. We can replace the cast with a drop + // followed by a direct return of the value, though. if (ref->type.isNull()) { // We can materialize the resulting null value directly. // @@ -2313,7 +2317,7 @@ struct OptimizeInstructions // would be, aside from the interesting corner case of // uninhabitable types: // - // (ref.cast func + // (ref.cast (ref func) // (block (result (ref nofunc)) // (unreachable) // ) @@ -2360,18 +2364,20 @@ struct OptimizeInstructions } [[fallthrough]]; case GCTypeUtils::SuccessOnlyIfNull: { - auto nullType = Type(curr->type.getHeapType().getBottom(), Nullable); // The cast either returns null or traps. In trapsNeverHappen mode // we know the result, since by assumption it will not trap. if (getPassOptions().trapsNeverHappen) { - replaceCurrent(builder.makeBlock( - {builder.makeDrop(curr->ref), builder.makeRefNull(nullType)}, - curr->type)); + replaceCurrent( + builder.makeBlock({builder.makeDrop(curr->ref), + builder.makeRefNull(curr->type.getHeapType())}, + curr->type)); return; } // Otherwise, we should have already refined the cast type to cast - // directly to null. - assert(curr->type == nullType); + // directly to null. We do not further refine the cast type to exact + // null because the extra precision is not useful and doing so would + // increase the size of the instruction encoding. + assert(curr->type.isNull()); break; } case GCTypeUtils::Unreachable: diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 6bf9114d604..6fa69e70cd7 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -859,8 +859,8 @@ struct RemoveUnusedBrs : public WalkerPass> { if (Type::isSubType(expr->type, type)) { return expr; } - if (HeapType::isSubType(expr->type.getHeapType(), - type.getHeapType())) { + if (type.isNonNullable() && expr->type.isNullable() && + Type::isSubType(expr->type.with(NonNullable), type)) { return builder.makeRefAs(RefAsNonNull, expr); } return builder.makeRefCast(expr, type); diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 383e5af70c1..bb43fa39fa2 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -360,6 +360,10 @@ class TranslateToFuzzReader { // instruction for EH is supposed to exist only at the beginning of a 'catch' // block, so it shouldn't be moved around or deleted freely. bool canBeArbitrarilyReplaced(Expression* curr) { + // TODO: Remove this once we better support exact references. + if (curr->type.isExact()) { + return false; + } return curr->type.isDefaultable() && !EHUtils::containsValidDanglingPop(curr); } @@ -521,7 +525,9 @@ class TranslateToFuzzReader { Type getLoggableType(); bool isLoggableType(Type type); Nullability getNullability(); + Exactness getExactness(); Nullability getSubType(Nullability nullability); + Exactness getSubType(Exactness exactness); HeapType getSubType(HeapType type); Type getSubType(Type type); Nullability getSuperType(Nullability nullability); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 683ec6849bc..864705cea83 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1566,20 +1566,22 @@ void TranslateToFuzzReader::recombine(Function* func) { } std::vector ret; - auto heapType = type.getHeapType(); - auto nullability = type.getNullability(); + ret.push_back(type); - if (nullability == NonNullable) { - ret = getRelevantTypes(Type(heapType, Nullable)); + if (type.isNonNullable()) { + auto nullable = getRelevantTypes(type.with(Nullable)); + ret.insert(ret.end(), nullable.begin(), nullable.end()); + } + if (type.isExact()) { + auto inexact = getRelevantTypes(type.with(Inexact)); + ret.insert(ret.end(), inexact.begin(), inexact.end()); + // Do not consider exact references to supertypes. + return ret; } - while (1) { - ret.push_back(Type(heapType, nullability)); - auto super = heapType.getSuperType(); - if (!super) { - break; - } - heapType = *super; + for (auto heapType = type.getHeapType().getSuperType(); heapType; + heapType = heapType->getSuperType()) { + ret.push_back(type.with(*heapType)); } return ret; @@ -4968,9 +4970,17 @@ static auto makeArrayBoundsCheck(Expression* ref, Function* func, Builder& builder, Expression* length = nullptr) { - auto tempRef = builder.addVar(func, ref->type); + // The reference might be a RefNull, in which case its type is exact. But we + // want to avoid creating exact-typed locals until we support them more widely + // in the fuzzer, so adjust the type. TODO: remove this once exact references + // are better supported. + Type refType = ref->type; + if (refType.isExact()) { + refType = refType.with(Inexact); + } + auto tempRef = builder.addVar(func, refType); auto tempIndex = builder.addVar(func, index->type); - auto* teeRef = builder.makeLocalTee(tempRef, ref, ref->type); + auto* teeRef = builder.makeLocalTee(tempRef, ref, refType); auto* teeIndex = builder.makeLocalTee(tempIndex, index, index->type); auto* getSize = builder.makeArrayLen(teeRef); @@ -4997,7 +5007,7 @@ static auto makeArrayBoundsCheck(Expression* ref, // An additional use of the length, if it was provided. Expression* getLength = nullptr; } result = {builder.makeBinary(LtUInt32, effectiveIndex, getSize), - builder.makeLocalGet(tempRef, ref->type), + builder.makeLocalGet(tempRef, refType), builder.makeLocalGet(tempIndex, index->type), getLength}; return result; @@ -5386,6 +5396,23 @@ Nullability TranslateToFuzzReader::getNullability() { return Nullable; } +Exactness TranslateToFuzzReader::getExactness() { + // Without GC, the only heap types are func and extern, neither of which is + // exactly inhabitable. To avoid introducing uninhabitable types, only + // generate exact references when GC is enabled. We don't need custom + // descriptors to be enabled even though that is the feature that introduces + // exact references because the binary writer can always generalize the exact + // reference types away. + // + // if (wasm.features.hasGC() && oneIn(8)) { + // return Exact; + // } + // + // However, we cannot yet handle creating exact references in general, so for + // now we always generate inexact references when given the choice. TODO. + return Inexact; +} + Nullability TranslateToFuzzReader::getSubType(Nullability nullability) { if (nullability == NonNullable) { return NonNullable; @@ -5393,6 +5420,13 @@ Nullability TranslateToFuzzReader::getSubType(Nullability nullability) { return getNullability(); } +Exactness TranslateToFuzzReader::getSubType(Exactness exactness) { + if (exactness == Exact) { + return Exact; + } + return getExactness(); +} + HeapType TranslateToFuzzReader::getSubType(HeapType type) { if (oneIn(3)) { return type; @@ -5486,9 +5520,18 @@ Type TranslateToFuzzReader::getSubType(Type type) { if (!funcContext && heapType.isMaybeShared(HeapType::exn)) { return type; } - heapType = getSubType(heapType); + if (type.isExact()) { + // The only other possible heap type is bottom, but we don't want to + // generate too many bottom types. + if (!heapType.isBottom() && oneIn(20)) { + heapType = heapType.getBottom(); + } + } else { + heapType = getSubType(heapType); + } auto nullability = getSubType(type.getNullability()); - auto subType = Type(heapType, nullability); + auto exactness = getSubType(type.getExactness()); + auto subType = Type(heapType, nullability, exactness); // We don't want to emit lots of uninhabitable types like (ref none), so // avoid them with high probability. Specifically, if the original type was // inhabitable then return that; avoid adding more uninhabitability. diff --git a/src/wasm-binary.h b/src/wasm-binary.h index e83645837fc..211b15183d8 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -304,6 +304,13 @@ enum SegmentFlag { UsesExpressions = 1 << 2 }; +enum BrOnCastFlag { + InputNullable = 1 << 0, + OutputNullable = 1 << 1, + InputExact = 1 << 2, + OutputExact = 1 << 3, +}; + enum EncodedType { // value types i32 = -0x1, // 0x7f @@ -327,6 +334,7 @@ enum EncodedType { eqref = -0x13, // 0x6d nonnullable = -0x1c, // 0x64 nullable = -0x1d, // 0x63 + exact = -0x1e, // 0x62 contref = -0x18, // 0x68 nullcontref = -0x0b, // 0x75 // exception handling @@ -1127,6 +1135,8 @@ enum ASTNodes { I31GetS = 0x1d, I31GetU = 0x1e, RefI31Shared = 0x1f, + RefTestRT = 0x20, + RefCastRT = 0x21, // Shared GC Opcodes @@ -1499,6 +1509,7 @@ class WasmBinaryReader { Type getType(); // Get a type given the initial S32LEB has already been read, and is provided. Type getType(int code); + Type getTypeNoExact(int code); HeapType getHeapType(); HeapType getIndexedHeapType(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index a8a76f4740c..04b9cb2a1ad 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -670,11 +670,11 @@ class Builder { } RefNull* makeRefNull(HeapType type) { auto* ret = wasm.allocator.alloc(); - ret->finalize(Type(type.getBottom(), Nullable)); + ret->finalize(Type(type.getBottom(), Nullable, Exact)); return ret; } RefNull* makeRefNull(Type type) { - assert(type.isNullable() && type.isNull()); + assert(type.isNullable() && type.isNull() && type.isExact()); auto* ret = wasm.allocator.alloc(); ret->finalize(type); return ret; @@ -1270,7 +1270,7 @@ class Builder { return makeConst(value); } if (value.isNull()) { - return makeRefNull(type); + return makeRefNull(type.getHeapType()); } if (type.isFunction()) { return makeRefFunc(value.getFunc(), type.getHeapType()); @@ -1435,8 +1435,8 @@ class Builder { return maybeWrap(makeConstantExpression(Literal::makeZeros(curr->type))); } if (curr->type.isNullable()) { - return maybeWrap(ExpressionManipulator::refNull( - curr, Type(curr->type.getHeapType().getBottom(), Nullable))); + return maybeWrap( + ExpressionManipulator::refNull(curr, curr->type.getHeapType())); } if (curr->type.isRef() && curr->type.getHeapType().isMaybeShared(HeapType::i31)) { diff --git a/src/wasm-type.h b/src/wasm-type.h index a72ba9d2cfa..b250e38c9c8 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -62,6 +62,7 @@ using Tuple = TypeList; enum Nullability { NonNullable, Nullable }; enum Mutability { Immutable, Mutable }; +enum Exactness { Inexact, Exact }; // HeapType name information used for printing. struct TypeNames { @@ -95,13 +96,13 @@ class HeapType { // should also be passed by value. uintptr_t id; - static constexpr int TypeBits = 2; + static constexpr int TypeBits = 3; static constexpr int UsedBits = TypeBits + 1; static constexpr int SharedMask = 1 << TypeBits; public: - // Bits 0-1 are used by the Type representation, so need to be left free. - // Bit 2 determines whether the basic heap type is shared (1) or unshared (0). + // Bits 0-2 are used by the Type representation, so need to be left free. + // Bit 3 determines whether the basic heap type is shared (1) or unshared (0). enum BasicHeapType : uint32_t { ext = 1 << UsedBits, func = 2 << UsedBits, @@ -278,7 +279,7 @@ class Type { // bit 0 set. When that bit is masked off, they are pointers to the underlying // vectors of types. Otherwise, the type is a reference type, and is // represented as a heap type with bit 1 set iff the reference type is - // nullable. + // nullable and bit 2 set iff the reference type is exact. // // Since `Type` is really just a single integer, it should be passed by value. // This is a uintptr_t rather than a TypeID (uint64_t) to save memory on @@ -287,6 +288,7 @@ class Type { static constexpr int TupleMask = 1 << 0; static constexpr int NullMask = 1 << 1; + static constexpr int ExactMask = 1 << 2; public: enum BasicType : uint32_t { @@ -317,9 +319,10 @@ class Type { // Construct from a heap type description. Also covers construction from // Signature, Struct or Array via implicit conversion to HeapType. - Type(HeapType heapType, Nullability nullable) - : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0)) { - assert(heapType.isBasic() || !(heapType.getID() & (TupleMask | NullMask))); + Type(HeapType heapType, Nullability nullable, Exactness exact = Inexact) + : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | + (exact == Exact ? ExactMask : 0)) { + assert(!(heapType.getID() & (TupleMask | NullMask | ExactMask))); } // Predicates @@ -368,9 +371,11 @@ class Type { bool isRef() const { return !isBasic() && !(id & TupleMask); } bool isNullable() const { return isRef() && (id & NullMask); } bool isNonNullable() const { return isRef() && !(id & NullMask); } + bool isExact() const { return isRef() && (id & ExactMask); } + bool isInexact() const { return isRef() && !(id & ExactMask); } HeapType getHeapType() const { assert(isRef()); - return HeapType(id & ~NullMask); + return HeapType(id & ~(NullMask | ExactMask)); } bool isFunction() const { return isRef() && getHeapType().isFunction(); } @@ -392,11 +397,20 @@ class Type { Nullability getNullability() const { return isNullable() ? Nullable : NonNullable; } + Exactness getExactness() const { + assert(isRef()); + return isExact() ? Exact : Inexact; + } // Return a new reference type with some part updated to the specified value. - Type with(HeapType heapType) { return Type(heapType, getNullability()); } + Type with(HeapType heapType) { + return Type(heapType, getNullability(), getExactness()); + } Type with(Nullability nullability) { - return Type(getHeapType(), nullability); + return Type(getHeapType(), nullability, getExactness()); + } + Type with(Exactness exactness) { + return Type(getHeapType(), getNullability(), exactness); } private: @@ -716,7 +730,8 @@ struct TypeBuilder { return t; } assert(t.isRef()); - return getTempRefType(map(t.getHeapType()), t.getNullability()); + return getTempRefType( + map(t.getHeapType()), t.getNullability(), t.getExactness()); }; auto copyType = [&](Type t) -> Type { if (t.isTuple()) { @@ -769,7 +784,9 @@ struct TypeBuilder { // TypeBuilder's HeapTypes. For Ref types, the HeapType may be a temporary // HeapType owned by this builder or a canonical HeapType. Type getTempTupleType(const Tuple&); - Type getTempRefType(HeapType heapType, Nullability nullable); + Type getTempRefType(HeapType heapType, + Nullability nullable, + Exactness exact = Inexact); // Declare the HeapType being built at index `i` to be an immediate subtype of // the given HeapType. diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 9e015501a0a..de95fe565bf 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -72,7 +72,9 @@ Literal::Literal(const uint8_t init[16]) : type(Type::v128) { } Literal::Literal(std::shared_ptr gcData, HeapType type) - : gcData(gcData), type(type, gcData ? NonNullable : Nullable) { + : gcData(gcData), + type(type, gcData ? NonNullable : Nullable, gcData ? Inexact : Exact) { + // TODO: Use exact types for more than just nulls. // The type must be a proper type for GC data: either a struct, array, or // string; or an externalized version of the same; or a null; or an // internalized string (which appears as an anyref). diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index a841d44638d..5f113a93811 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1574,6 +1574,12 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) { void WasmBinaryWriter::writeType(Type type) { if (type.isRef()) { + // Exact references are introduced by the custom descriptors feature, but + // can be used internally even when it is not enabled. In that case, we have + // to generalize the types to be inexact before writing them. + if (!wasm->features.hasCustomDescriptors()) { + type = Type(type.getHeapType(), type.getNullability(), Inexact); + } // The only reference types allowed without GC are funcref, externref, and // exnref. We internally use more refined versions of those types, but we // cannot emit those without GC. @@ -1590,6 +1596,12 @@ void WasmBinaryWriter::writeType(Type type) { type = Type(type.getHeapType().getTop(), Nullable); } } + // If the type is exact, emit the exact prefix and continue on without + // considering exactness. + if (type.isExact()) { + o << S32LEB(BinaryConsts::EncodedType::exact); + type = Type(type.getHeapType(), type.getNullability(), Inexact); + } auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { switch (heapType.getBasic(Unshared)) { @@ -2178,7 +2190,7 @@ Signature WasmBinaryReader::getBlockType() { return Signature(Type::none, getType(code)); } -Type WasmBinaryReader::getType(int code) { +Type WasmBinaryReader::getTypeNoExact(int code) { Type type; if (getBasicType(code, type)) { return type; @@ -2194,6 +2206,17 @@ Type WasmBinaryReader::getType(int code) { WASM_UNREACHABLE("unexpected type"); } +Type WasmBinaryReader::getType(int code) { + if (code == BinaryConsts::EncodedType::exact) { + auto type = getTypeNoExact(getS32LEB()); + if (!type.isRef()) { + throwError("invalid exact prefix on non-reference type"); + } + return Type(type.getHeapType(), type.getNullability(), Exact); + } + return getTypeNoExact(code); +} + Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { @@ -2354,7 +2377,7 @@ void WasmBinaryReader::readTypes() { } return builder.getTempHeapType(size_t(htCode)); }; - auto makeType = [&](int32_t typeCode) { + auto makeTypeNoExact = [&](int32_t typeCode) { Type type; if (getBasicType(typeCode, type)) { return type; @@ -2379,6 +2402,17 @@ void WasmBinaryReader::readTypes() { } WASM_UNREACHABLE("unexpected type"); }; + auto makeType = [&](int32_t typeCode) { + if (typeCode == BinaryConsts::EncodedType::exact) { + auto type = makeTypeNoExact(getS32LEB()); + if (!type.isRef()) { + throwError("unexpected exact prefix on non-reference type"); + } + return builder.getTempRefType( + type.getHeapType(), type.getNullability(), Exact); + } + return makeTypeNoExact(typeCode); + }; auto readType = [&]() { return makeType(getS32LEB()); }; auto readSignatureDef = [&]() { @@ -4252,16 +4286,30 @@ Result<> WasmBinaryReader::readInst() { return builder.makeRefTest(Type(getHeapType(), NonNullable)); case BinaryConsts::RefTestNull: return builder.makeRefTest(Type(getHeapType(), Nullable)); + case BinaryConsts::RefTestRT: + return builder.makeRefTest(getType()); case BinaryConsts::RefCast: return builder.makeRefCast(Type(getHeapType(), NonNullable)); case BinaryConsts::RefCastNull: return builder.makeRefCast(Type(getHeapType(), Nullable)); + case BinaryConsts::RefCastRT: + return builder.makeRefCast(getType()); case BinaryConsts::BrOnCast: case BinaryConsts::BrOnCastFail: { auto flags = getInt8(); auto label = getU32LEB(); - auto in = Type(getHeapType(), (flags & 1) ? Nullable : NonNullable); - auto cast = Type(getHeapType(), (flags & 2) ? Nullable : NonNullable); + auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) + ? Nullable + : NonNullable; + auto dstNull = (flags & BinaryConsts::BrOnCastFlag::OutputNullable) + ? Nullable + : NonNullable; + auto srcExact = + (flags & BinaryConsts::BrOnCastFlag::InputExact) ? Exact : Inexact; + auto dstExact = + (flags & BinaryConsts::BrOnCastFlag::OutputExact) ? Exact : Inexact; + auto in = Type(getHeapType(), srcNull, srcExact); + auto cast = Type(getHeapType(), dstNull, dstExact); auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; return builder.makeBrOn(label, kind, in, cast); } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 060b01b04ee..506b11a085e 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2260,22 +2260,38 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { void BinaryInstWriter::visitRefTest(RefTest* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->castType.isNullable()) { - o << U32LEB(BinaryConsts::RefTestNull); + if (curr->castType.isExact() && + parent.getModule()->features.hasCustomDescriptors()) { + // Fall back to the general form with a reftype immediate. + o << U32LEB(BinaryConsts::RefTestRT); + parent.writeType(curr->castType); } else { - o << U32LEB(BinaryConsts::RefTest); + // Use the special-case form with heap type immediate. + if (curr->castType.isNullable()) { + o << U32LEB(BinaryConsts::RefTestNull); + } else { + o << U32LEB(BinaryConsts::RefTest); + } + parent.writeHeapType(curr->castType.getHeapType()); } - parent.writeHeapType(curr->castType.getHeapType()); } void BinaryInstWriter::visitRefCast(RefCast* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->type.isNullable()) { - o << U32LEB(BinaryConsts::RefCastNull); + if (curr->type.isExact() && + parent.getModule()->features.hasCustomDescriptors()) { + // Fall back to the general form with a reftype immediate. + o << U32LEB(BinaryConsts::RefCastRT); + parent.writeType(curr->type); } else { - o << U32LEB(BinaryConsts::RefCast); + // Use the special-case form with heap type immediate. + if (curr->type.isNullable()) { + o << U32LEB(BinaryConsts::RefCastNull); + } else { + o << U32LEB(BinaryConsts::RefCast); + } + parent.writeHeapType(curr->type.getHeapType()); } - parent.writeHeapType(curr->type.getHeapType()); } void BinaryInstWriter::visitBrOn(BrOn* curr) { @@ -2298,8 +2314,24 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { } assert(curr->ref->type.isRef()); assert(Type::isSubType(curr->castType, curr->ref->type)); - uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | - (curr->castType.isNullable() ? 2 : 0); + uint8_t flags = 0; + if (curr->ref->type.isNullable()) { + flags |= BinaryConsts::BrOnCastFlag::InputNullable; + } + if (curr->castType.isNullable()) { + flags |= BinaryConsts::BrOnCastFlag::OutputNullable; + } + if (parent.getModule()->features.hasCustomDescriptors()) { + // If custom descriptors (and therefore exact references) are not + // enabled, then these flags wouldn't be recognized, and we will be + // generalizing all exact references to be non-exact anyway. + if (curr->ref->type.isExact()) { + flags |= BinaryConsts::BrOnCastFlag::InputExact; + } + if (curr->castType.isExact()) { + flags |= BinaryConsts::BrOnCastFlag::OutputExact; + } + } o << flags; o << U32LEB(getBreakIndex(curr->name)); parent.writeHeapType(curr->ref->type.getHeapType()); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 5cdb76c19dd..ada53eb150b 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -790,11 +790,19 @@ Type Type::getLeastUpperBound(Type a, Type b) { return Type(elems); } if (a.isRef() && b.isRef()) { - if (auto heapType = - HeapType::getLeastUpperBound(a.getHeapType(), b.getHeapType())) { + auto heapTypeA = a.getHeapType(); + auto heapTypeB = b.getHeapType(); + if (auto heapType = HeapType::getLeastUpperBound(heapTypeA, heapTypeB)) { auto nullability = (a.isNullable() || b.isNullable()) ? Nullable : NonNullable; - return Type(*heapType, nullability); + auto exactness = (a.isInexact() || b.isInexact()) ? Inexact : Exact; + // The LUB can only be exact if the heap types are the same or one of them + // is bottom. + if (heapTypeA != heapTypeB && !heapTypeA.isBottom() && + !heapTypeB.isBottom()) { + exactness = Inexact; + } + return Type(*heapType, nullability, exactness); } } return Type::none; @@ -828,6 +836,7 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } auto nullability = (a.isNonNullable() || b.isNonNullable()) ? NonNullable : Nullable; + auto exactness = (a.isExact() || b.isExact()) ? Exact : Inexact; HeapType heapType; if (HeapType::isSubType(heapA, heapB)) { heapType = heapA; @@ -836,7 +845,13 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } else { heapType = heapA.getBottom(); } - return Type(heapType, nullability); + // If one of the types is exact, but the GLB heap type is different than its + // heap type, then we must make the GLB heap type bottom. + if ((a.isExact() && heapType != heapA) || + (b.isExact() && heapType != heapB)) { + heapType = heapA.getBottom(); + } + return Type(heapType, nullability, exactness); } const Type& Type::Iterator::operator*() const { @@ -1491,14 +1506,24 @@ bool SubTyper::isSubType(Type a, Type b) { if (a == Type::unreachable) { return true; } - if (a.isRef() && b.isRef()) { - return (a.isNullable() == b.isNullable() || !a.isNullable()) && - isSubType(a.getHeapType(), b.getHeapType()); - } if (a.isTuple() && b.isTuple()) { return isSubType(a.getTuple(), b.getTuple()); } - return false; + if (!a.isRef() || !b.isRef()) { + return false; + } + if (a.isNullable() && !b.isNullable()) { + return false; + } + if (a.isInexact() && !b.isInexact()) { + return false; + } + auto heapTypeA = a.getHeapType(); + auto heapTypeB = b.getHeapType(); + if (b.isExact() && !heapTypeA.isBottom()) { + return heapTypeA == heapTypeB; + } + return isSubType(heapTypeA, heapTypeB); } bool SubTyper::isSubType(HeapType a, HeapType b) { @@ -1646,44 +1671,69 @@ std::ostream& TypePrinter::print(Type type) { } else if (type.isRef()) { auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { + if (type.isExact()) { + os << "(exact "; + } // Print shorthands for certain basic heap types. switch (heapType.getBasic(Unshared)) { case HeapType::ext: - return os << "externref"; + os << "externref"; + break; case HeapType::func: - return os << "funcref"; + os << "funcref"; + break; case HeapType::cont: - return os << "contref"; + os << "contref"; + break; case HeapType::any: - return os << "anyref"; + os << "anyref"; + break; case HeapType::eq: - return os << "eqref"; + os << "eqref"; + break; case HeapType::i31: - return os << "i31ref"; + os << "i31ref"; + break; case HeapType::struct_: - return os << "structref"; + os << "structref"; + break; case HeapType::array: - return os << "arrayref"; + os << "arrayref"; + break; case HeapType::exn: - return os << "exnref"; + os << "exnref"; + break; case HeapType::string: - return os << "stringref"; + os << "stringref"; + break; case HeapType::none: - return os << "nullref"; + os << "nullref"; + break; case HeapType::noext: - return os << "nullexternref"; + os << "nullexternref"; + break; case HeapType::nofunc: - return os << "nullfuncref"; + os << "nullfuncref"; + break; case HeapType::nocont: - return os << "nullcontref"; + os << "nullcontref"; + break; case HeapType::noexn: - return os << "nullexnref"; + os << "nullexnref"; + break; } + if (type.isExact()) { + os << ')'; + } + return os; } os << "(ref "; if (type.isNullable()) { os << "null "; } + if (type.isExact()) { + os << "exact "; + } printHeapTypeName(heapType); os << ')'; } else { @@ -1927,8 +1977,9 @@ size_t RecGroupHasher::hash(Type type) const { return digest; } assert(type.isRef()); - rehash(digest, type.getNullability()); - rehash(digest, hash(type.getHeapType())); + wasm::rehash(digest, type.getNullability()); + wasm::rehash(digest, type.getExactness()); + hash_combine(digest, hash(type.getHeapType())); return digest; } @@ -2059,6 +2110,7 @@ bool RecGroupEquator::eq(Type a, Type b) const { } if (a.isRef() && b.isRef()) { return a.getNullability() == b.getNullability() && + a.getExactness() == b.getExactness() && eq(a.getHeapType(), b.getHeapType()); } return false; @@ -2269,8 +2321,10 @@ Type TypeBuilder::getTempTupleType(const Tuple& tuple) { return impl->tupleStore.insert(tuple); } -Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) { - return Type(type, nullable); +Type TypeBuilder::getTempRefType(HeapType type, + Nullability nullable, + Exactness exact) { + return Type(type, nullable, exact); } void TypeBuilder::setSubType(size_t i, std::optional super) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index e33d5c3ef0d..3b7e33390f3 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2288,6 +2288,10 @@ void FunctionValidator::visitRefNull(RefNull* curr) { curr->type.isNullable(), curr, "ref.null types must be nullable")) { return; } + if (!shouldBeTrue( + curr->type.isExact(), curr, "ref.null types must be exact")) { + return; + } shouldBeTrue( curr->type.isNull(), curr, "ref.null must have a bottom heap type"); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 0bc888646cc..6c5694c6848 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -800,7 +800,7 @@ void MemoryGrow::finalize() { void RefNull::finalize(HeapType heapType) { assert(heapType.isBottom()); - type = Type(heapType, Nullable); + type = Type(heapType, Nullable, Exact); } void RefNull::finalize(Type type_) { type = type_; } @@ -925,6 +925,7 @@ static void populateTryTableSentTypes(TryTable* curr, Module* wasm) { // wasm spec defines when GC is enabled (=== non-nullable types are allowed). // If GC is not enabled then we emit a nullable type in the binary format in // WasmBinaryWriter::writeType. + // TODO: Make this exact. Type exnref = Type(HeapType::exn, NonNullable); for (Index i = 0; i < curr->catchTags.size(); i++) { auto tagName = curr->catchTags[i]; @@ -979,6 +980,7 @@ void RefI31::finalize() { if (value->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. assert(type.isRef() && type.getHeapType().isMaybeShared(HeapType::i31)); } } @@ -1014,10 +1016,12 @@ void CallRef::finalize() { // unreachable instead (and similar in other GC accessors), although this // would currently cause the parser to admit more invalid modules. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } else if (type.isTuple()) { Tuple elems; for (auto t : type) { + // TODO: Make this exact. elems.push_back( t.isRef() ? Type(t.getHeapType().getBottom(), NonNullable) : t); } @@ -1153,6 +1157,7 @@ void StructGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1228,6 +1233,7 @@ void ArrayGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1309,11 +1315,13 @@ void RefAs::finalize() { break; case AnyConvertExtern: type = Type(HeapTypes::any.getBasic(valHeapType.getShared()), - value->type.getNullability()); + value->type.getNullability(), + Inexact); break; case ExternConvertAny: type = Type(HeapTypes::ext.getBasic(valHeapType.getShared()), - value->type.getNullability()); + value->type.getNullability(), + Inexact); break; default: WASM_UNREACHABLE("invalid ref.as_*"); @@ -1326,11 +1334,15 @@ void StringNew::finalize() { (end && end->type == Type::unreachable)) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } -void StringConst::finalize() { type = Type(HeapType::string, NonNullable); } +void StringConst::finalize() { + // TODO: Make this exact. + type = Type(HeapType::string, NonNullable); +} void StringMeasure::finalize() { if (ref->type == Type::unreachable) { @@ -1353,6 +1365,7 @@ void StringConcat::finalize() { if (left->type == Type::unreachable || right->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } @@ -1378,6 +1391,7 @@ void StringSliceWTF::finalize() { end->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 78f1c73552f..9bfc45ddf72 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -20,17 +20,17 @@ BinaryenTypeAuto: -1 BinaryenPackedTypeNotPacked: 0 BinaryenPackedTypeInt8: 1 BinaryenPackedTypeInt16: 2 -BinaryenHeapTypeExt: 8 -BinaryenHeapTypeFunc: 16 -BinaryenHeapTypeAny: 32 -BinaryenHeapTypeEq: 40 -BinaryenHeapTypeI31: 48 -BinaryenHeapTypeStruct: 56 -BinaryenHeapTypeArray: 64 -BinaryenHeapTypeString: 80 -BinaryenHeapTypeNone: 88 -BinaryenHeapTypeNoext: 96 -BinaryenHeapTypeNofunc: 104 +BinaryenHeapTypeExt: 16 +BinaryenHeapTypeFunc: 32 +BinaryenHeapTypeAny: 64 +BinaryenHeapTypeEq: 80 +BinaryenHeapTypeI31: 96 +BinaryenHeapTypeStruct: 112 +BinaryenHeapTypeArray: 128 +BinaryenHeapTypeString: 160 +BinaryenHeapTypeNone: 176 +BinaryenHeapTypeNoext: 192 +BinaryenHeapTypeNofunc: 208 BinaryenFeatureMVP: 0 BinaryenFeatureAtomics: 1 BinaryenFeatureBulkMemory: 16 diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 1e676b7194a..0f8463d55da 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -428,6 +428,32 @@ TEST_F(TypeTest, CanonicalizeUses) { EXPECT_NE(built[4], built[6]); } +TEST_F(TypeTest, CanonicalizeExactRefs) { + TypeBuilder builder(4); + + // Types that vary in exactness or nullability of references are different. + Type a = builder.getTempRefType(builder[0], Nullable, Inexact); + Type b = builder.getTempRefType(builder[1], NonNullable, Inexact); + Type c = builder.getTempRefType(builder[2], Nullable, Exact); + Type d = builder.getTempRefType(builder[3], NonNullable, Exact); + + builder[0] = Struct({Field(a, Mutable)}); + builder[1] = Struct({Field(b, Mutable)}); + builder[2] = Struct({Field(c, Mutable)}); + builder[3] = Struct({Field(d, Mutable)}); + + auto result = builder.build(); + ASSERT_TRUE(result); + auto built = *result; + + EXPECT_NE(built[0], built[1]); + EXPECT_NE(built[0], built[2]); + EXPECT_NE(built[0], built[3]); + EXPECT_NE(built[1], built[2]); + EXPECT_NE(built[1], built[3]); + EXPECT_NE(built[2], built[3]); +} + TEST_F(TypeTest, CanonicalizeSelfReferences) { TypeBuilder builder(5); // Single self-reference @@ -1147,18 +1173,26 @@ FUZZ_TEST(TypeFuzzTest, TestHeapTypeRelationsFuzz) #endif // FUZZTEST TEST_F(TypeTest, TestTypeRelations) { - Type any = Type(HeapType::any, NonNullable); - Type nullAny = Type(HeapType::any, Nullable); + Type any = Type(HeapType::any, NonNullable, Inexact); + Type nullAny = Type(HeapType::any, Nullable, Inexact); + Type exactAny = Type(HeapType::any, NonNullable, Exact); + Type nullExactAny = Type(HeapType::any, Nullable, Exact); HeapType defined = Struct(); - Type def = Type(defined, NonNullable); - Type nullDef = Type(defined, Nullable); + Type def = Type(defined, NonNullable, Inexact); + Type nullDef = Type(defined, Nullable, Inexact); + Type exactDef = Type(defined, NonNullable, Exact); + Type nullExactDef = Type(defined, Nullable, Exact); - Type none = Type(HeapType::none, NonNullable); - Type nullNone = Type(HeapType::none, Nullable); + Type none = Type(HeapType::none, NonNullable, Inexact); + Type nullNone = Type(HeapType::none, Nullable, Inexact); + Type exactNone = Type(HeapType::none, NonNullable, Exact); + Type nullExactNone = Type(HeapType::none, Nullable, Exact); - Type func = Type(HeapType::func, NonNullable); - Type nullFunc = Type(HeapType::func, Nullable); + Type func = Type(HeapType::func, NonNullable, Inexact); + Type nullFunc = Type(HeapType::func, Nullable, Inexact); + Type exactFunc = Type(HeapType::func, NonNullable, Exact); + Type nullExactFunc = Type(HeapType::func, Nullable, Exact); Type i32 = Type::i32; Type unreachable = Type::unreachable; @@ -1217,54 +1251,165 @@ TEST_F(TypeTest, TestTypeRelations) { assertLUB(any, any, any, any); assertLUB(any, nullAny, nullAny, any); + assertLUB(any, exactAny, any, exactAny); + assertLUB(any, nullExactAny, nullAny, exactAny); assertLUB(any, def, any, def); assertLUB(any, nullDef, nullAny, def); + assertLUB(any, exactDef, any, exactDef); + assertLUB(any, nullExactDef, nullAny, exactDef); assertLUB(any, none, any, none); assertLUB(any, nullNone, nullAny, none); + assertLUB(any, exactNone, any, exactNone); + assertLUB(any, nullExactNone, nullAny, exactNone); assertLUB(any, func, Type(Type::none), unreachable); assertLUB(any, nullFunc, Type(Type::none), unreachable); + assertLUB(any, exactFunc, Type(Type::none), unreachable); + assertLUB(any, nullExactFunc, Type(Type::none), unreachable); assertLUB(any, i32, Type(Type::none), unreachable); assertLUB(any, unreachable, any, unreachable); assertLUB(nullAny, nullAny, nullAny, nullAny); + assertLUB(nullAny, exactAny, nullAny, exactAny); + assertLUB(nullAny, nullExactAny, nullAny, nullExactAny); assertLUB(nullAny, def, nullAny, def); assertLUB(nullAny, nullDef, nullAny, nullDef); + assertLUB(nullAny, exactDef, nullAny, exactDef); + assertLUB(nullAny, nullExactDef, nullAny, nullExactDef); assertLUB(nullAny, none, nullAny, none); assertLUB(nullAny, nullNone, nullAny, nullNone); + assertLUB(nullAny, exactNone, nullAny, exactNone); + assertLUB(nullAny, nullExactNone, nullAny, nullExactNone); assertLUB(nullAny, func, Type(Type::none), unreachable); assertLUB(nullAny, nullFunc, Type(Type::none), unreachable); + assertLUB(nullAny, exactFunc, Type(Type::none), unreachable); + assertLUB(nullAny, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullAny, i32, Type(Type::none), unreachable); assertLUB(nullAny, unreachable, nullAny, unreachable); + assertLUB(exactAny, exactAny, exactAny, exactAny); + assertLUB(exactAny, nullExactAny, nullExactAny, exactAny); + assertLUB(exactAny, def, any, exactNone); + assertLUB(exactAny, nullDef, nullAny, exactNone); + assertLUB(exactAny, exactDef, any, exactNone); + assertLUB(exactAny, nullExactDef, nullAny, exactNone); + assertLUB(exactAny, none, any, exactNone); + assertLUB(exactAny, nullNone, nullAny, exactNone); + assertLUB(exactAny, exactNone, exactAny, exactNone); + assertLUB(exactAny, nullExactNone, nullExactAny, exactNone); + assertLUB(exactAny, func, Type(Type::none), unreachable); + assertLUB(exactAny, nullFunc, Type(Type::none), unreachable); + assertLUB(exactAny, exactFunc, Type(Type::none), unreachable); + assertLUB(exactAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactAny, i32, Type(Type::none), unreachable); + assertLUB(exactAny, unreachable, exactAny, unreachable); + + assertLUB(nullExactAny, nullExactAny, nullExactAny, nullExactAny); + assertLUB(nullExactAny, def, nullAny, exactNone); + assertLUB(nullExactAny, nullDef, nullAny, nullExactNone); + assertLUB(nullExactAny, exactDef, nullAny, exactNone); + assertLUB(nullExactAny, nullExactDef, nullAny, nullExactNone); + assertLUB(nullExactAny, none, nullAny, exactNone); + assertLUB(nullExactAny, nullNone, nullAny, nullExactNone); + assertLUB(nullExactAny, exactNone, nullExactAny, exactNone); + assertLUB(nullExactAny, nullExactNone, nullExactAny, nullExactNone); + assertLUB(nullExactAny, func, Type(Type::none), unreachable); + assertLUB(nullExactAny, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, i32, Type(Type::none), unreachable); + assertLUB(nullExactAny, unreachable, nullExactAny, unreachable); + assertLUB(def, def, def, def); assertLUB(def, nullDef, nullDef, def); + assertLUB(def, exactDef, def, exactDef); + assertLUB(def, nullExactDef, nullDef, exactDef); assertLUB(def, none, def, none); assertLUB(def, nullNone, nullDef, none); + assertLUB(def, exactNone, def, exactNone); + assertLUB(def, nullExactNone, nullDef, exactNone); assertLUB(def, func, Type(Type::none), unreachable); assertLUB(def, nullFunc, Type(Type::none), unreachable); + assertLUB(def, exactFunc, Type(Type::none), unreachable); + assertLUB(def, nullExactFunc, Type(Type::none), unreachable); assertLUB(def, i32, Type(Type::none), unreachable); assertLUB(def, unreachable, def, unreachable); assertLUB(nullDef, nullDef, nullDef, nullDef); + assertLUB(nullDef, exactDef, nullDef, exactDef); + assertLUB(nullDef, nullExactDef, nullDef, nullExactDef); assertLUB(nullDef, none, nullDef, none); assertLUB(nullDef, nullNone, nullDef, nullNone); + assertLUB(nullDef, exactNone, nullDef, exactNone); + assertLUB(nullDef, nullExactNone, nullDef, nullExactNone); assertLUB(nullDef, func, Type(Type::none), unreachable); assertLUB(nullDef, nullFunc, Type(Type::none), unreachable); + assertLUB(nullDef, exactFunc, Type(Type::none), unreachable); + assertLUB(nullDef, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullDef, i32, Type(Type::none), unreachable); assertLUB(nullDef, unreachable, nullDef, unreachable); + assertLUB(exactDef, exactDef, exactDef, exactDef); + assertLUB(exactDef, nullExactDef, nullExactDef, exactDef); + assertLUB(exactDef, none, def, exactNone); + assertLUB(exactDef, nullNone, nullDef, exactNone); + assertLUB(exactDef, exactNone, exactDef, exactNone); + assertLUB(exactDef, nullExactNone, nullExactDef, exactNone); + assertLUB(exactDef, func, Type(Type::none), unreachable); + assertLUB(exactDef, nullFunc, Type(Type::none), unreachable); + assertLUB(exactDef, exactFunc, Type(Type::none), unreachable); + assertLUB(exactDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactDef, i32, Type(Type::none), unreachable); + assertLUB(exactDef, unreachable, exactDef, unreachable); + + assertLUB(nullExactDef, nullExactDef, nullExactDef, nullExactDef); + assertLUB(nullExactDef, none, nullDef, exactNone); + assertLUB(nullExactDef, nullNone, nullDef, nullExactNone); + assertLUB(nullExactDef, exactNone, nullExactDef, exactNone); + assertLUB(nullExactDef, nullExactNone, nullExactDef, nullExactNone); + assertLUB(nullExactDef, func, Type(Type::none), unreachable); + assertLUB(nullExactDef, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, i32, Type(Type::none), unreachable); + assertLUB(nullExactDef, unreachable, nullExactDef, unreachable); + assertLUB(none, none, none, none); assertLUB(none, nullNone, nullNone, none); + assertLUB(none, exactNone, none, exactNone); + assertLUB(none, nullExactNone, nullNone, exactNone); assertLUB(none, func, Type(Type::none), unreachable); assertLUB(none, nullFunc, Type(Type::none), unreachable); + assertLUB(none, exactFunc, Type(Type::none), unreachable); + assertLUB(none, nullExactFunc, Type(Type::none), unreachable); assertLUB(none, i32, Type(Type::none), unreachable); assertLUB(none, unreachable, none, unreachable); assertLUB(nullNone, nullNone, nullNone, nullNone); + assertLUB(nullNone, exactNone, nullNone, exactNone); + assertLUB(nullNone, nullExactNone, nullNone, nullExactNone); assertLUB(nullNone, func, Type(Type::none), unreachable); assertLUB(nullNone, nullFunc, Type(Type::none), unreachable); + assertLUB(nullNone, exactFunc, Type(Type::none), unreachable); + assertLUB(nullNone, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullNone, i32, Type(Type::none), unreachable); assertLUB(nullNone, unreachable, nullNone, unreachable); + + assertLUB(exactNone, exactNone, exactNone, exactNone); + assertLUB(exactNone, nullExactNone, nullExactNone, exactNone); + assertLUB(exactNone, func, Type(Type::none), unreachable); + assertLUB(exactNone, nullFunc, Type(Type::none), unreachable); + assertLUB(exactNone, exactFunc, Type(Type::none), unreachable); + assertLUB(exactNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactNone, i32, Type(Type::none), unreachable); + assertLUB(exactNone, unreachable, exactNone, unreachable); + + assertLUB(nullExactNone, nullExactNone, nullExactNone, nullExactNone); + assertLUB(nullExactNone, func, Type(Type::none), unreachable); + assertLUB(nullExactNone, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, i32, Type(Type::none), unreachable); + assertLUB(nullExactNone, unreachable, nullExactNone, unreachable); } TEST_F(TypeTest, TestSubtypeErrors) { diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast new file mode 100644 index 00000000000..e6c1599ea48 --- /dev/null +++ b/test/lit/basic/exact-references.wast @@ -0,0 +1,672 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +;; Also check that if we emit a binary without custom descriptors enabled, the +;; types are generalized to be inexact. + +;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o %t.noexact.wasm +;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s --check-prefix=NO-EXACT + +(module + ;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; CHECK-BIN: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; NO-EXACT: (type $foo (struct (field anyref) (field (ref any)) (field (ref null $foo)) (field (ref $foo)))) + (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) + + + ;; CHECK-TEXT: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + + ;; CHECK-TEXT: (type $2 (func (param anyref) (result anyref))) + + ;; CHECK-TEXT: (type $3 (func (param (exact i31ref)))) + + ;; CHECK-TEXT: (type $4 (func (param (exact eqref)))) + + ;; CHECK-TEXT: (type $5 (func (param (exact anyref)))) + + ;; CHECK-TEXT: (type $6 (func (param (exact anyref)) (result (exact anyref)))) + + ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) + ;; CHECK-BIN: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + + ;; CHECK-BIN: (type $2 (func (param anyref) (result anyref))) + + ;; CHECK-BIN: (type $3 (func (param (exact i31ref)))) + + ;; CHECK-BIN: (type $4 (func (param (exact eqref)))) + + ;; CHECK-BIN: (type $5 (func (param (exact anyref)))) + + ;; CHECK-BIN: (type $6 (func (param (exact anyref)) (result (exact anyref)))) + + ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) + ;; NO-EXACT: (type $1 (func (param anyref) (result anyref))) + + ;; NO-EXACT: (type $2 (func (param anyref) (result (ref any)))) + + ;; NO-EXACT: (type $3 (func (param i31ref))) + + ;; NO-EXACT: (type $4 (func (param eqref))) + + ;; NO-EXACT: (type $5 (func (param anyref))) + + ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) + (import "" "g1" (global $g1 (exact anyref))) + + ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact any))) + ;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact any))) + ;; NO-EXACT: (import "" "g2" (global $g2 (ref any))) + (import "" "g2" (global $g2 (ref exact any))) + + ;; CHECK-TEXT: (import "" "g3" (global $g3 (ref null exact $foo))) + ;; CHECK-BIN: (import "" "g3" (global $g3 (ref null exact $foo))) + ;; NO-EXACT: (import "" "g3" (global $g3 (ref null $foo))) + (import "" "g3" (global $g3 (ref null exact $foo))) + + ;; CHECK-TEXT: (import "" "g4" (global $g4 (ref exact $foo))) + ;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo))) + ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) + (import "" "g4" (global $g4 (ref exact $foo))) + + ;; CHECK-TEXT: (func $ref-test (type $3) (param $0 (exact i31ref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.test (ref exact i31) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.test (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-test (type $3) (param $0 (exact i31ref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (ref exact i31) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $ref-test (type $3) (param $0 i31ref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test (ref i31) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $ref-test (param (ref null exact i31)) + (drop + (ref.test (ref exact i31) + (local.get 0) + ) + ) + (drop + (ref.test (ref null exact i31) + (local.get 0) + ) + ) + ) + + ;; CHECK-TEXT: (func $ref-cast (type $4) (param $0 (exact eqref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact none) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast (type $4) (param $0 (exact eqref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (ref exact none) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $ref-cast (type $4) (param $0 eqref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast (ref none) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $ref-cast (param (ref null exact eq)) + (drop + (ref.cast (ref exact eq) + (local.get 0) + ) + ) + (drop + (ref.cast (ref null exact eq) + (local.get 0) + ) + ) + (drop + (ref.cast (ref exact i31) + (local.get 0) + ) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $br-on-cast (param anyref) (result anyref) + (drop + (br_on_cast 0 anyref (ref null exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast 0 anyref (ref exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast 0 anyref (ref null exact i31) + (local.get 0) + ) + ) + (local.get 0) + ) + + ;; CHECK-TEXT: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $br-on-cast-fail (param anyref) (result anyref) + (drop + (br_on_cast_fail 0 anyref (ref null exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast_fail 0 anyref (ref exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast_fail 0 anyref (ref null exact i31) + (local.get 0) + ) + ) + (local.get 0) + ) + + ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (ref.as_non_null + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (ref.as_non_null + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-ref-as-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (ref.as_non_null + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-ref-as-non-null (param (ref null exact any)) (result (ref exact any)) + (ref.as_non_null + (local.get 0) + ) + ) + + ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-TEXT-NEXT: (block $label + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_null $label + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_null $block + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-null (type $5) (param $0 anyref) + ;; NO-EXACT-NEXT: (block $block + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_null $block + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-null (param (ref null exact any)) + (drop + (block (result (ref exact any)) + (br_on_null 1 + (local.get 0) + ) + ) + ) + ) + + ;; CHECK-TEXT: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_non_null $label + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN-NEXT: (br_on_non_null $block + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT-NEXT: (br_on_non_null $block + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-non-null (param (ref null exact any)) (result (ref exact any)) + (br_on_non_null 0 + (local.get 0) + ) + (unreachable) + ) + + ;; CHECK-TEXT: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (block $label (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) + ;; CHECK-BIN-NEXT: (block $block (result (exact anyref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref anyref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-cast (param (ref null exact any)) (result (ref null exact any)) + (drop + (block (result (ref exact any)) + (br_on_cast 1 (ref null exact any) (ref null exact any) + (local.get 0) + ) + ) + ) + (unreachable) + ) + + ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-cast-fail (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref anyref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-cast-fail (param (ref null exact any)) (result (ref exact any)) + (drop + (block (result (ref null exact any)) + (br_on_cast_fail 1 (ref null exact any) (ref null exact any) + (local.get 0) + ) + ) + ) + (unreachable) + ) +) +;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) + +;; CHECK-BIN-NODEBUG: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param anyref) (result anyref))) + +;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact i31ref)))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param (exact eqref)))) + +;; CHECK-BIN-NODEBUG: (type $5 (func (param (exact anyref)))) + +;; CHECK-BIN-NODEBUG: (type $6 (func (param (exact anyref)) (result (exact anyref)))) + +;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) + +;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any))) + +;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0))) + +;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0))) + +;; CHECK-BIN-NODEBUG: (func $0 (type $3) (param $0 (exact i31ref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact i31) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $4) (param $0 (exact eqref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact none) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $2) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $3 (type $2) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $4 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (ref.as_non_null +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_null $block +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $6 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_non_null $block +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $7 (type $6) (param $0 (exact anyref)) (result (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $8 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 82b1f8da4e3..7c4f2a23e76 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -961,7 +961,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block2 (result eqref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block2 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -987,7 +987,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block5 (result funcref) - ;; CHECK-BIN-NEXT: (ref.cast nullfuncref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullfuncref) ;; CHECK-BIN-NEXT: (br_if $block5 ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1023,7 +1023,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block9 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block9 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1043,7 +1043,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block11 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block11 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -2298,7 +2298,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block2 (result eqref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block2 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2324,7 +2324,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block5 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullfuncref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullfuncref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block5 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2360,7 +2360,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block9 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block9 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2380,7 +2380,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block11 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block11 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) diff --git a/test/lit/ctor-eval/materialize-null-local.wast b/test/lit/ctor-eval/materialize-null-local.wast new file mode 100644 index 00000000000..1f880f6816b --- /dev/null +++ b/test/lit/ctor-eval/materialize-null-local.wast @@ -0,0 +1,26 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-ctor-eval %s -all --ctors=test --kept-exports=test --ignore-external-input -S -o - \ +;; RUN: | filecheck %s + +;; Check that materializing a non-data null local, which at time of writing uses +;; Builder::makeConstantExpression, does not trigger an assertion failure. + +(module + (func $test (export "test") (param $0 externref) + (local $3 anyref) + (local.set $3 + (any.convert_extern + (local.get $0) + ) + ) + ) +) +;; CHECK: (type $0 (func (param externref))) + +;; CHECK: (export "test" (func $test_1)) + +;; CHECK: (func $test_1 (type $0) (param $0 externref) +;; CHECK-NEXT: (local $3 anyref) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) diff --git a/test/lit/exec/exact.wast b/test/lit/exec/exact.wast new file mode 100644 index 00000000000..2667e0e4dce --- /dev/null +++ b/test/lit/exec/exact.wast @@ -0,0 +1,21 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +(module + ;; CHECK: [fuzz-exec] calling convert-null-extern + ;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null + (func $convert-null-extern (export "convert-null-extern") (result (exact nullref)) + (local externref) + ;; The value produced by this cast must be exact to avoid triggering an + ;; assertion. + (ref.cast (exact nullref) + (any.convert_extern + (local.get 0) + ) + ) + ) +) +;; CHECK: [fuzz-exec] calling convert-null-extern +;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null +;; CHECK-NEXT: [fuzz-exec] comparing convert-null-extern diff --git a/test/lit/heap-types.wast b/test/lit/heap-types.wast index 4613f4894fd..baf77196611 100644 --- a/test/lit/heap-types.wast +++ b/test/lit/heap-types.wast @@ -12,7 +12,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.test (ref none) + ;; CHECK-NEXT: (ref.test (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -29,7 +29,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 0bc4372ec35..ab9b7f8a745 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -1034,7 +1034,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) @@ -1233,7 +1233,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) diff --git a/test/lit/passes/coalesce-locals-exact.wast b/test/lit/passes/coalesce-locals-exact.wast new file mode 100644 index 00000000000..1568d3634cf --- /dev/null +++ b/test/lit/passes/coalesce-locals-exact.wast @@ -0,0 +1,47 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that TypeUpdating::handleNonDefaultableLocals handles locals with exact +;; reference types correctly, and in particular that it preserves the exactness +;; of the types. + +;; RUN: wasm-opt %s -all --coalesce-locals -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 (exact i31ref)) (result (ref exact i31)) + ;; CHECK-NEXT: (local $1 (exact i31ref)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param (exact i31ref)) (result (ref exact i31)) + (local $l (ref exact i31)) + ;; This dead set will be optimized out. + (local.set $l + (ref.as_non_null + (local.get 0) + ) + ) + (block $l + ;; This remaining set does not structurally dominate the get. + (local.set $l + (ref.as_non_null + (local.get 0) + ) + ) + ) + ;; This will have to be fixed up and the local made nullable. + (local.get $l) + ) +) diff --git a/test/lit/passes/code-pushing-gc.wast b/test/lit/passes/code-pushing-gc.wast index 1aac5c55cf7..fb9ff12f7da 100644 --- a/test/lit/passes/code-pushing-gc.wast +++ b/test/lit/passes/code-pushing-gc.wast @@ -7,7 +7,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $out (result (ref func)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) + ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -48,7 +48,7 @@ ;; CHECK-NEXT: (ref.func $br_on_no) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) + ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index 8cefbe88184..a93f3e7347f 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -262,7 +262,7 @@ ) ;; This function is called in ways that allow us to make the first parameter ;; non-nullable. - ;; CHECK: (func $various-params-null (type $13) (param $x (ref none)) (param $y (ref null $"{i32}")) + ;; CHECK: (func $various-params-null (type $13) (param $x (ref exact none)) (param $y (ref null $"{i32}")) ;; CHECK-NEXT: (local $temp i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 1f39567d146..015e515c02b 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -110,7 +110,7 @@ ) ;; CHECK: (func $bar (type $2) (param $0 i31ref) - ;; CHECK-NEXT: (local $1 nullref) + ;; CHECK-NEXT: (local $1 (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/flatten_all-features.wast b/test/lit/passes/flatten_all-features.wast index b601b8a1295..6c117d0a907 100644 --- a/test/lit/passes/flatten_all-features.wast +++ b/test/lit/passes/flatten_all-features.wast @@ -3581,8 +3581,8 @@ ;; CHECK: (func $subtype (type $7) (result anyref) ;; CHECK-NEXT: (local $0 eqref) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 nullref) - ;; CHECK-NEXT: (local $3 nullref) + ;; CHECK-NEXT: (local $2 (exact nullref)) + ;; CHECK-NEXT: (local $3 (exact nullref)) ;; CHECK-NEXT: (local $4 eqref) ;; CHECK-NEXT: (local $5 eqref) ;; CHECK-NEXT: (local $6 eqref) @@ -3680,7 +3680,7 @@ ;; CHECK: (type $0 (func (result funcref))) ;; CHECK: (func $0 (type $0) (result funcref) - ;; CHECK-NEXT: (local $0 (ref nofunc)) + ;; CHECK-NEXT: (local $0 (ref exact nofunc)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index 927dfd1f26c..7be5a9e4e2a 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -11,8 +11,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) @@ -32,8 +32,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) @@ -202,16 +202,16 @@ ;; (ref null func) to nullfuncref only when not exported, and if exported, then ;; only when immutable in open world. (module - ;; CHECK: (global $mut (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $mut (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) (global $mut (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm nullfuncref (ref.null nofunc)) - ;; CLOSD: (global $imm nullfuncref (ref.null nofunc)) + ;; CHECK: (global $imm (exact nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $imm (exact nullfuncref) (ref.null nofunc)) (global $imm (ref null func) (ref.null nofunc)) ;; CHECK: (global $mut-exp (mut funcref) (ref.null nofunc)) ;; CLOSD: (global $mut-exp (mut funcref) (ref.null nofunc)) (global $mut-exp (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm-exp nullfuncref (ref.null nofunc)) + ;; CHECK: (global $imm-exp (exact nullfuncref) (ref.null nofunc)) ;; CLOSD: (global $imm-exp funcref (ref.null nofunc)) (global $imm-exp (ref null func) (ref.null nofunc)) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 6d48d651a5f..6142550638d 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -955,7 +955,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 1 ;; CHECK-NEXT: (local.get $child) @@ -970,7 +970,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) @@ -1072,9 +1072,9 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result nullref) + ;; CHECK-NEXT: (block $block (result (exact nullref)) ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1085,9 +1085,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block0 (result nullref) + ;; CHECK-NEXT: (block $block0 (result (exact nullref)) ;; CHECK-NEXT: (br $block0 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1098,13 +1098,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block1 (result nullref) + ;; CHECK-NEXT: (block $block1 (result (exact nullref)) ;; CHECK-NEXT: (br $block1 - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1237,7 +1237,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) @@ -1577,7 +1577,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $null ;; CHECK-NEXT: (array.new_default $null @@ -1775,10 +1775,10 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $x - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $storage - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $pass-through ;; CHECK-NEXT: (ref.null none) @@ -1928,7 +1928,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2153,7 +2153,7 @@ ;; CHECK-NEXT: (pop (tuple anyref anyref)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2213,7 +2213,7 @@ ;; CHECK: (func $func (type $1) (result (ref $"{}")) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (ref none)) + ;; CHECK-NEXT: (block $block (result (ref exact none)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2539,10 +2539,10 @@ ;; CHECK: (func $test-nulls (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -2554,9 +2554,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (select (result i31ref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.i31 @@ -3256,7 +3256,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -3793,7 +3793,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $chars ;; CHECK-NEXT: (local.get $chars) @@ -6056,7 +6056,7 @@ ) (module - ;; CHECK: (type $0 (func (result i64 nullref i32))) + ;; CHECK: (type $0 (func (result i64 (exact nullref) i32))) ;; CHECK: (type $array (sub (array (mut i8)))) (type $array (sub (array (mut i8)))) @@ -6096,7 +6096,7 @@ ;; CHECK: (func $loop-tuple-br_on (type $2) ;; CHECK-NEXT: (tuple.drop 3 - ;; CHECK-NEXT: (loop $loop (type $0) (result i64 nullref i32) + ;; CHECK-NEXT: (loop $loop (type $0) (result i64 (exact nullref) i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index bd731d89150..eb26820b74d 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -1087,7 +1087,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct3 2 ;; CHECK-NEXT: (local.get $ref) @@ -1329,7 +1329,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $create3) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local-rmw.wast b/test/lit/passes/heap2local-rmw.wast index 33fef1cb506..f0b397dcc99 100644 --- a/test/lit/passes/heap2local-rmw.wast +++ b/test/lit/passes/heap2local-rmw.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -107,7 +107,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -140,7 +140,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -173,7 +173,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -206,7 +206,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -239,7 +239,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -270,7 +270,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -312,7 +312,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -345,7 +345,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -378,7 +378,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -411,7 +411,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -444,7 +444,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -477,7 +477,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -508,7 +508,7 @@ ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -550,7 +550,7 @@ ;; CHECK-NEXT: (local $2 (ref null $struct)) ;; CHECK-NEXT: (local $3 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -581,7 +581,7 @@ ;; CHECK-NEXT: (local $4 (ref null $struct)) ;; CHECK-NEXT: (local $5 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -623,7 +623,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -657,7 +657,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 619a33a23c5..8b0384671c3 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -102,7 +102,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -135,7 +135,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -162,7 +162,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -193,7 +193,7 @@ ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) @@ -259,7 +259,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -318,7 +318,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -388,7 +388,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) @@ -418,7 +418,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -456,7 +456,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -494,7 +494,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -559,7 +559,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -673,7 +673,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -713,7 +713,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -771,7 +771,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -821,7 +821,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -901,7 +901,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -927,7 +927,7 @@ ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -975,7 +975,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (local $2 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new_default $struct.recursive) ;; CHECK-NEXT: ) @@ -1070,7 +1070,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) @@ -1104,7 +1104,7 @@ ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (loop $outer ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -1271,7 +1271,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1287,7 +1287,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1303,7 +1303,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1343,7 +1343,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1362,7 +1362,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1411,7 +1411,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1422,7 +1422,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1553,7 +1553,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1749,7 +1749,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1800,7 +1800,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1851,7 +1851,7 @@ ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $block - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) @@ -1905,7 +1905,7 @@ ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1938,7 +1938,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1977,7 +1977,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2061,7 +2061,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2096,7 +2096,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2138,7 +2138,7 @@ ;; CHECK-NEXT: (local.get $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2174,7 +2174,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2216,7 +2216,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2253,7 +2253,7 @@ ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2287,7 +2287,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2334,7 +2334,7 @@ ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (local $7 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2351,7 +2351,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2396,7 +2396,7 @@ ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2450,7 +2450,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2472,7 +2472,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2494,7 +2494,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) @@ -2560,7 +2560,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2582,7 +2582,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2648,7 +2648,7 @@ ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2697,7 +2697,7 @@ ;; CHECK-NEXT: (local $2 (ref $A)) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2736,7 +2736,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2776,7 +2776,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2819,9 +2819,9 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2870,7 +2870,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2907,7 +2907,7 @@ ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -3005,7 +3005,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3151,7 +3151,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3197,7 +3197,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) @@ -3327,11 +3327,11 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3391,7 +3391,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -3500,7 +3500,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3512,7 +3512,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3533,7 +3533,7 @@ ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $24 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) @@ -3706,7 +3706,7 @@ ;; CHECK: (func $array.nested.refinalize.get (type $3) (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3726,7 +3726,7 @@ ;; CHECK: (func $array.nested.refinalize.set (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3756,7 +3756,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3849,7 +3849,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3867,7 +3867,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3916,7 +3916,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3983,7 +3983,7 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4030,7 +4030,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4070,7 +4070,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -4173,7 +4173,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4253,7 +4253,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4319,7 +4319,7 @@ ;; CHECK: (func $array.cast.struct (type $0) (result (ref struct)) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4341,7 +4341,7 @@ ;; CHECK: (func $array.cast.struct.null (type $3) (result structref) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4380,7 +4380,7 @@ ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (local $array (ref array)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4405,7 +4405,7 @@ ;; CHECK-NEXT: (local.tee $struct ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4442,7 +4442,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result structref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -4513,7 +4513,7 @@ ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) @@ -4565,7 +4565,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4620,7 +4620,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (block (result (ref null exact (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4666,7 +4666,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (block (result (ref null exact (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/local-subtyping-exact.wast b/test/lit/passes/local-subtyping-exact.wast new file mode 100644 index 00000000000..725c7d499fb --- /dev/null +++ b/test/lit/passes/local-subtyping-exact.wast @@ -0,0 +1,39 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that LocalSubtyping handles exact references properly when it +;; determines that a local that would otherwise be non-nullable must be nullable +;; because of control flow dominance constraints. + +;; RUN: wasm-opt %s -all --local-subtyping -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 (exact nullref)) (result anyref) + ;; CHECK-NEXT: (local $1 (exact nullref)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $test (param (exact nullref)) (result anyref) + (local (exact nullref)) + (if + (i32.const 0) + (then + (local.set 1 + ;; This would let the local be (ref exact none) if it dominated the get. + (ref.as_non_null + (local.get 0) + ) + ) + ) + ) + (local.get 1) + ) +) diff --git a/test/lit/passes/local-subtyping-nn.wast b/test/lit/passes/local-subtyping-nn.wast index 3754230d82f..f2237156f01 100644 --- a/test/lit/passes/local-subtyping-nn.wast +++ b/test/lit/passes/local-subtyping-nn.wast @@ -8,7 +8,7 @@ (import "out" "i32" (func $i32 (result i32))) ;; CHECK: (func $non-nullable (type $1) - ;; CHECK-NEXT: (local $x (ref none)) + ;; CHECK-NEXT: (local $x (ref exact none)) ;; CHECK-NEXT: (local $y (ref $0)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.as_non_null @@ -41,7 +41,7 @@ ) ;; CHECK: (func $uses-default (type $2) (param $i i32) - ;; CHECK-NEXT: (local $x nullref) + ;; CHECK-NEXT: (local $x (exact nullref)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $i) ;; CHECK-NEXT: (then diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 795a0a01cd1..777df93524f 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -273,7 +273,7 @@ ) ;; CHECK: (func $multiple-iterations-refinalize-call-ref-bottom (type $0) - ;; CHECK-NEXT: (local $f nullfuncref) + ;; CHECK-NEXT: (local $f (exact nullfuncref)) ;; CHECK-NEXT: (local $x (ref none)) ;; CHECK-NEXT: (local.set $f ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 86181f7a488..7a517ccbc27 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -20,7 +20,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $label$1 (result i31ref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label$1 nullref (ref none) + ;; CHECK-NEXT: (br_on_cast $label$1 (exact nullref) (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/monomorphize-context.wast b/test/lit/passes/monomorphize-context.wast index 7f9cb2f566e..92b483a7ae1 100644 --- a/test/lit/passes/monomorphize-context.wast +++ b/test/lit/passes/monomorphize-context.wast @@ -277,7 +277,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast nullref +;; ALWAYS-NEXT: (ref.cast (exact nullref) ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -555,7 +555,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast nullref +;; ALWAYS-NEXT: (ref.cast (exact nullref) ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast new file mode 100644 index 00000000000..61769b78f34 --- /dev/null +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -0,0 +1,33 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that optimizations on casts involving exact reference types work +;; correctly. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (func $cast-any-to-exact-none (type $0) (param $0 anyref) (result (exact nullref)) + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-any-to-exact-none (param anyref) (result (exact nullref)) + ;; This will not be changed, but should not trigger an assertion. + (ref.cast (exact nullref) + (local.get 0) + ) + ) + ;; CHECK: (func $cast-null-to-exact-none (type $1) (result (exact nullref)) + ;; CHECK-NEXT: (local $0 nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + (func $cast-null-to-exact-none (result (exact nullref)) + (local nullref) + (ref.cast (exact nullref) + (local.get 0) + ) + ) +) diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index c930f2fc19a..3893e048e14 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -509,7 +509,7 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: (block ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result nullref) + ;; TNH-NEXT: (block (result (exact nullref)) ;; TNH-NEXT: (drop ;; TNH-NEXT: (call $get-i32) ;; TNH-NEXT: ) @@ -532,7 +532,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (block ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result nullref) + ;; NO_TNH-NEXT: (block (result (exact nullref)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (call $get-i32) ;; NO_TNH-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 4b88507ffc7..86804d7ddbd 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -1570,7 +1570,7 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $a ;; CHECK-NEXT: (ref.null none) @@ -1590,7 +1590,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.cast nullref diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast index fc4cc7c2ee9..94adaa53fbb 100644 --- a/test/lit/passes/precompute-gc.wast +++ b/test/lit/passes/precompute-gc.wast @@ -28,7 +28,7 @@ ;; CHECK: (func $test-fallthrough (type $func-return-i32) (result i32) ;; CHECK-NEXT: (local $x funcref) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (block (result nullfuncref) + ;; CHECK-NEXT: (block (result (exact nullfuncref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $test-fallthrough) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/remove-unused-brs-exact.wast b/test/lit/passes/remove-unused-brs-exact.wast new file mode 100644 index 00000000000..3bebd07de02 --- /dev/null +++ b/test/lit/passes/remove-unused-brs-exact.wast @@ -0,0 +1,40 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --remove-unused-brs -S -o - | filecheck %s + +;; Check that we optimize the cast correctly when the fallthrough has exact +;; type. In particular, we should not insert a ref.as_non_null, which would +;; trap. + +(module + ;; CHECK: (func $br_on_cast_fail (type $0) (param $0 (exact nullref)) + ;; CHECK-NEXT: (local $1 nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail (param (ref null exact none)) + (local $1 nullref) + (drop + (block $block (result (ref none)) + (drop + (br_on_cast_fail $block nullref nullref + (local.tee $1 + (local.get 0) + ) + ) + ) + (return) + ) + ) + ) +) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 268bbde2092..64f11f8ca0b 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -63,7 +63,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -119,7 +119,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.cast (ref null $struct) ;; CHECK-NEXT: (local.tee $any @@ -438,7 +438,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $nullable-struct2) ;; CHECK-NEXT: ) @@ -509,7 +509,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (local.get $nullable-struct2) @@ -571,7 +571,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (local.get $nullable-struct2) @@ -706,7 +706,7 @@ ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (ref.test (ref none) + ;; CHECK-NEXT: (ref.test (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -716,13 +716,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result nullref) + ;; CHECK-NEXT: (if (result (exact nullref)) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -734,7 +734,7 @@ ;; CHECK-NEXT: (then ;; CHECK-NEXT: (block $something (result (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $something ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -750,8 +750,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result nullref) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (select (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (block $nothing ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -858,7 +858,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result nullref) + ;; CHECK-NEXT: (select (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/remove-unused-types-exact.wast b/test/lit/passes/remove-unused-types-exact.wast new file mode 100644 index 00000000000..6cb9c1ddd74 --- /dev/null +++ b/test/lit/passes/remove-unused-types-exact.wast @@ -0,0 +1,16 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --closed-world --remove-unused-types -S -o - | filecheck %s + +;; Test that a simple type rewrite handles exact references in heap type +;; definitions correctly. In particular, the function should continue returning +;; an exact nullref and the call expression should have the same type. + +(module + ;; CHECK: (func $return-exact (type $0) (result (exact nullref)) + ;; CHECK-NEXT: (call $return-exact) + ;; CHECK-NEXT: ) + (func $return-exact (result (exact nullref)) + (call $return-exact) + ) +) diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat index c69eeb24455..1eedcd4f67c 100644 --- a/test/lit/passes/signature-refining_gto.wat +++ b/test/lit/passes/signature-refining_gto.wat @@ -9,11 +9,11 @@ ;; CHECK-NOT: (type $A (type $A (struct (field (mut (ref null $A))))) - ;; CHECK: (type $0 (func (param (ref none)))) + ;; CHECK: (type $0 (func (param (ref exact none)))) ;; CHECK: (type $1 (func (param funcref i32))) - ;; CHECK: (func $struct.get (type $0) (param $0 (ref none)) + ;; CHECK: (func $struct.get (type $0) (param $0 (ref exact none)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast index 0d696f75a0d..a0b18b0777d 100644 --- a/test/lit/passes/ssa.wast +++ b/test/lit/passes/ssa.wast @@ -36,9 +36,9 @@ ;; CHECK: (func $refine-to-null (type $3) (result (ref $A)) ;; CHECK-NEXT: (local $0 (ref null $A)) - ;; CHECK-NEXT: (block $label (result (ref none)) + ;; CHECK-NEXT: (block $label (result (ref exact none)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label nullref (ref none) + ;; CHECK-NEXT: (br_on_cast $label (exact nullref) (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index c866d80dca2..2304086078c 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -28,7 +28,7 @@ ;; NRML: (rec ;; NRML-NEXT: (type $A (sub (struct (field (mut nullref))))) ;; GUFA: (rec - ;; GUFA-NEXT: (type $A (sub (struct (field (mut nullref))))) + ;; GUFA-NEXT: (type $A (sub (struct (field (mut (exact nullref)))))) ;; O3O3: (rec ;; O3O3-NEXT: (type $A (sub (struct))) (type $A (sub (struct (field (mut anyref))))) @@ -376,7 +376,7 @@ ;; the field to nullref. (module ;; NRML: (type $struct (struct (field nullfuncref))) - ;; GUFA: (type $struct (struct (field nullfuncref))) + ;; GUFA: (type $struct (struct (field (exact nullfuncref)))) (type $struct (struct (field funcref))) ;; NRML: (global $C (ref $struct) (struct.new_default $struct)) diff --git a/test/lit/passes/type-refining-isorecursive.wast b/test/lit/passes/type-refining-isorecursive.wast index 537f99e668e..025b8cec9f5 100644 --- a/test/lit/passes/type-refining-isorecursive.wast +++ b/test/lit/passes/type-refining-isorecursive.wast @@ -5,11 +5,11 @@ ;; The types should be refined to a set of three mutually recursive types. ;; CHECK: (rec - ;; CHECK-NEXT: (type $2 (sub (struct (field nullexternref) (field (ref $0))))) + ;; CHECK-NEXT: (type $2 (sub (struct (field (exact nullexternref)) (field (ref $0))))) - ;; CHECK: (type $1 (sub (struct (field nullfuncref) (field (ref $2))))) + ;; CHECK: (type $1 (sub (struct (field (exact nullfuncref)) (field (ref $2))))) - ;; CHECK: (type $0 (sub (struct (field nullref) (field (ref $1))))) + ;; CHECK: (type $0 (sub (struct (field (exact nullref)) (field (ref $1))))) (type $0 (sub (struct nullref anyref))) (type $1 (sub (struct nullfuncref anyref))) (type $2 (sub (struct nullexternref anyref))) diff --git a/test/lit/passes/type-refining-rmw.wast b/test/lit/passes/type-refining-rmw.wast index a4bb0a85cae..7739fceb0f4 100644 --- a/test/lit/passes/type-refining-rmw.wast +++ b/test/lit/passes/type-refining-rmw.wast @@ -6,7 +6,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared any))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) @@ -75,7 +75,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared eq))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 622e7422011..02357e8b6c0 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1101,7 +1101,7 @@ ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1180,7 +1180,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut nullref)))) + ;; CHECK-NEXT: (type $A (struct (field (mut (exact nullref))))) (type $A (struct (field (mut anyref)))) ;; CHECK: (type $B (struct (field (mut nullref)))) (type $B (struct (field (mut (ref null $A))))) @@ -1234,7 +1234,7 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut (ref noextern))))) + ;; CHECK-NEXT: (type $A (struct (field (mut (ref exact noextern))))) (type $A (struct (field (mut externref)))) ;; CHECK: (type $1 (func)) @@ -1252,7 +1252,7 @@ ;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref) ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (ref.cast (ref exact noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1304,7 +1304,7 @@ ;; CHECK: (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (ref.cast (ref exact noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1581,7 +1581,7 @@ (type $never (sub (struct (field i32)))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $optimizable (struct (field (mut nullfuncref)))) + ;; CHECK-NEXT: (type $optimizable (struct (field (mut (exact nullfuncref))))) (type $optimizable (struct (field (mut (ref null func))))) ;; CHECK: (type $2 (func)) @@ -1604,7 +1604,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (block (result (ref exact none)) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index 189adadcec1..1372035920f 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -286,7 +286,7 @@ ) ) (drop - (block $l2 (result nullexternref) + (block $l2 (result (exact nullexternref)) (drop (block $l3 (global.set $global-mut From 5dab9613aff9d6e7076f8a34d72ec8785b4d8085 Mon Sep 17 00:00:00 2001 From: Gulg <65360617+GulgDev@users.noreply.github.com> Date: Wed, 16 Apr 2025 02:24:45 +0500 Subject: [PATCH 427/622] [C/JS] Expose GC API to Binaryen.JS (#7466) Adds `TypeBuilder` interface and the following WasmGC instructions to JS: `ref.test`, `ref.cast`, `any.convert_extern`, `extern.convert_any`, `br_on_null`, `br_on_non_null`, `br_on_cast`, `br_on_cast_fail`, `struct.new`, `struct.new_default`, `struct.get`, `struct.set`, `array.new`, `array.new_default`, `array.new_fixed`, `array.new_data`, `array.new_elem`\*, `array.get`, `array.set`, `array.len`, `array.fill`\*, `array.copy`, `array.init_data`\*, `array.init_elem`\*. Instructions that were added both to JS and to C are marked with an asterisk (`*`). The corresponding test case was also added (`gc.js`). This PR also fixes memory leaks caused by stack allocations that are not wrapped in `preserveStack`. --- src/binaryen-c.cpp | 283 +++++++- src/binaryen-c.h | 122 +++- src/js/binaryen.js-post.js | 973 +++++++++++++++++++++++----- test/binaryen.js/expressions.js | 753 +++++++++++++++++++++ test/binaryen.js/expressions.js.txt | 107 +++ test/binaryen.js/gc.js | 178 +++++ test/binaryen.js/gc.js.txt | 116 ++++ test/example/c-api-kitchen-sink.c | 39 +- test/example/c-api-kitchen-sink.txt | 39 +- 9 files changed, 2437 insertions(+), 173 deletions(-) create mode 100644 test/binaryen.js/gc.js create mode 100644 test/binaryen.js/gc.js.txt diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index cd5bb737a4d..bd4746313a2 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1796,7 +1796,16 @@ BinaryenExpressionRef BinaryenArrayNewData(BinaryenModuleRef module, .makeArrayNewData( HeapType(type), name, (Expression*)offset, (Expression*)size)); } - +BinaryenExpressionRef BinaryenArrayNewElem(BinaryenModuleRef module, + BinaryenHeapType type, + const char* name, + BinaryenExpressionRef offset, + BinaryenExpressionRef size) { + return static_cast( + Builder(*(Module*)module) + .makeArrayNewElem( + HeapType(type), name, (Expression*)offset, (Expression*)size)); +} BinaryenExpressionRef BinaryenArrayNewFixed(BinaryenModuleRef module, BinaryenHeapType type, BinaryenExpressionRef* values, @@ -1843,6 +1852,43 @@ BinaryenExpressionRef BinaryenArrayCopy(BinaryenModuleRef module, (Expression*)srcIndex, (Expression*)length)); } +BinaryenExpressionRef BinaryenArrayFill(BinaryenModuleRef module, + BinaryenExpressionRef ref, + BinaryenExpressionRef index, + BinaryenExpressionRef value, + BinaryenExpressionRef size) { + return static_cast(Builder(*(Module*)module) + .makeArrayFill((Expression*)ref, + (Expression*)index, + (Expression*)value, + (Expression*)size)); +} +BinaryenExpressionRef BinaryenArrayInitData(BinaryenModuleRef module, + const char* name, + BinaryenExpressionRef ref, + BinaryenExpressionRef index, + BinaryenExpressionRef offset, + BinaryenExpressionRef size) { + return static_cast(Builder(*(Module*)module) + .makeArrayInitData(name, + (Expression*)ref, + (Expression*)index, + (Expression*)offset, + (Expression*)size)); +} +BinaryenExpressionRef BinaryenArrayInitElem(BinaryenModuleRef module, + const char* seg, + BinaryenExpressionRef ref, + BinaryenExpressionRef index, + BinaryenExpressionRef offset, + BinaryenExpressionRef size) { + return static_cast(Builder(*(Module*)module) + .makeArrayInitElem(seg, + (Expression*)ref, + (Expression*)index, + (Expression*)offset, + (Expression*)size)); +} BinaryenExpressionRef BinaryenStringNew(BinaryenModuleRef module, BinaryenOp op, BinaryenExpressionRef ptr, @@ -4276,6 +4322,76 @@ BinaryenArrayNewFixedRemoveValueAt(BinaryenExpressionRef expr, assert(expression->is()); return static_cast(expression)->values.removeAt(index); } +// ArrayNewData +const char* BinaryenArrayNewDataGetSegment(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->segment.str.data(); +} +void BinaryenArrayNewDataSetSegment(BinaryenExpressionRef expr, + const char* segment) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->segment = Name(segment); +} +BinaryenExpressionRef +BinaryenArrayNewDataGetOffset(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->offset; +} +void BinaryenArrayNewDataSetOffset(BinaryenExpressionRef expr, + BinaryenExpressionRef offset) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->offset = offset; +} +BinaryenExpressionRef BinaryenArrayNewDataGetSize(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->size; +} +void BinaryenArrayNewDataSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef size) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->size = size; +} +// ArrayNewElem +const char* BinaryenArrayNewElemGetSegment(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->segment.str.data(); +} +void BinaryenArrayNewElemSetSegment(BinaryenExpressionRef expr, + const char* segment) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->segment = Name(segment); +} +BinaryenExpressionRef +BinaryenArrayNewElemGetOffset(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->offset; +} +void BinaryenArrayNewElemSetOffset(BinaryenExpressionRef expr, + BinaryenExpressionRef offset) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->offset = offset; +} +BinaryenExpressionRef BinaryenArrayNewElemGetSize(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->size; +} +void BinaryenArrayNewElemSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef size) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->size = size; +} // ArrayGet BinaryenExpressionRef BinaryenArrayGetGetRef(BinaryenExpressionRef expr) { auto* expression = (Expression*)expr; @@ -4361,6 +4477,55 @@ void BinaryenArrayLenSetRef(BinaryenExpressionRef expr, assert(refExpr); static_cast(expression)->ref = (Expression*)refExpr; } +// ArrayFill +BinaryenExpressionRef BinaryenArrayFillGetRef(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->ref; +} +void BinaryenArrayFillSetRef(BinaryenExpressionRef expr, + BinaryenExpressionRef refExpr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + assert(refExpr); + static_cast(expression)->ref = (Expression*)refExpr; +} +BinaryenExpressionRef BinaryenArrayFillGetIndex(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->index; +} +void BinaryenArrayFillSetIndex(BinaryenExpressionRef expr, + BinaryenExpressionRef indexExpr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + assert(indexExpr); + static_cast(expression)->index = (Expression*)indexExpr; +} +BinaryenExpressionRef BinaryenArrayFillGetValue(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->value; +} +void BinaryenArrayFillSetValue(BinaryenExpressionRef expr, + BinaryenExpressionRef valueExpr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + assert(valueExpr); + static_cast(expression)->value = (Expression*)valueExpr; +} +BinaryenExpressionRef BinaryenArrayFillGetSize(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->size; +} +void BinaryenArrayFillSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef sizeExpr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + assert(sizeExpr); + static_cast(expression)->size = (Expression*)sizeExpr; +} // ArrayCopy BinaryenExpressionRef BinaryenArrayCopyGetDestRef(BinaryenExpressionRef expr) { auto* expression = (Expression*)expr; @@ -4423,6 +4588,122 @@ void BinaryenArrayCopySetLength(BinaryenExpressionRef expr, assert(lengthExpr); static_cast(expression)->length = (Expression*)lengthExpr; } +// ArrayInitData +const char* BinaryenArrayInitDataGetSegment(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->segment.str.data(); +} +void BinaryenArrayInitDataSetSegment(BinaryenExpressionRef expr, + const char* segment) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->segment = Name(segment); +} +BinaryenExpressionRef BinaryenArrayInitDataGetRef(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->ref; +} +void BinaryenArrayInitDataSetRef(BinaryenExpressionRef expr, + BinaryenExpressionRef ref) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->ref = ref; +} +BinaryenExpressionRef +BinaryenArrayInitDataGetIndex(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->index; +} +void BinaryenArrayInitDataSetIndex(BinaryenExpressionRef expr, + BinaryenExpressionRef index) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->index = index; +} +BinaryenExpressionRef +BinaryenArrayInitDataGetOffset(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->offset; +} +void BinaryenArrayInitDataSetOffset(BinaryenExpressionRef expr, + BinaryenExpressionRef offset) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->offset = offset; +} +BinaryenExpressionRef BinaryenArrayInitDataGetSize(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->size; +} +void BinaryenArrayInitDataSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef size) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->size = size; +} +// ArrayInitElem +const char* BinaryenArrayInitElemGetSegment(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->segment.str.data(); +} +void BinaryenArrayInitElemSetSegment(BinaryenExpressionRef expr, + const char* segment) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->segment = Name(segment); +} +BinaryenExpressionRef BinaryenArrayInitElemGetRef(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->ref; +} +void BinaryenArrayInitElemSetRef(BinaryenExpressionRef expr, + BinaryenExpressionRef ref) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->ref = ref; +} +BinaryenExpressionRef +BinaryenArrayInitElemGetIndex(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->index; +} +void BinaryenArrayInitElemSetIndex(BinaryenExpressionRef expr, + BinaryenExpressionRef index) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->index = index; +} +BinaryenExpressionRef +BinaryenArrayInitElemGetOffset(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->offset; +} +void BinaryenArrayInitElemSetOffset(BinaryenExpressionRef expr, + BinaryenExpressionRef offset) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->offset = offset; +} +BinaryenExpressionRef BinaryenArrayInitElemGetSize(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is()); + return static_cast(expression)->size; +} +void BinaryenArrayInitElemSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef size) { + auto* expression = (Expression*)expr; + assert(expression->is()); + static_cast(expression)->size = size; +} // StringNew BinaryenOp BinaryenStringNewGetOp(BinaryenExpressionRef expr) { auto* expression = (Expression*)expr; diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 65a2f12f451..67948d8b79c 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1050,7 +1050,6 @@ BINARYEN_API BinaryenExpressionRef BinaryenArrayNew(BinaryenModuleRef module, BinaryenHeapType type, BinaryenExpressionRef size, BinaryenExpressionRef init); - BINARYEN_API BinaryenExpressionRef BinaryenArrayNewData(BinaryenModuleRef module, BinaryenHeapType type, @@ -1058,6 +1057,12 @@ BinaryenArrayNewData(BinaryenModuleRef module, BinaryenExpressionRef offset, BinaryenExpressionRef size); BINARYEN_API BinaryenExpressionRef +BinaryenArrayNewElem(BinaryenModuleRef module, + BinaryenHeapType type, + const char* seg, + BinaryenExpressionRef offset, + BinaryenExpressionRef size); +BINARYEN_API BinaryenExpressionRef BinaryenArrayNewFixed(BinaryenModuleRef module, BinaryenHeapType type, BinaryenExpressionRef* values, @@ -1082,11 +1087,31 @@ BinaryenArrayCopy(BinaryenModuleRef module, BinaryenExpressionRef srcIndex, BinaryenExpressionRef length); BINARYEN_API BinaryenExpressionRef +BinaryenArrayFill(BinaryenModuleRef module, + BinaryenExpressionRef ref, + BinaryenExpressionRef index, + BinaryenExpressionRef value, + BinaryenExpressionRef size); +BINARYEN_API BinaryenExpressionRef BinaryenStringNew(BinaryenModuleRef module, BinaryenOp op, BinaryenExpressionRef ref, BinaryenExpressionRef start, BinaryenExpressionRef end); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitData(BinaryenModuleRef module, + const char* name, + BinaryenExpressionRef ref, + BinaryenExpressionRef index, + BinaryenExpressionRef offset, + BinaryenExpressionRef size); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitElem(BinaryenModuleRef module, + const char* seg, + BinaryenExpressionRef ref, + BinaryenExpressionRef index, + BinaryenExpressionRef offset, + BinaryenExpressionRef size); BINARYEN_API BinaryenExpressionRef BinaryenStringConst(BinaryenModuleRef module, const char* name); BINARYEN_API BinaryenExpressionRef BinaryenStringMeasure( @@ -2444,6 +2469,36 @@ BinaryenArrayNewFixedInsertValueAt(BinaryenExpressionRef expr, BINARYEN_API BinaryenExpressionRef BinaryenArrayNewFixedRemoveValueAt( BinaryenExpressionRef expr, BinaryenIndex index); +// ArrayNewData + +BINARYEN_API const char* +BinaryenArrayNewDataGetSegment(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayNewDataSetSegment(BinaryenExpressionRef expr, + const char* segment); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayNewDataGetOffset(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayNewDataSetOffset(BinaryenExpressionRef expr, + BinaryenExpressionRef offset); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayNewDataGetSize(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayNewDataSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef size); + +// ArrayNewElem + +BINARYEN_API const char* +BinaryenArrayNewElemGetSegment(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayNewElemSetSegment(BinaryenExpressionRef expr, + const char* segment); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayNewElemGetOffset(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayNewElemSetOffset(BinaryenExpressionRef expr, + BinaryenExpressionRef offset); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayNewElemGetSize(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayNewElemSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef size); + // ArrayGet BINARYEN_API BinaryenExpressionRef @@ -2480,6 +2535,25 @@ BinaryenArrayLenGetRef(BinaryenExpressionRef expr); BINARYEN_API void BinaryenArrayLenSetRef(BinaryenExpressionRef expr, BinaryenExpressionRef refExpr); +// ArrayFill + +BINARYEN_API BinaryenExpressionRef +BinaryenArrayFillGetRef(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayFillSetRef(BinaryenExpressionRef expr, + BinaryenExpressionRef refExpr); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayFillGetIndex(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayFillSetIndex(BinaryenExpressionRef expr, + BinaryenExpressionRef indexExpr); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayFillGetValue(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayFillSetValue(BinaryenExpressionRef expr, + BinaryenExpressionRef valueExpr); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayFillGetSize(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayFillSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef sizeExpr); + // ArrayCopy BINARYEN_API BinaryenExpressionRef @@ -2506,6 +2580,52 @@ BinaryenArrayCopyGetLength(BinaryenExpressionRef expr); BINARYEN_API void BinaryenArrayCopySetLength(BinaryenExpressionRef expr, BinaryenExpressionRef lengthExpr); +// ArrayInitData + +BINARYEN_API const char* +BinaryenArrayInitDataGetSegment(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitDataSetSegment(BinaryenExpressionRef expr, + const char* segment); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitDataGetRef(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitDataSetRef(BinaryenExpressionRef expr, + BinaryenExpressionRef ref); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitDataGetIndex(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitDataSetIndex(BinaryenExpressionRef expr, + BinaryenExpressionRef index); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitDataGetOffset(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitDataSetOffset(BinaryenExpressionRef expr, + BinaryenExpressionRef offset); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitDataGetSize(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitDataSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef size); + +// ArrayInitElem + +BINARYEN_API const char* +BinaryenArrayInitElemGetSegment(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitElemSetSegment(BinaryenExpressionRef expr, + const char* segment); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitElemGetRef(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitElemSetRef(BinaryenExpressionRef expr, + BinaryenExpressionRef ref); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitElemGetIndex(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitElemSetIndex(BinaryenExpressionRef expr, + BinaryenExpressionRef index); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitElemGetOffset(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitElemSetOffset(BinaryenExpressionRef expr, + BinaryenExpressionRef offset); +BINARYEN_API BinaryenExpressionRef +BinaryenArrayInitElemGetSize(BinaryenExpressionRef expr); +BINARYEN_API void BinaryenArrayInitElemSetSize(BinaryenExpressionRef expr, + BinaryenExpressionRef size); + // StringNew BINARYEN_API BinaryenOp BinaryenStringNewGetOp(BinaryenExpressionRef expr); diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 6d886d26b1d..5521cc6cca8 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -40,12 +40,22 @@ function initializeConstants() { ['i31ref', 'I31ref'], ['structref', 'Structref'], ['stringref', 'Stringref'], + ['nullref', 'Nullref'], + ['nullexternref', 'NullExternref'], + ['nullfuncref', 'NullFuncref'], ['unreachable', 'Unreachable'], ['auto', 'Auto'] ].forEach(entry => { Module[entry[0]] = Module['_BinaryenType' + entry[1]](); }); + [ ['notPacked', 'NotPacked'], + ['i8', 'Int8'], + ['i16', 'Int16'] + ].forEach(entry => { + Module[entry[0]] = Module['_BinaryenPackedType' + entry[1]](); + }); + // Expression ids Module['ExpressionIds'] = {}; [ 'Invalid', @@ -113,10 +123,15 @@ function initializeConstants() { 'StructSet', 'ArrayNew', 'ArrayNewFixed', + 'ArrayNewData', + 'ArrayNewElem', 'ArrayGet', 'ArraySet', 'ArrayLen', + 'ArrayFill', 'ArrayCopy', + 'ArrayInitData', + 'ArrayInitElem', 'RefAs', 'StringNew', 'StringConst', @@ -705,54 +720,54 @@ function wrapModule(module, self = {}) { self['global'] = { 'get'(name, type) { - return Module['_BinaryenGlobalGet'](module, strToStack(name), type); + return preserveStack(() => Module['_BinaryenGlobalGet'](module, strToStack(name), type)); }, 'set'(name, value) { - return Module['_BinaryenGlobalSet'](module, strToStack(name), value); + return preserveStack(() => Module['_BinaryenGlobalSet'](module, strToStack(name), value)); } } self['table'] = { 'get'(name, index, type) { - return Module['_BinaryenTableGet'](module, strToStack(name), index, type); + return preserveStack(() => Module['_BinaryenTableGet'](module, strToStack(name), index, type)); }, 'set'(name, index, value) { - return Module['_BinaryenTableSet'](module, strToStack(name), index, value); + return preserveStack(() => Module['_BinaryenTableSet'](module, strToStack(name), index, value)); }, 'size'(name) { - return Module['_BinaryenTableSize'](module, strToStack(name)); + return preserveStack(() => Module['_BinaryenTableSize'](module, strToStack(name))); }, 'grow'(name, value, delta) { - return Module['_BinaryenTableGrow'](module, strToStack(name), value, delta); + return preserveStack(() => Module['_BinaryenTableGrow'](module, strToStack(name), value, delta)); } } self['memory'] = { // memory64 defaults to undefined/false. 'size'(name, memory64) { - return Module['_BinaryenMemorySize'](module, strToStack(name), memory64); + return preserveStack(() => Module['_BinaryenMemorySize'](module, strToStack(name), memory64)); }, 'grow'(value, name, memory64) { - return Module['_BinaryenMemoryGrow'](module, value, strToStack(name), memory64); + return preserveStack(() => Module['_BinaryenMemoryGrow'](module, value, strToStack(name), memory64)); }, 'init'(segment, dest, offset, size, name) { return preserveStack(() => Module['_BinaryenMemoryInit'](module, strToStack(segment), dest, offset, size, strToStack(name))); }, 'copy'(dest, source, size, destMemory, sourceMemory) { - return Module['_BinaryenMemoryCopy'](module, dest, source, size, strToStack(destMemory), strToStack(sourceMemory)); + return preserveStack(() => Module['_BinaryenMemoryCopy'](module, dest, source, size, strToStack(destMemory), strToStack(sourceMemory))); }, 'fill'(dest, value, size, name) { - return Module['_BinaryenMemoryFill'](module, dest, value, size, strToStack(name)); + return preserveStack(() => Module['_BinaryenMemoryFill'](module, dest, value, size, strToStack(name))); }, 'atomic': { 'notify'(ptr, notifyCount, name) { - return Module['_BinaryenAtomicNotify'](module, ptr, notifyCount, strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicNotify'](module, ptr, notifyCount, strToStack(name))); }, 'wait32'(ptr, expected, timeout, name) { - return Module['_BinaryenAtomicWait'](module, ptr, expected, timeout, Module['i32'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicWait'](module, ptr, expected, timeout, Module['i32'], strToStack(name))); }, 'wait64'(ptr, expected, timeout, name) { - return Module['_BinaryenAtomicWait'](module, ptr, expected, timeout, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicWait'](module, ptr, expected, timeout, Module['i64'], strToStack(name))); } } } @@ -765,28 +780,28 @@ function wrapModule(module, self = {}) { self['i32'] = { 'load'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i32'], ptr, strToStack(name))); }, 'load8_s'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 1, true, offset, align, Module['i32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 1, true, offset, align, Module['i32'], ptr, strToStack(name))); }, 'load8_u'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 1, false, offset, align, Module['i32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 1, false, offset, align, Module['i32'], ptr, strToStack(name))); }, 'load16_s'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 2, true, offset, align, Module['i32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 2, true, offset, align, Module['i32'], ptr, strToStack(name))); }, 'load16_u'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 2, false, offset, align, Module['i32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 2, false, offset, align, Module['i32'], ptr, strToStack(name))); }, 'store'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i32'], strToStack(name))); }, 'store8'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 1, offset, align, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 1, offset, align, ptr, value, Module['i32'], strToStack(name))); }, 'store16'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i32'], strToStack(name))); }, 'const'(x) { return preserveStack(() => { @@ -928,90 +943,111 @@ function wrapModule(module, self = {}) { }, 'atomic': { 'load'(offset, ptr, name) { - return Module['_BinaryenAtomicLoad'](module, 4, offset, Module['i32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicLoad'](module, 4, offset, Module['i32'], ptr, strToStack(name))); }, 'load8_u'(offset, ptr, name) { - return Module['_BinaryenAtomicLoad'](module, 1, offset, Module['i32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicLoad'](module, 1, offset, Module['i32'], ptr, strToStack(name))); }, 'load16_u'(offset, ptr, name) { - return Module['_BinaryenAtomicLoad'](module, 2, offset, Module['i32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicLoad'](module, 2, offset, Module['i32'], ptr, strToStack(name))); }, 'store'(offset, ptr, value, name) { - return Module['_BinaryenAtomicStore'](module, 4, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicStore'](module, 4, offset, ptr, value, Module['i32'], strToStack(name))); }, 'store8'(offset, ptr, value, name) { - return Module['_BinaryenAtomicStore'](module, 1, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicStore'](module, 1, offset, ptr, value, Module['i32'], strToStack(name))); }, 'store16'(offset, ptr, value, name) { - return Module['_BinaryenAtomicStore'](module, 2, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicStore'](module, 2, offset, ptr, value, Module['i32'], strToStack(name))); }, 'rmw': { 'add'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 4, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 4, offset, ptr, value, Module['i32'], strToStack(name))); }, 'sub'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 4, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 4, offset, ptr, value, Module['i32'], strToStack(name))); }, 'and'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 4, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 4, offset, ptr, value, Module['i32'], strToStack(name))); }, 'or'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 4, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 4, offset, ptr, value, Module['i32'], strToStack(name))); }, 'xor'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 4, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 4, offset, ptr, value, Module['i32'], strToStack(name))); }, 'xchg'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 4, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 4, offset, ptr, value, Module['i32'], strToStack(name))); }, 'cmpxchg'(offset, ptr, expected, replacement, name) { - return Module['_BinaryenAtomicCmpxchg'](module, 4, offset, ptr, expected, replacement, Module['i32'], strToStack(name)) + return preserveStack(() => + Module['_BinaryenAtomicCmpxchg'](module, 4, offset, ptr, expected, replacement, Module['i32'], strToStack(name))); }, }, 'rmw8_u': { 'add'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 1, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 1, offset, ptr, value, Module['i32'], strToStack(name))); }, 'sub'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 1, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 1, offset, ptr, value, Module['i32'], strToStack(name))); }, 'and'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 1, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 1, offset, ptr, value, Module['i32'], strToStack(name))); }, 'or'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 1, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 1, offset, ptr, value, Module['i32'], strToStack(name))); }, 'xor'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 1, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 1, offset, ptr, value, Module['i32'], strToStack(name))); }, 'xchg'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 1, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 1, offset, ptr, value, Module['i32'], strToStack(name))); }, 'cmpxchg'(offset, ptr, expected, replacement, name) { - return Module['_BinaryenAtomicCmpxchg'](module, 1, offset, ptr, expected, replacement, Module['i32'], strToStack(name)) + return preserveStack(() => + Module['_BinaryenAtomicCmpxchg'](module, 1, offset, ptr, expected, replacement, Module['i32'], strToStack(name))); }, }, 'rmw16_u': { 'add'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 2, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 2, offset, ptr, value, Module['i32'], strToStack(name))); }, 'sub'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 2, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 2, offset, ptr, value, Module['i32'], strToStack(name))); }, 'and'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 2, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 2, offset, ptr, value, Module['i32'], strToStack(name))); }, 'or'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 2, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 2, offset, ptr, value, Module['i32'], strToStack(name))); }, 'xor'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 2, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 2, offset, ptr, value, Module['i32'], strToStack(name))); }, 'xchg'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 2, offset, ptr, value, Module['i32'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 2, offset, ptr, value, Module['i32'], strToStack(name))); }, 'cmpxchg'(offset, ptr, expected, replacement, name) { - return Module['_BinaryenAtomicCmpxchg'](module, 2, offset, ptr, expected, replacement, Module['i32'], strToStack(name)) + return preserveStack(() => + Module['_BinaryenAtomicCmpxchg'](module, 2, offset, ptr, expected, replacement, Module['i32'], strToStack(name))); }, }, }, @@ -1022,37 +1058,37 @@ function wrapModule(module, self = {}) { self['i64'] = { 'load'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 8, true, offset, align, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 8, true, offset, align, Module['i64'], ptr, strToStack(name))); }, 'load8_s'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 1, true, offset, align, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 1, true, offset, align, Module['i64'], ptr, strToStack(name))); }, 'load8_u'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 1, false, offset, align, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 1, false, offset, align, Module['i64'], ptr, strToStack(name))); }, 'load16_s'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 2, true, offset, align, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 2, true, offset, align, Module['i64'], ptr, strToStack(name))); }, 'load16_u'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 2, false, offset, align, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 2, false, offset, align, Module['i64'], ptr, strToStack(name))); }, 'load32_s'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i64'], ptr, strToStack(name))); }, 'load32_u'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 4, false, offset, align, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 4, false, offset, align, Module['i64'], ptr, strToStack(name))); }, 'store'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['i64'], strToStack(name))); }, 'store8'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 1, offset, align, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 1, offset, align, ptr, value, Module['i64'], strToStack(name))); }, 'store16'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i64'], strToStack(name))); }, 'store32'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i64'], strToStack(name))); }, 'const'(x, y) { return preserveStack(() => { @@ -1200,119 +1236,147 @@ function wrapModule(module, self = {}) { }, 'atomic': { 'load'(offset, ptr, name) { - return Module['_BinaryenAtomicLoad'](module, 8, offset, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicLoad'](module, 8, offset, Module['i64'], ptr, strToStack(name))); }, 'load8_u'(offset, ptr, name) { - return Module['_BinaryenAtomicLoad'](module, 1, offset, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicLoad'](module, 1, offset, Module['i64'], ptr, strToStack(name))); }, 'load16_u'(offset, ptr, name) { - return Module['_BinaryenAtomicLoad'](module, 2, offset, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicLoad'](module, 2, offset, Module['i64'], ptr, strToStack(name))); }, 'load32_u'(offset, ptr, name) { - return Module['_BinaryenAtomicLoad'](module, 4, offset, Module['i64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicLoad'](module, 4, offset, Module['i64'], ptr, strToStack(name))); }, 'store'(offset, ptr, value, name) { - return Module['_BinaryenAtomicStore'](module, 8, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicStore'](module, 8, offset, ptr, value, Module['i64'], strToStack(name))); }, 'store8'(offset, ptr, value, name) { - return Module['_BinaryenAtomicStore'](module, 1, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicStore'](module, 1, offset, ptr, value, Module['i64'], strToStack(name))); }, 'store16'(offset, ptr, value, name) { - return Module['_BinaryenAtomicStore'](module, 2, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicStore'](module, 2, offset, ptr, value, Module['i64'], strToStack(name))); }, 'store32'(offset, ptr, value, name) { - return Module['_BinaryenAtomicStore'](module, 4, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenAtomicStore'](module, 4, offset, ptr, value, Module['i64'], strToStack(name))); }, 'rmw': { 'add'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 8, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 8, offset, ptr, value, Module['i64'], strToStack(name))); }, 'sub'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 8, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 8, offset, ptr, value, Module['i64'], strToStack(name))); }, 'and'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 8, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 8, offset, ptr, value, Module['i64'], strToStack(name))); }, 'or'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 8, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 8, offset, ptr, value, Module['i64'], strToStack(name))); }, 'xor'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 8, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 8, offset, ptr, value, Module['i64'], strToStack(name))); }, 'xchg'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 8, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 8, offset, ptr, value, Module['i64'], strToStack(name))); }, 'cmpxchg'(offset, ptr, expected, replacement, name) { - return Module['_BinaryenAtomicCmpxchg'](module, 8, offset, ptr, expected, replacement, Module['i64'], strToStack(name)) + return preserveStack(() => + Module['_BinaryenAtomicCmpxchg'](module, 8, offset, ptr, expected, replacement, Module['i64'], strToStack(name))); }, }, 'rmw8_u': { 'add'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 1, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 1, offset, ptr, value, Module['i64'], strToStack(name))); }, 'sub'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 1, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 1, offset, ptr, value, Module['i64'], strToStack(name))); }, 'and'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 1, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 1, offset, ptr, value, Module['i64'], strToStack(name))); }, 'or'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 1, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 1, offset, ptr, value, Module['i64'], strToStack(name))); }, 'xor'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 1, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 1, offset, ptr, value, Module['i64'], strToStack(name))); }, 'xchg'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 1, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 1, offset, ptr, value, Module['i64'], strToStack(name))); }, 'cmpxchg'(offset, ptr, expected, replacement, name) { - return Module['_BinaryenAtomicCmpxchg'](module, 1, offset, ptr, expected, replacement, Module['i64'], strToStack(name)) + return preserveStack(() => + Module['_BinaryenAtomicCmpxchg'](module, 1, offset, ptr, expected, replacement, Module['i64'], strToStack(name))); }, }, 'rmw16_u': { 'add'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 2, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 2, offset, ptr, value, Module['i64'], strToStack(name))); }, 'sub'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 2, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 2, offset, ptr, value, Module['i64'], strToStack(name))); }, 'and'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 2, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 2, offset, ptr, value, Module['i64'], strToStack(name))); }, 'or'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 2, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 2, offset, ptr, value, Module['i64'], strToStack(name))); }, 'xor'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 2, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 2, offset, ptr, value, Module['i64'], strToStack(name))); }, 'xchg'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 2, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 2, offset, ptr, value, Module['i64'], strToStack(name))); }, 'cmpxchg'(offset, ptr, expected, replacement, name) { - return Module['_BinaryenAtomicCmpxchg'](module, 2, offset, ptr, expected, replacement, Module['i64'], strToStack(name)) + return preserveStack(() => + Module['_BinaryenAtomicCmpxchg'](module, 2, offset, ptr, expected, replacement, Module['i64'], strToStack(name))); }, }, 'rmw32_u': { 'add'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 4, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 4, offset, ptr, value, Module['i64'], strToStack(name))); }, 'sub'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 4, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 4, offset, ptr, value, Module['i64'], strToStack(name))); }, 'and'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 4, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 4, offset, ptr, value, Module['i64'], strToStack(name))); }, 'or'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 4, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 4, offset, ptr, value, Module['i64'], strToStack(name))); }, 'xor'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 4, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 4, offset, ptr, value, Module['i64'], strToStack(name))); }, 'xchg'(offset, ptr, value, name) { - return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 4, offset, ptr, value, Module['i64'], strToStack(name)); + return preserveStack(() => + Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 4, offset, ptr, value, Module['i64'], strToStack(name))); }, 'cmpxchg'(offset, ptr, expected, replacement, name) { - return Module['_BinaryenAtomicCmpxchg'](module, 4, offset, ptr, expected, replacement, Module['i64'], strToStack(name)) + return preserveStack(() => + Module['_BinaryenAtomicCmpxchg'](module, 4, offset, ptr, expected, replacement, Module['i64'], strToStack(name))); }, }, }, @@ -1323,10 +1387,10 @@ function wrapModule(module, self = {}) { self['f32'] = { 'load'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['f32'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 4, true, offset, align, Module['f32'], ptr, strToStack(name))); }, 'store'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['f32'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['f32'], strToStack(name))); }, 'const'(x) { return preserveStack(() => { @@ -1431,10 +1495,10 @@ function wrapModule(module, self = {}) { self['f64'] = { 'load'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 8, true, offset, align, Module['f64'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 8, true, offset, align, Module['f64'], ptr, strToStack(name))); }, 'store'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['f64'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['f64'], strToStack(name))); }, 'const'(x) { return preserveStack(() => { @@ -1539,70 +1603,78 @@ function wrapModule(module, self = {}) { self['v128'] = { 'load'(offset, align, ptr, name) { - return Module['_BinaryenLoad'](module, 16, false, offset, align, Module['v128'], ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenLoad'](module, 16, false, offset, align, Module['v128'], ptr, strToStack(name))); }, 'load8_splat'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load8SplatVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load8SplatVec128'], offset, align, ptr, strToStack(name))); }, 'load16_splat'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load16SplatVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load16SplatVec128'], offset, align, ptr, strToStack(name))); }, 'load32_splat'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load32SplatVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load32SplatVec128'], offset, align, ptr, strToStack(name))); }, 'load64_splat'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load64SplatVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load64SplatVec128'], offset, align, ptr, strToStack(name))); }, 'load8x8_s'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load8x8SVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load8x8SVec128'], offset, align, ptr, strToStack(name))); }, 'load8x8_u'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load8x8UVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load8x8UVec128'], offset, align, ptr, strToStack(name))); }, 'load16x4_s'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load16x4SVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load16x4SVec128'], offset, align, ptr, strToStack(name))); }, 'load16x4_u'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load16x4UVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load16x4UVec128'], offset, align, ptr, strToStack(name))); }, 'load32x2_s'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load32x2SVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load32x2SVec128'], offset, align, ptr, strToStack(name))); }, 'load32x2_u'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load32x2UVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load32x2UVec128'], offset, align, ptr, strToStack(name))); }, 'load32_zero'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load32ZeroVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load32ZeroVec128'], offset, align, ptr, strToStack(name))); }, 'load64_zero'(offset, align, ptr, name) { - return Module['_BinaryenSIMDLoad'](module, Module['Load64ZeroVec128'], offset, align, ptr, strToStack(name)); + return preserveStack(() => Module['_BinaryenSIMDLoad'](module, Module['Load64ZeroVec128'], offset, align, ptr, strToStack(name))); }, 'load8_lane'(offset, align, index, ptr, vec, name) { - return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load8LaneVec128'], offset, align, index, ptr, vec, strToStack(name)); + return preserveStack(() => + Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load8LaneVec128'], offset, align, index, ptr, vec, strToStack(name))); }, 'load16_lane'(offset, align, index, ptr, vec, name) { - return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load16LaneVec128'], offset, align, index, ptr, vec, strToStack(name)); + return preserveStack(() => + Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load16LaneVec128'], offset, align, index, ptr, vec, strToStack(name))); }, 'load32_lane'(offset, align, index, ptr, vec, name) { - return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load32LaneVec128'], offset, align, index, ptr, vec, strToStack(name)); + return preserveStack(() => + Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load32LaneVec128'], offset, align, index, ptr, vec, strToStack(name))); }, 'load64_lane'(offset, align, index, ptr, vec, name) { - return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load64LaneVec128'], offset, align, index, ptr, vec, strToStack(name)); + return preserveStack(() => + Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load64LaneVec128'], offset, align, index, ptr, vec, strToStack(name))); }, 'store8_lane'(offset, align, index, ptr, vec, name) { - return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store8LaneVec128'], offset, align, index, ptr, vec, strToStack(name)); + return preserveStack(() => + Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store8LaneVec128'], offset, align, index, ptr, vec, strToStack(name))); }, 'store16_lane'(offset, align, index, ptr, vec, name) { - return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store16LaneVec128'], offset, align, index, ptr, vec, strToStack(name)); + return preserveStack(() => + Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store16LaneVec128'], offset, align, index, ptr, vec, strToStack(name))); }, 'store32_lane'(offset, align, index, ptr, vec, name) { - return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store32LaneVec128'], offset, align, index, ptr, vec, strToStack(name)); + return preserveStack(() => + Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store32LaneVec128'], offset, align, index, ptr, vec, strToStack(name))); }, 'store64_lane'(offset, align, index, ptr, vec, name) { - return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store64LaneVec128'], offset, align, index, ptr, vec, strToStack(name)); + return preserveStack(() => + Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store64LaneVec128'], offset, align, index, ptr, vec, strToStack(name))); }, 'store'(offset, align, ptr, value, name) { - return Module['_BinaryenStore'](module, 16, offset, align, ptr, value, Module['v128'], strToStack(name)); + return preserveStack(() => Module['_BinaryenStore'](module, 16, offset, align, ptr, value, Module['v128'], strToStack(name))); }, 'const'(i8s) { return preserveStack(() => { @@ -2333,6 +2405,12 @@ function wrapModule(module, self = {}) { }, 'eq'(left, right) { return Module['_BinaryenRefEq'](module, left, right); + }, + 'test'(value, castType) { + return Module['_BinaryenRefTest'](module, value, castType); + }, + 'cast'(value, castType) { + return Module['_BinaryenRefCast'](module, value, castType); } }; @@ -2366,7 +2444,7 @@ function wrapModule(module, self = {}) { return preserveStack(() => Module['_BinaryenThrow'](module, strToStack(tag), i32sToStack(operands), operands.length)); }; self['rethrow'] = function(target) { - return Module['_BinaryenRethrow'](module, strToStack(target)); + return preserveStack(() => Module['_BinaryenRethrow'](module, strToStack(target))); }; self['tuple'] = { @@ -2387,13 +2465,90 @@ function wrapModule(module, self = {}) { } }; - // TODO: any.convert_extern - // TODO: extern.convert_any - // TODO: ref.test - // TODO: ref.cast - // TODO: br_on_* - // TODO: struct.* - // TODO: array.* + self['any'] = { + 'convert_extern'() { + return Module['_BinaryenRefAsAnyConvertExtern'](); + } + }; + + self['extern'] = { + 'convert_any'() { + return Module['_BinaryenRefAsExternConvertAny'](); + } + }; + + self['br_on_null'] = function(name, value) { + return preserveStack(() => Module['_BinaryenBrOn'](module, Module['BrOnNull'], strToStack(name), value, Module['unreachable'])); + }; + + self['br_on_non_null'] = function(name, value) { + return preserveStack(() => Module['_BinaryenBrOn'](module, Module['BrOnNonNull'], strToStack(name), value, Module['unreachable'])); + }; + + self['br_on_cast'] = function(name, value, castType) { + return preserveStack(() => Module['_BinaryenBrOn'](module, Module['BrOnCast'], strToStack(name), value, castType)); + }; + + self['br_on_cast_fail'] = function(name, value, castType) { + return preserveStack(() => Module['_BinaryenBrOn'](module, Module['BrOnCastFail'], strToStack(name), value, castType)); + }; + + self['struct'] = { + 'new'(operands, type) { + return preserveStack(() => Module['_BinaryenStructNew'](module, i32sToStack(operands), operands.length, type)); + }, + 'new_default'(type) { + // Passing in null for |operands| (and 0 for |numOperands|) implies this is + // struct.new_default. + return Module['_BinaryenStructNew'](module, 0, 0, type); + }, + 'get'(index, ref, type, isSigned) { + return Module['_BinaryenStructGet'](module, index, ref, type, isSigned); + }, + 'set'(index, ref, value) { + return Module['_BinaryenStructSet'](module, index, ref, value); + } + }; + + self['array'] = { + 'new'(type, size, init) { + return Module['_BinaryenArrayNew'](module, type, size, init); + }, + 'new_default'(type, size) { + return Module['_BinaryenArrayNew'](module, type, size, 0); + }, + 'new_fixed'(type, values) { + return preserveStack(() => Module['_BinaryenArrayNewFixed'](module, type, i32sToStack(values), values.length)); + }, + 'new_data'(type, name, offset, size) { + return preserveStack(() => Module['_BinaryenArrayNewData'](module, type, strToStack(name), offset, size)); + }, + 'new_elem'(type, name, offset, size) { + return preserveStack(() => Module['_BinaryenArrayNewElem'](module, type, strToStack(name), offset, size)); + }, + 'get'(ref, index, type, isSigned) { + return Module['_BinaryenArrayGet'](module, ref, index, type, isSigned); + }, + 'set'(ref, index, value) { + return Module['_BinaryenArraySet'](module, ref, index, value); + }, + 'len'(ref) { + return Module['_BinaryenArrayLen'](module, ref); + }, + 'fill'(ref, index, value, size) { + return Module['_BinaryenArrayFill'](module, ref, index, value, size); + }, + 'copy'(destRef, destIndex, srcRef, srcIndex, length) { + return Module['_BinaryenArrayCopy'](module, destRef, destIndex, srcRef, srcIndex, length); + }, + 'init_data'(name, ref, index, offset, size) { + return preserveStack(() => Module['_BinaryenArrayInitData'](module, strToStack(name), ref, index, offset, size)); + }, + 'init_elem'(name, ref, index, offset, size) { + return preserveStack(() => Module['_BinaryenArrayInitElem'](module, strToStack(name), ref, index, offset, size)); + } + }; + // TODO: string.* // 'Module' operations @@ -2556,40 +2711,44 @@ function wrapModule(module, self = {}) { return Boolean(Module['_BinaryenHasMemory'](module)); }; self['getMemoryInfo'] = function(name) { - var memoryInfo = { - 'module': UTF8ToString(Module['_BinaryenMemoryImportGetModule'](module, strToStack(name))), - 'base': UTF8ToString(Module['_BinaryenMemoryImportGetBase'](module, strToStack(name))), - 'initial': Module['_BinaryenMemoryGetInitial'](module, strToStack(name)), - 'shared': Boolean(Module['_BinaryenMemoryIsShared'](module, strToStack(name))), - 'is64': Boolean(Module['_BinaryenMemoryIs64'](module, strToStack(name))), - }; - if (Module['_BinaryenMemoryHasMax'](module, strToStack(name))) { - memoryInfo['max'] = Module['_BinaryenMemoryGetMax'](module, strToStack(name)); - } - return memoryInfo; + return preserveStack(() => { + var memoryInfo = { + 'module': UTF8ToString(Module['_BinaryenMemoryImportGetModule'](module, strToStack(name))), + 'base': UTF8ToString(Module['_BinaryenMemoryImportGetBase'](module, strToStack(name))), + 'initial': Module['_BinaryenMemoryGetInitial'](module, strToStack(name)), + 'shared': Boolean(Module['_BinaryenMemoryIsShared'](module, strToStack(name))), + 'is64': Boolean(Module['_BinaryenMemoryIs64'](module, strToStack(name))), + }; + if (Module['_BinaryenMemoryHasMax'](module, strToStack(name))) { + memoryInfo['max'] = Module['_BinaryenMemoryGetMax'](module, strToStack(name)); + } + return memoryInfo; + }); }; self['getNumMemorySegments'] = function() { return Module['_BinaryenGetNumMemorySegments'](module); }; self['getMemorySegmentInfo'] = function(name) { - const passive = Boolean(Module['_BinaryenGetMemorySegmentPassive'](module, strToStack(name))); - let offset = null; - if (!passive) { - offset = Module['_BinaryenGetMemorySegmentByteOffset'](module, strToStack(name)); - } - return { - 'offset': offset, - 'data': (function(){ - const size = Module['_BinaryenGetMemorySegmentByteLength'](module, strToStack(name)); - const ptr = _malloc(size); - Module['_BinaryenCopyMemorySegmentData'](module, strToStack(name), ptr); - const res = new Uint8Array(size); - res.set(HEAP8.subarray(ptr, ptr + size)); - _free(ptr); - return res.buffer; - })(), - 'passive': passive - }; + return preserveStack(() => { + const passive = Boolean(Module['_BinaryenGetMemorySegmentPassive'](module, strToStack(name))); + let offset = null; + if (!passive) { + offset = Module['_BinaryenGetMemorySegmentByteOffset'](module, strToStack(name)); + } + return { + 'offset': offset, + 'data': (function(){ + const size = Module['_BinaryenGetMemorySegmentByteLength'](module, strToStack(name)); + const ptr = _malloc(size); + Module['_BinaryenCopyMemorySegmentData'](module, strToStack(name), ptr); + const res = new Uint8Array(size); + res.set(HEAP8.subarray(ptr, ptr + size)); + _free(ptr); + return res.buffer; + })(), + 'passive': passive + }; + }); }; self['setStart'] = function(start) { return Module['_BinaryenSetStart'](module, start); @@ -2603,6 +2762,16 @@ function wrapModule(module, self = {}) { self['setFeatures'] = function(features) { Module['_BinaryenModuleSetFeatures'](module, features); }; + self['setTypeName'] = function(heapType, name) { + return preserveStack(() => + Module['_BinaryenModuleSetTypeName'](module, heapType, strToStack(name)) + ); + }; + self['setFieldName'] = function(heapType, index, name) { + return preserveStack(() => + Module['_BinaryenModuleSetFieldName'](module, heapType, index, strToStack(name)) + ); + }; self['addCustomSection'] = function(name, contents) { return preserveStack(() => Module['_BinaryenAddCustomSection'](module, strToStack(name), i8sToStack(contents), contents.length) @@ -2724,6 +2893,92 @@ function wrapModule(module, self = {}) { } Module['wrapModule'] = wrapModule; +// 'TypeBuilder' interface +/** @constructor */ +Module['TypeBuilder'] = function(size) { + const builder = Module['_TypeBuilderCreate'](size); + this['ptr'] = builder; + + this['grow'] = function(count) { + Module['_TypeBuilderGrow'](builder, count); + }; + this['getSize'] = function() { + return Module['_TypeBuilderGetSize'](builder); + }; + this['setSignatureType'] = function(index, paramTypes, resultTypes) { + Module['_TypeBuilderSetSignatureType'](builder, index, paramTypes, resultTypes); + }; + this['setStructType'] = function(index, fields = []) { + // fields are assumed to be { type: type ref, packedType: type ref, mutable: bool } + preserveStack(() => { + const numFields = fields.length; + const types = new Array(numFields); + const packedTypes = new Array(numFields); + const mutables = new Array(numFields); + for (let i = 0; i < numFields; i++) { + const { ['type']: type, ['packedType']: packedType, ['mutable']: mutable } = fields[i]; + types[i] = type; + packedTypes[i] = packedType; + mutables[i] = mutable; + } + Module['_TypeBuilderSetStructType'](builder, + index, + i32sToStack(types), i32sToStack(packedTypes), + i8sToStack(mutables), + numFields + ); + }); + }; + this['setArrayType'] = function(index, elementType, elementPackedType, elementMutable) { + Module['_TypeBuilderSetArrayType'](builder, + index, elementType, elementPackedType, elementMutable + ); + }; + this['getTempHeapType'] = function(index) { + return Module['_TypeBuilderGetTempHeapType'](builder, index); + }; + this['getTempTupleType'] = function(types) { + return preserveStack(() => { + return Module['_TypeBuilderGetTempTupleType'](builder, i32sToStack(types), types.length); + }); + }; + this['getTempRefType'] = function(heapType, nullable) { + return Module['_TypeBuilderGetTempRefType'](builder, heapType, nullable); + }; + this['setSubType'] = function(index, superType) { + Module['_TypeBuilderSetSubType'](builder, index, superType); + }; + this['setOpen'] = function(index) { + Module['_TypeBuilderSetOpen'](builder, index); + }; + this['createRecGroup'] = function(index, length) { + Module['_TypeBuilderCreateRecGroup'](builder, index, length); + }; + this['buildAndDispose'] = function() { + return preserveStack(() => { + const numTypes = this['getSize'](); + const array = stackAlloc(numTypes << 2); + if (!Module['_TypeBuilderBuildAndDispose'](builder, array, 0, 0)) + throw new TypeError('TypeBuilder.buildAndDispose failed'); + const types = new Array(numTypes); + for (let i = 0; i < numTypes; i++) { + types[i] = HEAPU32[(array >>> 2) + i]; + } + return types; + }); + }; +} + +// Gets the type from a heap type generated by TypeBuilder +Module['getTypeFromHeapType'] = function(heapType, nullable) { + return Module['_BinaryenTypeFromHeapType'](heapType, nullable); +}; + +// Gets the heap type of a type +Module['getHeapType'] = function(type) { + return Module['_BinaryenTypeGetHeapType'](type); +}; + // 'Relooper' interface /** @constructor */ Module['Relooper'] = function(module) { @@ -2803,7 +3058,7 @@ Module['getExpressionType'] = function(expr) { Module['getExpressionInfo'] = function(expr) { const id = Module['_BinaryenExpressionGetId'](expr); const type = Module['_BinaryenExpressionGetType'](expr); - switch (id) { + switch (id) { // TODO: GC instructions case Module['BlockId']: return { 'id': id, @@ -4693,6 +4948,406 @@ Module['RefEq'] = makeExpressionWrapper({ } }); +Module['RefTest'] = makeExpressionWrapper({ + 'getRef'(expr) { + return Module['_BinaryenRefTestGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenRefTestSetRef'](expr, ref); + }, + 'getCastType'(expr) { + return Module['_BinaryenRefTestGetCastType'](expr); + }, + 'setCastType'(expr, castType) { + Module['_BinaryenRefTestSetCastType'](expr, castType); + } +}); + +Module['RefCast'] = makeExpressionWrapper({ + 'getRef'(expr) { + return Module['_BinaryenRefCastGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenRefCastSetRef'](expr, ref); + } +}); + +// TODO: any.convert_extern +// TODO: extern.convert_any + +Module['BrOn'] = makeExpressionWrapper({ + 'getOp'(expr) { + return Module['_BinaryenBrOnGetOp'](expr); + }, + 'setOp'(expr, op) { + Module['_BinaryenBrOnSetOp'](expr, op); + }, + 'getName'(expr) { + return UTF8ToString(Module['_BinaryenBrOnGetName'](expr)); + }, + 'setName'(expr, name) { + preserveStack(() => Module['_BinaryenBrOnSetName'](expr, strToStack(name))); + }, + 'getRef'(expr) { + return Module['_BinaryenBrOnGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenBrOnSetRef'](expr, ref); + }, + 'getCastType'(expr) { + return Module['_BinaryenBrOnGetCastType'](expr); + }, + 'setCastType'(expr, castType) { + Module['_BinaryenBrOnSetCastType'](expr, castType); + } +}); + +Module['StructNew'] = makeExpressionWrapper({ + 'getNumOperands'(expr) { + return Module['_BinaryenStructNewGetNumOperands'](expr); + }, + 'getOperands'(expr) { + return getAllNested(expr, Module['_BinaryenStructNewGetNumOperands'], Module['_BinaryenStructNewGetOperandAt']); + }, + 'setOperands'(expr, operands) { + setAllNested( + expr, + operands, + Module['_BinaryenStructNewGetNumOperands'], + Module['_BinaryenStructNewSetOperandAt'], + Module['_BinaryenStructNewAppendOperand'], + Module['_BinaryenStructNewRemoveOperandAt'] + ); + }, + 'getOperandAt'(expr, index) { + return Module['_BinaryenStructNewGetOperandAt'](expr, index); + }, + 'setOperandAt'(expr, index, operandExpr) { + Module['_BinaryenStructNewSetOperandAt'](expr, index, operandExpr); + }, + 'appendOperand'(expr, operandExpr) { + return Module['_BinaryenStructNewAppendOperand'](expr, operandExpr); + }, + 'insertOperandAt'(expr, index, operandExpr) { + Module['_BinaryenStructNewInsertOperandAt'](expr, index, operandExpr); + }, + 'removeOperandAt'(expr, index) { + return Module['_BinaryenStructNewRemoveOperandAt'](expr, index); + } +}); + +Module['StructGet'] = makeExpressionWrapper({ + 'getIndex'(expr) { + return Module['_BinaryenStructGetGetIndex'](expr); + }, + 'setIndex'(expr, index) { + Module['_BinaryenStructGetSetIndex'](expr, index); + }, + 'getRef'(expr) { + return Module['_BinaryenStructGetGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenStructGetSetRef'](expr, ref); + }, + 'isSigned'(expr) { + return Boolean(Module['_BinaryenStructGetIsSigned'](expr)); + }, + 'setSigned'(expr, signed) { + Module['_BinaryenStructGetSetSigned'](expr, signed); + } +}); + +Module['StructSet'] = makeExpressionWrapper({ + 'getIndex'(expr) { + return Module['_BinaryenStructSetGetIndex'](expr); + }, + 'setIndex'(expr, index) { + Module['_BinaryenStructSetSetIndex'](expr, index); + }, + 'getRef'(expr) { + return Module['_BinaryenStructSetGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenStructSetSetRef'](expr, ref); + }, + 'getValue'(expr) { + return Module['_BinaryenStructSetGetValue'](expr); + }, + 'setValue'(expr, value) { + Module['_BinaryenStructSetSetValue'](expr, value); + } +}); + +Module['ArrayNew'] = makeExpressionWrapper({ + 'getInit'(expr) { + return Module['_BinaryenArrayNewGetInit'](expr); + }, + 'setInit'(expr, init) { + Module['_BinaryenArrayNewSetInit'](expr, init); + }, + 'getSize'(expr) { + return Module['_BinaryenArrayNewGetSize'](expr); + }, + 'setSize'(expr, size) { + Module['_BinaryenArrayNewSetSize'](expr, size); + } +}); + +Module['ArrayNewFixed'] = makeExpressionWrapper({ + 'getNumValues'(expr) { + return Module['_BinaryenArrayNewFixedGetNumValues'](expr); + }, + 'getValues'(expr) { + return getAllNested(expr, + Module['_BinaryenArrayNewFixedGetNumValues'], + Module['_BinaryenArrayNewFixedGetValueAt']); + }, + 'setValues'(expr, values) { + setAllNested( + expr, + values, + Module['_BinaryenArrayNewFixedGetNumValues'], + Module['_BinaryenArrayNewFixedSetValueAt'], + Module['_BinaryenArrayNewFixedAppendValue'], + Module['_BinaryenArrayNewFixedRemoveValueAt'] + ); + }, + 'getValueAt'(expr, index) { + return Module['_BinaryenArrayNewFixedGetValueAt'](expr, index); + }, + 'setValueAt'(expr, index, valueExpr) { + Module['_BinaryenArrayNewFixedSetValueAt'](expr, index, valueExpr); + }, + 'appendValue'(expr, valueExpr) { + return Module['_BinaryenArrayNewFixedAppendValue'](expr, valueExpr); + }, + 'insertValueAt'(expr, index, valueExpr) { + Module['_BinaryenArrayNewFixedInsertValueAt'](expr, index, valueExpr); + }, + 'removeValueAt'(expr, index) { + return Module['_BinaryenArrayNewFixedRemoveValueAt'](expr, index); + } +}); + +Module['ArrayNewData'] = makeExpressionWrapper({ + 'getSegment'(expr) { + return UTF8ToString(Module['_BinaryenArrayNewDataGetSegment'](expr)); + }, + 'setSegment'(expr, segment) { + preserveStack(() => Module['_BinaryenArrayNewDataSetSegment'](expr, strToStack(segment))); + }, + 'getOffset'(expr) { + return Module['_BinaryenArrayNewDataGetOffset'](expr); + }, + 'setOffset'(expr, offset) { + Module['_BinaryenArrayNewDataSetOffset'](expr, offset); + }, + 'getSize'(expr) { + return Module['_BinaryenArrayNewDataGetSize'](expr); + }, + 'setSize'(expr, size) { + Module['_BinaryenArrayNewDataSetSize'](expr, size); + } +}); + +Module['ArrayNewElem'] = makeExpressionWrapper({ + 'getSegment'(expr) { + return UTF8ToString(Module['_BinaryenArrayNewElemGetSegment'](expr)); + }, + 'setSegment'(expr, segment) { + preserveStack(() => Module['_BinaryenArrayNewElemSetSegment'](expr, strToStack(segment))); + }, + 'getOffset'(expr) { + return Module['_BinaryenArrayNewElemGetOffset'](expr); + }, + 'setOffset'(expr, offset) { + Module['_BinaryenArrayNewElemSetOffset'](expr, offset); + }, + 'getSize'(expr) { + return Module['_BinaryenArrayNewElemGetSize'](expr); + }, + 'setSize'(expr, size) { + Module['_BinaryenArrayNewElemSetSize'](expr, size); + } +}); + +Module['ArrayGet'] = makeExpressionWrapper({ + 'getRef'(expr) { + return Module['_BinaryenArrayGetGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenArrayGetSetRef'](expr, ref); + }, + 'getIndex'(expr) { + return Module['_BinaryenArrayGetGetIndex'](expr); + }, + 'setIndex'(expr, index) { + Module['_BinaryenArrayGetSetIndex'](expr, index); + }, + 'isSigned'(expr) { + return Boolean(Module['_BinaryenArrayGetIsSigned'](expr)); + }, + 'setSigned'(expr, signed) { + Module['_BinaryenArrayGetSetSigned'](expr, signed); + } +}); + +Module['ArraySet'] = makeExpressionWrapper({ + 'getRef'(expr) { + return Module['_BinaryenArraySetGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenArraySetSetRef'](expr, ref); + }, + 'getIndex'(expr) { + return Module['_BinaryenArraySetGetIndex'](expr); + }, + 'setIndex'(expr, index) { + Module['_BinaryenArraySetSetIndex'](expr, index); + }, + 'getValue'(expr) { + return Module['_BinaryenArraySetGetValue'](expr); + }, + 'setValue'(expr, value) { + Module['_BinaryenArraySetSetValue'](expr, value); + } +}); + +Module['ArrayLen'] = makeExpressionWrapper({ + 'getRef'(expr) { + return Module['_BinaryenArrayLenGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenArrayLenSetRef'](expr, ref); + } +}); + +Module['ArrayFill'] = makeExpressionWrapper({ + 'getRef'(expr) { + return Module['_BinaryenArrayFillGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenArrayFillSetRef'](expr, ref); + }, + 'getIndex'(expr) { + return Module['_BinaryenArrayFillGetIndex'](expr); + }, + 'setIndex'(expr, index) { + Module['_BinaryenArrayFillSetIndex'](expr, index); + }, + 'getValue'(expr) { + return Module['_BinaryenArrayFillGetValue'](expr); + }, + 'setValue'(expr, value) { + Module['_BinaryenArrayFillSetValue'](expr, value); + }, + 'getSize'(expr) { + return Module['_BinaryenArrayFillGetSize'](expr); + }, + 'setSize'(expr, size) { + Module['_BinaryenArrayFillSetSize'](expr, size); + } +}); + +Module['ArrayCopy'] = makeExpressionWrapper({ + 'getDestRef'(expr) { + return Module['_BinaryenArrayCopyGetDestRef'](expr); + }, + 'setDestRef'(expr, ref) { + Module['_BinaryenArrayCopySetDestRef'](expr, ref); + }, + 'getDestIndex'(expr) { + return Module['_BinaryenArrayCopyGetDestIndex'](expr); + }, + 'setDestIndex'(expr, index) { + Module['_BinaryenArrayCopySetDestIndex'](expr, index); + }, + 'getSrcRef'(expr) { + return Module['_BinaryenArrayCopyGetSrcRef'](expr); + }, + 'setSrcRef'(expr, ref) { + Module['_BinaryenArrayCopySetSrcRef'](expr, ref); + }, + 'getSrcIndex'(expr) { + return Module['_BinaryenArrayCopyGetSrcIndex'](expr); + }, + 'setSrcIndex'(expr, index) { + Module['_BinaryenArrayCopySetSrcIndex'](expr, index); + }, + 'getLength'(expr) { + return Module['_BinaryenArrayCopyGetLength'](expr); + }, + 'setLength'(expr, length) { + Module['_BinaryenArrayCopySetLength'](expr, length); + } +}); + +Module['ArrayInitData'] = makeExpressionWrapper({ + 'getSegment'(expr) { + return UTF8ToString(Module['_BinaryenArrayInitDataGetSegment'](expr)); + }, + 'setSegment'(expr, segment) { + preserveStack(() => Module['_BinaryenArrayInitDataSetSegment'](expr, strToStack(segment))); + }, + 'getRef'(expr) { + return Module['_BinaryenArrayInitDataGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenArrayInitDataSetRef'](expr, ref); + }, + 'getIndex'(expr) { + return Module['_BinaryenArrayInitDataGetIndex'](expr); + }, + 'setIndex'(expr, index) { + Module['_BinaryenArrayInitDataSetIndex'](expr, index); + }, + 'getOffset'(expr) { + return Module['_BinaryenArrayInitDataGetOffset'](expr); + }, + 'setOffset'(expr, offset) { + Module['_BinaryenArrayInitDataSetOffset'](expr, offset); + }, + 'getSize'(expr) { + return Module['_BinaryenArrayInitDataGetSize'](expr); + }, + 'setSize'(expr, size) { + Module['_BinaryenArrayInitDataSetSize'](expr, size); + } +}); + +Module['ArrayInitElem'] = makeExpressionWrapper({ + 'getSegment'(expr) { + return UTF8ToString(Module['_BinaryenArrayInitElemGetSegment'](expr)); + }, + 'setSegment'(expr, segment) { + preserveStack(() => Module['_BinaryenArrayInitElemSetSegment'](expr, strToStack(segment))); + }, + 'getRef'(expr) { + return Module['_BinaryenArrayInitElemGetRef'](expr); + }, + 'setRef'(expr, ref) { + Module['_BinaryenArrayInitElemSetRef'](expr, ref); + }, + 'getIndex'(expr) { + return Module['_BinaryenArrayInitElemGetIndex'](expr); + }, + 'setIndex'(expr, index) { + Module['_BinaryenArrayInitElemSetIndex'](expr, index); + }, + 'getOffset'(expr) { + return Module['_BinaryenArrayInitElemGetOffset'](expr); + }, + 'setOffset'(expr, offset) { + Module['_BinaryenArrayInitElemSetOffset'](expr, offset); + }, + 'getSize'(expr) { + return Module['_BinaryenArrayInitElemGetSize'](expr); + }, + 'setSize'(expr, size) { + Module['_BinaryenArrayInitElemSetSize'](expr, size); + } +}); + Module['Try'] = makeExpressionWrapper({ 'getName'(expr) { const name = Module['_BinaryenTryGetName'](expr); diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index 0d1c9669084..544f2b97806 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -1531,6 +1531,759 @@ console.log("# RefEq"); module.dispose(); })(); +console.log("# RefTest"); +(function testRefTest() { + const module = new binaryen.Module(); + + var ref = module.local.get(0, binaryen.anyref); + var castType = binaryen.anyref; + const theRefTest = binaryen.RefTest(module.ref.test(ref, castType)); + assert(theRefTest instanceof binaryen.RefTest); + assert(theRefTest instanceof binaryen.Expression); + assert(theRefTest.ref === ref); + assert(theRefTest.castType === castType); + assert(theRefTest.type === binaryen.i32); + + theRefTest.ref = ref = module.local.get(2, binaryen.externref); + assert(theRefTest.ref === ref); + theRefTest.castType = castType = binaryen.externref; + assert(theRefTest.castType === castType); + theRefTest.type = binaryen.f64; + theRefTest.finalize(); + assert(theRefTest.type === binaryen.i32); + + console.log(theRefTest.toText()); + assert( + theRefTest.toText() + == + "(ref.test externref\n (local.get $2)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# RefCast"); +(function testRefCast() { + const module = new binaryen.Module(); + + var ref = module.local.get(0, binaryen.anyref); + var type = binaryen.anyref; + const theRefCast = binaryen.RefCast(module.ref.cast(ref, type)); + assert(theRefCast instanceof binaryen.RefCast); + assert(theRefCast instanceof binaryen.Expression); + assert(theRefCast.ref === ref); + assert(theRefCast.type === type); + + theRefCast.ref = ref = module.local.get(2, binaryen.externref); + assert(theRefCast.ref === ref); + theRefCast.type = type = binaryen.externref; + theRefCast.finalize(); + assert(theRefCast.type === type); + + console.log(theRefCast.toText()); + assert( + theRefCast.toText() + == + "(ref.cast externref\n (local.get $2)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# BrOn"); +(function testBrOn() { + const module = new binaryen.Module(); + + var name = "br"; + var ref = module.local.get(0, binaryen.externref); + var op = binaryen.Operations.BrOnNull; + var castType = binaryen.unreachable; + const theBrOn = binaryen.BrOn(module.br_on_null(name, ref)); + assert(theBrOn instanceof binaryen.BrOn); + assert(theBrOn instanceof binaryen.Expression); + assert(theBrOn.name === name); + assert(theBrOn.ref === ref); + assert(theBrOn.op === op); + assert(theBrOn.castType === castType); + + // TODO: What should theBrOn.type be equal to? + + theBrOn.name = name = "br2"; + assert(theBrOn.name === name); + theBrOn.ref = ref = module.local.get(1, binaryen.anyref); + assert(theBrOn.ref === ref); + theBrOn.op = op = binaryen.Operations.BrOnCast; + assert(theBrOn.op === op); + theBrOn.castType = castType = binaryen.i31ref; + assert(theBrOn.castType === castType); + theBrOn.finalize(); + + console.log(theBrOn.toText()); + assert( + theBrOn.toText() + == + "(br_on_cast $br2 anyref i31ref\n (local.get $1)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# StructNew"); +(function testStructNew() { + const builder = new binaryen.TypeBuilder(2); + builder.setStructType(0, [ + { type: binaryen.i32, packedType: binaryen.notPacked, mutable: true }, + ]); + builder.setStructType(1, [ + { type: binaryen.i32, packedType: binaryen.i16, mutable: true }, + { type: binaryen.i64, packedType: binaryen.notPacked, mutable: true } + ]); + var [ + struct0Type, + struct1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var operands = [ + module.i32.const(1), + module.i32.const(2) + ]; + var type = struct0Type; + const theStructNew = binaryen.StructNew(module.struct.new(operands, type)); + assert(theStructNew instanceof binaryen.StructNew); + assert(theStructNew instanceof binaryen.Expression); + assertDeepEqual(theStructNew.operands, operands); + assertDeepEqual(theStructNew.getOperands(), operands); + assert(theStructNew.type === type); + + theStructNew.operands = operands = [ + module.i32.const(3), // set + module.i32.const(4), // set + module.i32.const(5) // append + ]; + assertDeepEqual(theStructNew.operands, operands); + operands = [ + module.i32.const(6) // set + // remove + // remove + ]; + theStructNew.setOperands(operands); + assertDeepEqual(theStructNew.operands, operands); + theStructNew.insertOperandAt(0, module.i32.const(7)); + theStructNew.type = type = struct1Type; + theStructNew.finalize(); + assert(theStructNew.type === type); + + console.log(theStructNew.toText()); + assert( + theStructNew.toText() + == + "(struct.new $struct.0\n (i32.const 7)\n (i32.const 6)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# StructGet"); +(function testStructGet() { + const builder = new binaryen.TypeBuilder(2); + builder.setStructType(0, [ + { type: binaryen.i32, packedType: binaryen.notPacked, mutable: true }, + ]); + builder.setStructType(1, [ + { type: binaryen.i32, packedType: binaryen.i16, mutable: true }, + { type: binaryen.i64, packedType: binaryen.notPacked, mutable: true } + ]); + var [ + struct0Type, + struct1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var index = 0; + var ref = module.local.get(0, struct0Type); + var type = binaryen.i32; + var signed = false; + const theStructGet = binaryen.StructGet(module.struct.get(index, ref, type, signed)); + assert(theStructGet instanceof binaryen.StructGet); + assert(theStructGet instanceof binaryen.Expression); + assert(theStructGet.index === index); + assert(theStructGet.ref === ref); + assert(theStructGet.signed === signed); + assert(theStructGet.type === type); + + theStructGet.index = index = 1; + assert(theStructGet.index === index); + theStructGet.ref = ref = module.local.get(1, struct1Type); + assert(theStructGet.ref === ref); + theStructGet.signed = signed = true; + assert(theStructGet.signed === signed); + theStructGet.type = type = binaryen.i64; + theStructGet.finalize(); + assert(theStructGet.type === type); + + console.log(theStructGet.toText()); + assert( + theStructGet.toText() + == + "(struct.get $struct.0 1\n (local.get $1)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# StructSet"); +(function testStructSet() { + const builder = new binaryen.TypeBuilder(2); + builder.setStructType(0, [ + { type: binaryen.i32, packedType: binaryen.notPacked, mutable: true }, + ]); + builder.setStructType(1, [ + { type: binaryen.i32, packedType: binaryen.i16, mutable: true }, + { type: binaryen.i64, packedType: binaryen.notPacked, mutable: true } + ]); + var [ + struct0Type, + struct1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var index = 0; + var ref = module.local.get(0, struct0Type); + var value = module.local.get(1, binaryen.i32); + const theStructSet = binaryen.StructSet(module.struct.set(index, ref, value)); + assert(theStructSet instanceof binaryen.StructSet); + assert(theStructSet instanceof binaryen.Expression); + assert(theStructSet.index === index); + assert(theStructSet.ref === ref); + assert(theStructSet.value === value); + assert(theStructSet.type === binaryen.none); + + theStructSet.index = index = 1; + assert(theStructSet.index === index); + theStructSet.ref = ref = module.local.get(2, struct1Type); + assert(theStructSet.ref === ref); + theStructSet.value = value = module.local.get(3, binaryen.i64); + assert(theStructSet.value === value); + theStructSet.type = binaryen.f64; + theStructSet.finalize(); + assert(theStructSet.type === binaryen.none); + + console.log(theStructSet.toText()); + assert( + theStructSet.toText() + == + "(struct.set $struct.0 1\n (local.get $2)\n (local.get $3)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayNew"); +(function testArrayNew() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i32, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var type = array0Type; + var size = module.i32.const(2); + var init = module.i32.const(1); + const theArrayNew = binaryen.ArrayNew(module.array.new(type, size, init)); + assert(theArrayNew instanceof binaryen.ArrayNew); + assert(theArrayNew instanceof binaryen.Expression); + assert(theArrayNew.size === size); + assert(theArrayNew.init === init); + assert(theArrayNew.type === type); + + theArrayNew.size = size = module.i32.const(4); + assert(theArrayNew.size === size); + theArrayNew.init = init = module.i32.const(3); + assert(theArrayNew.init === init); + theArrayNew.type = type = array1Type; + theArrayNew.finalize(); + assert(theArrayNew.type === type); + + console.log(theArrayNew.toText()); + assert( + theArrayNew.toText() + == + "(array.new $array.0\n (i32.const 3)\n (i32.const 4)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayNewFixed"); +(function testArrayNewFixed() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i32, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var type = array0Type; + var values = [ + module.i32.const(1), + module.i32.const(2) + ]; + const theArrayNewFixed = binaryen.ArrayNewFixed(module.array.new_fixed(type, values)); + assert(theArrayNewFixed instanceof binaryen.ArrayNewFixed); + assert(theArrayNewFixed instanceof binaryen.Expression); + assertDeepEqual(theArrayNewFixed.values, values); + assertDeepEqual(theArrayNewFixed.getValues(), values); + assert(theArrayNewFixed.type === type); + + theArrayNewFixed.values = values = [ + module.i32.const(3), // set + module.i32.const(4), // set + module.i32.const(5) // append + ]; + assertDeepEqual(theArrayNewFixed.values, values); + values = [ + module.i32.const(6) // set + // remove + // remove + ]; + theArrayNewFixed.setValues(values); + assertDeepEqual(theArrayNewFixed.values, values); + theArrayNewFixed.insertValueAt(0, module.i32.const(7)); + theArrayNewFixed.type = type = array1Type; + theArrayNewFixed.finalize(); + assert(theArrayNewFixed.type === type); + + console.log(theArrayNewFixed.toText()); + assert( + theArrayNewFixed.toText() + == + "(array.new_fixed $array.0 2\n (i32.const 7)\n (i32.const 6)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayNewData"); +(function testArrayNewData() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i32, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var type = array0Type; + var segment = "0"; + var offset = module.i32.const(1); + var size = module.i32.const(2); + const theArrayNewData = binaryen.ArrayNewData(module.array.new_data(type, segment, offset, size)); + assert(theArrayNewData instanceof binaryen.ArrayNewData); + assert(theArrayNewData instanceof binaryen.Expression); + assert(theArrayNewData.segment === segment); + assert(theArrayNewData.offset === offset); + assert(theArrayNewData.size === size); + assert(theArrayNewData.type === type); + + theArrayNewData.segment = segment = "3"; + assert(theArrayNewData.segment === segment); + theArrayNewData.offset = offset = module.i32.const(4); + assert(theArrayNewData.offset === offset); + theArrayNewData.size = size = module.i32.const(5); + assert(theArrayNewData.size === size); + theArrayNewData.type = type = array1Type; + theArrayNewData.finalize(); + assert(theArrayNewData.type === type); + + console.log(theArrayNewData.toText()); + assert( + theArrayNewData.toText() + == + "(array.new_data $array.0 $3\n (i32.const 4)\n (i32.const 5)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayNewElem"); +(function testArrayNewElem() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i32, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var type = array0Type; + var segment = "0"; + var offset = module.i32.const(1); + var size = module.i32.const(2); + const theArrayNewElem = binaryen.ArrayNewElem(module.array.new_elem(type, segment, offset, size)); + assert(theArrayNewElem instanceof binaryen.ArrayNewElem); + assert(theArrayNewElem instanceof binaryen.Expression); + assert(theArrayNewElem.segment === segment); + assert(theArrayNewElem.offset === offset); + assert(theArrayNewElem.size === size); + assert(theArrayNewElem.type === type); + + theArrayNewElem.segment = segment = "3"; + assert(theArrayNewElem.segment === segment); + theArrayNewElem.offset = offset = module.i32.const(4); + assert(theArrayNewElem.offset === offset); + theArrayNewElem.size = size = module.i32.const(5); + assert(theArrayNewElem.size === size); + theArrayNewElem.type = type = array1Type; + theArrayNewElem.finalize(); + assert(theArrayNewElem.type === type); + + console.log(theArrayNewElem.toText()); + assert( + theArrayNewElem.toText() + == + "(array.new_elem $array.0 $3\n (i32.const 4)\n (i32.const 5)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayGet"); +(function testArrayGet() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i64, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var ref = module.local.get(0, array0Type); + var index = module.i32.const(0); + var type = binaryen.i32; + var signed = false; + const theArrayGet = binaryen.ArrayGet(module.array.get(ref, index, type, signed)); + assert(theArrayGet instanceof binaryen.ArrayGet); + assert(theArrayGet instanceof binaryen.Expression); + assert(theArrayGet.ref === ref); + assert(theArrayGet.index === index); + assert(theArrayGet.signed === signed); + assert(theArrayGet.type === type); + + theArrayGet.ref = ref = module.local.get(1, array1Type); + assert(theArrayGet.ref === ref); + theArrayGet.index = index = module.i32.const(1); + assert(theArrayGet.index === index); + theArrayGet.signed = signed = true; + assert(theArrayGet.signed === signed); + theArrayGet.type = type = binaryen.i64; + theArrayGet.finalize(); + assert(theArrayGet.type === type); + + console.log(theArrayGet.toText()); + assert( + theArrayGet.toText() + == + "(array.get $array.0\n (local.get $1)\n (i32.const 1)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArraySet"); +(function testArraySet() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i64, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var ref = module.local.get(0, array0Type); + var index = module.i32.const(0); + var value = module.local.get(1, binaryen.i32); + const theArraySet = binaryen.ArraySet(module.array.set(ref, index, value)); + assert(theArraySet instanceof binaryen.ArraySet); + assert(theArraySet instanceof binaryen.Expression); + assert(theArraySet.ref === ref); + assert(theArraySet.index === index); + assert(theArraySet.value === value); + assert(theArraySet.type === binaryen.none); + + theArraySet.ref = ref = module.local.get(2, array1Type); + assert(theArraySet.ref === ref); + theArraySet.index = index = module.i32.const(1); + assert(theArraySet.index === index); + theArraySet.value = value = module.local.get(3, binaryen.i64); + assert(theArraySet.value === value); + theArraySet.type = binaryen.i64; + theArraySet.finalize(); + assert(theArraySet.type === binaryen.none); + + console.log(theArraySet.toText()); + assert( + theArraySet.toText() + == + "(array.set $array.0\n (local.get $2)\n (i32.const 1)\n (local.get $3)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayLen"); +(function testArrayLen() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i64, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var ref = module.local.get(0, array0Type); + const theArrayLen = binaryen.ArrayLen(module.array.len(ref)); + assert(theArrayLen instanceof binaryen.ArrayLen); + assert(theArrayLen instanceof binaryen.Expression); + assert(theArrayLen.ref === ref); + assert(theArrayLen.type === binaryen.i32); + + theArrayLen.ref = ref = module.local.get(1, array1Type); + assert(theArrayLen.ref === ref); + theArrayLen.type = binaryen.i64; + theArrayLen.finalize(); + assert(theArrayLen.type === binaryen.i32); + + console.log(theArrayLen.toText()); + assert( + theArrayLen.toText() + == + "(array.len\n (local.get $1)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayFill"); +(function testArrayFill() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i64, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var ref = module.local.get(0, array0Type); + var index = module.i32.const(0); + var value = module.local.get(1, binaryen.i32); + var size = module.i32.const(1); + const theArrayFill = binaryen.ArrayFill(module.array.fill(ref, index, value, size)); + assert(theArrayFill instanceof binaryen.ArrayFill); + assert(theArrayFill instanceof binaryen.Expression); + assert(theArrayFill.ref === ref); + assert(theArrayFill.index === index); + assert(theArrayFill.value === value); + assert(theArrayFill.size === size); + assert(theArrayFill.type === binaryen.none); + + theArrayFill.ref = ref = module.local.get(2, array1Type); + assert(theArrayFill.ref === ref); + theArrayFill.index = index = module.i32.const(2); + assert(theArrayFill.index === index); + theArrayFill.value = value = module.local.get(3, binaryen.i64); + assert(theArrayFill.value = value); + theArrayFill.size = size = module.i32.const(3); + assert(theArrayFill.size === size); + theArrayFill.type = binaryen.i64; + theArrayFill.finalize(); + assert(theArrayFill.type === binaryen.none); + + console.log(theArrayFill.toText()); + assert( + theArrayFill.toText() + == + "(array.fill $array.0\n (local.get $2)\n (i32.const 2)\n (local.get $3)\n (i32.const 3)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayCopy"); +(function testArrayCopy() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i64, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var destRef = module.local.get(0, array0Type); + var destIndex = module.i32.const(0); + var srcRef = module.local.get(1, array0Type); + var srcIndex = module.i32.const(1); + var length = module.i32.const(1); + const theArrayCopy = binaryen.ArrayCopy(module.array.copy(destRef, destIndex, srcRef, srcIndex, length)); + assert(theArrayCopy instanceof binaryen.ArrayCopy); + assert(theArrayCopy instanceof binaryen.Expression); + assert(theArrayCopy.destRef === destRef); + assert(theArrayCopy.destIndex === destIndex); + assert(theArrayCopy.srcRef === srcRef); + assert(theArrayCopy.srcIndex === srcIndex); + assert(theArrayCopy.length === length); + assert(theArrayCopy.type === binaryen.none); + + theArrayCopy.destRef = destRef = module.local.get(2, array1Type); + assert(theArrayCopy.destRef === destRef); + theArrayCopy.destIndex = destIndex = module.i32.const(2); + assert(theArrayCopy.destIndex === destIndex); + theArrayCopy.srcRef = srcRef = module.local.get(3, array1Type); + assert(theArrayCopy.srcRef === srcRef); + theArrayCopy.srcIndex = srcIndex = module.i32.const(3); + assert(theArrayCopy.srcIndex === srcIndex); + theArrayCopy.length = length = module.i32.const(2); + assert(theArrayCopy.length === length); + theArrayCopy.type = binaryen.i64; + theArrayCopy.finalize(); + assert(theArrayCopy.type === binaryen.none); + + console.log(theArrayCopy.toText()); + assert( + theArrayCopy.toText() + == + "(array.copy $array.0 $array.0\n (local.get $2)\n (i32.const 2)\n (local.get $3)\n (i32.const 3)\n (i32.const 2)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayInitData"); +(function testArrayInitData() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i32, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var segment = "0"; + var ref = module.local.get(0, array0Type); + var index = module.i32.const(0); + var offset = module.i32.const(1); + var size = module.i32.const(2); + const theArrayInitData = binaryen.ArrayInitData(module.array.init_data(segment, ref, index, offset, size)); + assert(theArrayInitData instanceof binaryen.ArrayInitData); + assert(theArrayInitData instanceof binaryen.Expression); + assert(theArrayInitData.segment === segment); + assert(theArrayInitData.ref === ref); + assert(theArrayInitData.index === index); + assert(theArrayInitData.offset === offset); + assert(theArrayInitData.size === size); + assert(theArrayInitData.type === binaryen.none); + + theArrayInitData.segment = segment = "1"; + assert(theArrayInitData.segment === segment); + theArrayInitData.ref = ref = module.local.get(1, array1Type); + assert(theArrayInitData.ref === ref); + theArrayInitData.index = index = module.i32.const(3); + assert(theArrayInitData.index === index); + theArrayInitData.offset = offset = module.i32.const(4); + assert(theArrayInitData.offset === offset); + theArrayInitData.size = size = module.i32.const(5); + assert(theArrayInitData.size === size); + theArrayInitData.type = binaryen.i64; + theArrayInitData.finalize(); + assert(theArrayInitData.type === binaryen.none); + + console.log(theArrayInitData.toText()); + assert( + theArrayInitData.toText() + == + "(array.init_data $array.0 $1\n (local.get $1)\n (i32.const 3)\n (i32.const 4)\n (i32.const 5)\n)\n" + ); + + module.dispose(); +})(); + +console.log("# ArrayInitElem"); +(function testArrayInitElem() { + const builder = new binaryen.TypeBuilder(2); + builder.setArrayType(0, binaryen.i32, binaryen.i16, true); + builder.setArrayType(1, binaryen.i32, binaryen.notPacked, true); + var [ + array0Type, + array1Type + ] = builder.buildAndDispose(); + + const module = new binaryen.Module(); + + var segment = "0"; + var ref = module.local.get(0, array0Type); + var index = module.i32.const(0); + var offset = module.i32.const(1); + var size = module.i32.const(2); + const theArrayInitElem = binaryen.ArrayInitElem(module.array.init_elem(segment, ref, index, offset, size)); + assert(theArrayInitElem instanceof binaryen.ArrayInitElem); + assert(theArrayInitElem instanceof binaryen.Expression); + assert(theArrayInitElem.segment === segment); + assert(theArrayInitElem.ref === ref); + assert(theArrayInitElem.index === index); + assert(theArrayInitElem.offset === offset); + assert(theArrayInitElem.size === size); + assert(theArrayInitElem.type === binaryen.none); + + theArrayInitElem.segment = segment = "1"; + assert(theArrayInitElem.segment === segment); + theArrayInitElem.ref = ref = module.local.get(1, array1Type); + assert(theArrayInitElem.ref === ref); + theArrayInitElem.index = index = module.i32.const(3); + assert(theArrayInitElem.index === index); + theArrayInitElem.offset = offset = module.i32.const(4); + assert(theArrayInitElem.offset === offset); + theArrayInitElem.size = size = module.i32.const(5); + assert(theArrayInitElem.size === size); + theArrayInitElem.type = binaryen.i64; + theArrayInitElem.finalize(); + assert(theArrayInitElem.type === binaryen.none); + + console.log(theArrayInitElem.toText()); + assert( + theArrayInitElem.toText() + == + "(array.init_elem $array.0 $1\n (local.get $1)\n (i32.const 3)\n (i32.const 4)\n (i32.const 5)\n)\n" + ); + + module.dispose(); +})(); + console.log("# Try"); (function testTry() { const module = new binaryen.Module(); diff --git a/test/binaryen.js/expressions.js.txt b/test/binaryen.js/expressions.js.txt index 5d6c37c6bc6..47d7cf43fb4 100644 --- a/test/binaryen.js/expressions.js.txt +++ b/test/binaryen.js/expressions.js.txt @@ -239,6 +239,113 @@ (local.get $3) ) +# RefTest +(ref.test externref + (local.get $2) +) + +# RefCast +(ref.cast externref + (local.get $2) +) + +# BrOn +(br_on_cast $br2 anyref i31ref + (local.get $1) +) + +# StructNew +(struct.new $struct.0 + (i32.const 7) + (i32.const 6) +) + +# StructGet +(struct.get $struct.0 1 + (local.get $1) +) + +# StructSet +(struct.set $struct.0 1 + (local.get $2) + (local.get $3) +) + +# ArrayNew +(array.new $array.0 + (i32.const 3) + (i32.const 4) +) + +# ArrayNewFixed +(array.new_fixed $array.0 2 + (i32.const 7) + (i32.const 6) +) + +# ArrayNewData +(array.new_data $array.0 $3 + (i32.const 4) + (i32.const 5) +) + +# ArrayNewElem +(array.new_elem $array.0 $3 + (i32.const 4) + (i32.const 5) +) + +# ArrayGet +(array.get $array.0 + (local.get $1) + (i32.const 1) +) + +# ArraySet +(array.set $array.0 + (local.get $2) + (i32.const 1) + (local.get $3) +) + +# ArrayLen +(array.len + (local.get $1) +) + +# ArrayFill +(array.fill $array.0 + (local.get $2) + (i32.const 2) + (local.get $3) + (i32.const 3) +) + +# ArrayCopy +(array.copy $array.0 $array.0 + (local.get $2) + (i32.const 2) + (local.get $3) + (i32.const 3) + (i32.const 2) +) + +# ArrayInitData +(array.init_data $array.0 $1 + (local.get $1) + (i32.const 3) + (i32.const 4) + (i32.const 5) +) + +# ArrayInitElem +(array.init_elem $array.0 $1 + (local.get $1) + (i32.const 3) + (i32.const 4) + (i32.const 5) +) + # Try (try (result i32) (do diff --git a/test/binaryen.js/gc.js b/test/binaryen.js/gc.js new file mode 100644 index 00000000000..0f12184b4c4 --- /dev/null +++ b/test/binaryen.js/gc.js @@ -0,0 +1,178 @@ +var builder = new binaryen.TypeBuilder(4); +builder.setSignatureType(0, binaryen.createType([binaryen.i32]), binaryen.none); +builder.setStructType(1, [ + { type: binaryen.i32, packedType: binaryen.i16, mutable: true }, + { type: binaryen.f64, packedType: binaryen.notPacked, mutable: true } +]); +builder.setArrayType(2, binaryen.i32, binaryen.i8, true); +builder.setArrayType(3, binaryen.funcref, binaryen.notPacked, true); +var [ + signatureHeapType, + structHeapType, + arrayHeapType, + funcArrayHeapType +] = builder.buildAndDispose(); + +var signatureType = binaryen.getTypeFromHeapType(signatureHeapType, true); +var structType = binaryen.getTypeFromHeapType(structHeapType, true); +var arrayType = binaryen.getTypeFromHeapType(arrayHeapType, true); +var funcArrayType = binaryen.getTypeFromHeapType(funcArrayHeapType, true); + +var module = new binaryen.Module(); +module.setFeatures(binaryen.Features.ReferenceTypes | binaryen.Features.BulkMemory | binaryen.Features.GC); + +module.addFunction("add", binaryen.createType([binaryen.i32, binaryen.i32]), binaryen.i32, [], + module.i32.add( + module.local.get("0", binaryen.i32), + module.local.get("1", binaryen.i32) + ) +); + +module.setMemory(1, -1, null, [ + { offset: module.i32.const(0), data: [4, 3, 2, 1] } +]); + +module.addTable("0", 1, -1); +module.addActiveElementSegment("0", "0", ["add"]); + +module.addGlobal("struct-global", + structType, + true, + module.struct.new_default( + binaryen.getHeapType(structType) + ) +); + +module.addGlobal("array-global", + arrayType, + true, + module.array.new_default( + binaryen.getHeapType(arrayType), + module.i32.const(4) + ) +); + +module.addGlobal("funcArray-global", + funcArrayType, + true, + module.array.new_default( + binaryen.getHeapType(funcArrayType), + module.i32.const(4) + ) +); + +var valueList = [ + // ref + + // struct + module.struct.new( + [ + module.i32.const(1), + module.f64.const(2.3) + ], + binaryen.getHeapType(structType) + ), + module.struct.new_default( + binaryen.getHeapType(structType) + ), + module.struct.get( + 0, + module.global.get("struct-global", structType), + binaryen.i32, + true + ), + module.struct.set( + 1, + module.global.get("struct-global", structType), + module.f64.const(1.23) + ), + + // array + module.array.new( + binaryen.getHeapType(arrayType), + module.i32.const(1), + module.i32.const(0) + ), + module.array.new_default( + binaryen.getHeapType(arrayType), + module.i32.const(1) + ), + module.array.new_fixed( + binaryen.getHeapType(arrayType), + [ + module.i32.const(1), + module.i32.const(2), + module.i32.const(3) + ] + ), + module.array.new_data( + binaryen.getHeapType(arrayType), + "0", + module.i32.const(0), + module.i32.const(4) + ), + module.array.new_elem( + binaryen.getHeapType(funcArrayType), + "0", + module.i32.const(0), + module.i32.const(1) + ), + module.array.get( + module.global.get("array-global", arrayType), + module.i32.const(0), + binaryen.i32, + true + ), + module.array.set( + module.global.get("array-global", arrayType), + module.i32.const(1), + module.i32.const(2) + ), + module.array.len( + module.global.get("array-global", arrayType) + ), + module.array.fill( + module.global.get("array-global", arrayType), + module.i32.const(0), + module.i32.const(1), + module.i32.const(2) + ), + module.array.copy( + module.global.get("array-global", arrayType), + module.i32.const(0), + module.global.get("array-global", arrayType), + module.i32.const(1), + module.i32.const(2) + ), + module.array.init_data( + "0", + module.global.get("array-global", arrayType), + module.i32.const(0), + module.i32.const(1), + module.i32.const(2) + ), + module.array.init_elem( + "0", + module.global.get("funcArray-global", funcArrayType), + module.i32.const(0), + module.i32.const(1), + module.i32.const(2) + ) +]; +module.addFunction("main", binaryen.none, binaryen.none, [], + module.block( + null, + valueList.map(value => { + var type = binaryen.getExpressionType(value); + if (type === binaryen.none || type === binaryen.unreachable) + return value; + else + return module.drop(value); + }), + binaryen.none + ) +); + +assert(module.validate()); + +console.log(module.emitText()); diff --git a/test/binaryen.js/gc.js.txt b/test/binaryen.js/gc.js.txt new file mode 100644 index 00000000000..7b557c7c743 --- /dev/null +++ b/test/binaryen.js/gc.js.txt @@ -0,0 +1,116 @@ +(module + (type $0 (array (mut i8))) + (type $1 (struct (field (mut i16)) (field (mut f64)))) + (type $2 (array (mut funcref))) + (type $3 (func (param i32 i32) (result i32))) + (type $4 (func)) + (global $struct-global (mut (ref null $1)) (struct.new_default $1)) + (global $array-global (mut (ref null $0)) (array.new_default $0 + (i32.const 4) + )) + (global $funcArray-global (mut (ref null $2)) (array.new_default $2 + (i32.const 4) + )) + (memory $0 1) + (data $0 (i32.const 0) "\04\03\02\01") + (table $0 1 funcref) + (elem $0 (i32.const 0) $add) + (func $add (type $3) (param $0 i32) (param $1 i32) (result i32) + (i32.add + (local.get $0) + (local.get $1) + ) + ) + (func $main (type $4) + (drop + (struct.new $1 + (i32.const 1) + (f64.const 2.3) + ) + ) + (drop + (struct.new_default $1) + ) + (drop + (struct.get_s $1 0 + (global.get $struct-global) + ) + ) + (struct.set $1 1 + (global.get $struct-global) + (f64.const 1.23) + ) + (drop + (array.new $0 + (i32.const 0) + (i32.const 1) + ) + ) + (drop + (array.new_default $0 + (i32.const 1) + ) + ) + (drop + (array.new_fixed $0 3 + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + ) + (drop + (array.new_data $0 $0 + (i32.const 0) + (i32.const 4) + ) + ) + (drop + (array.new_elem $2 $0 + (i32.const 0) + (i32.const 1) + ) + ) + (drop + (array.get_s $0 + (global.get $array-global) + (i32.const 0) + ) + ) + (array.set $0 + (global.get $array-global) + (i32.const 1) + (i32.const 2) + ) + (drop + (array.len + (global.get $array-global) + ) + ) + (array.fill $0 + (global.get $array-global) + (i32.const 0) + (i32.const 1) + (i32.const 2) + ) + (array.copy $0 $0 + (global.get $array-global) + (i32.const 0) + (global.get $array-global) + (i32.const 1) + (i32.const 2) + ) + (array.init_data $0 $0 + (global.get $array-global) + (i32.const 0) + (i32.const 1) + (i32.const 2) + ) + (array.init_elem $2 $0 + (global.get $funcArray-global) + (i32.const 0) + (i32.const 1) + (i32.const 2) + ) + ) +) + diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index c97de45a366..85937929d7e 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -518,25 +518,29 @@ void test_core() { BinaryenType v128 = BinaryenTypeVec128(); BinaryenType i8Array; BinaryenType i16Array; + BinaryenType funcArray; BinaryenType i32Struct; { - TypeBuilderRef tb = TypeBuilderCreate(3); + TypeBuilderRef tb = TypeBuilderCreate(4); TypeBuilderSetArrayType( tb, 0, BinaryenTypeInt32(), BinaryenPackedTypeInt8(), true); TypeBuilderSetArrayType( tb, 1, BinaryenTypeInt32(), BinaryenPackedTypeInt16(), true); + TypeBuilderSetArrayType( + tb, 2, BinaryenTypeFuncref(), BinaryenPackedTypeNotPacked(), true); TypeBuilderSetStructType( tb, - 2, + 3, (BinaryenType[]){BinaryenTypeInt32()}, (BinaryenPackedType[]){BinaryenPackedTypeNotPacked()}, (bool[]){true}, 1); - BinaryenHeapType builtHeapTypes[3]; + BinaryenHeapType builtHeapTypes[4]; TypeBuilderBuildAndDispose(tb, (BinaryenHeapType*)&builtHeapTypes, 0, 0); i8Array = BinaryenTypeFromHeapType(builtHeapTypes[0], true); i16Array = BinaryenTypeFromHeapType(builtHeapTypes[1], true); - i32Struct = BinaryenTypeFromHeapType(builtHeapTypes[2], true); + funcArray = BinaryenTypeFromHeapType(builtHeapTypes[2], true); + i32Struct = BinaryenTypeFromHeapType(builtHeapTypes[3], true); } // Memory. Add it before creating any memory-using instructions. @@ -1170,12 +1174,30 @@ void test_core() { makeInt32(module, 42)), BinaryenArrayLen(module, BinaryenGlobalGet(module, "i8Array-global", i8Array)), + BinaryenArrayFill(module, + BinaryenGlobalGet(module, "i8Array-global", i8Array), + makeInt32(module, 0), + makeInt32(module, 1), + makeInt32(module, 2)), BinaryenArrayCopy(module, BinaryenGlobalGet(module, "i8Array-global", i8Array), makeInt32(module, 0), BinaryenGlobalGet(module, "i8Array-global", i8Array), makeInt32(module, 1), makeInt32(module, 2)), + BinaryenArrayInitData(module, + "0", + BinaryenGlobalGet(module, "i8Array-global", i8Array), + makeInt32(module, 0), + makeInt32(module, 1), + makeInt32(module, 2)), + BinaryenArrayInitElem( + module, + "0", + BinaryenGlobalGet(module, "funcArray-global", funcArray), + makeInt32(module, 0), + makeInt32(module, 1), + makeInt32(module, 2)), // Strings BinaryenStringNew(module, BinaryenStringNewLossyUTF8Array(), @@ -1302,6 +1324,15 @@ void test_core() { true, BinaryenArrayNew( module, BinaryenTypeGetHeapType(i16Array), makeInt32(module, 0), 0)); + BinaryenAddGlobal( + module, + "funcArray-global", + funcArray, + true, + BinaryenArrayNew(module, + BinaryenTypeGetHeapType(funcArray), + makeInt32(module, 0), + BinaryenRefNull(module, BinaryenTypeNullFuncref()))); BinaryenAddGlobal( module, "i32Struct-global", diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 9bfc45ddf72..5736b5a5c27 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -67,17 +67,22 @@ BinaryenFeatureAll: 4194303 (type $0 (array (mut i8))) (type $1 (struct (field (mut i32)))) (type $2 (func (param i32 i64 f32 f64) (result i32))) - (type $3 (array (mut i16))) - (type $4 (func (param i32))) - (type $5 (func (param i32 f64) (result f32))) - (type $6 (func)) - (import "module" "base" (func $an-imported (type $5) (param i32 f64) (result f32))) + (type $3 (array (mut funcref))) + (type $4 (array (mut i16))) + (type $5 (func (param i32))) + (type $6 (func (param i32 f64) (result f32))) + (type $7 (func)) + (import "module" "base" (func $an-imported (type $6) (param i32 f64) (result f32))) (global $a-global i32 (i32.const 7)) (global $a-mutable-global (mut f32) (f32.const 7.5)) (global $i8Array-global (mut (ref null $0)) (array.new_default $0 (i32.const 0) )) - (global $i16Array-global (mut (ref null $3)) (array.new_default $3 + (global $i16Array-global (mut (ref null $4)) (array.new_default $4 + (i32.const 0) + )) + (global $funcArray-global (mut (ref null $3)) (array.new $3 + (ref.null nofunc) (i32.const 0) )) (global $i32Struct-global (mut (ref null $1)) (struct.new_default $1)) @@ -91,7 +96,7 @@ BinaryenFeatureAll: 4194303 (table $0 1 1 funcref) (elem $0 (table $0) (i32.const 0) func $"kitchen()sinker") (elem $passive func $"kitchen()sinker") - (tag $a-tag (type $4) (param i32)) + (tag $a-tag (type $5) (param i32)) (export "mem" (memory $0)) (export "kitchen_sinker" (func $"kitchen()sinker")) (start $starter) @@ -2327,6 +2332,12 @@ BinaryenFeatureAll: 4194303 (global.get $i8Array-global) ) ) + (array.fill $0 + (global.get $i8Array-global) + (i32.const 0) + (i32.const 1) + (i32.const 2) + ) (array.copy $0 $0 (global.get $i8Array-global) (i32.const 0) @@ -2334,6 +2345,18 @@ BinaryenFeatureAll: 4194303 (i32.const 1) (i32.const 2) ) + (array.init_data $0 $0 + (global.get $i8Array-global) + (i32.const 0) + (i32.const 1) + (i32.const 2) + ) + (array.init_elem $3 $0 + (global.get $funcArray-global) + (i32.const 0) + (i32.const 1) + (i32.const 2) + ) (drop (string.new_lossy_utf8_array (global.get $i8Array-global) @@ -2419,7 +2442,7 @@ BinaryenFeatureAll: 4194303 (i32.const 42) ) ) - (func $starter (type $6) + (func $starter (type $7) (nop) ) ) From ed04c5c06dab176d9c1b65ef0dcc8c3a1fbc94a8 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Apr 2025 17:51:19 -0700 Subject: [PATCH 428/622] Type RefNull as inexact (#7498) In preparation for disallowing inexact references to abstract heap types, which are no longer allowed by the Custom Descriptors proposal. --- scripts/test/fuzzing.py | 1 - src/ir/manipulation.h | 2 +- src/literal.h | 2 +- src/wasm-builder.h | 2 +- src/wasm/literal.cpp | 7 +- src/wasm/wasm-validator.cpp | 4 - test/lit/basic/reference-types.wast | 16 +- test/lit/exec/exact.wast | 21 --- test/lit/heap-types.wast | 4 +- test/lit/passes/cfp.wast | 4 +- test/lit/passes/code-pushing-gc.wast | 4 +- test/lit/passes/dae-gc-refine-params.wast | 2 +- test/lit/passes/dae-gc.wast | 2 +- test/lit/passes/flatten_all-features.wast | 6 +- test/lit/passes/global-refining.wast | 18 +- test/lit/passes/gufa-refs.wast | 52 ++--- test/lit/passes/gufa-vs-cfp.wast | 4 +- test/lit/passes/heap2local-rmw.wast | 38 ++-- test/lit/passes/heap2local.wast | 178 +++++++++--------- test/lit/passes/local-subtyping-nn.wast | 4 +- test/lit/passes/local-subtyping.wast | 2 +- test/lit/passes/merge-blocks.wast | 2 +- test/lit/passes/monomorphize-context.wast | 4 +- .../passes/optimize-instructions-exact.wast | 13 -- .../passes/optimize-instructions-gc-tnh.wast | 4 +- test/lit/passes/optimize-instructions-gc.wast | 4 +- .../lit/passes/optimize-instructions-mvp.wast | 16 +- test/lit/passes/precompute-gc.wast | 2 +- test/lit/passes/remove-unused-brs-gc.wast | 24 +-- test/lit/passes/signature-refining_gto.wat | 4 +- test/lit/passes/ssa.wast | 4 +- test/lit/passes/type-refining-gufa.wast | 4 +- .../passes/type-refining-isorecursive.wast | 6 +- test/lit/passes/type-refining-rmw.wast | 4 +- test/lit/passes/type-refining.wast | 14 +- test/passes/precompute_all-features.txt | 2 +- 36 files changed, 220 insertions(+), 260 deletions(-) delete mode 100644 test/lit/exec/exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index a65d3a92a21..19f3df3ef5e 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -118,7 +118,6 @@ 'remove-unused-types-exact.wast', 'coalesce-locals-exact.wast', 'remove-unused-brs-exact.wast', - 'exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/ir/manipulation.h b/src/ir/manipulation.h index c766fb8719e..1ad2b1161bb 100644 --- a/src/ir/manipulation.h +++ b/src/ir/manipulation.h @@ -42,7 +42,7 @@ template inline Nop* nop(InputType* target) { template inline RefNull* refNull(InputType* target, HeapType type) { auto* ret = convert(target); - ret->finalize(Type(type.getBottom(), Nullable, Exact)); + ret->finalize(Type(type.getBottom(), Nullable)); return ret; } diff --git a/src/literal.h b/src/literal.h index 4f9e3d535ad..c3cf2c15f70 100644 --- a/src/literal.h +++ b/src/literal.h @@ -246,7 +246,7 @@ class Literal { } } static Literal makeNull(HeapType type) { - return Literal(Type(type.getBottom(), Nullable, Exact)); + return Literal(Type(type.getBottom(), Nullable)); } static Literal makeFunc(Name func, HeapType type) { return Literal(func, type); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 04b9cb2a1ad..440ded4b678 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -670,7 +670,7 @@ class Builder { } RefNull* makeRefNull(HeapType type) { auto* ret = wasm.allocator.alloc(); - ret->finalize(Type(type.getBottom(), Nullable, Exact)); + ret->finalize(Type(type.getBottom(), Nullable)); return ret; } RefNull* makeRefNull(Type type) { diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index de95fe565bf..3a67526dad2 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -53,7 +53,7 @@ Literal::Literal(Type type) : type(type) { } if (type.isNull()) { - assert(type.isNullable()); + assert(type.isNullable() && !type.isExact()); new (&gcData) std::shared_ptr(); return; } @@ -72,9 +72,8 @@ Literal::Literal(const uint8_t init[16]) : type(Type::v128) { } Literal::Literal(std::shared_ptr gcData, HeapType type) - : gcData(gcData), - type(type, gcData ? NonNullable : Nullable, gcData ? Inexact : Exact) { - // TODO: Use exact types for more than just nulls. + : gcData(gcData), type(type, gcData ? NonNullable : Nullable) { + // TODO: Use exact types for gcData. // The type must be a proper type for GC data: either a struct, array, or // string; or an externalized version of the same; or a null; or an // internalized string (which appears as an anyref). diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 3b7e33390f3..e33d5c3ef0d 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2288,10 +2288,6 @@ void FunctionValidator::visitRefNull(RefNull* curr) { curr->type.isNullable(), curr, "ref.null types must be nullable")) { return; } - if (!shouldBeTrue( - curr->type.isExact(), curr, "ref.null types must be exact")) { - return; - } shouldBeTrue( curr->type.isNull(), curr, "ref.null must have a bottom heap type"); } diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 7c4f2a23e76..82b1f8da4e3 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -961,7 +961,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block2 (result eqref) - ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) + ;; CHECK-BIN-NEXT: (ref.cast nullref ;; CHECK-BIN-NEXT: (br_if $block2 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -987,7 +987,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block5 (result funcref) - ;; CHECK-BIN-NEXT: (ref.cast (exact nullfuncref) + ;; CHECK-BIN-NEXT: (ref.cast nullfuncref ;; CHECK-BIN-NEXT: (br_if $block5 ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1023,7 +1023,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block9 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) + ;; CHECK-BIN-NEXT: (ref.cast nullref ;; CHECK-BIN-NEXT: (br_if $block9 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1043,7 +1043,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block11 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) + ;; CHECK-BIN-NEXT: (ref.cast nullref ;; CHECK-BIN-NEXT: (br_if $block11 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -2298,7 +2298,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block2 (result eqref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block2 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2324,7 +2324,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block5 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullfuncref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullfuncref ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block5 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2360,7 +2360,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block9 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block9 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2380,7 +2380,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block11 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block11 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) diff --git a/test/lit/exec/exact.wast b/test/lit/exec/exact.wast deleted file mode 100644 index 2667e0e4dce..00000000000 --- a/test/lit/exec/exact.wast +++ /dev/null @@ -1,21 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. - -;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s - -(module - ;; CHECK: [fuzz-exec] calling convert-null-extern - ;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null - (func $convert-null-extern (export "convert-null-extern") (result (exact nullref)) - (local externref) - ;; The value produced by this cast must be exact to avoid triggering an - ;; assertion. - (ref.cast (exact nullref) - (any.convert_extern - (local.get 0) - ) - ) - ) -) -;; CHECK: [fuzz-exec] calling convert-null-extern -;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null -;; CHECK-NEXT: [fuzz-exec] comparing convert-null-extern diff --git a/test/lit/heap-types.wast b/test/lit/heap-types.wast index baf77196611..4613f4894fd 100644 --- a/test/lit/heap-types.wast +++ b/test/lit/heap-types.wast @@ -12,7 +12,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.test (ref exact none) + ;; CHECK-NEXT: (ref.test (ref none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -29,7 +29,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index ab9b7f8a745..0bc4372ec35 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -1034,7 +1034,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) @@ -1233,7 +1233,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) diff --git a/test/lit/passes/code-pushing-gc.wast b/test/lit/passes/code-pushing-gc.wast index fb9ff12f7da..1aac5c55cf7 100644 --- a/test/lit/passes/code-pushing-gc.wast +++ b/test/lit/passes/code-pushing-gc.wast @@ -7,7 +7,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $out (result (ref func)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) + ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -48,7 +48,7 @@ ;; CHECK-NEXT: (ref.func $br_on_no) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) + ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index a93f3e7347f..8cefbe88184 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -262,7 +262,7 @@ ) ;; This function is called in ways that allow us to make the first parameter ;; non-nullable. - ;; CHECK: (func $various-params-null (type $13) (param $x (ref exact none)) (param $y (ref null $"{i32}")) + ;; CHECK: (func $various-params-null (type $13) (param $x (ref none)) (param $y (ref null $"{i32}")) ;; CHECK-NEXT: (local $temp i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 015e515c02b..1f39567d146 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -110,7 +110,7 @@ ) ;; CHECK: (func $bar (type $2) (param $0 i31ref) - ;; CHECK-NEXT: (local $1 (exact nullref)) + ;; CHECK-NEXT: (local $1 nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/flatten_all-features.wast b/test/lit/passes/flatten_all-features.wast index 6c117d0a907..b601b8a1295 100644 --- a/test/lit/passes/flatten_all-features.wast +++ b/test/lit/passes/flatten_all-features.wast @@ -3581,8 +3581,8 @@ ;; CHECK: (func $subtype (type $7) (result anyref) ;; CHECK-NEXT: (local $0 eqref) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (exact nullref)) - ;; CHECK-NEXT: (local $3 (exact nullref)) + ;; CHECK-NEXT: (local $2 nullref) + ;; CHECK-NEXT: (local $3 nullref) ;; CHECK-NEXT: (local $4 eqref) ;; CHECK-NEXT: (local $5 eqref) ;; CHECK-NEXT: (local $6 eqref) @@ -3680,7 +3680,7 @@ ;; CHECK: (type $0 (func (result funcref))) ;; CHECK: (func $0 (type $0) (result funcref) - ;; CHECK-NEXT: (local $0 (ref exact nofunc)) + ;; CHECK-NEXT: (local $0 (ref nofunc)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index 7be5a9e4e2a..927dfd1f26c 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -11,8 +11,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) @@ -32,8 +32,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) @@ -202,16 +202,16 @@ ;; (ref null func) to nullfuncref only when not exported, and if exported, then ;; only when immutable in open world. (module - ;; CHECK: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) - ;; CLOSD: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CHECK: (global $mut (mut nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $mut (mut nullfuncref) (ref.null nofunc)) (global $mut (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm (exact nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $imm (exact nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $imm nullfuncref (ref.null nofunc)) + ;; CLOSD: (global $imm nullfuncref (ref.null nofunc)) (global $imm (ref null func) (ref.null nofunc)) ;; CHECK: (global $mut-exp (mut funcref) (ref.null nofunc)) ;; CLOSD: (global $mut-exp (mut funcref) (ref.null nofunc)) (global $mut-exp (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm-exp (exact nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $imm-exp nullfuncref (ref.null nofunc)) ;; CLOSD: (global $imm-exp funcref (ref.null nofunc)) (global $imm-exp (ref null func) (ref.null nofunc)) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 6142550638d..6d48d651a5f 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -955,7 +955,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 1 ;; CHECK-NEXT: (local.get $child) @@ -970,7 +970,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) @@ -1072,9 +1072,9 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (exact nullref)) + ;; CHECK-NEXT: (block $block (result nullref) ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1085,9 +1085,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block0 (result (exact nullref)) + ;; CHECK-NEXT: (block $block0 (result nullref) ;; CHECK-NEXT: (br $block0 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1098,13 +1098,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block1 (result (exact nullref)) + ;; CHECK-NEXT: (block $block1 (result nullref) ;; CHECK-NEXT: (br $block1 - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1237,7 +1237,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) @@ -1577,7 +1577,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $null ;; CHECK-NEXT: (array.new_default $null @@ -1775,10 +1775,10 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $x - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $storage - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $pass-through ;; CHECK-NEXT: (ref.null none) @@ -1928,7 +1928,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2153,7 +2153,7 @@ ;; CHECK-NEXT: (pop (tuple anyref anyref)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2213,7 +2213,7 @@ ;; CHECK: (func $func (type $1) (result (ref $"{}")) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (ref exact none)) + ;; CHECK-NEXT: (block $block (result (ref none)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2539,10 +2539,10 @@ ;; CHECK: (func $test-nulls (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -2554,9 +2554,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (select (result i31ref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.i31 @@ -3256,7 +3256,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -3793,7 +3793,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $chars ;; CHECK-NEXT: (local.get $chars) @@ -6056,7 +6056,7 @@ ) (module - ;; CHECK: (type $0 (func (result i64 (exact nullref) i32))) + ;; CHECK: (type $0 (func (result i64 nullref i32))) ;; CHECK: (type $array (sub (array (mut i8)))) (type $array (sub (array (mut i8)))) @@ -6096,7 +6096,7 @@ ;; CHECK: (func $loop-tuple-br_on (type $2) ;; CHECK-NEXT: (tuple.drop 3 - ;; CHECK-NEXT: (loop $loop (type $0) (result i64 (exact nullref) i32) + ;; CHECK-NEXT: (loop $loop (type $0) (result i64 nullref i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index eb26820b74d..bd731d89150 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -1087,7 +1087,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct3 2 ;; CHECK-NEXT: (local.get $ref) @@ -1329,7 +1329,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $create3) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local-rmw.wast b/test/lit/passes/heap2local-rmw.wast index f0b397dcc99..33fef1cb506 100644 --- a/test/lit/passes/heap2local-rmw.wast +++ b/test/lit/passes/heap2local-rmw.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -107,7 +107,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -140,7 +140,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -173,7 +173,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -206,7 +206,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -239,7 +239,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -270,7 +270,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -312,7 +312,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -345,7 +345,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -378,7 +378,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -411,7 +411,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -444,7 +444,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -477,7 +477,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -508,7 +508,7 @@ ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -550,7 +550,7 @@ ;; CHECK-NEXT: (local $2 (ref null $struct)) ;; CHECK-NEXT: (local $3 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -581,7 +581,7 @@ ;; CHECK-NEXT: (local $4 (ref null $struct)) ;; CHECK-NEXT: (local $5 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -623,7 +623,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -657,7 +657,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 8b0384671c3..619a33a23c5 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -102,7 +102,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -135,7 +135,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -162,7 +162,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -193,7 +193,7 @@ ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) @@ -259,7 +259,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -318,7 +318,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -388,7 +388,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) @@ -418,7 +418,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -456,7 +456,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -494,7 +494,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -559,7 +559,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -673,7 +673,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -713,7 +713,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -771,7 +771,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -821,7 +821,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -901,7 +901,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -927,7 +927,7 @@ ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -975,7 +975,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (local $2 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new_default $struct.recursive) ;; CHECK-NEXT: ) @@ -1070,7 +1070,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) @@ -1104,7 +1104,7 @@ ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (loop $outer ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -1271,7 +1271,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1287,7 +1287,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1303,7 +1303,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1343,7 +1343,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1362,7 +1362,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1411,7 +1411,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1422,7 +1422,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1553,7 +1553,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1749,7 +1749,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1800,7 +1800,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1851,7 +1851,7 @@ ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $block - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) @@ -1905,7 +1905,7 @@ ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1938,7 +1938,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1977,7 +1977,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2061,7 +2061,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2096,7 +2096,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2138,7 +2138,7 @@ ;; CHECK-NEXT: (local.get $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2174,7 +2174,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2216,7 +2216,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2253,7 +2253,7 @@ ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2287,7 +2287,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2334,7 +2334,7 @@ ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (local $7 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2351,7 +2351,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2396,7 +2396,7 @@ ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2450,7 +2450,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2472,7 +2472,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2494,7 +2494,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) @@ -2560,7 +2560,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2582,7 +2582,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2648,7 +2648,7 @@ ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2697,7 +2697,7 @@ ;; CHECK-NEXT: (local $2 (ref $A)) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2736,7 +2736,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2776,7 +2776,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2819,9 +2819,9 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) - ;; CHECK-NEXT: (block (result (exact nullref)) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2870,7 +2870,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2907,7 +2907,7 @@ ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -3005,7 +3005,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3151,7 +3151,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3197,7 +3197,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) @@ -3327,11 +3327,11 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3391,7 +3391,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -3500,7 +3500,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3512,7 +3512,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3533,7 +3533,7 @@ ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $24 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) @@ -3706,7 +3706,7 @@ ;; CHECK: (func $array.nested.refinalize.get (type $3) (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3726,7 +3726,7 @@ ;; CHECK: (func $array.nested.refinalize.set (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3756,7 +3756,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3849,7 +3849,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3867,7 +3867,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3916,7 +3916,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3983,7 +3983,7 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4030,7 +4030,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4070,7 +4070,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -4173,7 +4173,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4253,7 +4253,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4319,7 +4319,7 @@ ;; CHECK: (func $array.cast.struct (type $0) (result (ref struct)) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4341,7 +4341,7 @@ ;; CHECK: (func $array.cast.struct.null (type $3) (result structref) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4380,7 +4380,7 @@ ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (local $array (ref array)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4405,7 +4405,7 @@ ;; CHECK-NEXT: (local.tee $struct ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4442,7 +4442,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result structref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -4513,7 +4513,7 @@ ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) @@ -4565,7 +4565,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4620,7 +4620,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null exact (shared none))) + ;; CHECK-NEXT: (block (result (ref null (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4666,7 +4666,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null exact (shared none))) + ;; CHECK-NEXT: (block (result (ref null (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/local-subtyping-nn.wast b/test/lit/passes/local-subtyping-nn.wast index f2237156f01..3754230d82f 100644 --- a/test/lit/passes/local-subtyping-nn.wast +++ b/test/lit/passes/local-subtyping-nn.wast @@ -8,7 +8,7 @@ (import "out" "i32" (func $i32 (result i32))) ;; CHECK: (func $non-nullable (type $1) - ;; CHECK-NEXT: (local $x (ref exact none)) + ;; CHECK-NEXT: (local $x (ref none)) ;; CHECK-NEXT: (local $y (ref $0)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.as_non_null @@ -41,7 +41,7 @@ ) ;; CHECK: (func $uses-default (type $2) (param $i i32) - ;; CHECK-NEXT: (local $x (exact nullref)) + ;; CHECK-NEXT: (local $x nullref) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $i) ;; CHECK-NEXT: (then diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 777df93524f..795a0a01cd1 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -273,7 +273,7 @@ ) ;; CHECK: (func $multiple-iterations-refinalize-call-ref-bottom (type $0) - ;; CHECK-NEXT: (local $f (exact nullfuncref)) + ;; CHECK-NEXT: (local $f nullfuncref) ;; CHECK-NEXT: (local $x (ref none)) ;; CHECK-NEXT: (local.set $f ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 7a517ccbc27..86181f7a488 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -20,7 +20,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $label$1 (result i31ref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label$1 (exact nullref) (ref exact none) + ;; CHECK-NEXT: (br_on_cast $label$1 nullref (ref none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/monomorphize-context.wast b/test/lit/passes/monomorphize-context.wast index 92b483a7ae1..7f9cb2f566e 100644 --- a/test/lit/passes/monomorphize-context.wast +++ b/test/lit/passes/monomorphize-context.wast @@ -277,7 +277,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast (exact nullref) +;; ALWAYS-NEXT: (ref.cast nullref ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -555,7 +555,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast (exact nullref) +;; ALWAYS-NEXT: (ref.cast nullref ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast index 61769b78f34..95125305984 100644 --- a/test/lit/passes/optimize-instructions-exact.wast +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -17,17 +17,4 @@ (local.get 0) ) ) - ;; CHECK: (func $cast-null-to-exact-none (type $1) (result (exact nullref)) - ;; CHECK-NEXT: (local $0 nullref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - (func $cast-null-to-exact-none (result (exact nullref)) - (local nullref) - (ref.cast (exact nullref) - (local.get 0) - ) - ) ) diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index 3893e048e14..c930f2fc19a 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -509,7 +509,7 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: (block ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result (exact nullref)) + ;; TNH-NEXT: (block (result nullref) ;; TNH-NEXT: (drop ;; TNH-NEXT: (call $get-i32) ;; TNH-NEXT: ) @@ -532,7 +532,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (block ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result (exact nullref)) + ;; NO_TNH-NEXT: (block (result nullref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (call $get-i32) ;; NO_TNH-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 86804d7ddbd..4b88507ffc7 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -1570,7 +1570,7 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $a ;; CHECK-NEXT: (ref.null none) @@ -1590,7 +1590,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.cast nullref diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index fa1060edfcc..9f505a7586f 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -11131,13 +11131,13 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.load - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -11319,7 +11319,7 @@ (i32.const -1) )) (drop (i32.le_u - (i32.load + (i32.load (i32.const 0) ) (i32.const -1) @@ -11329,7 +11329,7 @@ (i64.const -1) )) (drop (i64.le_u - (i64.load + (i64.load (i32.const 0) ) (i64.const -1) diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast index 94adaa53fbb..fc4cc7c2ee9 100644 --- a/test/lit/passes/precompute-gc.wast +++ b/test/lit/passes/precompute-gc.wast @@ -28,7 +28,7 @@ ;; CHECK: (func $test-fallthrough (type $func-return-i32) (result i32) ;; CHECK-NEXT: (local $x funcref) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (block (result (exact nullfuncref)) + ;; CHECK-NEXT: (block (result nullfuncref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $test-fallthrough) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 64f11f8ca0b..268bbde2092 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -63,7 +63,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -119,7 +119,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.cast (ref null $struct) ;; CHECK-NEXT: (local.tee $any @@ -438,7 +438,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $nullable-struct2) ;; CHECK-NEXT: ) @@ -509,7 +509,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (local.get $nullable-struct2) @@ -571,7 +571,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (local.get $nullable-struct2) @@ -706,7 +706,7 @@ ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (ref.test (ref exact none) + ;; CHECK-NEXT: (ref.test (ref none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -716,13 +716,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result (exact nullref)) + ;; CHECK-NEXT: (if (result nullref) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -734,7 +734,7 @@ ;; CHECK-NEXT: (then ;; CHECK-NEXT: (block $something (result (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (br_on_non_null $something ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -750,8 +750,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result (exact nullref)) - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (select (result nullref) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (block $nothing ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -858,7 +858,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result (exact nullref)) + ;; CHECK-NEXT: (select (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat index 1eedcd4f67c..c69eeb24455 100644 --- a/test/lit/passes/signature-refining_gto.wat +++ b/test/lit/passes/signature-refining_gto.wat @@ -9,11 +9,11 @@ ;; CHECK-NOT: (type $A (type $A (struct (field (mut (ref null $A))))) - ;; CHECK: (type $0 (func (param (ref exact none)))) + ;; CHECK: (type $0 (func (param (ref none)))) ;; CHECK: (type $1 (func (param funcref i32))) - ;; CHECK: (func $struct.get (type $0) (param $0 (ref exact none)) + ;; CHECK: (func $struct.get (type $0) (param $0 (ref none)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast index a0b18b0777d..0d696f75a0d 100644 --- a/test/lit/passes/ssa.wast +++ b/test/lit/passes/ssa.wast @@ -36,9 +36,9 @@ ;; CHECK: (func $refine-to-null (type $3) (result (ref $A)) ;; CHECK-NEXT: (local $0 (ref null $A)) - ;; CHECK-NEXT: (block $label (result (ref exact none)) + ;; CHECK-NEXT: (block $label (result (ref none)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label (exact nullref) (ref exact none) + ;; CHECK-NEXT: (br_on_cast $label nullref (ref none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index 2304086078c..c866d80dca2 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -28,7 +28,7 @@ ;; NRML: (rec ;; NRML-NEXT: (type $A (sub (struct (field (mut nullref))))) ;; GUFA: (rec - ;; GUFA-NEXT: (type $A (sub (struct (field (mut (exact nullref)))))) + ;; GUFA-NEXT: (type $A (sub (struct (field (mut nullref))))) ;; O3O3: (rec ;; O3O3-NEXT: (type $A (sub (struct))) (type $A (sub (struct (field (mut anyref))))) @@ -376,7 +376,7 @@ ;; the field to nullref. (module ;; NRML: (type $struct (struct (field nullfuncref))) - ;; GUFA: (type $struct (struct (field (exact nullfuncref)))) + ;; GUFA: (type $struct (struct (field nullfuncref))) (type $struct (struct (field funcref))) ;; NRML: (global $C (ref $struct) (struct.new_default $struct)) diff --git a/test/lit/passes/type-refining-isorecursive.wast b/test/lit/passes/type-refining-isorecursive.wast index 025b8cec9f5..537f99e668e 100644 --- a/test/lit/passes/type-refining-isorecursive.wast +++ b/test/lit/passes/type-refining-isorecursive.wast @@ -5,11 +5,11 @@ ;; The types should be refined to a set of three mutually recursive types. ;; CHECK: (rec - ;; CHECK-NEXT: (type $2 (sub (struct (field (exact nullexternref)) (field (ref $0))))) + ;; CHECK-NEXT: (type $2 (sub (struct (field nullexternref) (field (ref $0))))) - ;; CHECK: (type $1 (sub (struct (field (exact nullfuncref)) (field (ref $2))))) + ;; CHECK: (type $1 (sub (struct (field nullfuncref) (field (ref $2))))) - ;; CHECK: (type $0 (sub (struct (field (exact nullref)) (field (ref $1))))) + ;; CHECK: (type $0 (sub (struct (field nullref) (field (ref $1))))) (type $0 (sub (struct nullref anyref))) (type $1 (sub (struct nullfuncref anyref))) (type $2 (sub (struct nullexternref anyref))) diff --git a/test/lit/passes/type-refining-rmw.wast b/test/lit/passes/type-refining-rmw.wast index 7739fceb0f4..a4bb0a85cae 100644 --- a/test/lit/passes/type-refining-rmw.wast +++ b/test/lit/passes/type-refining-rmw.wast @@ -6,7 +6,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared any))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) @@ -75,7 +75,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared eq))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 02357e8b6c0..622e7422011 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1101,7 +1101,7 @@ ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1180,7 +1180,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut (exact nullref))))) + ;; CHECK-NEXT: (type $A (struct (field (mut nullref)))) (type $A (struct (field (mut anyref)))) ;; CHECK: (type $B (struct (field (mut nullref)))) (type $B (struct (field (mut (ref null $A))))) @@ -1234,7 +1234,7 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut (ref exact noextern))))) + ;; CHECK-NEXT: (type $A (struct (field (mut (ref noextern))))) (type $A (struct (field (mut externref)))) ;; CHECK: (type $1 (func)) @@ -1252,7 +1252,7 @@ ;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref) ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.cast (ref exact noextern) + ;; CHECK-NEXT: (ref.cast (ref noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1304,7 +1304,7 @@ ;; CHECK: (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: (ref.cast (ref exact noextern) + ;; CHECK-NEXT: (ref.cast (ref noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1581,7 +1581,7 @@ (type $never (sub (struct (field i32)))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $optimizable (struct (field (mut (exact nullfuncref))))) + ;; CHECK-NEXT: (type $optimizable (struct (field (mut nullfuncref)))) (type $optimizable (struct (field (mut (ref null func))))) ;; CHECK: (type $2 (func)) @@ -1604,7 +1604,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref exact none)) + ;; CHECK-NEXT: (block (result (ref none)) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index 1372035920f..189adadcec1 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -286,7 +286,7 @@ ) ) (drop - (block $l2 (result (exact nullexternref)) + (block $l2 (result nullexternref) (drop (block $l3 (global.set $global-mut From ac98ba6eba30ac5ff3ec258df43a16115adb8dab Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Apr 2025 18:58:12 -0700 Subject: [PATCH 429/622] Update exact reference semantics (#7499) Disallow exact references to abstract heap types and update subtyping to match the current proposed semantics. --- src/wasm-builder.h | 6 - src/wasm-type.h | 1 + src/wasm/wasm-type.cpp | 28 +- src/wasm/wasm.cpp | 2 +- test/gtest/type-builder.cpp | 270 ++++++------ test/lit/basic/exact-references.wast | 410 ++++++++---------- test/lit/passes/coalesce-locals-exact.wast | 10 +- test/lit/passes/local-subtyping-exact.wast | 13 +- .../passes/optimize-instructions-exact.wast | 10 +- test/lit/passes/remove-unused-brs-exact.wast | 16 +- .../lit/passes/remove-unused-types-exact.wast | 9 +- .../passes/string-lowering-instructions.wast | 2 +- 12 files changed, 364 insertions(+), 413 deletions(-) diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 440ded4b678..ee313a3341a 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -673,12 +673,6 @@ class Builder { ret->finalize(Type(type.getBottom(), Nullable)); return ret; } - RefNull* makeRefNull(Type type) { - assert(type.isNullable() && type.isNull() && type.isExact()); - auto* ret = wasm.allocator.alloc(); - ret->finalize(type); - return ret; - } RefIsNull* makeRefIsNull(Expression* value) { auto* ret = wasm.allocator.alloc(); ret->value = value; diff --git a/src/wasm-type.h b/src/wasm-type.h index b250e38c9c8..8fe6f3490ea 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -323,6 +323,7 @@ class Type { : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | (exact == Exact ? ExactMask : 0)) { assert(!(heapType.getID() & (TupleMask | NullMask | ExactMask))); + assert(!heapType.isBasic() || exact == Inexact); } // Predicates diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index ada53eb150b..ee7301c3032 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -795,13 +795,14 @@ Type Type::getLeastUpperBound(Type a, Type b) { if (auto heapType = HeapType::getLeastUpperBound(heapTypeA, heapTypeB)) { auto nullability = (a.isNullable() || b.isNullable()) ? Nullable : NonNullable; - auto exactness = (a.isInexact() || b.isInexact()) ? Inexact : Exact; - // The LUB can only be exact if the heap types are the same or one of them - // is bottom. - if (heapTypeA != heapTypeB && !heapTypeA.isBottom() && - !heapTypeB.isBottom()) { - exactness = Inexact; - } + // If one heap type is bottom, then the exactness comes from the other + // heap type. Otherwise, the LUB can only be exact if both of the inputs + // are the same exact heap type. + auto exactness = heapTypeA.isBottom() ? b.getExactness() + : heapTypeB.isBottom() ? a.getExactness() + : (a.isInexact() || b.isInexact()) ? Inexact + : (heapTypeA != heapTypeB) ? Inexact + : Exact; return Type(*heapType, nullability, exactness); } } @@ -850,6 +851,7 @@ Type Type::getGreatestLowerBound(Type a, Type b) { if ((a.isExact() && heapType != heapA) || (b.isExact() && heapType != heapB)) { heapType = heapA.getBottom(); + exactness = Inexact; } return Type(heapType, nullability, exactness); } @@ -1515,13 +1517,15 @@ bool SubTyper::isSubType(Type a, Type b) { if (a.isNullable() && !b.isNullable()) { return false; } - if (a.isInexact() && !b.isInexact()) { - return false; - } auto heapTypeA = a.getHeapType(); auto heapTypeB = b.getHeapType(); - if (b.isExact() && !heapTypeA.isBottom()) { - return heapTypeA == heapTypeB; + if (b.isExact()) { + if (a.isExact()) { + return heapTypeA == heapTypeB; + } + if (!heapTypeA.isBottom()) { + return false; + } } return isSubType(heapTypeA, heapTypeB); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 6c5694c6848..3f885cbccdf 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -800,7 +800,7 @@ void MemoryGrow::finalize() { void RefNull::finalize(HeapType heapType) { assert(heapType.isBottom()); - type = Type(heapType, Nullable, Exact); + type = Type(heapType, Nullable); } void RefNull::finalize(Type type_) { type = type_; } diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 0f8463d55da..a11af0e6b33 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -1173,26 +1173,36 @@ FUZZ_TEST(TypeFuzzTest, TestHeapTypeRelationsFuzz) #endif // FUZZTEST TEST_F(TypeTest, TestTypeRelations) { + HeapType definedSuper, definedSub; + { + TypeBuilder builder(2); + builder[0].setOpen() = Struct(); + builder[1] = Struct(); + builder[1].subTypeOf(builder[0]); + auto built = builder.build(); + ASSERT_TRUE(built); + definedSuper = (*built)[0]; + definedSub = (*built)[1]; + } + Type any = Type(HeapType::any, NonNullable, Inexact); Type nullAny = Type(HeapType::any, Nullable, Inexact); - Type exactAny = Type(HeapType::any, NonNullable, Exact); - Type nullExactAny = Type(HeapType::any, Nullable, Exact); - HeapType defined = Struct(); - Type def = Type(defined, NonNullable, Inexact); - Type nullDef = Type(defined, Nullable, Inexact); - Type exactDef = Type(defined, NonNullable, Exact); - Type nullExactDef = Type(defined, Nullable, Exact); + Type sup = Type(definedSuper, NonNullable, Inexact); + Type nullSup = Type(definedSuper, Nullable, Inexact); + Type exactSup = Type(definedSuper, NonNullable, Exact); + Type nullExactSup = Type(definedSuper, Nullable, Exact); + + Type sub = Type(definedSub, NonNullable, Inexact); + Type nullSub = Type(definedSub, Nullable, Inexact); + Type exactSub = Type(definedSub, NonNullable, Exact); + Type nullExactSub = Type(definedSub, Nullable, Exact); Type none = Type(HeapType::none, NonNullable, Inexact); Type nullNone = Type(HeapType::none, Nullable, Inexact); - Type exactNone = Type(HeapType::none, NonNullable, Exact); - Type nullExactNone = Type(HeapType::none, Nullable, Exact); Type func = Type(HeapType::func, NonNullable, Inexact); Type nullFunc = Type(HeapType::func, Nullable, Inexact); - Type exactFunc = Type(HeapType::func, NonNullable, Exact); - Type nullExactFunc = Type(HeapType::func, Nullable, Exact); Type i32 = Type::i32; Type unreachable = Type::unreachable; @@ -1251,165 +1261,141 @@ TEST_F(TypeTest, TestTypeRelations) { assertLUB(any, any, any, any); assertLUB(any, nullAny, nullAny, any); - assertLUB(any, exactAny, any, exactAny); - assertLUB(any, nullExactAny, nullAny, exactAny); - assertLUB(any, def, any, def); - assertLUB(any, nullDef, nullAny, def); - assertLUB(any, exactDef, any, exactDef); - assertLUB(any, nullExactDef, nullAny, exactDef); + assertLUB(any, sup, any, sup); + assertLUB(any, nullSup, nullAny, sup); + assertLUB(any, exactSup, any, exactSup); + assertLUB(any, nullExactSup, nullAny, exactSup); + assertLUB(any, sub, any, sub); + assertLUB(any, nullSub, nullAny, sub); + assertLUB(any, exactSub, any, exactSub); + assertLUB(any, nullExactSub, nullAny, exactSub); assertLUB(any, none, any, none); assertLUB(any, nullNone, nullAny, none); - assertLUB(any, exactNone, any, exactNone); - assertLUB(any, nullExactNone, nullAny, exactNone); assertLUB(any, func, Type(Type::none), unreachable); assertLUB(any, nullFunc, Type(Type::none), unreachable); - assertLUB(any, exactFunc, Type(Type::none), unreachable); - assertLUB(any, nullExactFunc, Type(Type::none), unreachable); assertLUB(any, i32, Type(Type::none), unreachable); assertLUB(any, unreachable, any, unreachable); assertLUB(nullAny, nullAny, nullAny, nullAny); - assertLUB(nullAny, exactAny, nullAny, exactAny); - assertLUB(nullAny, nullExactAny, nullAny, nullExactAny); - assertLUB(nullAny, def, nullAny, def); - assertLUB(nullAny, nullDef, nullAny, nullDef); - assertLUB(nullAny, exactDef, nullAny, exactDef); - assertLUB(nullAny, nullExactDef, nullAny, nullExactDef); + assertLUB(nullAny, sup, nullAny, sup); + assertLUB(nullAny, nullSup, nullAny, nullSup); + assertLUB(nullAny, exactSup, nullAny, exactSup); + assertLUB(nullAny, nullExactSup, nullAny, nullExactSup); + assertLUB(nullAny, sub, nullAny, sub); + assertLUB(nullAny, nullSub, nullAny, nullSub); + assertLUB(nullAny, exactSub, nullAny, exactSub); + assertLUB(nullAny, nullExactSub, nullAny, nullExactSub); assertLUB(nullAny, none, nullAny, none); assertLUB(nullAny, nullNone, nullAny, nullNone); - assertLUB(nullAny, exactNone, nullAny, exactNone); - assertLUB(nullAny, nullExactNone, nullAny, nullExactNone); assertLUB(nullAny, func, Type(Type::none), unreachable); assertLUB(nullAny, nullFunc, Type(Type::none), unreachable); - assertLUB(nullAny, exactFunc, Type(Type::none), unreachable); - assertLUB(nullAny, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullAny, i32, Type(Type::none), unreachable); assertLUB(nullAny, unreachable, nullAny, unreachable); - assertLUB(exactAny, exactAny, exactAny, exactAny); - assertLUB(exactAny, nullExactAny, nullExactAny, exactAny); - assertLUB(exactAny, def, any, exactNone); - assertLUB(exactAny, nullDef, nullAny, exactNone); - assertLUB(exactAny, exactDef, any, exactNone); - assertLUB(exactAny, nullExactDef, nullAny, exactNone); - assertLUB(exactAny, none, any, exactNone); - assertLUB(exactAny, nullNone, nullAny, exactNone); - assertLUB(exactAny, exactNone, exactAny, exactNone); - assertLUB(exactAny, nullExactNone, nullExactAny, exactNone); - assertLUB(exactAny, func, Type(Type::none), unreachable); - assertLUB(exactAny, nullFunc, Type(Type::none), unreachable); - assertLUB(exactAny, exactFunc, Type(Type::none), unreachable); - assertLUB(exactAny, nullExactFunc, Type(Type::none), unreachable); - assertLUB(exactAny, i32, Type(Type::none), unreachable); - assertLUB(exactAny, unreachable, exactAny, unreachable); - - assertLUB(nullExactAny, nullExactAny, nullExactAny, nullExactAny); - assertLUB(nullExactAny, def, nullAny, exactNone); - assertLUB(nullExactAny, nullDef, nullAny, nullExactNone); - assertLUB(nullExactAny, exactDef, nullAny, exactNone); - assertLUB(nullExactAny, nullExactDef, nullAny, nullExactNone); - assertLUB(nullExactAny, none, nullAny, exactNone); - assertLUB(nullExactAny, nullNone, nullAny, nullExactNone); - assertLUB(nullExactAny, exactNone, nullExactAny, exactNone); - assertLUB(nullExactAny, nullExactNone, nullExactAny, nullExactNone); - assertLUB(nullExactAny, func, Type(Type::none), unreachable); - assertLUB(nullExactAny, nullFunc, Type(Type::none), unreachable); - assertLUB(nullExactAny, exactFunc, Type(Type::none), unreachable); - assertLUB(nullExactAny, nullExactFunc, Type(Type::none), unreachable); - assertLUB(nullExactAny, i32, Type(Type::none), unreachable); - assertLUB(nullExactAny, unreachable, nullExactAny, unreachable); - - assertLUB(def, def, def, def); - assertLUB(def, nullDef, nullDef, def); - assertLUB(def, exactDef, def, exactDef); - assertLUB(def, nullExactDef, nullDef, exactDef); - assertLUB(def, none, def, none); - assertLUB(def, nullNone, nullDef, none); - assertLUB(def, exactNone, def, exactNone); - assertLUB(def, nullExactNone, nullDef, exactNone); - assertLUB(def, func, Type(Type::none), unreachable); - assertLUB(def, nullFunc, Type(Type::none), unreachable); - assertLUB(def, exactFunc, Type(Type::none), unreachable); - assertLUB(def, nullExactFunc, Type(Type::none), unreachable); - assertLUB(def, i32, Type(Type::none), unreachable); - assertLUB(def, unreachable, def, unreachable); - - assertLUB(nullDef, nullDef, nullDef, nullDef); - assertLUB(nullDef, exactDef, nullDef, exactDef); - assertLUB(nullDef, nullExactDef, nullDef, nullExactDef); - assertLUB(nullDef, none, nullDef, none); - assertLUB(nullDef, nullNone, nullDef, nullNone); - assertLUB(nullDef, exactNone, nullDef, exactNone); - assertLUB(nullDef, nullExactNone, nullDef, nullExactNone); - assertLUB(nullDef, func, Type(Type::none), unreachable); - assertLUB(nullDef, nullFunc, Type(Type::none), unreachable); - assertLUB(nullDef, exactFunc, Type(Type::none), unreachable); - assertLUB(nullDef, nullExactFunc, Type(Type::none), unreachable); - assertLUB(nullDef, i32, Type(Type::none), unreachable); - assertLUB(nullDef, unreachable, nullDef, unreachable); - - assertLUB(exactDef, exactDef, exactDef, exactDef); - assertLUB(exactDef, nullExactDef, nullExactDef, exactDef); - assertLUB(exactDef, none, def, exactNone); - assertLUB(exactDef, nullNone, nullDef, exactNone); - assertLUB(exactDef, exactNone, exactDef, exactNone); - assertLUB(exactDef, nullExactNone, nullExactDef, exactNone); - assertLUB(exactDef, func, Type(Type::none), unreachable); - assertLUB(exactDef, nullFunc, Type(Type::none), unreachable); - assertLUB(exactDef, exactFunc, Type(Type::none), unreachable); - assertLUB(exactDef, nullExactFunc, Type(Type::none), unreachable); - assertLUB(exactDef, i32, Type(Type::none), unreachable); - assertLUB(exactDef, unreachable, exactDef, unreachable); - - assertLUB(nullExactDef, nullExactDef, nullExactDef, nullExactDef); - assertLUB(nullExactDef, none, nullDef, exactNone); - assertLUB(nullExactDef, nullNone, nullDef, nullExactNone); - assertLUB(nullExactDef, exactNone, nullExactDef, exactNone); - assertLUB(nullExactDef, nullExactNone, nullExactDef, nullExactNone); - assertLUB(nullExactDef, func, Type(Type::none), unreachable); - assertLUB(nullExactDef, nullFunc, Type(Type::none), unreachable); - assertLUB(nullExactDef, exactFunc, Type(Type::none), unreachable); - assertLUB(nullExactDef, nullExactFunc, Type(Type::none), unreachable); - assertLUB(nullExactDef, i32, Type(Type::none), unreachable); - assertLUB(nullExactDef, unreachable, nullExactDef, unreachable); + assertLUB(sup, sup, sup, sup); + assertLUB(sup, nullSup, nullSup, sup); + assertLUB(sup, exactSup, sup, exactSup); + assertLUB(sup, nullExactSup, nullSup, exactSup); + assertLUB(sup, sub, sup, sub); + assertLUB(sup, nullSub, nullSup, sub); + assertLUB(sup, exactSub, sup, exactSub); + assertLUB(sup, nullExactSub, nullSup, exactSub); + assertLUB(sup, none, sup, none); + assertLUB(sup, nullNone, nullSup, none); + assertLUB(sup, func, Type(Type::none), unreachable); + assertLUB(sup, nullFunc, Type(Type::none), unreachable); + assertLUB(sup, i32, Type(Type::none), unreachable); + assertLUB(sup, unreachable, sup, unreachable); + + assertLUB(nullSup, nullSup, nullSup, nullSup); + assertLUB(nullSup, exactSup, nullSup, exactSup); + assertLUB(nullSup, nullExactSup, nullSup, nullExactSup); + assertLUB(nullSup, sub, nullSup, sub); + assertLUB(nullSup, nullSub, nullSup, nullSub); + assertLUB(nullSup, exactSub, nullSup, exactSub); + assertLUB(nullSup, nullExactSub, nullSup, nullExactSub); + assertLUB(nullSup, none, nullSup, none); + assertLUB(nullSup, nullNone, nullSup, nullNone); + assertLUB(nullSup, func, Type(Type::none), unreachable); + assertLUB(nullSup, nullFunc, Type(Type::none), unreachable); + assertLUB(nullSup, i32, Type(Type::none), unreachable); + assertLUB(nullSup, unreachable, nullSup, unreachable); + + assertLUB(exactSup, exactSup, exactSup, exactSup); + assertLUB(exactSup, nullExactSup, nullExactSup, exactSup); + assertLUB(exactSup, sub, sup, none); + assertLUB(exactSup, nullSub, nullSup, none); + assertLUB(exactSup, exactSub, sup, none); + assertLUB(exactSup, nullExactSub, nullSup, none); + assertLUB(exactSup, none, exactSup, none); + assertLUB(exactSup, nullNone, nullExactSup, none); + assertLUB(exactSup, func, Type(Type::none), unreachable); + assertLUB(exactSup, nullFunc, Type(Type::none), unreachable); + assertLUB(exactSup, i32, Type(Type::none), unreachable); + assertLUB(exactSup, unreachable, exactSup, unreachable); + + assertLUB(nullExactSup, nullExactSup, nullExactSup, nullExactSup); + assertLUB(nullExactSup, sub, nullSup, none); + assertLUB(nullExactSup, nullSub, nullSup, nullNone); + assertLUB(nullExactSup, exactSub, nullSup, none); + assertLUB(nullExactSup, nullExactSub, nullSup, nullNone); + assertLUB(nullExactSup, none, nullExactSup, none); + assertLUB(nullExactSup, nullNone, nullExactSup, nullNone); + assertLUB(nullExactSup, func, Type(Type::none), unreachable); + assertLUB(nullExactSup, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactSup, i32, Type(Type::none), unreachable); + assertLUB(nullExactSup, unreachable, nullExactSup, unreachable); + + assertLUB(sub, sub, sub, sub); + assertLUB(sub, nullSub, nullSub, sub); + assertLUB(sub, exactSub, sub, exactSub); + assertLUB(sub, nullExactSub, nullSub, exactSub); + assertLUB(sub, none, sub, none); + assertLUB(sub, nullNone, nullSub, none); + assertLUB(sub, func, Type(Type::none), unreachable); + assertLUB(sub, nullFunc, Type(Type::none), unreachable); + assertLUB(sub, i32, Type(Type::none), unreachable); + assertLUB(sub, unreachable, sub, unreachable); + + assertLUB(nullSub, nullSub, nullSub, nullSub); + assertLUB(nullSub, exactSub, nullSub, exactSub); + assertLUB(nullSub, nullExactSub, nullSub, nullExactSub); + assertLUB(nullSub, none, nullSub, none); + assertLUB(nullSub, nullNone, nullSub, nullNone); + assertLUB(nullSub, func, Type(Type::none), unreachable); + assertLUB(nullSub, nullFunc, Type(Type::none), unreachable); + assertLUB(nullSub, i32, Type(Type::none), unreachable); + assertLUB(nullSub, unreachable, nullSub, unreachable); + + assertLUB(exactSub, exactSub, exactSub, exactSub); + assertLUB(exactSub, nullExactSub, nullExactSub, exactSub); + assertLUB(exactSub, none, exactSub, none); + assertLUB(exactSub, nullNone, nullExactSub, none); + assertLUB(exactSub, func, Type(Type::none), unreachable); + assertLUB(exactSub, nullFunc, Type(Type::none), unreachable); + assertLUB(exactSub, i32, Type(Type::none), unreachable); + assertLUB(exactSub, unreachable, exactSub, unreachable); + + assertLUB(nullExactSub, nullExactSub, nullExactSub, nullExactSub); + assertLUB(nullExactSub, none, nullExactSub, none); + assertLUB(nullExactSub, nullNone, nullExactSub, nullNone); + assertLUB(nullExactSub, func, Type(Type::none), unreachable); + assertLUB(nullExactSub, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactSub, i32, Type(Type::none), unreachable); + assertLUB(nullExactSub, unreachable, nullExactSub, unreachable); assertLUB(none, none, none, none); assertLUB(none, nullNone, nullNone, none); - assertLUB(none, exactNone, none, exactNone); - assertLUB(none, nullExactNone, nullNone, exactNone); assertLUB(none, func, Type(Type::none), unreachable); assertLUB(none, nullFunc, Type(Type::none), unreachable); - assertLUB(none, exactFunc, Type(Type::none), unreachable); - assertLUB(none, nullExactFunc, Type(Type::none), unreachable); assertLUB(none, i32, Type(Type::none), unreachable); assertLUB(none, unreachable, none, unreachable); assertLUB(nullNone, nullNone, nullNone, nullNone); - assertLUB(nullNone, exactNone, nullNone, exactNone); - assertLUB(nullNone, nullExactNone, nullNone, nullExactNone); assertLUB(nullNone, func, Type(Type::none), unreachable); assertLUB(nullNone, nullFunc, Type(Type::none), unreachable); - assertLUB(nullNone, exactFunc, Type(Type::none), unreachable); - assertLUB(nullNone, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullNone, i32, Type(Type::none), unreachable); assertLUB(nullNone, unreachable, nullNone, unreachable); - - assertLUB(exactNone, exactNone, exactNone, exactNone); - assertLUB(exactNone, nullExactNone, nullExactNone, exactNone); - assertLUB(exactNone, func, Type(Type::none), unreachable); - assertLUB(exactNone, nullFunc, Type(Type::none), unreachable); - assertLUB(exactNone, exactFunc, Type(Type::none), unreachable); - assertLUB(exactNone, nullExactFunc, Type(Type::none), unreachable); - assertLUB(exactNone, i32, Type(Type::none), unreachable); - assertLUB(exactNone, unreachable, exactNone, unreachable); - - assertLUB(nullExactNone, nullExactNone, nullExactNone, nullExactNone); - assertLUB(nullExactNone, func, Type(Type::none), unreachable); - assertLUB(nullExactNone, nullFunc, Type(Type::none), unreachable); - assertLUB(nullExactNone, exactFunc, Type(Type::none), unreachable); - assertLUB(nullExactNone, nullExactFunc, Type(Type::none), unreachable); - assertLUB(nullExactNone, i32, Type(Type::none), unreachable); - assertLUB(nullExactNone, unreachable, nullExactNone, unreachable); } TEST_F(TypeTest, TestSubtypeErrors) { diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index e6c1599ea48..72d103c3d6f 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -16,157 +16,160 @@ ;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s --check-prefix=NO-EXACT (module - ;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) - ;; CHECK-BIN: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) - ;; NO-EXACT: (type $foo (struct (field anyref) (field (ref any)) (field (ref null $foo)) (field (ref $foo)))) - (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) - - - ;; CHECK-TEXT: (type $1 (func (param (exact anyref)) (result (ref exact any)))) - - ;; CHECK-TEXT: (type $2 (func (param anyref) (result anyref))) - - ;; CHECK-TEXT: (type $3 (func (param (exact i31ref)))) - - ;; CHECK-TEXT: (type $4 (func (param (exact eqref)))) + ;; CHECK-TEXT: (type $foo (struct (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; CHECK-BIN: (type $foo (struct (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; NO-EXACT: (type $foo (struct (field (ref null $foo)) (field (ref $foo)))) + (type $foo (struct (field (ref null exact $foo) (ref exact $foo)))) + + (rec + ;; CHECK-TEXT: (rec + ;; CHECK-TEXT-NEXT: (type $super (sub (struct))) + ;; CHECK-BIN: (rec + ;; CHECK-BIN-NEXT: (type $super (sub (struct))) + ;; NO-EXACT: (rec + ;; NO-EXACT-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK-TEXT: (type $sub1 (sub $super (struct))) + ;; CHECK-BIN: (type $sub1 (sub $super (struct))) + ;; NO-EXACT: (type $sub1 (sub $super (struct))) + (type $sub1 (sub $super (struct))) + ;; CHECK-TEXT: (type $sub2 (sub $super (struct))) + ;; CHECK-BIN: (type $sub2 (sub $super (struct))) + ;; NO-EXACT: (type $sub2 (sub $super (struct))) + (type $sub2 (sub $super (struct))) + ) - ;; CHECK-TEXT: (type $5 (func (param (exact anyref)))) + ;; CHECK-TEXT: (type $4 (func (param (ref null exact $foo)) (result (ref exact $foo)))) - ;; CHECK-TEXT: (type $6 (func (param (exact anyref)) (result (exact anyref)))) + ;; CHECK-TEXT: (type $5 (func (param (ref null exact $foo)))) - ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) - ;; CHECK-BIN: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + ;; CHECK-TEXT: (type $6 (func (param (ref null $super)) (result (ref null $super)))) - ;; CHECK-BIN: (type $2 (func (param anyref) (result anyref))) + ;; CHECK-TEXT: (type $7 (func (param (ref null exact $sub1)))) - ;; CHECK-BIN: (type $3 (func (param (exact i31ref)))) + ;; CHECK-TEXT: (type $8 (func (param (ref null exact $foo)) (result (ref null exact $foo)))) - ;; CHECK-BIN: (type $4 (func (param (exact eqref)))) + ;; CHECK-TEXT: (import "" "g1" (global $g1 (ref null exact $foo))) + ;; CHECK-BIN: (type $4 (func (param (ref null exact $foo)) (result (ref exact $foo)))) - ;; CHECK-BIN: (type $5 (func (param (exact anyref)))) + ;; CHECK-BIN: (type $5 (func (param (ref null exact $foo)))) - ;; CHECK-BIN: (type $6 (func (param (exact anyref)) (result (exact anyref)))) + ;; CHECK-BIN: (type $6 (func (param (ref null $super)) (result (ref null $super)))) - ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) - ;; NO-EXACT: (type $1 (func (param anyref) (result anyref))) + ;; CHECK-BIN: (type $7 (func (param (ref null exact $sub1)))) - ;; NO-EXACT: (type $2 (func (param anyref) (result (ref any)))) + ;; CHECK-BIN: (type $8 (func (param (ref null exact $foo)) (result (ref null exact $foo)))) - ;; NO-EXACT: (type $3 (func (param i31ref))) + ;; CHECK-BIN: (import "" "g1" (global $g1 (ref null exact $foo))) + ;; NO-EXACT: (type $4 (func (param (ref null $foo)) (result (ref $foo)))) - ;; NO-EXACT: (type $4 (func (param eqref))) + ;; NO-EXACT: (type $5 (func (param (ref null $foo)))) - ;; NO-EXACT: (type $5 (func (param anyref))) + ;; NO-EXACT: (type $6 (func (param (ref null $super)) (result (ref null $super)))) - ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) - (import "" "g1" (global $g1 (exact anyref))) + ;; NO-EXACT: (type $7 (func (param (ref null $sub1)))) - ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact any))) - ;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact any))) - ;; NO-EXACT: (import "" "g2" (global $g2 (ref any))) - (import "" "g2" (global $g2 (ref exact any))) + ;; NO-EXACT: (type $8 (func (param (ref null $foo)) (result (ref null $foo)))) - ;; CHECK-TEXT: (import "" "g3" (global $g3 (ref null exact $foo))) - ;; CHECK-BIN: (import "" "g3" (global $g3 (ref null exact $foo))) - ;; NO-EXACT: (import "" "g3" (global $g3 (ref null $foo))) - (import "" "g3" (global $g3 (ref null exact $foo))) + ;; NO-EXACT: (import "" "g1" (global $g1 (ref null $foo))) + (import "" "g1" (global $g1 (ref null exact $foo))) - ;; CHECK-TEXT: (import "" "g4" (global $g4 (ref exact $foo))) - ;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo))) - ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) - (import "" "g4" (global $g4 (ref exact $foo))) + ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact $foo))) + ;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact $foo))) + ;; NO-EXACT: (import "" "g2" (global $g2 (ref $foo))) + (import "" "g2" (global $g2 (ref exact $foo))) - ;; CHECK-TEXT: (func $ref-test (type $3) (param $0 (exact i31ref)) + ;; CHECK-TEXT: (func $ref-test (type $5) (param $0 (ref null exact $foo)) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.test (ref exact i31) + ;; CHECK-TEXT-NEXT: (ref.test (ref exact $foo) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.test (exact i31ref) + ;; CHECK-TEXT-NEXT: (ref.test (ref null exact $foo) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-test (type $3) (param $0 (exact i31ref)) + ;; CHECK-BIN: (func $ref-test (type $5) (param $0 (ref null exact $foo)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (ref exact i31) + ;; CHECK-BIN-NEXT: (ref.test (ref exact $foo) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (exact i31ref) + ;; CHECK-BIN-NEXT: (ref.test (ref null exact $foo) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-test (type $3) (param $0 i31ref) + ;; NO-EXACT: (func $ref-test (type $5) (param $0 (ref null $foo)) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test (ref i31) + ;; NO-EXACT-NEXT: (ref.test (ref $foo) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test i31ref + ;; NO-EXACT-NEXT: (ref.test (ref null $foo) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $ref-test (param (ref null exact i31)) + (func $ref-test (param (ref null exact $foo)) (drop - (ref.test (ref exact i31) + (ref.test (ref exact $foo) (local.get 0) ) ) (drop - (ref.test (ref null exact i31) + (ref.test (ref null exact $foo) (local.get 0) ) ) ) - ;; CHECK-TEXT: (func $ref-cast (type $4) (param $0 (exact eqref)) + ;; CHECK-TEXT: (func $ref-cast (type $7) (param $0 (ref null exact $sub1)) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.cast (ref exact eq) + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact $sub1) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.cast (exact eqref) + ;; CHECK-TEXT-NEXT: (ref.cast (ref null exact $sub1) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.cast (ref exact none) + ;; CHECK-TEXT-NEXT: (ref.cast (ref none) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast (type $4) (param $0 (exact eqref)) + ;; CHECK-BIN: (func $ref-cast (type $7) (param $0 (ref null exact $sub1)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.cast (ref exact eq) + ;; CHECK-BIN-NEXT: (ref.cast (ref exact $sub1) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.cast (exact eqref) + ;; CHECK-BIN-NEXT: (ref.cast (ref null exact $sub1) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.cast (ref exact none) + ;; CHECK-BIN-NEXT: (ref.cast (ref none) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-cast (type $4) (param $0 eqref) + ;; NO-EXACT: (func $ref-cast (type $7) (param $0 (ref null $sub1)) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.cast (ref eq) + ;; NO-EXACT-NEXT: (ref.cast (ref $sub1) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.cast eqref + ;; NO-EXACT-NEXT: (ref.cast (ref null $sub1) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) @@ -176,207 +179,167 @@ ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $ref-cast (param (ref null exact eq)) + (func $ref-cast (param (ref null exact $sub1)) (drop - (ref.cast (ref exact eq) + (ref.cast (ref exact $sub1) (local.get 0) ) ) (drop - (ref.cast (ref null exact eq) + (ref.cast (ref null exact $sub1) (local.get 0) ) ) (drop - (ref.cast (ref exact i31) + (ref.cast (ref exact $sub2) (local.get 0) ) ) ) - ;; CHECK-TEXT: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) - ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT: (func $br-on-cast (type $6) (param $0 (ref null $super)) (result (ref null $super)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref null $super)) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null $super) (ref null exact $sub1) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (ref exact eq) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null $super) (ref exact $sub1) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) - ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN: (func $br-on-cast (type $6) (param $0 (ref null $super)) (result (ref null $super)) + ;; CHECK-BIN-NEXT: (block $block (result (ref null $super)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null $super) (ref null exact $sub1) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (ref exact eq) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null $super) (ref exact $sub1) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) - ;; NO-EXACT-NEXT: (block $block (result anyref) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block anyref eqref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) + ;; NO-EXACT: (func $br-on-cast (type $6) (param $0 (ref null $super)) (result (ref null $super)) + ;; NO-EXACT-NEXT: (block $block (result (ref null $super)) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block anyref (ref eq) + ;; NO-EXACT-NEXT: (br_on_cast $block (ref null $super) (ref null $sub1) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block anyref i31ref + ;; NO-EXACT-NEXT: (br_on_cast $block (ref null $super) (ref $sub1) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $br-on-cast (param anyref) (result anyref) + (func $br-on-cast (param (ref null $super)) (result (ref null $super)) (drop - (br_on_cast 0 anyref (ref null exact eq) + (br_on_cast 0 anyref (ref null exact $sub1) (local.get 0) ) ) (drop - (br_on_cast 0 anyref (ref exact eq) - (local.get 0) - ) - ) - (drop - (br_on_cast 0 anyref (ref null exact i31) + (br_on_cast 0 anyref (ref exact $sub1) (local.get 0) ) ) (local.get 0) ) - ;; CHECK-TEXT: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) - ;; CHECK-TEXT-NEXT: (block $label (result anyref) - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact eqref) - ;; CHECK-TEXT-NEXT: (local.get $0) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT: (func $br-on-cast-fail (type $6) (param $0 (ref null $super)) (result (ref null $super)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref null $super)) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null $super) (ref null exact $sub1) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null $super) (ref exact $sub1) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) - ;; CHECK-BIN-NEXT: (block $block (result anyref) - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact eqref) - ;; CHECK-BIN-NEXT: (local.get $0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-fail (type $6) (param $0 (ref null $super)) (result (ref null $super)) + ;; CHECK-BIN-NEXT: (block $block (result (ref null $super)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null $super) (ref null exact $sub1) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null $super) (ref exact $sub1) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) - ;; NO-EXACT-NEXT: (block $block (result anyref) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref eqref - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) + ;; NO-EXACT: (func $br-on-cast-fail (type $6) (param $0 (ref null $super)) (result (ref null $super)) + ;; NO-EXACT-NEXT: (block $block (result (ref null $super)) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref (ref eq) + ;; NO-EXACT-NEXT: (br_on_cast_fail $block (ref null $super) (ref null $sub1) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref i31ref + ;; NO-EXACT-NEXT: (br_on_cast_fail $block (ref null $super) (ref $sub1) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $br-on-cast-fail (param anyref) (result anyref) + (func $br-on-cast-fail (param (ref null $super)) (result (ref null $super)) (drop - (br_on_cast_fail 0 anyref (ref null exact eq) + (br_on_cast_fail 0 anyref (ref null exact $sub1) (local.get 0) ) ) (drop - (br_on_cast_fail 0 anyref (ref exact eq) - (local.get 0) - ) - ) - (drop - (br_on_cast_fail 0 anyref (ref null exact i31) + (br_on_cast_fail 0 anyref (ref exact $sub1) (local.get 0) ) ) (local.get 0) ) - ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) ;; CHECK-TEXT-NEXT: (ref.as_non_null ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN: (func $valid-ref-as-non-null (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) ;; CHECK-BIN-NEXT: (ref.as_non_null ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-ref-as-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT: (func $valid-ref-as-non-null (type $4) (param $0 (ref null $foo)) (result (ref $foo)) ;; NO-EXACT-NEXT: (ref.as_non_null ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-ref-as-non-null (param (ref null exact any)) (result (ref exact any)) + (func $valid-ref-as-non-null (param (ref null exact $foo)) (result (ref exact $foo)) (ref.as_non_null (local.get 0) ) ) - ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (ref null exact $foo)) ;; CHECK-TEXT-NEXT: (block $label ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block (result (ref exact $foo)) ;; CHECK-TEXT-NEXT: (br_on_null $label ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) @@ -384,7 +347,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (ref null exact $foo)) ;; CHECK-BIN-NEXT: (block $block ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (br_on_null $block @@ -393,7 +356,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-null (type $5) (param $0 anyref) + ;; NO-EXACT: (func $valid-br-on-null (type $5) (param $0 (ref null $foo)) ;; NO-EXACT-NEXT: (block $block ;; NO-EXACT-NEXT: (drop ;; NO-EXACT-NEXT: (br_on_null $block @@ -402,9 +365,9 @@ ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-br-on-null (param (ref null exact any)) + (func $valid-br-on-null (param (ref null exact $foo)) (drop - (block (result (ref exact any)) + (block (result (ref exact $foo)) (br_on_null 1 (local.get 0) ) @@ -412,42 +375,42 @@ ) ) - ;; CHECK-TEXT: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT: (func $valid-br-on-non-null (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact $foo)) ;; CHECK-TEXT-NEXT: (br_on_non_null $label ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN: (func $valid-br-on-non-null (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact $foo)) ;; CHECK-BIN-NEXT: (br_on_non_null $block ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-non-null (type $2) (param $0 anyref) (result (ref any)) - ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT: (func $valid-br-on-non-null (type $4) (param $0 (ref null $foo)) (result (ref $foo)) + ;; NO-EXACT-NEXT: (block $block (result (ref $foo)) ;; NO-EXACT-NEXT: (br_on_non_null $block ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (unreachable) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-br-on-non-null (param (ref null exact any)) (result (ref exact any)) + (func $valid-br-on-non-null (param (ref null exact $foo)) (result (ref exact $foo)) (br_on_non_null 0 (local.get 0) ) (unreachable) ) - ;; CHECK-TEXT: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) - ;; CHECK-TEXT-NEXT: (block $label (result (exact anyref)) + ;; CHECK-TEXT: (func $valid-br-on-cast (type $8) (param $0 (ref null exact $foo)) (result (ref null exact $foo)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref null exact $foo)) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (br_on_cast $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (block (result (ref exact $foo)) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null exact $foo) (ref null exact $foo) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -455,30 +418,30 @@ ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) - ;; CHECK-BIN-NEXT: (block $block (result (exact anyref)) + ;; CHECK-BIN: (func $valid-br-on-cast (type $8) (param $0 (ref null exact $foo)) (result (ref null exact $foo)) + ;; CHECK-BIN-NEXT: (block $block (result (ref null exact $foo)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null exact $foo) (ref null exact $foo) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-cast (type $1) (param $0 anyref) (result anyref) - ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT: (func $valid-br-on-cast (type $8) (param $0 (ref null $foo)) (result (ref null $foo)) + ;; NO-EXACT-NEXT: (block $block (result (ref null $foo)) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block anyref anyref + ;; NO-EXACT-NEXT: (br_on_cast $block (ref null $foo) (ref null $foo) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (unreachable) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-br-on-cast (param (ref null exact any)) (result (ref null exact any)) + (func $valid-br-on-cast (param (ref null exact $foo)) (result (ref null exact $foo)) (drop - (block (result (ref exact any)) - (br_on_cast 1 (ref null exact any) (ref null exact any) + (block (result (ref exact $foo)) + (br_on_cast 1 (ref null exact $foo) (ref null exact $foo) (local.get 0) ) ) @@ -486,11 +449,11 @@ (unreachable) ) - ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact $foo)) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (exact anyref)) - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (block (result (ref null exact $foo)) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null exact $foo) (ref null exact $foo) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -498,30 +461,30 @@ ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) - ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact $foo)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null exact $foo) (ref null exact $foo) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-cast-fail (type $2) (param $0 anyref) (result (ref any)) - ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT: (func $valid-br-on-cast-fail (type $4) (param $0 (ref null $foo)) (result (ref $foo)) + ;; NO-EXACT-NEXT: (block $block (result (ref $foo)) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref anyref + ;; NO-EXACT-NEXT: (br_on_cast_fail $block (ref null $foo) (ref null $foo) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (unreachable) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-br-on-cast-fail (param (ref null exact any)) (result (ref exact any)) + (func $valid-br-on-cast-fail (param (ref null exact $foo)) (result (ref exact $foo)) (drop - (block (result (ref null exact any)) - (br_on_cast_fail 1 (ref null exact any) (ref null exact any) + (block (result (ref null exact $foo)) + (br_on_cast_fail 1 (ref null exact $foo) (ref null exact $foo) (local.get 0) ) ) @@ -529,73 +492,69 @@ (unreachable) ) ) -;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) +;; CHECK-BIN-NODEBUG: (type $0 (struct (field (ref null exact $0)) (field (ref exact $0)))) -;; CHECK-BIN-NODEBUG: (type $1 (func (param (exact anyref)) (result (ref exact any)))) +;; CHECK-BIN-NODEBUG: (rec +;; CHECK-BIN-NODEBUG-NEXT: (type $1 (sub (struct))) -;; CHECK-BIN-NODEBUG: (type $2 (func (param anyref) (result anyref))) +;; CHECK-BIN-NODEBUG: (type $2 (sub $1 (struct))) -;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact i31ref)))) +;; CHECK-BIN-NODEBUG: (type $3 (sub $1 (struct))) -;; CHECK-BIN-NODEBUG: (type $4 (func (param (exact eqref)))) +;; CHECK-BIN-NODEBUG: (type $4 (func (param (ref null exact $0)) (result (ref exact $0)))) -;; CHECK-BIN-NODEBUG: (type $5 (func (param (exact anyref)))) +;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref null exact $0)))) -;; CHECK-BIN-NODEBUG: (type $6 (func (param (exact anyref)) (result (exact anyref)))) +;; CHECK-BIN-NODEBUG: (type $6 (func (param (ref null $1)) (result (ref null $1)))) -;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) +;; CHECK-BIN-NODEBUG: (type $7 (func (param (ref null exact $2)))) -;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any))) +;; CHECK-BIN-NODEBUG: (type $8 (func (param (ref null exact $0)) (result (ref null exact $0)))) -;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0))) +;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (ref null exact $0))) -;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0))) +;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact $0))) -;; CHECK-BIN-NODEBUG: (func $0 (type $3) (param $0 (exact i31ref)) +;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 (ref null exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact i31) +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref null exact $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $1 (type $4) (param $0 (exact eqref)) +;; CHECK-BIN-NODEBUG: (func $1 (type $7) (param $0 (ref null exact $2)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact $2) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref null exact $2) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact none) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref none) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $2 (type $2) (param $0 anyref) (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG: (func $2 (type $6) (param $0 (ref null $1)) (result (ref null $1)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null $1) (ref null exact $2) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (ref exact eq) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null $1) (ref exact $2) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -603,20 +562,15 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $3 (type $2) (param $0 anyref) (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact eqref) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG: (func $3 (type $6) (param $0 (ref null $1)) (result (ref null $1)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null $1) (ref null exact $2) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null $1) (ref exact $2) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -624,13 +578,13 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $4 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG: (func $4 (type $4) (param $0 (ref null exact $0)) (result (ref exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (ref.as_non_null ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (exact anyref)) +;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (ref null exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (block $block ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (br_on_null $block @@ -640,8 +594,8 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $6 (type $1) (param $0 (exact anyref)) (result (ref exact any)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG: (func $6 (type $4) (param $0 (ref null exact $0)) (result (ref exact $0)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (br_on_non_null $block ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -649,10 +603,10 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $7 (type $6) (param $0 (exact anyref)) (result (exact anyref)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (exact anyref)) +;; CHECK-BIN-NODEBUG: (func $7 (type $8) (param $0 (ref null exact $0)) (result (ref null exact $0)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null exact $0) (ref null exact $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -660,10 +614,10 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $8 (type $1) (param $0 (exact anyref)) (result (ref exact any)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG: (func $8 (type $4) (param $0 (ref null exact $0)) (result (ref exact $0)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null exact $0) (ref null exact $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/passes/coalesce-locals-exact.wast b/test/lit/passes/coalesce-locals-exact.wast index 1568d3634cf..6021f8a4620 100644 --- a/test/lit/passes/coalesce-locals-exact.wast +++ b/test/lit/passes/coalesce-locals-exact.wast @@ -7,8 +7,10 @@ ;; RUN: wasm-opt %s -all --coalesce-locals -S -o - | filecheck %s (module - ;; CHECK: (func $test (type $0) (param $0 (exact i31ref)) (result (ref exact i31)) - ;; CHECK-NEXT: (local $1 (exact i31ref)) + ;; CHECK: (type $struct (struct)) + (type $struct (struct)) + ;; CHECK: (func $test (type $1) (param $0 (ref null exact $struct)) (result (ref exact $struct)) + ;; CHECK-NEXT: (local $1 (ref null exact $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $0) @@ -25,8 +27,8 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $test (param (exact i31ref)) (result (ref exact i31)) - (local $l (ref exact i31)) + (func $test (param (ref null exact $struct)) (result (ref exact $struct)) + (local $l (ref exact $struct)) ;; This dead set will be optimized out. (local.set $l (ref.as_non_null diff --git a/test/lit/passes/local-subtyping-exact.wast b/test/lit/passes/local-subtyping-exact.wast index 725c7d499fb..86400f6c441 100644 --- a/test/lit/passes/local-subtyping-exact.wast +++ b/test/lit/passes/local-subtyping-exact.wast @@ -7,8 +7,11 @@ ;; RUN: wasm-opt %s -all --local-subtyping -S -o - | filecheck %s (module - ;; CHECK: (func $test (type $0) (param $0 (exact nullref)) (result anyref) - ;; CHECK-NEXT: (local $1 (exact nullref)) + ;; CHECK: (type $struct (struct)) + (type $struct (struct)) + + ;; CHECK: (func $test (type $1) (param $0 (ref exact $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (local $1 (ref null exact $struct)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -21,13 +24,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - (func $test (param (exact nullref)) (result anyref) - (local (exact nullref)) + (func $test (param (ref exact $struct)) (result (ref null $struct)) + (local (ref null exact $struct)) (if (i32.const 0) (then (local.set 1 - ;; This would let the local be (ref exact none) if it dominated the get. + ;; This would let the local be (ref (exact $struct)) if it dominated the get. (ref.as_non_null (local.get 0) ) diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast index 95125305984..c3f42c21ad3 100644 --- a/test/lit/passes/optimize-instructions-exact.wast +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -6,14 +6,16 @@ ;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s (module - ;; CHECK: (func $cast-any-to-exact-none (type $0) (param $0 anyref) (result (exact nullref)) - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK: (type $struct (struct)) + (type $struct (struct)) + ;; CHECK: (func $cast-to-exact (type $1) (param $0 anyref) (result (ref exact $struct)) + ;; CHECK-NEXT: (ref.cast (ref exact $struct) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast-any-to-exact-none (param anyref) (result (exact nullref)) + (func $cast-to-exact (param anyref) (result (ref exact $struct)) ;; This will not be changed, but should not trigger an assertion. - (ref.cast (exact nullref) + (ref.cast (ref exact $struct) (local.get 0) ) ) diff --git a/test/lit/passes/remove-unused-brs-exact.wast b/test/lit/passes/remove-unused-brs-exact.wast index 3bebd07de02..db946b3a4c7 100644 --- a/test/lit/passes/remove-unused-brs-exact.wast +++ b/test/lit/passes/remove-unused-brs-exact.wast @@ -7,12 +7,14 @@ ;; trap. (module - ;; CHECK: (func $br_on_cast_fail (type $0) (param $0 (exact nullref)) - ;; CHECK-NEXT: (local $1 nullref) + ;; CHECK: (type $struct (struct)) + (type $struct (struct)) + ;; CHECK: (func $br_on_cast_fail (type $1) (param $0 (ref null exact $struct)) + ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (ref.cast (ref null exact $struct) ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -22,12 +24,12 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $br_on_cast_fail (param (ref null exact none)) - (local $1 nullref) + (func $br_on_cast_fail (param (ref null exact $struct)) + (local $1 (ref null $struct)) (drop - (block $block (result (ref none)) + (block $block (result (ref $struct)) (drop - (br_on_cast_fail $block nullref nullref + (br_on_cast_fail $block (ref null $struct) (ref null $struct) (local.tee $1 (local.get 0) ) diff --git a/test/lit/passes/remove-unused-types-exact.wast b/test/lit/passes/remove-unused-types-exact.wast index 6cb9c1ddd74..754412c0b91 100644 --- a/test/lit/passes/remove-unused-types-exact.wast +++ b/test/lit/passes/remove-unused-types-exact.wast @@ -4,13 +4,16 @@ ;; Test that a simple type rewrite handles exact references in heap type ;; definitions correctly. In particular, the function should continue returning -;; an exact nullref and the call expression should have the same type. +;; an exact reference and the call expression should have the same type. (module - ;; CHECK: (func $return-exact (type $0) (result (exact nullref)) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (struct)) + (type $struct (struct)) + ;; CHECK: (func $return-exact (type $1) (result (ref null exact $struct)) ;; CHECK-NEXT: (call $return-exact) ;; CHECK-NEXT: ) - (func $return-exact (result (exact nullref)) + (func $return-exact (result (ref null exact $struct)) (call $return-exact) ) ) diff --git a/test/lit/passes/string-lowering-instructions.wast b/test/lit/passes/string-lowering-instructions.wast index d9a9fe6e77c..1a27ac8adb4 100644 --- a/test/lit/passes/string-lowering-instructions.wast +++ b/test/lit/passes/string-lowering-instructions.wast @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt -all --preserve-type-order --string-lowering -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --preserve-type-order --string-lowering -S -o - | filecheck %s (module (rec From 5045571596e2efde6cc5e5dbde63757cd0bea405 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Apr 2025 19:53:39 -0700 Subject: [PATCH 430/622] [NFC] Represent exactness and sharedness with one bit (#7500) Since the Type representation only needs to include a bit for sharedness for basic heap types and only needs to include a bit for exactness for non-basic heap types, they can use the same bit. Using fewer bits in the Type representation is beneficial because it reduces the minimum alignment of HeapTypeInfos required to avoid trampling the reserved bits. --- src/wasm-type.h | 22 ++++++++++++++++++---- test/example/c-api-kitchen-sink.txt | 22 +++++++++++----------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 8fe6f3490ea..ba69bcbb623 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -96,9 +96,10 @@ class HeapType { // should also be passed by value. uintptr_t id; - static constexpr int TypeBits = 3; + static constexpr int TypeBits = 2; static constexpr int UsedBits = TypeBits + 1; static constexpr int SharedMask = 1 << TypeBits; + friend class Type; public: // Bits 0-2 are used by the Type representation, so need to be left free. @@ -290,6 +291,11 @@ class Type { static constexpr int NullMask = 1 << 1; static constexpr int ExactMask = 1 << 2; + // Only abstract heap types store sharedness as a bit in the representation + // and only non-abstract heap types can be exact, so exactness and sharedness + // can use the same bit. + static_assert(ExactMask == HeapType::SharedMask); + public: enum BasicType : uint32_t { none = 0, @@ -322,7 +328,8 @@ class Type { Type(HeapType heapType, Nullability nullable, Exactness exact = Inexact) : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | (exact == Exact ? ExactMask : 0)) { - assert(!(heapType.getID() & (TupleMask | NullMask | ExactMask))); + assert(!(heapType.getID() & + (TupleMask | NullMask | (heapType.isBasic() ? 0 : ExactMask)))); assert(!heapType.isBasic() || exact == Inexact); } @@ -372,11 +379,18 @@ class Type { bool isRef() const { return !isBasic() && !(id & TupleMask); } bool isNullable() const { return isRef() && (id & NullMask); } bool isNonNullable() const { return isRef() && !(id & NullMask); } - bool isExact() const { return isRef() && (id & ExactMask); } + bool isExact() const { + return isRef() && !getHeapType().isBasic() && (id & ExactMask); + } bool isInexact() const { return isRef() && !(id & ExactMask); } HeapType getHeapType() const { assert(isRef()); - return HeapType(id & ~(NullMask | ExactMask)); + HeapType masked(id & ~NullMask); + // Avoid masking off the shared bit on basic heap types. + if (!masked.isBasic()) { + masked = HeapType(masked.id & ~ExactMask); + } + return masked; } bool isFunction() const { return isRef() && getHeapType().isFunction(); } diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 5736b5a5c27..1961547c5a1 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -20,17 +20,17 @@ BinaryenTypeAuto: -1 BinaryenPackedTypeNotPacked: 0 BinaryenPackedTypeInt8: 1 BinaryenPackedTypeInt16: 2 -BinaryenHeapTypeExt: 16 -BinaryenHeapTypeFunc: 32 -BinaryenHeapTypeAny: 64 -BinaryenHeapTypeEq: 80 -BinaryenHeapTypeI31: 96 -BinaryenHeapTypeStruct: 112 -BinaryenHeapTypeArray: 128 -BinaryenHeapTypeString: 160 -BinaryenHeapTypeNone: 176 -BinaryenHeapTypeNoext: 192 -BinaryenHeapTypeNofunc: 208 +BinaryenHeapTypeExt: 8 +BinaryenHeapTypeFunc: 16 +BinaryenHeapTypeAny: 32 +BinaryenHeapTypeEq: 40 +BinaryenHeapTypeI31: 48 +BinaryenHeapTypeStruct: 56 +BinaryenHeapTypeArray: 64 +BinaryenHeapTypeString: 80 +BinaryenHeapTypeNone: 88 +BinaryenHeapTypeNoext: 96 +BinaryenHeapTypeNofunc: 104 BinaryenFeatureMVP: 0 BinaryenFeatureAtomics: 1 BinaryenFeatureBulkMemory: 16 From 9aa06be2cda33dc41213c6a750ad3cedbd4afe7e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Apr 2025 21:04:37 -0700 Subject: [PATCH 431/622] Update text format for exact heap types (#7501) Since exactness is syntactically part of the heap type, it introduces a new level of parentheses inside the reference type. Update type printing and the text parser accordingly. --- src/parser/context-defs.cpp | 2 +- src/parser/contexts.h | 39 ++-- src/parser/parsers.h | 83 +++---- src/wasm/wasm-type.cpp | 5 +- test/lit/basic/exact-references.wast | 216 +++++++++--------- test/lit/passes/coalesce-locals-exact.wast | 8 +- test/lit/passes/local-subtyping-exact.wast | 8 +- .../passes/optimize-instructions-exact.wast | 8 +- test/lit/passes/remove-unused-brs-exact.wast | 6 +- .../lit/passes/remove-unused-types-exact.wast | 4 +- 10 files changed, 191 insertions(+), 188 deletions(-) diff --git a/src/parser/context-defs.cpp b/src/parser/context-defs.cpp index 2d4d305edaf..629f94ac274 100644 --- a/src/parser/context-defs.cpp +++ b/src/parser/context-defs.cpp @@ -36,7 +36,7 @@ ParseDefsCtx::makeTypeUse(Index pos, auto sig = Signature(Type(paramTypes), Type(resultTypes)); - if (!type->isSignature() || type->getSignature() != sig) { + if (!type->type.isSignature() || type->type.getSignature() != sig) { return in.err(pos, "type does not match provided signature"); } } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index fa96c270670..fd835134eb5 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -127,9 +127,7 @@ struct NullTypeParserCtx { TypeT makeF64() { return Ok{}; } TypeT makeV128() { return Ok{}; } - TypeT makeRefType(HeapTypeT, Nullability, Exactness) { return Ok{}; } - - HeapTypeT getHeapTypeFromRefType(TypeT) { return Ok{}; } + TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; } TupleElemListT makeTupleElemList() { return Ok{}; } void appendTupleElem(TupleElemListT&, TypeT) {} @@ -169,6 +167,8 @@ struct NullTypeParserCtx { Result getTypeIndex(Name) { return 1; } Result getHeapTypeFromIdx(Index) { return Ok{}; } + HeapTypeT makeExact(HeapTypeT) { return Ok{}; } + DataStringT makeDataString() { return Ok{}; } void appendDataString(DataStringT&, std::string_view) {} @@ -182,8 +182,18 @@ struct NullTypeParserCtx { }; template struct TypeParserCtx { + // Exactness is syntactically part of the heap type, but it is not part of the + // HeapType in our IR, so we have to store it separately. + struct HeapTypeT { + HeapType type; + Exactness exactness = Inexact; + // Implicitly convert to and from HeapType. + HeapTypeT(HeapType::BasicHeapType type) : type(type) {} + HeapTypeT(HeapType type) : type(type) {} + operator HeapType() { return type; } + }; + using IndexT = Index; - using HeapTypeT = HeapType; using TypeT = Type; using ParamsT = std::vector; using ResultsT = std::vector; @@ -253,19 +263,21 @@ template struct TypeParserCtx { return HeapTypes::nocont.getBasic(share); } + HeapTypeT makeExact(HeapTypeT type) { + type.exactness = Exact; + return type; + } + TypeT makeI32() { return Type::i32; } TypeT makeI64() { return Type::i64; } TypeT makeF32() { return Type::f32; } TypeT makeF64() { return Type::f64; } TypeT makeV128() { return Type::v128; } - TypeT - makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { - return Type(ht, nullability, exactness); + TypeT makeRefType(HeapTypeT ht, Nullability nullability) { + return Type(ht.type, nullability, ht.exactness); } - HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } - std::vector makeTupleElemList() { return {}; } void appendTupleElem(std::vector& elems, Type elem) { elems.push_back(elem); @@ -1122,13 +1134,10 @@ struct ParseTypeDefsCtx : TypeParserCtx { : TypeParserCtx(typeIndices), in(in), builder(builder), names(builder.size()) {} - TypeT - makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { - return builder.getTempRefType(ht, nullability, exactness); + TypeT makeRefType(HeapTypeT ht, Nullability nullability) { + return builder.getTempRefType(ht.type, nullability, ht.exactness); } - HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } - TypeT makeTupleType(const std::vector types) { return builder.getTempTupleType(types); } @@ -1235,7 +1244,7 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { } auto sig = Signature(Type(paramTypes), Type(resultTypes)); - auto [it, inserted] = sigTypes.insert({sig, HeapType::func}); + auto [it, inserted] = sigTypes.insert({sig, HeapType(HeapType::func)}); if (inserted) { auto type = HeapType(sig); it->second = type; diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 26c20767938..2e2a6da7e39 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -30,8 +30,6 @@ using namespace std::string_view_literals; template Result absheaptype(Ctx&, Shareability); template Result heaptype(Ctx&); -template -MaybeResult maybeReftypeAbbrev(Ctx&); template MaybeResult maybeReftype(Ctx&); template Result reftype(Ctx&); template MaybeResult tupletype(Ctx&); @@ -436,6 +434,7 @@ Result absheaptype(Ctx& ctx, Shareability share) { } // heaptype ::= x:typeidx => types[x] +// | '(' 'exact' x:typeidx ')' => exact types[x] // | t:absheaptype => unshared t // | '(' 'shared' t:absheaptype ')' => shared t template Result heaptype(Ctx& ctx) { @@ -444,6 +443,15 @@ template Result heaptype(Ctx& ctx) { return *t; } + if (ctx.in.takeSExprStart("exact"sv)) { + auto t = typeidx(ctx); + CHECK_ERR(t); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of exact heap type"); + } + return ctx.makeExact(*t); + } + auto share = ctx.in.takeSExprStart("shared"sv) ? Shared : Unshared; auto t = absheaptype(ctx, share); CHECK_ERR(t); @@ -460,85 +468,68 @@ template Result heaptype(Ctx& ctx) { // | 'i31ref' => i31ref // | 'structref' => structref // | 'arrayref' => arrayref -// | ... -template -MaybeResult maybeReftypeAbbrev(Ctx& ctx) { +// | '(' ref null? t:heaptype ')' => ref null? t +template MaybeResult maybeReftype(Ctx& ctx) { if (ctx.in.takeKeyword("funcref"sv)) { - return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable); } if (ctx.in.takeKeyword("externref"sv)) { - return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable); } if (ctx.in.takeKeyword("anyref"sv)) { - return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable); } if (ctx.in.takeKeyword("eqref"sv)) { - return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable); } if (ctx.in.takeKeyword("i31ref"sv)) { - return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable); } if (ctx.in.takeKeyword("structref"sv)) { - return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable); } if (ctx.in.takeKeyword("arrayref"sv)) { - return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable); } if (ctx.in.takeKeyword("exnref"sv)) { - return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable); } if (ctx.in.takeKeyword("stringref"sv)) { - return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable); } if (ctx.in.takeKeyword("contref"sv)) { - return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeContType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullref"sv)) { - return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullexternref"sv)) { - return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullfuncref"sv)) { - return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullexnref"sv)) { - return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable); } if (ctx.in.takeKeyword("nullcontref"sv)) { - return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact); + return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable); } - return {}; -} -// reftype ::= ... -// | '(' 'exact' (ref null ht):shorthand ')' => ref null exact ht -// | '(' ref null? exact? ht:heaptype ')' => ref null? t -template MaybeResult maybeReftype(Ctx& ctx) { - if (ctx.in.takeSExprStart("exact"sv)) { - auto rt = maybeReftypeAbbrev(ctx); - CHECK_ERR(rt); - if (!rt) { - return ctx.in.err("expected reftype shorthand"); - } - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of reftype"); - } - return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact); + if (!ctx.in.takeSExprStart("ref"sv)) { + return {}; } - if (ctx.in.takeSExprStart("ref"sv)) { - auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; - auto exactness = ctx.in.takeKeyword("exact"sv) ? Exact : Inexact; - auto type = heaptype(ctx); - CHECK_ERR(type); - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of reftype"); - } - return ctx.makeRefType(*type, nullability, exactness); + auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; + + auto type = heaptype(ctx); + CHECK_ERR(type); + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); } - return maybeReftypeAbbrev(ctx); + return ctx.makeRefType(*type, nullability); } template Result reftype(Ctx& ctx) { diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index ee7301c3032..f1ddaa16e1c 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1736,9 +1736,12 @@ std::ostream& TypePrinter::print(Type type) { os << "null "; } if (type.isExact()) { - os << "exact "; + os << "(exact "; } printHeapTypeName(heapType); + if (type.isExact()) { + os << ')'; + } os << ')'; } else { WASM_UNREACHABLE("unexpected type"); diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index 72d103c3d6f..09e5bf48236 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -16,10 +16,10 @@ ;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s --check-prefix=NO-EXACT (module - ;; CHECK-TEXT: (type $foo (struct (field (ref null exact $foo)) (field (ref exact $foo)))) - ;; CHECK-BIN: (type $foo (struct (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; CHECK-TEXT: (type $foo (struct (field (ref null (exact $foo))) (field (ref (exact $foo))))) + ;; CHECK-BIN: (type $foo (struct (field (ref null (exact $foo))) (field (ref (exact $foo))))) ;; NO-EXACT: (type $foo (struct (field (ref null $foo)) (field (ref $foo)))) - (type $foo (struct (field (ref null exact $foo) (ref exact $foo)))) + (type $foo (struct (field (ref null (exact $foo)) (ref (exact $foo))))) (rec ;; CHECK-TEXT: (rec @@ -39,28 +39,28 @@ (type $sub2 (sub $super (struct))) ) - ;; CHECK-TEXT: (type $4 (func (param (ref null exact $foo)) (result (ref exact $foo)))) + ;; CHECK-TEXT: (type $4 (func (param (ref null (exact $foo))) (result (ref (exact $foo))))) - ;; CHECK-TEXT: (type $5 (func (param (ref null exact $foo)))) + ;; CHECK-TEXT: (type $5 (func (param (ref null (exact $foo))))) ;; CHECK-TEXT: (type $6 (func (param (ref null $super)) (result (ref null $super)))) - ;; CHECK-TEXT: (type $7 (func (param (ref null exact $sub1)))) + ;; CHECK-TEXT: (type $7 (func (param (ref null (exact $sub1))))) - ;; CHECK-TEXT: (type $8 (func (param (ref null exact $foo)) (result (ref null exact $foo)))) + ;; CHECK-TEXT: (type $8 (func (param (ref null (exact $foo))) (result (ref null (exact $foo))))) - ;; CHECK-TEXT: (import "" "g1" (global $g1 (ref null exact $foo))) - ;; CHECK-BIN: (type $4 (func (param (ref null exact $foo)) (result (ref exact $foo)))) + ;; CHECK-TEXT: (import "" "g1" (global $g1 (ref null (exact $foo)))) + ;; CHECK-BIN: (type $4 (func (param (ref null (exact $foo))) (result (ref (exact $foo))))) - ;; CHECK-BIN: (type $5 (func (param (ref null exact $foo)))) + ;; CHECK-BIN: (type $5 (func (param (ref null (exact $foo))))) ;; CHECK-BIN: (type $6 (func (param (ref null $super)) (result (ref null $super)))) - ;; CHECK-BIN: (type $7 (func (param (ref null exact $sub1)))) + ;; CHECK-BIN: (type $7 (func (param (ref null (exact $sub1))))) - ;; CHECK-BIN: (type $8 (func (param (ref null exact $foo)) (result (ref null exact $foo)))) + ;; CHECK-BIN: (type $8 (func (param (ref null (exact $foo))) (result (ref null (exact $foo))))) - ;; CHECK-BIN: (import "" "g1" (global $g1 (ref null exact $foo))) + ;; CHECK-BIN: (import "" "g1" (global $g1 (ref null (exact $foo)))) ;; NO-EXACT: (type $4 (func (param (ref null $foo)) (result (ref $foo)))) ;; NO-EXACT: (type $5 (func (param (ref null $foo)))) @@ -72,33 +72,33 @@ ;; NO-EXACT: (type $8 (func (param (ref null $foo)) (result (ref null $foo)))) ;; NO-EXACT: (import "" "g1" (global $g1 (ref null $foo))) - (import "" "g1" (global $g1 (ref null exact $foo))) + (import "" "g1" (global $g1 (ref null (exact $foo)))) - ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact $foo))) - ;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact $foo))) + ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref (exact $foo)))) + ;; CHECK-BIN: (import "" "g2" (global $g2 (ref (exact $foo)))) ;; NO-EXACT: (import "" "g2" (global $g2 (ref $foo))) - (import "" "g2" (global $g2 (ref exact $foo))) + (import "" "g2" (global $g2 (ref (exact $foo)))) - ;; CHECK-TEXT: (func $ref-test (type $5) (param $0 (ref null exact $foo)) + ;; CHECK-TEXT: (func $ref-test (type $5) (param $0 (ref null (exact $foo))) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.test (ref exact $foo) + ;; CHECK-TEXT-NEXT: (ref.test (ref (exact $foo)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.test (ref null exact $foo) + ;; CHECK-TEXT-NEXT: (ref.test (ref null (exact $foo)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-test (type $5) (param $0 (ref null exact $foo)) + ;; CHECK-BIN: (func $ref-test (type $5) (param $0 (ref null (exact $foo))) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (ref exact $foo) + ;; CHECK-BIN-NEXT: (ref.test (ref (exact $foo)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (ref null exact $foo) + ;; CHECK-BIN-NEXT: (ref.test (ref null (exact $foo)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -115,27 +115,27 @@ ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $ref-test (param (ref null exact $foo)) + (func $ref-test (param (ref null (exact $foo))) (drop - (ref.test (ref exact $foo) + (ref.test (ref (exact $foo)) (local.get 0) ) ) (drop - (ref.test (ref null exact $foo) + (ref.test (ref null (exact $foo)) (local.get 0) ) ) ) - ;; CHECK-TEXT: (func $ref-cast (type $7) (param $0 (ref null exact $sub1)) + ;; CHECK-TEXT: (func $ref-cast (type $7) (param $0 (ref null (exact $sub1))) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.cast (ref exact $sub1) + ;; CHECK-TEXT-NEXT: (ref.cast (ref (exact $sub1)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (ref.cast (ref null exact $sub1) + ;; CHECK-TEXT-NEXT: (ref.cast (ref null (exact $sub1)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -145,14 +145,14 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast (type $7) (param $0 (ref null exact $sub1)) + ;; CHECK-BIN: (func $ref-cast (type $7) (param $0 (ref null (exact $sub1))) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.cast (ref exact $sub1) + ;; CHECK-BIN-NEXT: (ref.cast (ref (exact $sub1)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.cast (ref null exact $sub1) + ;; CHECK-BIN-NEXT: (ref.cast (ref null (exact $sub1)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -179,19 +179,19 @@ ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $ref-cast (param (ref null exact $sub1)) + (func $ref-cast (param (ref null (exact $sub1))) (drop - (ref.cast (ref exact $sub1) + (ref.cast (ref (exact $sub1)) (local.get 0) ) ) (drop - (ref.cast (ref null exact $sub1) + (ref.cast (ref null (exact $sub1)) (local.get 0) ) ) (drop - (ref.cast (ref exact $sub2) + (ref.cast (ref (exact $sub2)) (local.get 0) ) ) @@ -200,12 +200,12 @@ ;; CHECK-TEXT: (func $br-on-cast (type $6) (param $0 (ref null $super)) (result (ref null $super)) ;; CHECK-TEXT-NEXT: (block $label (result (ref null $super)) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null $super) (ref null exact $sub1) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null $super) (ref null (exact $sub1)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null $super) (ref exact $sub1) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null $super) (ref (exact $sub1)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -215,12 +215,12 @@ ;; CHECK-BIN: (func $br-on-cast (type $6) (param $0 (ref null $super)) (result (ref null $super)) ;; CHECK-BIN-NEXT: (block $block (result (ref null $super)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null $super) (ref null exact $sub1) + ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null $super) (ref null (exact $sub1)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null $super) (ref exact $sub1) + ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null $super) (ref (exact $sub1)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -244,12 +244,12 @@ ;; NO-EXACT-NEXT: ) (func $br-on-cast (param (ref null $super)) (result (ref null $super)) (drop - (br_on_cast 0 anyref (ref null exact $sub1) + (br_on_cast 0 anyref (ref null (exact $sub1)) (local.get 0) ) ) (drop - (br_on_cast 0 anyref (ref exact $sub1) + (br_on_cast 0 anyref (ref (exact $sub1)) (local.get 0) ) ) @@ -259,12 +259,12 @@ ;; CHECK-TEXT: (func $br-on-cast-fail (type $6) (param $0 (ref null $super)) (result (ref null $super)) ;; CHECK-TEXT-NEXT: (block $label (result (ref null $super)) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null $super) (ref null exact $sub1) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null $super) (ref null (exact $sub1)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null $super) (ref exact $sub1) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null $super) (ref (exact $sub1)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -274,12 +274,12 @@ ;; CHECK-BIN: (func $br-on-cast-fail (type $6) (param $0 (ref null $super)) (result (ref null $super)) ;; CHECK-BIN-NEXT: (block $block (result (ref null $super)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null $super) (ref null exact $sub1) + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null $super) (ref null (exact $sub1)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null $super) (ref exact $sub1) + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null $super) (ref (exact $sub1)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -303,24 +303,24 @@ ;; NO-EXACT-NEXT: ) (func $br-on-cast-fail (param (ref null $super)) (result (ref null $super)) (drop - (br_on_cast_fail 0 anyref (ref null exact $sub1) + (br_on_cast_fail 0 anyref (ref null (exact $sub1)) (local.get 0) ) ) (drop - (br_on_cast_fail 0 anyref (ref exact $sub1) + (br_on_cast_fail 0 anyref (ref (exact $sub1)) (local.get 0) ) ) (local.get 0) ) - ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) + ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $4) (param $0 (ref null (exact $foo))) (result (ref (exact $foo))) ;; CHECK-TEXT-NEXT: (ref.as_non_null ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-ref-as-non-null (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) + ;; CHECK-BIN: (func $valid-ref-as-non-null (type $4) (param $0 (ref null (exact $foo))) (result (ref (exact $foo))) ;; CHECK-BIN-NEXT: (ref.as_non_null ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) @@ -330,16 +330,16 @@ ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-ref-as-non-null (param (ref null exact $foo)) (result (ref exact $foo)) + (func $valid-ref-as-non-null (param (ref null (exact $foo))) (result (ref (exact $foo))) (ref.as_non_null (local.get 0) ) ) - ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (ref null exact $foo)) + ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (ref null (exact $foo))) ;; CHECK-TEXT-NEXT: (block $label ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (ref exact $foo)) + ;; CHECK-TEXT-NEXT: (block (result (ref (exact $foo))) ;; CHECK-TEXT-NEXT: (br_on_null $label ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) @@ -347,7 +347,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (ref null exact $foo)) + ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (ref null (exact $foo))) ;; CHECK-BIN-NEXT: (block $block ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (br_on_null $block @@ -365,9 +365,9 @@ ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-br-on-null (param (ref null exact $foo)) + (func $valid-br-on-null (param (ref null (exact $foo))) (drop - (block (result (ref exact $foo)) + (block (result (ref (exact $foo))) (br_on_null 1 (local.get 0) ) @@ -375,16 +375,16 @@ ) ) - ;; CHECK-TEXT: (func $valid-br-on-non-null (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) - ;; CHECK-TEXT-NEXT: (block $label (result (ref exact $foo)) + ;; CHECK-TEXT: (func $valid-br-on-non-null (type $4) (param $0 (ref null (exact $foo))) (result (ref (exact $foo))) + ;; CHECK-TEXT-NEXT: (block $label (result (ref (exact $foo))) ;; CHECK-TEXT-NEXT: (br_on_non_null $label ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-non-null (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) - ;; CHECK-BIN-NEXT: (block $block (result (ref exact $foo)) + ;; CHECK-BIN: (func $valid-br-on-non-null (type $4) (param $0 (ref null (exact $foo))) (result (ref (exact $foo))) + ;; CHECK-BIN-NEXT: (block $block (result (ref (exact $foo))) ;; CHECK-BIN-NEXT: (br_on_non_null $block ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) @@ -399,18 +399,18 @@ ;; NO-EXACT-NEXT: (unreachable) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-br-on-non-null (param (ref null exact $foo)) (result (ref exact $foo)) + (func $valid-br-on-non-null (param (ref null (exact $foo))) (result (ref (exact $foo))) (br_on_non_null 0 (local.get 0) ) (unreachable) ) - ;; CHECK-TEXT: (func $valid-br-on-cast (type $8) (param $0 (ref null exact $foo)) (result (ref null exact $foo)) - ;; CHECK-TEXT-NEXT: (block $label (result (ref null exact $foo)) + ;; CHECK-TEXT: (func $valid-br-on-cast (type $8) (param $0 (ref null (exact $foo))) (result (ref null (exact $foo))) + ;; CHECK-TEXT-NEXT: (block $label (result (ref null (exact $foo))) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (ref exact $foo)) - ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null exact $foo) (ref null exact $foo) + ;; CHECK-TEXT-NEXT: (block (result (ref (exact $foo))) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (ref null (exact $foo)) (ref null (exact $foo)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -418,10 +418,10 @@ ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-cast (type $8) (param $0 (ref null exact $foo)) (result (ref null exact $foo)) - ;; CHECK-BIN-NEXT: (block $block (result (ref null exact $foo)) + ;; CHECK-BIN: (func $valid-br-on-cast (type $8) (param $0 (ref null (exact $foo))) (result (ref null (exact $foo))) + ;; CHECK-BIN-NEXT: (block $block (result (ref null (exact $foo))) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null exact $foo) (ref null exact $foo) + ;; CHECK-BIN-NEXT: (br_on_cast $block (ref null (exact $foo)) (ref null (exact $foo)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -438,10 +438,10 @@ ;; NO-EXACT-NEXT: (unreachable) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-br-on-cast (param (ref null exact $foo)) (result (ref null exact $foo)) + (func $valid-br-on-cast (param (ref null (exact $foo))) (result (ref null (exact $foo))) (drop - (block (result (ref exact $foo)) - (br_on_cast 1 (ref null exact $foo) (ref null exact $foo) + (block (result (ref (exact $foo))) + (br_on_cast 1 (ref null (exact $foo)) (ref null (exact $foo)) (local.get 0) ) ) @@ -449,11 +449,11 @@ (unreachable) ) - ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) - ;; CHECK-TEXT-NEXT: (block $label (result (ref exact $foo)) + ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $4) (param $0 (ref null (exact $foo))) (result (ref (exact $foo))) + ;; CHECK-TEXT-NEXT: (block $label (result (ref (exact $foo))) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (block (result (ref null exact $foo)) - ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null exact $foo) (ref null exact $foo) + ;; CHECK-TEXT-NEXT: (block (result (ref null (exact $foo))) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (ref null (exact $foo)) (ref null (exact $foo)) ;; CHECK-TEXT-NEXT: (local.get $0) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -461,10 +461,10 @@ ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $4) (param $0 (ref null exact $foo)) (result (ref exact $foo)) - ;; CHECK-BIN-NEXT: (block $block (result (ref exact $foo)) + ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $4) (param $0 (ref null (exact $foo))) (result (ref (exact $foo))) + ;; CHECK-BIN-NEXT: (block $block (result (ref (exact $foo))) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null exact $foo) (ref null exact $foo) + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (ref null (exact $foo)) (ref null (exact $foo)) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -481,10 +481,10 @@ ;; NO-EXACT-NEXT: (unreachable) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) - (func $valid-br-on-cast-fail (param (ref null exact $foo)) (result (ref exact $foo)) + (func $valid-br-on-cast-fail (param (ref null (exact $foo))) (result (ref (exact $foo))) (drop - (block (result (ref null exact $foo)) - (br_on_cast_fail 1 (ref null exact $foo) (ref null exact $foo) + (block (result (ref null (exact $foo))) + (br_on_cast_fail 1 (ref null (exact $foo)) (ref null (exact $foo)) (local.get 0) ) ) @@ -492,7 +492,7 @@ (unreachable) ) ) -;; CHECK-BIN-NODEBUG: (type $0 (struct (field (ref null exact $0)) (field (ref exact $0)))) +;; CHECK-BIN-NODEBUG: (type $0 (struct (field (ref null (exact $0))) (field (ref (exact $0))))) ;; CHECK-BIN-NODEBUG: (rec ;; CHECK-BIN-NODEBUG-NEXT: (type $1 (sub (struct))) @@ -501,41 +501,41 @@ ;; CHECK-BIN-NODEBUG: (type $3 (sub $1 (struct))) -;; CHECK-BIN-NODEBUG: (type $4 (func (param (ref null exact $0)) (result (ref exact $0)))) +;; CHECK-BIN-NODEBUG: (type $4 (func (param (ref null (exact $0))) (result (ref (exact $0))))) -;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref null exact $0)))) +;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref null (exact $0))))) ;; CHECK-BIN-NODEBUG: (type $6 (func (param (ref null $1)) (result (ref null $1)))) -;; CHECK-BIN-NODEBUG: (type $7 (func (param (ref null exact $2)))) +;; CHECK-BIN-NODEBUG: (type $7 (func (param (ref null (exact $2))))) -;; CHECK-BIN-NODEBUG: (type $8 (func (param (ref null exact $0)) (result (ref null exact $0)))) +;; CHECK-BIN-NODEBUG: (type $8 (func (param (ref null (exact $0))) (result (ref null (exact $0))))) -;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (ref null exact $0))) +;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (ref null (exact $0)))) -;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact $0))) +;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref (exact $0)))) -;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 (ref null exact $0)) +;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 (ref null (exact $0))) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact $0) +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref (exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref null exact $0) +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref null (exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $1 (type $7) (param $0 (ref null exact $2)) +;; CHECK-BIN-NODEBUG: (func $1 (type $7) (param $0 (ref null (exact $2))) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact $2) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref (exact $2)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref null exact $2) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref null (exact $2)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -549,12 +549,12 @@ ;; CHECK-BIN-NODEBUG: (func $2 (type $6) (param $0 (ref null $1)) (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null $1) (ref null exact $2) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null $1) (ref null (exact $2)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null $1) (ref exact $2) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null $1) (ref (exact $2)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -565,12 +565,12 @@ ;; CHECK-BIN-NODEBUG: (func $3 (type $6) (param $0 (ref null $1)) (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null $1) (ref null exact $2) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null $1) (ref null (exact $2)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null $1) (ref exact $2) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null $1) (ref (exact $2)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -578,13 +578,13 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $4 (type $4) (param $0 (ref null exact $0)) (result (ref exact $0)) +;; CHECK-BIN-NODEBUG: (func $4 (type $4) (param $0 (ref null (exact $0))) (result (ref (exact $0))) ;; CHECK-BIN-NODEBUG-NEXT: (ref.as_non_null ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (ref null exact $0)) +;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (ref null (exact $0))) ;; CHECK-BIN-NODEBUG-NEXT: (block $block ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (br_on_null $block @@ -594,8 +594,8 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $6 (type $4) (param $0 (ref null exact $0)) (result (ref exact $0)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact $0)) +;; CHECK-BIN-NODEBUG: (func $6 (type $4) (param $0 (ref null (exact $0))) (result (ref (exact $0))) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref (exact $0))) ;; CHECK-BIN-NODEBUG-NEXT: (br_on_non_null $block ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -603,10 +603,10 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $7 (type $8) (param $0 (ref null exact $0)) (result (ref null exact $0)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null exact $0)) +;; CHECK-BIN-NODEBUG: (func $7 (type $8) (param $0 (ref null (exact $0))) (result (ref null (exact $0))) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null (exact $0))) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null exact $0) (ref null exact $0) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (ref null (exact $0)) (ref null (exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -614,10 +614,10 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $8 (type $4) (param $0 (ref null exact $0)) (result (ref exact $0)) -;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact $0)) +;; CHECK-BIN-NODEBUG: (func $8 (type $4) (param $0 (ref null (exact $0))) (result (ref (exact $0))) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref (exact $0))) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null exact $0) (ref null exact $0) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (ref null (exact $0)) (ref null (exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/passes/coalesce-locals-exact.wast b/test/lit/passes/coalesce-locals-exact.wast index 6021f8a4620..74d9fb6979f 100644 --- a/test/lit/passes/coalesce-locals-exact.wast +++ b/test/lit/passes/coalesce-locals-exact.wast @@ -9,8 +9,8 @@ (module ;; CHECK: (type $struct (struct)) (type $struct (struct)) - ;; CHECK: (func $test (type $1) (param $0 (ref null exact $struct)) (result (ref exact $struct)) - ;; CHECK-NEXT: (local $1 (ref null exact $struct)) + ;; CHECK: (func $test (type $1) (param $0 (ref null (exact $struct))) (result (ref (exact $struct))) + ;; CHECK-NEXT: (local $1 (ref null (exact $struct))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $0) @@ -27,8 +27,8 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $test (param (ref null exact $struct)) (result (ref exact $struct)) - (local $l (ref exact $struct)) + (func $test (param (ref null (exact $struct))) (result (ref (exact $struct))) + (local $l (ref (exact $struct))) ;; This dead set will be optimized out. (local.set $l (ref.as_non_null diff --git a/test/lit/passes/local-subtyping-exact.wast b/test/lit/passes/local-subtyping-exact.wast index 86400f6c441..ad6a1150c07 100644 --- a/test/lit/passes/local-subtyping-exact.wast +++ b/test/lit/passes/local-subtyping-exact.wast @@ -10,8 +10,8 @@ ;; CHECK: (type $struct (struct)) (type $struct (struct)) - ;; CHECK: (func $test (type $1) (param $0 (ref exact $struct)) (result (ref null $struct)) - ;; CHECK-NEXT: (local $1 (ref null exact $struct)) + ;; CHECK: (func $test (type $1) (param $0 (ref (exact $struct))) (result (ref null $struct)) + ;; CHECK-NEXT: (local $1 (ref null (exact $struct))) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -24,8 +24,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - (func $test (param (ref exact $struct)) (result (ref null $struct)) - (local (ref null exact $struct)) + (func $test (param (ref (exact $struct))) (result (ref null $struct)) + (local (ref null (exact $struct))) (if (i32.const 0) (then diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast index c3f42c21ad3..cb603da0e68 100644 --- a/test/lit/passes/optimize-instructions-exact.wast +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -8,14 +8,14 @@ (module ;; CHECK: (type $struct (struct)) (type $struct (struct)) - ;; CHECK: (func $cast-to-exact (type $1) (param $0 anyref) (result (ref exact $struct)) - ;; CHECK-NEXT: (ref.cast (ref exact $struct) + ;; CHECK: (func $cast-to-exact (type $1) (param $0 anyref) (result (ref (exact $struct))) + ;; CHECK-NEXT: (ref.cast (ref (exact $struct)) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast-to-exact (param anyref) (result (ref exact $struct)) + (func $cast-to-exact (param anyref) (result (ref (exact $struct))) ;; This will not be changed, but should not trigger an assertion. - (ref.cast (ref exact $struct) + (ref.cast (ref (exact $struct)) (local.get 0) ) ) diff --git a/test/lit/passes/remove-unused-brs-exact.wast b/test/lit/passes/remove-unused-brs-exact.wast index db946b3a4c7..09d56d71c3d 100644 --- a/test/lit/passes/remove-unused-brs-exact.wast +++ b/test/lit/passes/remove-unused-brs-exact.wast @@ -9,12 +9,12 @@ (module ;; CHECK: (type $struct (struct)) (type $struct (struct)) - ;; CHECK: (func $br_on_cast_fail (type $1) (param $0 (ref null exact $struct)) + ;; CHECK: (func $br_on_cast_fail (type $1) (param $0 (ref null (exact $struct))) ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref null exact $struct) + ;; CHECK-NEXT: (ref.cast (ref null (exact $struct)) ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -24,7 +24,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $br_on_cast_fail (param (ref null exact $struct)) + (func $br_on_cast_fail (param (ref null (exact $struct))) (local $1 (ref null $struct)) (drop (block $block (result (ref $struct)) diff --git a/test/lit/passes/remove-unused-types-exact.wast b/test/lit/passes/remove-unused-types-exact.wast index 754412c0b91..56054334126 100644 --- a/test/lit/passes/remove-unused-types-exact.wast +++ b/test/lit/passes/remove-unused-types-exact.wast @@ -10,10 +10,10 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct)) (type $struct (struct)) - ;; CHECK: (func $return-exact (type $1) (result (ref null exact $struct)) + ;; CHECK: (func $return-exact (type $1) (result (ref null (exact $struct))) ;; CHECK-NEXT: (call $return-exact) ;; CHECK-NEXT: ) - (func $return-exact (result (ref null exact $struct)) + (func $return-exact (result (ref null (exact $struct))) (call $return-exact) ) ) From 4e1eaa70c22867bba85e7e30b28f4cc2c1ac99ae Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Apr 2025 21:06:06 -0700 Subject: [PATCH 432/622] Update the binary format for exact heap types (#7502) Although we now store exactness on `Type` instead of `HeapType`, the binary format still encodes exactness as part of the heap type. Update the binary parser to read exactness wherever a heap type (but not just a type index) is expected and update the binary writer similarly. --- src/wasm-binary.h | 12 +-- src/wasm-type.h | 2 +- src/wasm/wasm-binary.cpp | 165 +++++++++++++++++++-------------------- src/wasm/wasm-stack.cpp | 62 ++++----------- 4 files changed, 104 insertions(+), 137 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 211b15183d8..95725eff47a 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -307,8 +307,6 @@ enum SegmentFlag { enum BrOnCastFlag { InputNullable = 1 << 0, OutputNullable = 1 << 1, - InputExact = 1 << 2, - OutputExact = 1 << 3, }; enum EncodedType { @@ -334,7 +332,6 @@ enum EncodedType { eqref = -0x13, // 0x6d nonnullable = -0x1c, // 0x64 nullable = -0x1d, // 0x63 - exact = -0x1e, // 0x62 contref = -0x18, // 0x68 nullcontref = -0x0b, // 0x75 // exception handling @@ -351,6 +348,8 @@ enum EncodedType { SubFinal = 0x4f, Shared = 0x65, SharedLEB = -0x1b, // Also 0x65 as an SLEB128 + Exact = 0x62, + ExactLEB = -0x1e, // Also 0x62 as an SLEB128 Rec = 0x4e, Descriptor = 0x4d, Describes = 0x4c, @@ -1135,8 +1134,6 @@ enum ASTNodes { I31GetS = 0x1d, I31GetU = 0x1e, RefI31Shared = 0x1f, - RefTestRT = 0x20, - RefCastRT = 0x21, // Shared GC Opcodes @@ -1378,7 +1375,7 @@ class WasmBinaryWriter { // Writes an arbitrary heap type, which may be indexed or one of the // basic types like funcref. - void writeHeapType(HeapType type); + void writeHeapType(HeapType type, Exactness exact); // Writes an indexed heap type. Note that this is encoded differently than a // general heap type because it does not allow negative values for basic heap // types. @@ -1509,8 +1506,7 @@ class WasmBinaryReader { Type getType(); // Get a type given the initial S32LEB has already been read, and is provided. Type getType(int code); - Type getTypeNoExact(int code); - HeapType getHeapType(); + std::pair getHeapType(); HeapType getIndexedHeapType(); Type getConcreteType(); diff --git a/src/wasm-type.h b/src/wasm-type.h index ba69bcbb623..7f0cde2c48f 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -382,7 +382,7 @@ class Type { bool isExact() const { return isRef() && !getHeapType().isBasic() && (id & ExactMask); } - bool isInexact() const { return isRef() && !(id & ExactMask); } + bool isInexact() const { return isRef() && !isExact(); } HeapType getHeapType() const { assert(isRef()); HeapType masked(id & ~NullMask); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 5f113a93811..12b123c880e 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -279,7 +279,7 @@ void WasmBinaryWriter::writeTypes() { } if (super) { o << U32LEB(1); - writeHeapType(*super); + writeHeapType(*super, Inexact); } else { o << U32LEB(0); } @@ -289,11 +289,11 @@ void WasmBinaryWriter::writeTypes() { } if (auto desc = type.getDescribedType()) { o << uint8_t(BinaryConsts::EncodedType::Describes); - writeHeapType(*desc); + writeHeapType(*desc, Inexact); } if (auto desc = type.getDescriptorType()) { o << uint8_t(BinaryConsts::EncodedType::Descriptor); - writeHeapType(*desc); + writeHeapType(*desc, Inexact); } switch (type.getKind()) { case HeapTypeKind::Func: { @@ -322,7 +322,7 @@ void WasmBinaryWriter::writeTypes() { break; case HeapTypeKind::Cont: o << uint8_t(BinaryConsts::EncodedType::Cont); - writeHeapType(type.getContinuation().type); + writeHeapType(type.getContinuation().type, Inexact); break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); @@ -1574,12 +1574,6 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) { void WasmBinaryWriter::writeType(Type type) { if (type.isRef()) { - // Exact references are introduced by the custom descriptors feature, but - // can be used internally even when it is not enabled. In that case, we have - // to generalize the types to be inexact before writing them. - if (!wasm->features.hasCustomDescriptors()) { - type = Type(type.getHeapType(), type.getNullability(), Inexact); - } // The only reference types allowed without GC are funcref, externref, and // exnref. We internally use more refined versions of those types, but we // cannot emit those without GC. @@ -1596,12 +1590,6 @@ void WasmBinaryWriter::writeType(Type type) { type = Type(type.getHeapType().getTop(), Nullable); } } - // If the type is exact, emit the exact prefix and continue on without - // considering exactness. - if (type.isExact()) { - o << S32LEB(BinaryConsts::EncodedType::exact); - type = Type(type.getHeapType(), type.getNullability(), Inexact); - } auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { switch (heapType.getBasic(Unshared)) { @@ -1657,7 +1645,7 @@ void WasmBinaryWriter::writeType(Type type) { } else { o << S32LEB(BinaryConsts::EncodedType::nonnullable); } - writeHeapType(type.getHeapType()); + writeHeapType(type.getHeapType(), type.getExactness()); return; } int ret = 0; @@ -1688,14 +1676,20 @@ void WasmBinaryWriter::writeType(Type type) { o << S32LEB(ret); } -void WasmBinaryWriter::writeHeapType(HeapType type) { +void WasmBinaryWriter::writeHeapType(HeapType type, Exactness exactness) { // ref.null always has a bottom heap type in Binaryen IR, but those types are // only actually valid with GC. Otherwise, emit the corresponding valid top // types instead. + if (!wasm->features.hasCustomDescriptors()) { + exactness = Inexact; + } if (!wasm->features.hasGC()) { type = type.getTop(); } - + assert(!type.isBasic() || exactness == Inexact); + if (exactness == Exact) { + o << uint8_t(BinaryConsts::EncodedType::Exact); + } if (!type.isBasic()) { o << S64LEB(getTypeIndex(type)); // TODO: Actually s33 return; @@ -2190,43 +2184,41 @@ Signature WasmBinaryReader::getBlockType() { return Signature(Type::none, getType(code)); } -Type WasmBinaryReader::getTypeNoExact(int code) { +Type WasmBinaryReader::getType(int code) { Type type; if (getBasicType(code, type)) { return type; } + auto [heapType, exactness] = getHeapType(); switch (code) { case BinaryConsts::EncodedType::nullable: - return Type(getHeapType(), Nullable); + return Type(heapType, Nullable, exactness); case BinaryConsts::EncodedType::nonnullable: - return Type(getHeapType(), NonNullable); + return Type(heapType, NonNullable, exactness); default: throwError("invalid wasm type: " + std::to_string(code)); } WASM_UNREACHABLE("unexpected type"); } -Type WasmBinaryReader::getType(int code) { - if (code == BinaryConsts::EncodedType::exact) { - auto type = getTypeNoExact(getS32LEB()); - if (!type.isRef()) { - throwError("invalid exact prefix on non-reference type"); - } - return Type(type.getHeapType(), type.getNullability(), Exact); - } - return getTypeNoExact(code); -} - Type WasmBinaryReader::getType() { return getType(getS32LEB()); } -HeapType WasmBinaryReader::getHeapType() { +std::pair WasmBinaryReader::getHeapType() { auto type = getS64LEB(); // TODO: Actually s33 + auto exactness = Inexact; + if (type == BinaryConsts::EncodedType::ExactLEB) { + exactness = Exact; + type = getS64LEB(); // TODO: Actually s33 + } // Single heap types are negative; heap type indices are non-negative if (type >= 0) { if (size_t(type) >= types.size()) { - throwError("invalid signature index: " + std::to_string(type)); + throwError("invalid type index: " + std::to_string(type)); } - return types[type]; + return {types[type], exactness}; + } + if (exactness == Exact) { + throwError("invalid type index: " + std::to_string(type)); } auto share = Unshared; if (type == BinaryConsts::EncodedType::SharedLEB) { @@ -2235,11 +2227,9 @@ HeapType WasmBinaryReader::getHeapType() { } HeapType ht; if (getBasicHeapType(type, ht)) { - return ht.getBasic(share); - } else { - throwError("invalid wasm heap type: " + std::to_string(type)); + return {ht.getBasic(share), Inexact}; } - WASM_UNREACHABLE("unexpected type"); + throwError("invalid wasm heap type: " + std::to_string(type)); } HeapType WasmBinaryReader::getIndexedHeapType() { @@ -2361,8 +2351,22 @@ void WasmBinaryReader::readMemories() { void WasmBinaryReader::readTypes() { TypeBuilder builder(getU32LEB()); - auto readHeapType = [&]() -> HeapType { + auto readHeapType = [&]() -> std::pair { int64_t htCode = getS64LEB(); // TODO: Actually s33 + auto exactness = Inexact; + if (htCode == BinaryConsts::EncodedType::ExactLEB) { + exactness = Exact; + htCode = getS64LEB(); // TODO: Actually s33 + } + if (htCode >= 0) { + if (size_t(htCode) >= builder.size()) { + throwError("invalid type index: " + std::to_string(htCode)); + } + return {builder.getTempHeapType(size_t(htCode)), exactness}; + } + if (exactness == Exact) { + throwError("invalid type index: " + std::to_string(htCode)); + } auto share = Unshared; if (htCode == BinaryConsts::EncodedType::SharedLEB) { share = Shared; @@ -2370,14 +2374,11 @@ void WasmBinaryReader::readTypes() { } HeapType ht; if (getBasicHeapType(htCode, ht)) { - return ht.getBasic(share); - } - if (size_t(htCode) >= builder.size()) { - throwError("invalid type index: " + std::to_string(htCode)); + return {ht.getBasic(share), Inexact}; } - return builder.getTempHeapType(size_t(htCode)); + throwError("invalid wasm heap type: " + std::to_string(htCode)); }; - auto makeTypeNoExact = [&](int32_t typeCode) { + auto makeType = [&](int32_t typeCode) { Type type; if (getBasicType(typeCode, type)) { return type; @@ -2390,29 +2391,18 @@ void WasmBinaryReader::readTypes() { ? Nullable : NonNullable; - HeapType ht = readHeapType(); + auto [ht, exactness] = readHeapType(); if (ht.isBasic()) { - return Type(ht, nullability); + return Type(ht, nullability, exactness); } - return builder.getTempRefType(ht, nullability); + return builder.getTempRefType(ht, nullability, exactness); } default: throwError("unexpected type index: " + std::to_string(typeCode)); } WASM_UNREACHABLE("unexpected type"); }; - auto makeType = [&](int32_t typeCode) { - if (typeCode == BinaryConsts::EncodedType::exact) { - auto type = makeTypeNoExact(getS32LEB()); - if (!type.isRef()) { - throwError("unexpected exact prefix on non-reference type"); - } - return builder.getTempRefType( - type.getHeapType(), type.getNullability(), Exact); - } - return makeTypeNoExact(typeCode); - }; auto readType = [&]() { return makeType(getS32LEB()); }; auto readSignatureDef = [&]() { @@ -2431,7 +2421,10 @@ void WasmBinaryReader::readTypes() { }; auto readContinuationDef = [&]() { - HeapType ht = readHeapType(); + auto [ht, exactness] = readHeapType(); + if (exactness != Inexact) { + throw ParseException("invalid exact type in cont definition"); + } if (!ht.isSignature()) { throw ParseException("cont types must be built from function types"); } @@ -3046,8 +3039,12 @@ Result<> WasmBinaryReader::readInst() { return builder.visitCatchAll(); case BinaryConsts::Delegate: return builder.visitDelegate(getU32LEB()); - case BinaryConsts::RefNull: - return builder.makeRefNull(getHeapType()); + case BinaryConsts::RefNull: { + auto [heapType, exactness] = getHeapType(); + // Exactness is allowed but doesn't matter, since we always use the bottom + // heap type. + return builder.makeRefNull(heapType); + } case BinaryConsts::RefIsNull: return builder.makeRefIsNull(); case BinaryConsts::RefFunc: @@ -4282,34 +4279,36 @@ Result<> WasmBinaryReader::readInst() { return builder.makeI31Get(true); case BinaryConsts::I31GetU: return builder.makeI31Get(false); - case BinaryConsts::RefTest: - return builder.makeRefTest(Type(getHeapType(), NonNullable)); - case BinaryConsts::RefTestNull: - return builder.makeRefTest(Type(getHeapType(), Nullable)); - case BinaryConsts::RefTestRT: - return builder.makeRefTest(getType()); - case BinaryConsts::RefCast: - return builder.makeRefCast(Type(getHeapType(), NonNullable)); - case BinaryConsts::RefCastNull: - return builder.makeRefCast(Type(getHeapType(), Nullable)); - case BinaryConsts::RefCastRT: - return builder.makeRefCast(getType()); + case BinaryConsts::RefTest: { + auto [heapType, exactness] = getHeapType(); + return builder.makeRefTest(Type(heapType, NonNullable, exactness)); + } + case BinaryConsts::RefTestNull: { + auto [heapType, exactness] = getHeapType(); + return builder.makeRefTest(Type(heapType, Nullable, exactness)); + } + case BinaryConsts::RefCast: { + auto [heapType, exactness] = getHeapType(); + return builder.makeRefCast(Type(heapType, NonNullable, exactness)); + } + case BinaryConsts::RefCastNull: { + auto [heapType, exactness] = getHeapType(); + return builder.makeRefCast(Type(heapType, Nullable, exactness)); + } case BinaryConsts::BrOnCast: case BinaryConsts::BrOnCastFail: { auto flags = getInt8(); - auto label = getU32LEB(); auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) ? Nullable : NonNullable; auto dstNull = (flags & BinaryConsts::BrOnCastFlag::OutputNullable) ? Nullable : NonNullable; - auto srcExact = - (flags & BinaryConsts::BrOnCastFlag::InputExact) ? Exact : Inexact; - auto dstExact = - (flags & BinaryConsts::BrOnCastFlag::OutputExact) ? Exact : Inexact; - auto in = Type(getHeapType(), srcNull, srcExact); - auto cast = Type(getHeapType(), dstNull, dstExact); + auto label = getU32LEB(); + auto [srcType, srcExact] = getHeapType(); + auto [dstType, dstExact] = getHeapType(); + auto in = Type(srcType, srcNull, srcExact); + auto cast = Type(dstType, dstNull, dstExact); auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; return builder.makeBrOn(label, kind, in, cast); } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 506b11a085e..6f40a1ba3ab 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2074,7 +2074,8 @@ void BinaryInstWriter::visitMemoryGrow(MemoryGrow* curr) { void BinaryInstWriter::visitRefNull(RefNull* curr) { o << int8_t(BinaryConsts::RefNull); - parent.writeHeapType(curr->type.getHeapType()); + assert(curr->type.isInexact()); + parent.writeHeapType(curr->type.getHeapType(), Inexact); } void BinaryInstWriter::visitRefIsNull(RefIsNull* curr) { @@ -2260,38 +2261,23 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { void BinaryInstWriter::visitRefTest(RefTest* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->castType.isExact() && - parent.getModule()->features.hasCustomDescriptors()) { - // Fall back to the general form with a reftype immediate. - o << U32LEB(BinaryConsts::RefTestRT); - parent.writeType(curr->castType); + if (curr->castType.isNullable()) { + o << U32LEB(BinaryConsts::RefTestNull); } else { - // Use the special-case form with heap type immediate. - if (curr->castType.isNullable()) { - o << U32LEB(BinaryConsts::RefTestNull); - } else { - o << U32LEB(BinaryConsts::RefTest); - } - parent.writeHeapType(curr->castType.getHeapType()); + o << U32LEB(BinaryConsts::RefTest); } + parent.writeHeapType(curr->castType.getHeapType(), + curr->castType.getExactness()); } void BinaryInstWriter::visitRefCast(RefCast* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->type.isExact() && - parent.getModule()->features.hasCustomDescriptors()) { - // Fall back to the general form with a reftype immediate. - o << U32LEB(BinaryConsts::RefCastRT); - parent.writeType(curr->type); + if (curr->type.isNullable()) { + o << U32LEB(BinaryConsts::RefCastNull); } else { - // Use the special-case form with heap type immediate. - if (curr->type.isNullable()) { - o << U32LEB(BinaryConsts::RefCastNull); - } else { - o << U32LEB(BinaryConsts::RefCast); - } - parent.writeHeapType(curr->type.getHeapType()); + o << U32LEB(BinaryConsts::RefCast); } + parent.writeHeapType(curr->type.getHeapType(), curr->type.getExactness()); } void BinaryInstWriter::visitBrOn(BrOn* curr) { @@ -2314,28 +2300,14 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { } assert(curr->ref->type.isRef()); assert(Type::isSubType(curr->castType, curr->ref->type)); - uint8_t flags = 0; - if (curr->ref->type.isNullable()) { - flags |= BinaryConsts::BrOnCastFlag::InputNullable; - } - if (curr->castType.isNullable()) { - flags |= BinaryConsts::BrOnCastFlag::OutputNullable; - } - if (parent.getModule()->features.hasCustomDescriptors()) { - // If custom descriptors (and therefore exact references) are not - // enabled, then these flags wouldn't be recognized, and we will be - // generalizing all exact references to be non-exact anyway. - if (curr->ref->type.isExact()) { - flags |= BinaryConsts::BrOnCastFlag::InputExact; - } - if (curr->castType.isExact()) { - flags |= BinaryConsts::BrOnCastFlag::OutputExact; - } - } + uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | + (curr->castType.isNullable() ? 2 : 0); o << flags; o << U32LEB(getBreakIndex(curr->name)); - parent.writeHeapType(curr->ref->type.getHeapType()); - parent.writeHeapType(curr->castType.getHeapType()); + parent.writeHeapType(curr->ref->type.getHeapType(), + curr->ref->type.getExactness()); + parent.writeHeapType(curr->castType.getHeapType(), + curr->castType.getExactness()); return; } } From cfb9ff1cd971d363c51c0df72a5f63666c846ea8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 16 Apr 2025 13:11:06 -0700 Subject: [PATCH 433/622] [SIMD] wasm-ctor-eval: Fix relaxed SIMD in expressions (#7508) The previous fix #7495 handled functions computing something with relaxed SIMD, in which case an exception is thrown, but we also need to handle relaxed SIMD inside a function, since wasm-ctor-eval can partially-evaluate code. In that case we get NONCONSTANT_FLOW. Turns out adding --kept-exports is enough to test this, as keeping the exports around forces partial evaluation of their contents. --- src/tools/wasm-ctor-eval.cpp | 9 ++++++++- test/lit/ctor-eval/relaxed.wast | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index c4dcb6d4532..481c42cb591 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -1115,7 +1115,14 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, break; } catch (NonconstantException& fail) { if (!quiet) { - std::cout << " ...stopping due to non-constant code\n"; + std::cout << " ...stopping due to non-constant func\n"; + } + break; + } + + if (flow.breakTo == NONCONSTANT_FLOW) { + if (!quiet) { + std::cout << " ...stopping due to non-constant flow\n"; } break; } diff --git a/test/lit/ctor-eval/relaxed.wast b/test/lit/ctor-eval/relaxed.wast index fd1dc46e216..a2973ad9f65 100644 --- a/test/lit/ctor-eval/relaxed.wast +++ b/test/lit/ctor-eval/relaxed.wast @@ -1,5 +1,5 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: wasm-ctor-eval %s --ctors=return,drop,passthrough --quiet -all -S -o - | filecheck %s +;; RUN: wasm-ctor-eval %s --ctors=return,drop,passthrough --kept-exports=return,drop,passthrough --quiet -all -S -o - | filecheck %s ;; The relaxed SIMD operation here cannot be optimized by wasm-ctor-eval, as we ;; do not know how the VM that the code will run on should execute it. We can @@ -10,6 +10,8 @@ ;; CHECK: (type $1 (func)) + ;; CHECK: (export "return" (func $return)) + ;; CHECK: (export "drop" (func $drop)) ;; CHECK: (export "passthrough" (func $passthrough)) From 0db418ad830496a097c4663d619faaf90b30a7af Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 16 Apr 2025 13:11:32 -0700 Subject: [PATCH 434/622] [Fuzzing] Simplify V8 fuzzing flags: use --fuzzing over specific EH flag (#7509) ClusterFuzz already applies this flag, and it implies the EH flag, so we might as well do it the way V8 does it. This is simpler, though it does mean we are testing something even more different than production - but in the way V8 believes is best for fuzzing, at least. --- scripts/clusterfuzz/run.py | 4 +--- scripts/fuzz_opt.py | 17 ++++++++--------- test/unit/test_cluster_fuzz.py | 2 +- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index d44bde05cd4..4e7bf6659f0 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -33,9 +33,7 @@ # The V8 flags we put in the "fuzzer flags" files, which tell ClusterFuzz how to # run V8. By default we apply all staging flags. -# -# We also allow mixed EH, see the comment on the same flag in fuzz_opt.py -FUZZER_FLAGS_FILE_CONTENTS = '--wasm-staging --wasm-allow-mixed-eh-for-testing' +FUZZER_FLAGS_FILE_CONTENTS = '--wasm-staging' # Maximum size of the random data that we feed into wasm-opt -ttf. This is # smaller than fuzz_opt.py's INPUT_SIZE_MAX because that script is tuned for diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 5d3c2756246..80052431da7 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -670,15 +670,10 @@ def run_bynterp(wasm, args): # Enable even more things than V8_OPTS. V8_OPTS are the flags we want to use # when testing, on our fixed test suite, but when fuzzing we may want more. def get_v8_extra_flags(): - # Due to https://github.com/WebAssembly/exception-handling/issues/344 , VMs - # do not allow mixed old and new wasm EH. Our fuzzer will very frequently - # mix those instructions in a module, so we must use the flag to allow that. - # FIXME This is not great, as the majority of the wasm files we test on are - # not actually valid in VMs. But we get coverage this way for runtime - # linking of old and new EH (which VMs allow), that is, our compile- - # time combination of old and new simulates runtime linking to some - # extent. - flags = ['--wasm-allow-mixed-eh-for-testing'] + # It is important to use the --fuzzing flag because it does things like + # enable mixed old and new EH (which is an issue since + # https://github.com/WebAssembly/exception-handling/issues/344 ) + flags = ['--fuzzing'] # Sometimes add --future, which may enable new JITs and such, which is good # to fuzz for V8's sake. @@ -1663,6 +1658,10 @@ def handle(self, wasm): with open(flags_file, 'r') as f: flags = f.read() cmd += flags.split(' ') + # Get V8's extra fuzzing flags, the same as the ClusterFuzz runner does + # (as can be seen from the testcases having --fuzzing and a lot of other + # flags as well). + cmd += get_v8_extra_flags() # Run the fuzz file, which contains a modified fuzz_shell.js - we do # *not* run fuzz_shell.js normally. cmd.append(os.path.abspath(fuzz_file)) diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 6965e5f7c39..72197939d76 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -186,7 +186,7 @@ def test_file_contents(self): # The flags file must contain --wasm-staging with open(flags_file) as f: - self.assertEqual(f.read(), '--wasm-staging --wasm-allow-mixed-eh-for-testing') + self.assertEqual(f.read(), '--wasm-staging') # Extract the wasm file(s) from the JS. Make sure to not notice # stale files. From 1623ea8d97b4b7cb2fbd826259320352268a59c5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 16 Apr 2025 18:31:52 -0700 Subject: [PATCH 435/622] Preserve exactness when updating types (#7512) Fix a place where we failed to propagate exactness in type-updating.cpp. --- scripts/test/fuzzing.py | 1 + src/ir/type-updating.cpp | 5 ++- test/lit/passes/signature-refining-exact.wast | 31 +++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 test/lit/passes/signature-refining-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 19f3df3ef5e..08c11a3bbe3 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -118,6 +118,7 @@ 'remove-unused-types-exact.wast', 'coalesce-locals-exact.wast', 'remove-unused-brs-exact.wast', + 'signature-refining-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 59286734720..a05b316823c 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -346,9 +346,8 @@ Type GlobalTypeRewriter::getTempType(Type type) { if (type.isRef()) { auto heapType = type.getHeapType(); if (auto it = typeIndices.find(heapType); it != typeIndices.end()) { - // TODO: Handle exactness. - return typeBuilder.getTempRefType(typeBuilder[it->second], - type.getNullability()); + return typeBuilder.getTempRefType( + typeBuilder[it->second], type.getNullability(), type.getExactness()); } // This type is not one that is eligible for optimizing. That is fine; just // use it unmodified. diff --git a/test/lit/passes/signature-refining-exact.wast b/test/lit/passes/signature-refining-exact.wast new file mode 100644 index 00000000000..ecc495ed101 --- /dev/null +++ b/test/lit/passes/signature-refining-exact.wast @@ -0,0 +1,31 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s --signature-refining -all -S -o - | filecheck %s + +;; Check that we can refine signatures to contain exact references correctly. + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $f (func)) + (type $f (func)) + + ;; CHECK: (type $1 (func (param (ref null (exact $f))) (result (ref null (exact $f))))) + + ;; CHECK: (func $foo (type $1) (param $0 (ref null (exact $f))) (result (ref null (exact $f))) + ;; CHECK-NEXT: (local $1 (ref null (exact $f))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $foo + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $foo (param funcref) (result funcref) + (local (ref null (exact $f))) + (drop + (call $foo + (local.get 1) + ) + ) + (local.get 1) + ) +) From da4652b3ac3ca4382b557774e87055893cd60bfd Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 17 Apr 2025 09:29:11 -0700 Subject: [PATCH 436/622] Disallow exact casts without custom descriptors enabled (#7510) When we emit modules containing exact types without custom descriptors enabled, we generalize those types to be inexact so the written module is valid for the feature set. This fudging of the types changes the behavior of casts, though; casts to exact types that should have failed at runtime may in fact succeed after the binary is written and the cast target type is fudged to be inexact. To avoid the binary writer introducing this change in behavior, disallow casts to exact types when custom descriptors is not enabled. This will require optimizations to avoid introducing casts to exact types when custom descriptors is not enabled. We hypothesize that this will be simpler than the alternative, which would be to have all optimizations (and the interpreter) treat casts to exact types as casts to inexact types when custom descriptors is disabled. Test the new validation errors and and update an existing test that this change invalidates by splitting off the part that depended on custom descriptors being disabled into a separate test. --- scripts/test/fuzzing.py | 2 + src/wasm/wasm-validator.cpp | 17 +++ test/lit/basic/exact-references-lowering.wast | 24 ++++ test/lit/basic/exact-references.wast | 125 +----------------- test/lit/validation/exact-casts.wast | 52 ++++++++ 5 files changed, 96 insertions(+), 124 deletions(-) create mode 100644 test/lit/basic/exact-references-lowering.wast create mode 100644 test/lit/validation/exact-casts.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 08c11a3bbe3..811c780e0fb 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -113,6 +113,8 @@ 'vacuum-stack-switching.wast', # TODO: fuzzer support for exact references 'exact-references.wast', + 'exact-references-lowering.wast', + 'exact-casts.wast', 'optimize-instructions-exact.wast', 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index e33d5c3ef0d..0785956c86b 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2903,6 +2903,12 @@ void FunctionValidator::visitRefTest(RefTest* curr) { curr->ref->type.getHeapType().getBottom(), curr, "ref.test target type and ref type must have a common supertype"); + + shouldBeTrue(curr->castType.isInexact() || + getModule()->features.hasCustomDescriptors(), + curr, + "ref.test of exact type requires custom descriptors " + "[--enable-custom-descriptors]"); } void FunctionValidator::visitRefCast(RefCast* curr) { @@ -2941,6 +2947,12 @@ void FunctionValidator::visitRefCast(RefCast* curr) { shouldBeTrue(curr->ref->type.isNullable() || curr->type.isNonNullable(), curr, "ref.cast null of non-nullable references are not allowed"); + + shouldBeTrue(curr->type.isInexact() || + getModule()->features.hasCustomDescriptors(), + curr, + "ref.cast to exact type requires custom descriptors " + "[--enable-custom-descriptors]"); } void FunctionValidator::visitBrOn(BrOn* curr) { @@ -2970,6 +2982,11 @@ void FunctionValidator::visitBrOn(BrOn* curr) { curr->ref->type, curr, "br_on_cast* target type must be a subtype of its input type"); + shouldBeTrue(curr->castType.isInexact() || + getModule()->features.hasCustomDescriptors(), + curr, + "br_on_cast* to exact type requires custom descriptors " + "[--enable-custom-descriptors]"); } else { shouldBeEqual(curr->castType, Type(Type::none), diff --git a/test/lit/basic/exact-references-lowering.wast b/test/lit/basic/exact-references-lowering.wast new file mode 100644 index 00000000000..466b02fea05 --- /dev/null +++ b/test/lit/basic/exact-references-lowering.wast @@ -0,0 +1,24 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Check that if we emit a binary without custom descriptors enabled, the exact +;; types are generalized to be inexact. + +;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o %t.noexact.wasm +;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $foo (struct (field (ref null $foo)) (field (ref $foo)))) + (type $foo (struct (field (ref null (exact $foo)) (ref (exact $foo))))) + + ;; CHECK: (type $1 (func (param (ref $foo)))) + + ;; CHECK: (global $g (ref null $foo) (ref.null none)) + (global $g (ref null (exact $foo)) (ref.null none)) + + ;; CHECK: (func $f (type $1) (param $0 (ref $foo)) + ;; CHECK-NEXT: (local $1 (ref null $foo)) + ;; CHECK-NEXT: ) + (func $f (param (ref (exact $foo))) + (local (ref null (exact $foo))) + ) +) diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index 09e5bf48236..563454aa5f4 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -9,16 +9,9 @@ ;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN ;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG -;; Also check that if we emit a binary without custom descriptors enabled, the -;; types are generalized to be inexact. - -;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o %t.noexact.wasm -;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s --check-prefix=NO-EXACT - (module ;; CHECK-TEXT: (type $foo (struct (field (ref null (exact $foo))) (field (ref (exact $foo))))) ;; CHECK-BIN: (type $foo (struct (field (ref null (exact $foo))) (field (ref (exact $foo))))) - ;; NO-EXACT: (type $foo (struct (field (ref null $foo)) (field (ref $foo)))) (type $foo (struct (field (ref null (exact $foo)) (ref (exact $foo))))) (rec @@ -26,19 +19,16 @@ ;; CHECK-TEXT-NEXT: (type $super (sub (struct))) ;; CHECK-BIN: (rec ;; CHECK-BIN-NEXT: (type $super (sub (struct))) - ;; NO-EXACT: (rec - ;; NO-EXACT-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) ;; CHECK-TEXT: (type $sub1 (sub $super (struct))) ;; CHECK-BIN: (type $sub1 (sub $super (struct))) - ;; NO-EXACT: (type $sub1 (sub $super (struct))) (type $sub1 (sub $super (struct))) ;; CHECK-TEXT: (type $sub2 (sub $super (struct))) ;; CHECK-BIN: (type $sub2 (sub $super (struct))) - ;; NO-EXACT: (type $sub2 (sub $super (struct))) (type $sub2 (sub $super (struct))) ) + ;; CHECK-TEXT: (type $4 (func (param (ref null (exact $foo))) (result (ref (exact $foo))))) ;; CHECK-TEXT: (type $5 (func (param (ref null (exact $foo))))) @@ -61,22 +51,10 @@ ;; CHECK-BIN: (type $8 (func (param (ref null (exact $foo))) (result (ref null (exact $foo))))) ;; CHECK-BIN: (import "" "g1" (global $g1 (ref null (exact $foo)))) - ;; NO-EXACT: (type $4 (func (param (ref null $foo)) (result (ref $foo)))) - - ;; NO-EXACT: (type $5 (func (param (ref null $foo)))) - - ;; NO-EXACT: (type $6 (func (param (ref null $super)) (result (ref null $super)))) - - ;; NO-EXACT: (type $7 (func (param (ref null $sub1)))) - - ;; NO-EXACT: (type $8 (func (param (ref null $foo)) (result (ref null $foo)))) - - ;; NO-EXACT: (import "" "g1" (global $g1 (ref null $foo))) (import "" "g1" (global $g1 (ref null (exact $foo)))) ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref (exact $foo)))) ;; CHECK-BIN: (import "" "g2" (global $g2 (ref (exact $foo)))) - ;; NO-EXACT: (import "" "g2" (global $g2 (ref $foo))) (import "" "g2" (global $g2 (ref (exact $foo)))) ;; CHECK-TEXT: (func $ref-test (type $5) (param $0 (ref null (exact $foo))) @@ -103,18 +81,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-test (type $5) (param $0 (ref null $foo)) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test (ref $foo) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test (ref null $foo) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $ref-test (param (ref null (exact $foo))) (drop (ref.test (ref (exact $foo)) @@ -162,23 +128,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-cast (type $7) (param $0 (ref null $sub1)) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.cast (ref $sub1) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.cast (ref null $sub1) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.cast (ref none) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $ref-cast (param (ref null (exact $sub1))) (drop (ref.cast (ref (exact $sub1)) @@ -227,21 +176,6 @@ ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $br-on-cast (type $6) (param $0 (ref null $super)) (result (ref null $super)) - ;; NO-EXACT-NEXT: (block $block (result (ref null $super)) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block (ref null $super) (ref null $sub1) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block (ref null $super) (ref $sub1) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $br-on-cast (param (ref null $super)) (result (ref null $super)) (drop (br_on_cast 0 anyref (ref null (exact $sub1)) @@ -286,21 +220,6 @@ ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $br-on-cast-fail (type $6) (param $0 (ref null $super)) (result (ref null $super)) - ;; NO-EXACT-NEXT: (block $block (result (ref null $super)) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block (ref null $super) (ref null $sub1) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block (ref null $super) (ref $sub1) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $br-on-cast-fail (param (ref null $super)) (result (ref null $super)) (drop (br_on_cast_fail 0 anyref (ref null (exact $sub1)) @@ -325,11 +244,6 @@ ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-ref-as-non-null (type $4) (param $0 (ref null $foo)) (result (ref $foo)) - ;; NO-EXACT-NEXT: (ref.as_non_null - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $valid-ref-as-non-null (param (ref null (exact $foo))) (result (ref (exact $foo))) (ref.as_non_null (local.get 0) @@ -356,15 +270,6 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-null (type $5) (param $0 (ref null $foo)) - ;; NO-EXACT-NEXT: (block $block - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_null $block - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $valid-br-on-null (param (ref null (exact $foo))) (drop (block (result (ref (exact $foo))) @@ -391,14 +296,6 @@ ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-non-null (type $4) (param $0 (ref null $foo)) (result (ref $foo)) - ;; NO-EXACT-NEXT: (block $block (result (ref $foo)) - ;; NO-EXACT-NEXT: (br_on_non_null $block - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (unreachable) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $valid-br-on-non-null (param (ref null (exact $foo))) (result (ref (exact $foo))) (br_on_non_null 0 (local.get 0) @@ -428,16 +325,6 @@ ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-cast (type $8) (param $0 (ref null $foo)) (result (ref null $foo)) - ;; NO-EXACT-NEXT: (block $block (result (ref null $foo)) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast $block (ref null $foo) (ref null $foo) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (unreachable) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $valid-br-on-cast (param (ref null (exact $foo))) (result (ref null (exact $foo))) (drop (block (result (ref (exact $foo))) @@ -471,16 +358,6 @@ ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $valid-br-on-cast-fail (type $4) (param $0 (ref null $foo)) (result (ref $foo)) - ;; NO-EXACT-NEXT: (block $block (result (ref $foo)) - ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (br_on_cast_fail $block (ref null $foo) (ref null $foo) - ;; NO-EXACT-NEXT: (local.get $0) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: (unreachable) - ;; NO-EXACT-NEXT: ) - ;; NO-EXACT-NEXT: ) (func $valid-br-on-cast-fail (param (ref null (exact $foo))) (result (ref (exact $foo))) (drop (block (result (ref null (exact $foo))) diff --git a/test/lit/validation/exact-casts.wast b/test/lit/validation/exact-casts.wast new file mode 100644 index 00000000000..1b5dc846ade --- /dev/null +++ b/test/lit/validation/exact-casts.wast @@ -0,0 +1,52 @@ +;; Test that casting to exact types without custom descriptors is a validation +;; error. + +;; RUN: not wasm-opt %s -all --disable-custom-descriptors 2>&1 | filecheck %s + +;; CHECK: [wasm-validator error in function ref.cast] unexpected false: ref.cast to exact type requires custom descriptors [--enable-custom-descriptors], + +;; CHECK: [wasm-validator error in function ref.test] unexpected false: ref.test of exact type requires custom descriptors [--enable-custom-descriptors], + +;; CHECK: [wasm-validator error in function br_on_cast] unexpected false: br_on_cast* to exact type requires custom descriptors [--enable-custom-descriptors], + +;; CHECK: [wasm-validator error in function br_on_cast_fail] unexpected false: br_on_cast* to exact type requires custom descriptors [--enable-custom-descriptors], + +(module + (type $foo (struct)) + + (func $ref.cast (param anyref) + (drop + (ref.cast (ref null (exact $foo)) + (local.get 0) + ) + ) + ) + + (func $ref.test (param anyref) + (drop + (ref.test (ref (exact $foo)) + (local.get 0) + ) + ) + ) + + (func $br_on_cast (param anyref) + (drop + (block (result anyref) + (br_on_cast 0 anyref (ref null (exact $foo)) + (local.get 0) + ) + ) + ) + ) + + (func $br_on_cast_fail (param anyref) + (drop + (block (result anyref) + (br_on_cast_fail 0 anyref (ref (exact $foo)) + (local.get 0) + ) + ) + ) + ) +) From 355fae656b9d5c7aa4f0a3a16c4107b015abfb5d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 17 Apr 2025 16:23:27 -0700 Subject: [PATCH 437/622] Always allow trivial exact casts (#7516) We usually disallow casts to exact types when custom descriptors are disallowed to prevent the cast semantics changing when the binary writer makes the cast types inexact. At the same time, we plan to allow exact types to appear elsewhere in the IR even when custom descriptors are not enabled. This means that the input to a cast may have an exact type. Finalization of casts sets the cast target to be the GLB of the old cast target and the input type, so this could produce invalid exact casts. However, this situation only arises when the cast will trivially succeed because the cast target is a supertype of the input type. If the cast input were exact and the cast target were not a supertype of the input type, then the GLB would be an inexact bottom type, so there would be no problem. Since these trivial casts would remain trivial after binary writing removes exactness, it is safe to allow them to validate even when custom descriptors is disabled. Make this change to avoid finalization producing invalid casts. The alternative would be to add logic to cast finalization to avoid introducing exactness, but that would both be more complicated and would lose type information, harming optimization power. --- scripts/test/fuzzing.py | 1 + src/wasm/wasm-validator.cpp | 41 ++++++---- test/lit/validation/exact-casts-trivial.wast | 79 ++++++++++++++++++++ 3 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 test/lit/validation/exact-casts-trivial.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 811c780e0fb..64e96983d35 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -115,6 +115,7 @@ 'exact-references.wast', 'exact-references-lowering.wast', 'exact-casts.wast', + 'exact-casts-trivial.wast', 'optimize-instructions-exact.wast', 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 0785956c86b..c344080958c 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2904,11 +2904,17 @@ void FunctionValidator::visitRefTest(RefTest* curr) { curr, "ref.test target type and ref type must have a common supertype"); - shouldBeTrue(curr->castType.isInexact() || - getModule()->features.hasCustomDescriptors(), - curr, - "ref.test of exact type requires custom descriptors " - "[--enable-custom-descriptors]"); + // If custom descriptors is not enabled, only trivial exact casts are allowed, + // i.e. those where the operand has the same exact type. The result of these + // trivial exact casts does not change when the types are made inexact during + // binary writing. + if (!getModule()->features.hasCustomDescriptors()) { + shouldBeTrue(curr->castType.isInexact() || + curr->castType == curr->ref->type, + curr, + "ref.test of exact type requires custom descriptors " + "[--enable-custom-descriptors]"); + } } void FunctionValidator::visitRefCast(RefCast* curr) { @@ -2948,11 +2954,13 @@ void FunctionValidator::visitRefCast(RefCast* curr) { curr, "ref.cast null of non-nullable references are not allowed"); - shouldBeTrue(curr->type.isInexact() || - getModule()->features.hasCustomDescriptors(), - curr, - "ref.cast to exact type requires custom descriptors " - "[--enable-custom-descriptors]"); + // See comment about exactness on visitRefTest. + if (!getModule()->features.hasCustomDescriptors()) { + shouldBeTrue(curr->type.isInexact() || curr->type == curr->ref->type, + curr, + "ref.cast to exact type requires custom descriptors " + "[--enable-custom-descriptors]"); + } } void FunctionValidator::visitBrOn(BrOn* curr) { @@ -2982,11 +2990,14 @@ void FunctionValidator::visitBrOn(BrOn* curr) { curr->ref->type, curr, "br_on_cast* target type must be a subtype of its input type"); - shouldBeTrue(curr->castType.isInexact() || - getModule()->features.hasCustomDescriptors(), - curr, - "br_on_cast* to exact type requires custom descriptors " - "[--enable-custom-descriptors]"); + // See comment about exactness on visitRefTest. + if (!getModule()->features.hasCustomDescriptors()) { + shouldBeTrue(curr->castType.isInexact() || + curr->castType == curr->ref->type, + curr, + "br_on_cast* to exact type requires custom descriptors " + "[--enable-custom-descriptors]"); + } } else { shouldBeEqual(curr->castType, Type(Type::none), diff --git a/test/lit/validation/exact-casts-trivial.wast b/test/lit/validation/exact-casts-trivial.wast new file mode 100644 index 00000000000..637c0c016bb --- /dev/null +++ b/test/lit/validation/exact-casts-trivial.wast @@ -0,0 +1,79 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Test that trivial exact casts are ok even when custom descriptors is +;; disabled. + +;; RUN: wasm-opt %s -all --disable-custom-descriptors -S -o - | filecheck %s + +(module + ;; CHECK: (type $foo (struct)) + (type $foo (struct)) + + ;; CHECK: (func $ref.cast (type $1) (param $0 (ref null (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref null (exact $foo)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.cast (param (ref null (exact $foo))) + (drop + (ref.cast anyref + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $ref.test (type $2) (param $0 (ref (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.test (param (ref (exact $foo))) + (drop + (ref.test anyref + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $br_on_cast (type $1) (param $0 (ref null (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result anyref) + ;; CHECK-NEXT: (br_on_cast $block (ref null (exact $foo)) (ref null (exact $foo)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast (param (ref null (exact $foo))) + (drop + (block (result anyref) + (br_on_cast 0 anyref anyref + (local.get 0) + ) + ) + ) + ) + + ;; CHECK: (func $br_on_cast_fail (type $2) (param $0 (ref (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result anyref) + ;; CHECK-NEXT: (br_on_cast_fail $block (ref (exact $foo)) (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail (param (ref (exact $foo))) + (drop + (block (result anyref) + (br_on_cast_fail 0 anyref anyref + (local.get 0) + ) + ) + ) + ) +) From 38e0f4e1010cdcd903dec8cbf2fdc16feeea2453 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 17 Apr 2025 16:23:45 -0700 Subject: [PATCH 438/622] Only add exact casts when allowed in GUFA (#7518) When custom descriptors are disabled, make sure the types of added casts are inexact because exact casts would be invalid. --- scripts/test/fuzzing.py | 1 + src/passes/GUFA.cpp | 5 +++ test/lit/passes/gufa-cast-all-exact.wast | 54 ++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 test/lit/passes/gufa-cast-all-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 64e96983d35..4935f8b52ec 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -122,6 +122,7 @@ 'coalesce-locals-exact.wast', 'remove-unused-brs-exact.wast', 'signature-refining-exact.wast', + 'gufa-cast-all-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index a4c92fe81a0..35380c952f6 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -374,6 +374,11 @@ struct GUFAOptimizer } auto oracleType = parent.getContents(curr).getType(); + // Exact casts are only allowed when custom descriptors is enabled. + if (oracleType.isExact() && + !getModule()->features.hasCustomDescriptors()) { + oracleType = oracleType.with(Inexact); + } if (oracleType.isRef() && oracleType != curr->type && Type::isSubType(oracleType, curr->type)) { replaceCurrent(Builder(*getModule()).makeRefCast(curr, oracleType)); diff --git a/test/lit/passes/gufa-cast-all-exact.wast b/test/lit/passes/gufa-cast-all-exact.wast new file mode 100644 index 00000000000..a938c357397 --- /dev/null +++ b/test/lit/passes/gufa-cast-all-exact.wast @@ -0,0 +1,54 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that exact casts are only added when custom descriptors are enabled. + +;; RUN: wasm-opt %s -all --gufa-cast-all -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --disable-custom-descriptors --gufa-cast-all -S -o - | filecheck %s --check-prefix=NO_CD + +(module + ;; CHECK: (type $foo (struct)) + ;; NO_CD: (type $foo (struct)) + (type $foo (struct)) + + ;; CHECK: (import "" "" (global $g (ref (exact $foo)))) + ;; NO_CD: (import "" "" (global $g (ref (exact $foo)))) + (import "" "" (global $g (ref (exact $foo)))) + + ;; CHECK: (func $callee (type $1) (result (ref $foo)) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $callee (type $1) (result (ref $foo)) + ;; NO_CD-NEXT: (global.get $g) + ;; NO_CD-NEXT: ) + (func $callee (result (ref $foo)) + (global.get $g) + ) + + ;; CHECK: (func $caller (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (call $callee) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $caller (type $2) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (block (result (ref (exact $foo))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (call $callee) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (global.get $g) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $caller + (drop + (call $callee) + ) + ) +) From 93214fbd4c3e272e0ef8e61b93d3f1f0da1cc264 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 17 Apr 2025 17:10:11 -0700 Subject: [PATCH 439/622] Propagate exactness when canonicalizing types (#7519) The part of type canonicalization that replaces referenced heap types from previous recursion groups with their canonical versions was accidentally dropping exactness from the updated types. Fix it. --- src/wasm/wasm-type.cpp | 2 +- test/gtest/type-builder.cpp | 30 +++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index f1ddaa16e1c..2605a0aaf37 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -2518,7 +2518,7 @@ void updateReferencedHeapTypes( if (type->isRef()) { auto ht = type->getHeapType(); if (auto it = canonicalized.find(ht); it != canonicalized.end()) { - *type = Type(it->second, type->getNullability()); + *type = type->with(it->second); } } else if (type->isTuple()) { TypeGraphWalkerBase::scanType(type); diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index a11af0e6b33..da7f4c60f7c 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -429,7 +429,7 @@ TEST_F(TypeTest, CanonicalizeUses) { } TEST_F(TypeTest, CanonicalizeExactRefs) { - TypeBuilder builder(4); + TypeBuilder builder(10); // Types that vary in exactness or nullability of references are different. Type a = builder.getTempRefType(builder[0], Nullable, Inexact); @@ -442,10 +442,38 @@ TEST_F(TypeTest, CanonicalizeExactRefs) { builder[2] = Struct({Field(c, Mutable)}); builder[3] = Struct({Field(d, Mutable)}); + auto translate = [&](HeapType t) -> HeapType { + for (int i = 0; i < 4; ++i) { + if (t == builder[i]) { + return builder[4 + i]; + } + } + WASM_UNREACHABLE("unexpected type"); + }; + + builder[4].copy(builder[0], translate); + builder[5].copy(builder[1], translate); + builder[6].copy(builder[2], translate); + builder[7].copy(builder[3], translate); + + // Test with references to previous types as well. + Type ref0 = builder.getTempRefType(builder[0], Nullable, Exact); + Type ref4 = builder.getTempRefType(builder[4], Nullable, Exact); + + builder[8] = Struct({Field(ref0, Mutable)}); + builder[9] = Struct({Field(ref4, Mutable)}); + auto result = builder.build(); ASSERT_TRUE(result); auto built = *result; + EXPECT_EQ(built[0], built[4]); + EXPECT_EQ(built[1], built[5]); + EXPECT_EQ(built[2], built[6]); + EXPECT_EQ(built[3], built[7]); + + EXPECT_EQ(built[8], built[9]); + EXPECT_NE(built[0], built[1]); EXPECT_NE(built[0], built[2]); EXPECT_NE(built[0], built[3]); From b5a4e36ad2e058bf88e8fcedb796425c6abcaa78 Mon Sep 17 00:00:00 2001 From: Gulg <65360617+GulgDev@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:31:09 +0500 Subject: [PATCH 440/622] [JS API] Add missing ids to getExpressionInfo and fix existing bugs (#7507) This PR adds cases for GC expressions in `getExpressionInfo` and updates `CallIndirectId` (fixes https://github.com/WebAssembly/binaryen/issues/7490), `TryId`. In addition, it also adds tests for `getExpressionInfo` results, which were not checked before. --- src/js/binaryen.js-post.js | 185 ++++- test/binaryen.js/exception-handling.js.txt | 4 +- test/binaryen.js/expressions.js | 833 ++++++++++++++++++++- 3 files changed, 1002 insertions(+), 20 deletions(-) diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 5521cc6cca8..4695a5a4d85 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -3058,14 +3058,16 @@ Module['getExpressionType'] = function(expr) { Module['getExpressionInfo'] = function(expr) { const id = Module['_BinaryenExpressionGetId'](expr); const type = Module['_BinaryenExpressionGetType'](expr); - switch (id) { // TODO: GC instructions - case Module['BlockId']: + switch (id) { + case Module['BlockId']: { + const name = Module['_BinaryenBlockGetName'](expr); return { 'id': id, 'type': type, - 'name': UTF8ToString(Module['_BinaryenBlockGetName'](expr)), + 'name': name ? UTF8ToString(name) : null, 'children': getAllNested(expr, Module['_BinaryenBlockGetNumChildren'], Module['_BinaryenBlockGetChildAt']) }; + } case Module['IfId']: return { 'id': id, @@ -3074,21 +3076,25 @@ Module['getExpressionInfo'] = function(expr) { 'ifTrue': Module['_BinaryenIfGetIfTrue'](expr), 'ifFalse': Module['_BinaryenIfGetIfFalse'](expr) }; - case Module['LoopId']: + case Module['LoopId']: { + const name = Module['_BinaryenLoopGetName'](expr); return { 'id': id, 'type': type, - 'name': UTF8ToString(Module['_BinaryenLoopGetName'](expr)), + 'name': name ? UTF8ToString(name) : null, 'body': Module['_BinaryenLoopGetBody'](expr) }; - case Module['BreakId']: + } + case Module['BreakId']: { + const name = Module['_BinaryenBreakGetName'](expr); return { 'id': id, 'type': type, - 'name': UTF8ToString(Module['_BinaryenBreakGetName'](expr)), + 'name': name ? UTF8ToString(name) : null, 'condition': Module['_BinaryenBreakGetCondition'](expr), 'value': Module['_BinaryenBreakGetValue'](expr) }; + } case Module['SwitchId']: return { 'id': id, @@ -3113,8 +3119,10 @@ Module['getExpressionInfo'] = function(expr) { 'type': type, 'isReturn': Boolean(Module['_BinaryenCallIndirectIsReturn'](expr)), 'target': Module['_BinaryenCallIndirectGetTarget'](expr), - 'table': Module['_BinaryenCallIndirectGetTable'](expr), - 'operands': getAllNested(expr, Module['_BinaryenCallIndirectGetNumOperands'], Module['_BinaryenCallIndirectGetOperandAt']) + 'table': UTF8ToString(Module['_BinaryenCallIndirectGetTable'](expr)), + 'operands': getAllNested(expr, Module['_BinaryenCallIndirectGetNumOperands'], Module['_BinaryenCallIndirectGetOperandAt']), + 'params': Module['_BinaryenCallIndirectGetParams'](expr), + 'results': Module['_BinaryenCallIndirectGetResults'](expr) }; case Module['LocalGetId']: return { @@ -3192,7 +3200,8 @@ Module['getExpressionInfo'] = function(expr) { 'bytes': Module['_BinaryenStoreGetBytes'](expr), 'align': Module['_BinaryenStoreGetAlign'](expr), 'ptr': Module['_BinaryenStoreGetPtr'](expr), - 'value': Module['_BinaryenStoreGetValue'](expr) + 'value': Module['_BinaryenStoreGetValue'](expr), + 'valueType': Module['_BinaryenStoreGetValueType'](expr) }; case Module['ConstId']: { let value; @@ -3386,11 +3395,13 @@ Module['getExpressionInfo'] = function(expr) { 'align': Module['_BinaryenSIMDLoadStoreLaneGetAlign'](expr), 'index': Module['_BinaryenSIMDLoadStoreLaneGetIndex'](expr), 'ptr': Module['_BinaryenSIMDLoadStoreLaneGetPtr'](expr), - 'vec': Module['_BinaryenSIMDLoadStoreLaneGetVec'](expr) + 'vec': Module['_BinaryenSIMDLoadStoreLaneGetVec'](expr), + 'isStore': Boolean(Module['_BinaryenSIMDLoadStoreLaneIsStore'](expr)) }; case Module['MemoryInitId']: return { 'id': id, + 'type': type, 'segment': UTF8ToString(Module['_BinaryenMemoryInitGetSegment'](expr)), 'dest': Module['_BinaryenMemoryInitGetDest'](expr), 'offset': Module['_BinaryenMemoryInitGetOffset'](expr), @@ -3399,11 +3410,13 @@ Module['getExpressionInfo'] = function(expr) { case Module['DataDropId']: return { 'id': id, + 'type': type, 'segment': UTF8ToString(Module['_BinaryenDataDropGetSegment'](expr)), }; case Module['MemoryCopyId']: return { 'id': id, + 'type': type, 'dest': Module['_BinaryenMemoryCopyGetDest'](expr), 'source': Module['_BinaryenMemoryCopyGetSource'](expr), 'size': Module['_BinaryenMemoryCopyGetSize'](expr) @@ -3411,6 +3424,7 @@ Module['getExpressionInfo'] = function(expr) { case Module['MemoryFillId']: return { 'id': id, + 'type': type, 'dest': Module['_BinaryenMemoryFillGetDest'](expr), 'value': Module['_BinaryenMemoryFillGetValue'](expr), 'size': Module['_BinaryenMemoryFillGetSize'](expr) @@ -3446,18 +3460,155 @@ Module['getExpressionInfo'] = function(expr) { 'left': Module['_BinaryenRefEqGetLeft'](expr), 'right': Module['_BinaryenRefEqGetRight'](expr) }; - case Module['TryId']: + case Module['RefTestId']: + return { + 'id': id, + 'type': type, + 'ref': Module['_BinaryenRefTestGetRef'](expr), + 'castType': Module['_BinaryenRefTestGetCastType'](expr) + }; + case Module['RefCastId']: + return { + 'id': id, + 'type': type, + 'ref': Module['_BinaryenRefCastGetRef'](expr) + }; + case Module['BrOnId']: + return { + 'id': id, + 'type': type, + 'op': Module['_BinaryenBrOnGetOp'](expr), + 'name': UTF8ToString(Module['_BinaryenBrOnGetName'](expr)), + 'ref': Module['_BinaryenBrOnGetRef'](expr), + 'castType': Module['_BinaryenBrOnGetCastType'](expr) + }; + case Module['StructNewId']: + return { + 'id': id, + 'type': type, + 'operands': getAllNested(expr, Module['_BinaryenStructNewGetNumOperands'], Module['_BinaryenStructNewGetOperandAt']), + }; + case Module['StructGetId']: + return { + 'id': id, + 'type': type, + 'index': Module['_BinaryenStructGetGetIndex'](expr), + 'ref': Module['_BinaryenStructGetGetRef'](expr), + 'isSigned': Boolean(Module['_BinaryenStructGetIsSigned'](expr)) + }; + case Module['StructSetId']: + return { + 'id': id, + 'type': type, + 'index': Module['_BinaryenStructSetGetIndex'](expr), + 'ref': Module['_BinaryenStructSetGetRef'](expr), + 'value': Module['_BinaryenStructSetGetValue'](expr) + }; + case Module['ArrayNewId']: + return { + 'id': id, + 'type': type, + 'init': Module['_BinaryenArrayNewGetInit'](expr), + 'size': Module['_BinaryenArrayNewGetSize'](expr) + }; + case Module['ArrayNewFixedId']: + return { + 'id': id, + 'type': type, + 'values': getAllNested(expr, Module['_BinaryenArrayNewFixedGetNumValues'], Module['_BinaryenArrayNewFixedGetValueAt']) + }; + case Module['ArrayNewDataId']: + return { + 'id': id, + 'type': type, + 'segment': UTF8ToString(Module['_BinaryenArrayNewDataGetSegment'](expr)), + 'offset': Module['_BinaryenArrayNewDataGetOffset'](expr), + 'size': Module['_BinaryenArrayNewDataGetSize'](expr) + }; + case Module['ArrayNewElemId']: + return { + 'id': id, + 'type': type, + 'segment': UTF8ToString(Module['_BinaryenArrayNewElemGetSegment'](expr)), + 'offset': Module['_BinaryenArrayNewElemGetOffset'](expr), + 'size': Module['_BinaryenArrayNewElemGetSize'](expr) + }; + case Module['ArrayGetId']: + return { + 'id': id, + 'type': type, + 'ref': Module['_BinaryenArrayGetGetRef'](expr), + 'index': Module['_BinaryenArrayGetGetIndex'](expr), + 'isSigned': Boolean(Module['_BinaryenArrayGetIsSigned'](expr)) + }; + case Module['ArraySetId']: + return { + 'id': id, + 'type': type, + 'ref': Module['_BinaryenArraySetGetRef'](expr), + 'index': Module['_BinaryenArraySetGetIndex'](expr), + 'value': Module['_BinaryenArraySetGetValue'](expr) + }; + case Module['ArrayLenId']: + return { + 'id': id, + 'type': type, + 'ref': Module['_BinaryenArrayLenGetRef'](expr) + }; + case Module['ArrayFillId']: + return { + 'id': id, + 'type': type, + 'ref': Module['_BinaryenArrayFillGetRef'](expr), + 'index': Module['_BinaryenArrayFillGetIndex'](expr), + 'value': Module['_BinaryenArrayFillGetValue'](expr), + 'size': Module['_BinaryenArrayFillGetSize'](expr) + }; + case Module['ArrayCopyId']: + return { + 'id': id, + 'type': type, + 'destRef': Module['_BinaryenArrayCopyGetDestRef'](expr), + 'destIndex': Module['_BinaryenArrayCopyGetDestIndex'](expr), + 'srcRef': Module['_BinaryenArrayCopyGetSrcRef'](expr), + 'srcIndex': Module['_BinaryenArrayCopyGetSrcIndex'](expr), + 'length': Module['_BinaryenArrayCopyGetLength'](expr) + }; + case Module['ArrayInitDataId']: return { 'id': id, 'type': type, - 'name': UTF8ToString(Module['_BinaryenTryGetName'](expr)), + 'segment': UTF8ToString(Module['_BinaryenArrayInitDataGetSegment'](expr)), + 'ref': Module['_BinaryenArrayInitDataGetRef'](expr), + 'index': Module['_BinaryenArrayInitDataGetIndex'](expr), + 'offset': Module['_BinaryenArrayInitDataGetOffset'](expr), + 'size': Module['_BinaryenArrayInitDataGetSize'](expr) + }; + case Module['ArrayInitElemId']: + return { + 'id': id, + 'type': type, + 'segment': UTF8ToString(Module['_BinaryenArrayInitElemGetSegment'](expr)), + 'ref': Module['_BinaryenArrayInitElemGetRef'](expr), + 'index': Module['_BinaryenArrayInitElemGetIndex'](expr), + 'offset': Module['_BinaryenArrayInitElemGetOffset'](expr), + 'size': Module['_BinaryenArrayInitElemGetSize'](expr) + }; + case Module['TryId']: { + const name = Module['_BinaryenTryGetName'](expr); + const delegateTarget = Module['_BinaryenTryGetDelegateTarget'](expr); + return { + 'id': id, + 'type': type, + 'name': name ? UTF8ToString(name) : null, 'body': Module['_BinaryenTryGetBody'](expr), - 'catchTags': getAllNested(expr, Module['_BinaryenTryGetNumCatchTags'], Module['_BinaryenTryGetCatchTagAt']), + 'catchTags': getAllNested(expr, Module['_BinaryenTryGetNumCatchTags'], Module['_BinaryenTryGetCatchTagAt']).map(p => UTF8ToString(p)), 'catchBodies': getAllNested(expr, Module['_BinaryenTryGetNumCatchBodies'], Module['_BinaryenTryGetCatchBodyAt']), - 'hasCatchAll': Module['_BinaryenTryHasCatchAll'](expr), - 'delegateTarget': UTF8ToString(Module['_BinaryenTryGetDelegateTarget'](expr)), - 'isDelegate': Module['_BinaryenTryIsDelegate'](expr) + 'hasCatchAll': Boolean(Module['_BinaryenTryHasCatchAll'](expr)), + 'delegateTarget': delegateTarget ? UTF8ToString(delegateTarget) : null, + 'isDelegate': Boolean(Module['_BinaryenTryIsDelegate'](expr)) }; + } case Module['ThrowId']: return { 'id': id, diff --git a/test/binaryen.js/exception-handling.js.txt b/test/binaryen.js/exception-handling.js.txt index 6be0eb40a58..992fbb4c479 100644 --- a/test/binaryen.js/exception-handling.js.txt +++ b/test/binaryen.js/exception-handling.js.txt @@ -36,5 +36,5 @@ getExpressionInfo(throw) = {"id":54,"type":1,"tag":"e"} getExpressionInfo(rethrow) = {"id":55,"type":1,"target":"l0"} -getExpressionInfo(try_catch) = {"id":52,"type":1,"name":"l0","hasCatchAll":0,"delegateTarget":"","isDelegate":0} -getExpressionInfo(try_delegate) = {"id":52,"type":0,"name":"try_outer","hasCatchAll":1,"delegateTarget":"","isDelegate":0} +getExpressionInfo(try_catch) = {"id":52,"type":1,"name":"l0","hasCatchAll":false,"delegateTarget":null,"isDelegate":false} +getExpressionInfo(try_delegate) = {"id":52,"type":0,"name":"try_outer","hasCatchAll":true,"delegateTarget":null,"isDelegate":false} diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index 544f2b97806..3cbe7e59448 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -30,6 +30,12 @@ console.log("# Block"); assert(theBlock.id === binaryen.BlockId); assert(theBlock.name === null); assert(theBlock.type === binaryen.none); + + var info = binaryen.getExpressionInfo(theBlock); + assert(info.id === theBlock.id); + assert(info.type === theBlock.type); + assert(info.name === theBlock.name); + assertDeepEqual(info.children, theBlock.children); theBlock.name ="theName"; assert(theBlock.name === "theName"); @@ -69,6 +75,10 @@ console.log("# Block"); assert(theBlock.getChildAt(0) === child0); theBlock.finalize(); + info = binaryen.getExpressionInfo(theBlock); + assert(info.name === theBlock.name); + assertDeepEqual(info.children, theBlock.children); + console.log(theBlock.toText()); assert( theBlock.toText() @@ -97,6 +107,13 @@ console.log("# If"); assert(theIf.ifFalse === ifFalse); assert(theIf.type == binaryen.i32); + var info = binaryen.getExpressionInfo(theIf); + assert(info.id === theIf.id); + assert(info.type === theIf.type); + assert(info.condition === theIf.condition); + assert(info.ifTrue === theIf.ifTrue); + assert(info.ifFalse === theIf.ifFalse); + theIf.condition = condition = module.i32.const(4); assert(theIf.condition === condition); theIf.ifTrue = ifTrue = module.i32.const(5); @@ -105,6 +122,11 @@ console.log("# If"); assert(theIf.ifFalse === ifFalse); theIf.finalize(); + info = binaryen.getExpressionInfo(theIf); + assert(info.condition === theIf.condition); + assert(info.ifTrue === theIf.ifTrue); + assert(info.ifFalse === theIf.ifFalse); + console.log(theIf.toText()); assert( theIf.toText() @@ -114,6 +136,10 @@ console.log("# If"); theIf.ifFalse = null; assert(!theIf.ifFalse); + + info = binaryen.getExpressionInfo(theIf); + assert(info.ifFalse === theIf.ifFalse); + console.log(theIf.toText()); assert( theIf.toText() @@ -138,6 +164,12 @@ console.log("# Loop"); assert(theLoop.body === body); assert(theLoop.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theLoop); + assert(info.id === theLoop.id); + assert(info.type === theLoop.type); + assert(info.name === theLoop.name); + assert(info.body === theLoop.body); + theLoop.name = name = "theName"; assert(theLoop.name === name); theLoop.body = body = module.drop(body); @@ -145,6 +177,11 @@ console.log("# Loop"); theLoop.finalize(); assert(theLoop.type === binaryen.none); + info = binaryen.getExpressionInfo(theLoop); + assert(info.type === theLoop.type); + assert(info.name === theLoop.name); + assert(info.body === theLoop.body); + console.log(theLoop.toText()); assert( theLoop.toText() @@ -170,6 +207,13 @@ console.log("# Break"); assert(theBreak.value === value); assert(theBreak.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theBreak); + assert(info.id === theBreak.id); + assert(info.type === theBreak.type); + assert(info.name === theBreak.name); + assert(info.condition === theBreak.condition); + assert(info.value === theBreak.value); + theBreak.name = name = "theNewName"; assert(theBreak.name === "theNewName"); theBreak.condition = condition = module.i32.const(3); @@ -178,6 +222,11 @@ console.log("# Break"); assert(theBreak.value === value); theBreak.finalize(); + info = binaryen.getExpressionInfo(theBreak); + assert(info.name === theBreak.name); + assert(info.condition === theBreak.condition); + assert(info.value === theBreak.value); + console.log(theBreak.toText()); assert( theBreak.toText() @@ -206,6 +255,14 @@ console.log("# Switch"); assert(theSwitch.value === value); assert(theSwitch.type === binaryen.unreachable); + var info = binaryen.getExpressionInfo(theSwitch); + assert(info.id === theSwitch.id); + assert(info.type === theSwitch.type); + assertDeepEqual(info.names, theSwitch.names); + assert(info.defaultName === theSwitch.defaultName); + assert(info.condition === theSwitch.condition); + assert(info.value === theSwitch.value); + names = [ "1", // set "2", //set @@ -227,6 +284,12 @@ console.log("# Switch"); assert(theSwitch.value === value); theSwitch.finalize(); + info = binaryen.getExpressionInfo(theSwitch); + assertDeepEqual(info.names, theSwitch.names); + assert(info.defaultName === theSwitch.defaultName); + assert(info.condition === theSwitch.condition); + assert(info.value === theSwitch.value); + console.log(theSwitch.toText()); assert( theSwitch.toText() @@ -255,6 +318,13 @@ console.log("# Call"); assert(theCall.return === false); assert(theCall.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theCall); + assert(info.id === theCall.id); + assert(info.type === theCall.type); + assert(info.target === theCall.target); + assertDeepEqual(info.operands, theCall.operands); + assert(info.isReturn === theCall.return); + theCall.target = "bar"; assert(theCall.target === "bar"); theCall.operands = operands = [ @@ -276,11 +346,21 @@ console.log("# Call"); theCall.finalize(); assert(theCall.type === binaryen.unreachable); // finalized tail call + info = binaryen.getExpressionInfo(theCall); + assert(info.type === theCall.type); + assert(info.target === theCall.target); + assertDeepEqual(info.operands, theCall.operands); + assert(info.isReturn === theCall.return); + theCall.return = false; theCall.type = binaryen.i32; theCall.finalize(); assert(theCall.type === binaryen.i32); // finalized call + info = binaryen.getExpressionInfo(theCall); + assert(info.type === theCall.type); + assert(info.isReturn === theCall.return); + console.log(theCall.toText()); assert( theCall.toText() @@ -314,6 +394,16 @@ console.log("# CallIndirect"); assert(theCallIndirect.return === false); assert(theCallIndirect.type === theCallIndirect.results); + var info = binaryen.getExpressionInfo(theCallIndirect); + assert(info.id === theCallIndirect.id); + assert(info.type === theCallIndirect.type); + assert(info.table === theCallIndirect.table); + assert(info.target === theCallIndirect.target); + assertDeepEqual(info.operands, theCallIndirect.operands); + assert(info.params === theCallIndirect.params); + assert(info.results === theCallIndirect.results); + assert(info.isReturn === theCallIndirect.return); + theCallIndirect.target = target = module.i32.const(9000); assert(theCallIndirect.target === target); theCallIndirect.operands = operands = [ @@ -340,10 +430,21 @@ console.log("# CallIndirect"); theCallIndirect.finalize(); assert(theCallIndirect.type === binaryen.unreachable); // finalized tail call + info = binaryen.getExpressionInfo(theCallIndirect); + assert(info.type === theCallIndirect.type); + assert(info.target === theCallIndirect.target); + assertDeepEqual(info.operands, theCallIndirect.operands); + assert(info.params === theCallIndirect.params); + assert(info.results === theCallIndirect.results); + assert(info.isReturn === theCallIndirect.return); + theCallIndirect.return = false; theCallIndirect.finalize(); assert(theCallIndirect.type === results); // finalized call + info = binaryen.getExpressionInfo(theCallIndirect); + assert(info.isReturn === theCallIndirect.return); + console.log(theCallIndirect.toText()); assert( theCallIndirect.toText() @@ -366,12 +467,21 @@ console.log("# LocalGet"); assert(theLocalGet.index === index); assert(theLocalGet.type === type); + var info = binaryen.getExpressionInfo(theLocalGet); + assert(info.id === theLocalGet.id); + assert(info.type === theLocalGet.type); + assert(info.index === theLocalGet.index); + theLocalGet.index = index = 2; assert(theLocalGet.index === index); theLocalGet.type = type = binaryen.f64; assert(theLocalGet.type === type); theLocalGet.finalize(); + info = binaryen.getExpressionInfo(theLocalGet); + assert(info.type === theLocalGet.type); + assert(info.index === theLocalGet.index); + console.log(theLocalGet.toText()); assert( theLocalGet.toText() @@ -396,6 +506,13 @@ console.log("# LocalSet"); assert(theLocalSet.tee === false); assert(theLocalSet.type == binaryen.none); + var info = binaryen.getExpressionInfo(theLocalSet); + assert(info.id === theLocalSet.id); + assert(info.type === theLocalSet.type); + assert(info.index === theLocalSet.index); + assert(info.value === theLocalSet.value); + assert(info.isTee === theLocalSet.tee) + theLocalSet.index = index = 2; assert(theLocalSet.index === index); theLocalSet.value = value = module.i32.const(3); @@ -406,6 +523,12 @@ console.log("# LocalSet"); theLocalSet.type = binaryen.none; theLocalSet.finalize(); + info = binaryen.getExpressionInfo(theLocalSet); + assert(info.type === theLocalSet.type); + assert(info.index === theLocalSet.index); + assert(info.value === theLocalSet.value); + assert(info.isTee === theLocalSet.tee) + console.log(theLocalSet.toText()); assert( theLocalSet.toText() @@ -428,12 +551,21 @@ console.log("# GlobalGet"); assert(theGlobalGet.name === name); assert(theGlobalGet.type === type); + var info = binaryen.getExpressionInfo(theGlobalGet); + assert(info.id === theGlobalGet.id); + assert(info.type === theGlobalGet.type); + assert(info.name === theGlobalGet.name); + theGlobalGet.name = name = "b"; assert(theGlobalGet.name === name); theGlobalGet.type = type = binaryen.f64; assert(theGlobalGet.type === type); theGlobalGet.finalize(); + info = binaryen.getExpressionInfo(theGlobalGet); + assert(info.type === theGlobalGet.type); + assert(info.name === theGlobalGet.name); + console.log(theGlobalGet.toText()); assert( theGlobalGet.toText() @@ -457,12 +589,22 @@ console.log("# GlobalSet"); assert(theGlobalSet.value === value); assert(theGlobalSet.type == binaryen.none); + var info = binaryen.getExpressionInfo(theGlobalSet); + assert(info.id === theGlobalSet.id); + assert(info.type === theGlobalSet.type); + assert(info.name === theGlobalSet.name); + assert(info.value === theGlobalSet.value); + theGlobalSet.name = name = "b"; assert(theGlobalSet.name === name); theGlobalSet.value = value = module.f64.const(3); assert(theGlobalSet.value === value); theGlobalSet.finalize(); + info = binaryen.getExpressionInfo(theGlobalSet); + assert(info.name === theGlobalSet.name); + assert(info.value === theGlobalSet.value); + console.log(theGlobalSet.toText()); assert( theGlobalSet.toText() @@ -484,6 +626,10 @@ console.log("# MemorySize"); assert(theMemorySize.type === type); theMemorySize.finalize(); + var info = binaryen.getExpressionInfo(theMemorySize); + assert(info.id === theMemorySize.id); + assert(info.type === theMemorySize.type); + console.log(theMemorySize.toText()); assert( theMemorySize.toText() @@ -507,10 +653,18 @@ console.log("# MemoryGrow"); assert(theMemoryGrow.delta === delta); assert(theMemoryGrow.type === type); + var info = binaryen.getExpressionInfo(theMemoryGrow); + assert(info.id === theMemoryGrow.id); + assert(info.type === theMemoryGrow.type); + assert(info.delta === theMemoryGrow.delta); + theMemoryGrow.delta = delta = module.i32.const(2); assert(theMemoryGrow.delta === delta); theMemoryGrow.finalize(); + info = binaryen.getExpressionInfo(theMemoryGrow); + assert(info.delta === theMemoryGrow.delta); + console.log(theMemoryGrow.toText()); assert( theMemoryGrow.toText() @@ -540,6 +694,16 @@ console.log("# Load"); assert(theLoad.atomic === false); assert(theLoad.type == binaryen.i32); + var info = binaryen.getExpressionInfo(theLoad); + assert(info.id === theLoad.id); + assert(info.type === theLoad.type); + assert(info.offset === theLoad.offset); + assert(info.align === theLoad.align); + assert(info.ptr === theLoad.ptr); + assert(info.bytes === theLoad.bytes); + assert(info.isSigned === theLoad.signed); + assert(info.isAtomic === theLoad.atomic); + theLoad.offset = offset = 32; assert(theLoad.offset === offset); theLoad.align = align = 4; @@ -557,6 +721,14 @@ console.log("# Load"); theLoad.finalize(); assert(theLoad.align === 4); + info = binaryen.getExpressionInfo(theLoad); + assert(info.offset === theLoad.offset); + assert(info.align === theLoad.align); + assert(info.ptr === theLoad.ptr); + assert(info.bytes === theLoad.bytes); + assert(info.isSigned === theLoad.signed); + assert(info.isAtomic === theLoad.atomic); + console.log(theLoad.toText()); assert( theLoad.toText() @@ -588,6 +760,17 @@ console.log("# Store"); assert(theStore.valueType === binaryen.i32); assert(theStore.type === binaryen.none); + var info = binaryen.getExpressionInfo(theStore); + assert(info.id === theStore.id); + assert(info.type === theStore.type); + assert(info.offset === theStore.offset); + assert(info.align === theStore.align); + assert(info.ptr === theStore.ptr); + assert(info.value === theStore.value); + assert(info.bytes === theStore.bytes); + assert(info.isAtomic === theStore.atomic); + assert(info.valueType === theStore.valueType); + theStore.offset = offset = 32; assert(theStore.offset === offset); theStore.align = align = 4; @@ -607,6 +790,15 @@ console.log("# Store"); theStore.finalize(); assert(theStore.align === 4); + info = binaryen.getExpressionInfo(theStore); + assert(info.offset === theStore.offset); + assert(info.align === theStore.align); + assert(info.ptr === theStore.ptr); + assert(info.value === theStore.value); + assert(info.bytes === theStore.bytes); + assert(info.isAtomic === theStore.atomic); + assert(info.valueType === theStore.valueType); + console.log(theStore.toText()); assert( theStore.toText() @@ -629,6 +821,11 @@ console.log("# Const"); assert(theConst.valueI32 === 2); assert(theConst.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theConst); + assert(info.id === theConst.id); + assert(info.type === theConst.type); + assert(info.value === theConst.valueI32); + theConst.valueI64Low = 3; assert(theConst.valueI64Low === 3); theConst.valueI64High = 4; @@ -636,21 +833,38 @@ console.log("# Const"); theConst.finalize(); assert(theConst.type == binaryen.i64); + info = binaryen.getExpressionInfo(theConst); + assert(info.type === theConst.type); + assert(info.value.low === theConst.valueI64Low); + assert(info.value.high === theConst.valueI64High); + theConst.valueF32 = 5; assert(theConst.valueF32 === 5); theConst.finalize(); assert(theConst.type === binaryen.f32); + info = binaryen.getExpressionInfo(theConst); + assert(info.type === theConst.type); + assert(info.value === theConst.valueF32); + theConst.valueF64 = 6; assert(theConst.valueF64 === 6); theConst.finalize(); assert(theConst.type === binaryen.f64); + info = binaryen.getExpressionInfo(theConst); + assert(info.type === theConst.type); + assert(info.value === theConst.valueF64); + theConst.valueV128 = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]; assertDeepEqual(theConst.valueV128, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); theConst.finalize(); assert(theConst.type === binaryen.v128); + info = binaryen.getExpressionInfo(theConst); + assert(info.type === theConst.type); + assertDeepEqual(info.value, theConst.valueV128); + console.log(theConst.toText()); assert( theConst.toText() @@ -674,6 +888,12 @@ console.log("# Unary"); assert(theUnary.value === value); assert(theUnary.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theUnary); + assert(info.id === theUnary.id); + assert(info.type === theUnary.type); + assert(info.op === theUnary.op); + assert(info.value === theUnary.value); + theUnary.op = op = binaryen.Operations.EqZInt64; assert(theUnary.op === op); theUnary.value = value = module.i64.const(2); @@ -682,6 +902,11 @@ console.log("# Unary"); theUnary.finalize(); assert(theUnary.type === binaryen.i32); + info = binaryen.getExpressionInfo(theUnary); + assert(info.type === theUnary.type); + assert(info.op === theUnary.op); + assert(info.value === theUnary.value); + console.log(theUnary.toText()); assert( theUnary.toText() @@ -705,7 +930,14 @@ console.log("# Binary"); assert(theBinary.op === op); assert(theBinary.left === left); assert(theBinary.right === right); - assert(theBinary.type === binaryen.i32); + assert(theBinary.type === binaryen.i32) + + var info = binaryen.getExpressionInfo(theBinary); + assert(info.id === theBinary.id); + assert(info.type === theBinary.type); + assert(info.op === theBinary.op); + assert(info.left === theBinary.left); + assert(info.right === theBinary.right); theBinary.op = op = binaryen.Operations.AddInt64; assert(theBinary.op === op); @@ -717,6 +949,12 @@ console.log("# Binary"); theBinary.finalize(); assert(theBinary.type === binaryen.i64); + info = binaryen.getExpressionInfo(theBinary); + assert(info.type === theBinary.type); + assert(info.op === theBinary.op); + assert(info.left === theBinary.left); + assert(info.right === theBinary.right); + console.log(theBinary.toText()); assert( theBinary.toText() @@ -742,6 +980,13 @@ console.log("# Select"); assert(theSelect.ifFalse === ifFalse); assert(theSelect.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theSelect); + assert(info.id === theSelect.id); + assert(info.type === theSelect.type); + assert(info.condition === theSelect.condition); + assert(info.ifTrue === theSelect.ifTrue); + assert(info.ifFalse === theSelect.ifFalse); + theSelect.condition = condition = module.i32.const(4); assert(theSelect.condition === condition); theSelect.ifTrue = ifTrue = module.i64.const(5); @@ -751,6 +996,12 @@ console.log("# Select"); theSelect.finalize(); assert(theSelect.type === binaryen.i64); + info = binaryen.getExpressionInfo(theSelect); + assert(info.type === theSelect.type); + assert(info.condition === theSelect.condition); + assert(info.ifTrue === theSelect.ifTrue); + assert(info.ifFalse === theSelect.ifFalse); + console.log(theSelect.toText()); assert( theSelect.toText() @@ -772,12 +1023,21 @@ console.log("# Drop"); assert(theDrop.value === value); assert(theDrop.type === binaryen.none); + var info = binaryen.getExpressionInfo(theDrop); + assert(info.id === theDrop.id); + assert(info.type === theDrop.type); + assert(info.value === theDrop.value); + theDrop.value = value = module.i32.const(2); assert(theDrop.value === value); theDrop.finalize(); assert(theDrop.type === binaryen.none); + info = binaryen.getExpressionInfo(theDrop); + assert(info.type === theDrop.type); + assert(info.value === theDrop.value); + console.log(theDrop.toText()); assert( theDrop.toText() @@ -799,12 +1059,21 @@ console.log("# Return"); assert(theReturn.value === value); assert(theReturn.type === binaryen.unreachable); + var info = binaryen.getExpressionInfo(theReturn); + assert(info.id === theReturn.id); + assert(info.type === theReturn.type); + assert(info.value === theReturn.value); + theReturn.value = value = module.i32.const(2); assert(theReturn.value === value); theReturn.finalize(); assert(theReturn.type === binaryen.unreachable); + info = binaryen.getExpressionInfo(theReturn); + assert(info.type === theReturn.type); + assert(info.value === theReturn.value); + console.log(theReturn.toText()); assert( theReturn.toText() @@ -834,6 +1103,15 @@ console.log("# AtomicRMW"); assert(theAtomicRMW.value === value); assert(theAtomicRMW.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theAtomicRMW); + assert(info.id === theAtomicRMW.id); + assert(info.type === theAtomicRMW.type); + assert(info.op === theAtomicRMW.op); + assert(info.bytes === theAtomicRMW.bytes); + assert(info.offset === theAtomicRMW.offset); + assert(info.ptr === theAtomicRMW.ptr); + assert(info.value === theAtomicRMW.value); + theAtomicRMW.op = op = binaryen.Operations.AtomicRMWSub; assert(theAtomicRMW.op === op); theAtomicRMW.bytes = 2; @@ -848,6 +1126,14 @@ console.log("# AtomicRMW"); theAtomicRMW.finalize(); assert(theAtomicRMW.type === binaryen.i64); + info = binaryen.getExpressionInfo(theAtomicRMW); + assert(info.type === theAtomicRMW.type); + assert(info.op === theAtomicRMW.op); + assert(info.bytes === theAtomicRMW.bytes); + assert(info.offset === theAtomicRMW.offset); + assert(info.ptr === theAtomicRMW.ptr); + assert(info.value === theAtomicRMW.value); + console.log(theAtomicRMW.toText()); assert( theAtomicRMW.toText() @@ -877,6 +1163,15 @@ console.log("# AtomicCmpxchg"); assert(theAtomicCmpxchg.replacement === replacement); assert(theAtomicCmpxchg.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theAtomicCmpxchg); + assert(info.id === theAtomicCmpxchg.id); + assert(info.type === theAtomicCmpxchg.type); + assert(info.bytes === theAtomicCmpxchg.bytes); + assert(info.offset === theAtomicCmpxchg.offset); + assert(info.ptr === theAtomicCmpxchg.ptr); + assert(info.expected === theAtomicCmpxchg.expected); + assert(info.replacement === theAtomicCmpxchg.replacement); + theAtomicCmpxchg.bytes = 2; assert(theAtomicCmpxchg.bytes === 2); theAtomicCmpxchg.offset = offset = 16; @@ -891,6 +1186,14 @@ console.log("# AtomicCmpxchg"); theAtomicCmpxchg.finalize(); assert(theAtomicCmpxchg.type === binaryen.i64); + info = binaryen.getExpressionInfo(theAtomicCmpxchg); + assert(info.type === theAtomicCmpxchg.type); + assert(info.bytes === theAtomicCmpxchg.bytes); + assert(info.offset === theAtomicCmpxchg.offset); + assert(info.ptr === theAtomicCmpxchg.ptr); + assert(info.expected === theAtomicCmpxchg.expected); + assert(info.replacement === theAtomicCmpxchg.replacement); + console.log(theAtomicCmpxchg.toText()); assert( theAtomicCmpxchg.toText() @@ -918,6 +1221,14 @@ console.log("# AtomicWait"); assert(theAtomicWait.timeout === timeout); assert(theAtomicWait.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theAtomicWait); + assert(info.id === theAtomicWait.id); + assert(info.type === theAtomicWait.type); + assert(info.ptr === theAtomicWait.ptr); + assert(info.expected === theAtomicWait.expected); + assert(info.expectedType === theAtomicWait.expectedType); + assert(info.timeout === theAtomicWait.timeout); + theAtomicWait.ptr = ptr = module.i32.const(5); assert(theAtomicWait.ptr === ptr); theAtomicWait.expected = expected = module.i32.const(6); @@ -930,6 +1241,13 @@ console.log("# AtomicWait"); theAtomicWait.finalize(); assert(theAtomicWait.type === binaryen.i32); + info = binaryen.getExpressionInfo(theAtomicWait); + assert(info.type === theAtomicWait.type); + assert(info.ptr === theAtomicWait.ptr); + assert(info.expected === theAtomicWait.expected); + assert(info.expectedType === theAtomicWait.expectedType); + assert(info.timeout === theAtomicWait.timeout); + console.log(theAtomicWait.toText()); assert( theAtomicWait.toText() @@ -954,6 +1272,12 @@ console.log("# AtomicNotify"); assert(theAtomicNotify.notifyCount === notifyCount); assert(theAtomicNotify.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theAtomicNotify); + assert(info.id === theAtomicNotify.id); + assert(info.type === theAtomicNotify.type); + assert(info.ptr === theAtomicNotify.ptr); + assert(info.notifyCount === theAtomicNotify.notifyCount); + theAtomicNotify.ptr = ptr = module.i32.const(3); assert(theAtomicNotify.ptr === ptr); theAtomicNotify.notifyCount = notifyCount = module.i32.const(4); @@ -962,6 +1286,11 @@ console.log("# AtomicNotify"); theAtomicNotify.finalize(); assert(theAtomicNotify.type === binaryen.i32); + info = binaryen.getExpressionInfo(theAtomicNotify); + assert(info.type === theAtomicNotify.type); + assert(info.ptr === theAtomicNotify.ptr); + assert(info.notifyCount === theAtomicNotify.notifyCount); + console.log(theAtomicNotify.toText()); assert( theAtomicNotify.toText() @@ -982,12 +1311,21 @@ console.log("# AtomicFence"); assert(theAtomicFence.order === 0); // reserved, not yet used assert(theAtomicFence.type === binaryen.none); + var info = binaryen.getExpressionInfo(theAtomicFence); + assert(info.id === theAtomicFence.id); + assert(info.type === theAtomicFence.type); + assert(info.order === theAtomicFence.order); + theAtomicFence.order = 1; assert(theAtomicFence.order === 1); theAtomicFence.type = binaryen.f64; theAtomicFence.finalize(); assert(theAtomicFence.type === binaryen.none); + info = binaryen.getExpressionInfo(theAtomicFence); + assert(info.type === theAtomicFence.type); + assert(info.order === theAtomicFence.order); + console.log(theAtomicFence.toText()); assert( theAtomicFence.toText() @@ -1013,6 +1351,13 @@ console.log("# SIMDExtract"); assert(theSIMDExtract.index === index); assert(theSIMDExtract.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theSIMDExtract); + assert(info.id === theSIMDExtract.id); + assert(info.type === theSIMDExtract.type); + assert(info.op === theSIMDExtract.op); + assert(info.vec === theSIMDExtract.vec); + assert(info.index === theSIMDExtract.index); + theSIMDExtract.op = op = binaryen.Operations.ExtractLaneSVecI16x8; assert(theSIMDExtract.op === op); theSIMDExtract.vec = vec = module.v128.const([1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]); @@ -1022,6 +1367,12 @@ console.log("# SIMDExtract"); theSIMDExtract.type = binaryen.f64; theSIMDExtract.finalize(); assert(theSIMDExtract.type === binaryen.i32); + + info = binaryen.getExpressionInfo(theSIMDExtract); + assert(info.type === theSIMDExtract.type); + assert(info.op === theSIMDExtract.op); + assert(info.vec === theSIMDExtract.vec); + assert(info.index === theSIMDExtract.index); console.log(theSIMDExtract.toText()); assert( @@ -1050,6 +1401,14 @@ console.log("# SIMDReplace"); assert(theSIMDReplace.value === value); assert(theSIMDReplace.type === binaryen.v128); + var info = binaryen.getExpressionInfo(theSIMDReplace); + assert(info.id === theSIMDReplace.id); + assert(info.type === theSIMDReplace.type); + assert(info.op === theSIMDReplace.op); + assert(info.vec === theSIMDReplace.vec); + assert(info.index === theSIMDReplace.index); + assert(info.value === theSIMDReplace.value); + theSIMDReplace.op = op = binaryen.Operations.ReplaceLaneVecI16x8; assert(theSIMDReplace.op === op); theSIMDReplace.vec = vec = module.v128.const([1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]); @@ -1062,6 +1421,13 @@ console.log("# SIMDReplace"); theSIMDReplace.finalize(); assert(theSIMDReplace.type === binaryen.v128); + info = binaryen.getExpressionInfo(theSIMDReplace); + assert(info.type === theSIMDReplace.type); + assert(info.op === theSIMDReplace.op); + assert(info.vec === theSIMDReplace.vec); + assert(info.index === theSIMDReplace.index); + assert(info.value === theSIMDReplace.value); + console.log(theSIMDReplace.toText()); assert( theSIMDReplace.toText() @@ -1087,6 +1453,13 @@ console.log("# SIMDShuffle"); assertDeepEqual(theSIMDShuffle.mask, mask); assert(theSIMDShuffle.type === binaryen.v128); + var info = binaryen.getExpressionInfo(theSIMDShuffle); + assert(info.id === theSIMDShuffle.id); + assert(info.type === theSIMDShuffle.type); + assert(info.left === theSIMDShuffle.left); + assert(info.right === theSIMDShuffle.right); + assertDeepEqual(info.mask, theSIMDShuffle.mask); + theSIMDShuffle.left = left = module.v128.const([1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]); assert(theSIMDShuffle.left === left); theSIMDShuffle.right = right = module.v128.const([2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]); @@ -1097,6 +1470,12 @@ console.log("# SIMDShuffle"); theSIMDShuffle.finalize(); assert(theSIMDShuffle.type === binaryen.v128); + info = binaryen.getExpressionInfo(theSIMDShuffle); + assert(info.type === theSIMDShuffle.type); + assert(info.left === theSIMDShuffle.left); + assert(info.right === theSIMDShuffle.right); + assertDeepEqual(info.mask, theSIMDShuffle.mask); + console.log(theSIMDShuffle.toText()); assert( theSIMDShuffle.toText() @@ -1124,6 +1503,14 @@ console.log("# SIMDTernary"); assert(theSIMDTernary.c === c); assert(theSIMDTernary.type === binaryen.v128); + var info = binaryen.getExpressionInfo(theSIMDTernary); + assert(info.id === theSIMDTernary.id); + assert(info.type === theSIMDTernary.type); + assert(info.op === theSIMDTernary.op); + assert(info.a === theSIMDTernary.a); + assert(info.b === theSIMDTernary.b); + assert(info.c === theSIMDTernary.c); + console.log(theSIMDTernary.toText() + "\n"); assert( theSIMDTernary.toText() @@ -1149,6 +1536,13 @@ console.log("# SIMDShift"); assert(theSIMDShift.shift === shift); assert(theSIMDShift.type === binaryen.v128); + var info = binaryen.getExpressionInfo(theSIMDShift); + assert(info.id === theSIMDShift.id); + assert(info.type === theSIMDShift.type); + assert(info.op === theSIMDShift.op); + assert(info.vec === theSIMDShift.vec); + assert(info.shift === theSIMDShift.shift); + theSIMDShift.op = op = binaryen.Operations.ShrSVecI8x16; assert(theSIMDShift.op === op); theSIMDShift.vec = vec = module.v128.const([1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]); @@ -1159,6 +1553,12 @@ console.log("# SIMDShift"); theSIMDShift.finalize(); assert(theSIMDShift.type === binaryen.v128); + info = binaryen.getExpressionInfo(theSIMDShift); + assert(info.type === theSIMDShift.type); + assert(info.op === theSIMDShift.op); + assert(info.vec === theSIMDShift.vec); + assert(info.shift === theSIMDShift.shift); + console.log(theSIMDShift.toText()); assert( theSIMDShift.toText() @@ -1186,6 +1586,13 @@ console.log("# SIMDLoad"); assert(theSIMDLoad.ptr === ptr); assert(theSIMDLoad.type === binaryen.v128); + var info = binaryen.getExpressionInfo(theSIMDLoad); + assert(info.id === theSIMDLoad.id); + assert(info.type === theSIMDLoad.type); + assert(info.offset === theSIMDLoad.offset); + assert(info.align === theSIMDLoad.align); + assert(info.ptr === theSIMDLoad.ptr); + theSIMDLoad.op = op = binaryen.Operations.Load8SplatVec128; assert(theSIMDLoad.op === op); theSIMDLoad.offset = offset = 32; @@ -1198,6 +1605,12 @@ console.log("# SIMDLoad"); theSIMDLoad.finalize(); assert(theSIMDLoad.type === binaryen.v128); + info = binaryen.getExpressionInfo(theSIMDLoad); + assert(info.type === theSIMDLoad.type); + assert(info.offset === theSIMDLoad.offset); + assert(info.align === theSIMDLoad.align); + assert(info.ptr === theSIMDLoad.ptr); + console.log(theSIMDLoad.toText()); assert( theSIMDLoad.toText() @@ -1231,6 +1644,17 @@ console.log("# SIMDLoadStoreLane"); assert(theSIMDLoadStoreLane.type === binaryen.v128); assert(theSIMDLoadStoreLane.store === false); + var info = binaryen.getExpressionInfo(theSIMDLoadStoreLane); + assert(info.id === theSIMDLoadStoreLane.id); + assert(info.type === theSIMDLoadStoreLane.type); + assert(info.op === theSIMDLoadStoreLane.op); + assert(info.offset === theSIMDLoadStoreLane.offset); + assert(info.align === theSIMDLoadStoreLane.align); + assert(info.index === theSIMDLoadStoreLane.index); + assert(info.ptr === theSIMDLoadStoreLane.ptr); + assert(info.vec === theSIMDLoadStoreLane.vec); + assert(info.isStore === theSIMDLoadStoreLane.store); + theSIMDLoadStoreLane.op = op = binaryen.Operations.Load16LaneVec128; assert(theSIMDLoadStoreLane.op === op); theSIMDLoadStoreLane.offset = offset = 32; @@ -1247,6 +1671,15 @@ console.log("# SIMDLoadStoreLane"); theSIMDLoadStoreLane.finalize(); assert(theSIMDLoadStoreLane.type === binaryen.v128); + info = binaryen.getExpressionInfo(theSIMDLoadStoreLane); + assert(info.type === theSIMDLoadStoreLane.type); + assert(info.op === theSIMDLoadStoreLane.op); + assert(info.offset === theSIMDLoadStoreLane.offset); + assert(info.align === theSIMDLoadStoreLane.align); + assert(info.index === theSIMDLoadStoreLane.index); + assert(info.ptr === theSIMDLoadStoreLane.ptr); + assert(info.vec === theSIMDLoadStoreLane.vec); + console.log(theSIMDLoadStoreLane.toText()); assert( theSIMDLoadStoreLane.toText() @@ -1261,6 +1694,11 @@ console.log("# SIMDLoadStoreLane"); theSIMDLoadStoreLane.finalize(); assert(theSIMDLoadStoreLane.type === binaryen.none); + info = binaryen.getExpressionInfo(theSIMDLoadStoreLane); + assert(info.type === theSIMDLoadStoreLane.type); + assert(info.op === theSIMDLoadStoreLane.op); + assert(info.isStore === theSIMDLoadStoreLane.store); + console.log(theSIMDLoadStoreLane.toText()); assert( theSIMDLoadStoreLane.toText() @@ -1289,6 +1727,14 @@ console.log("# MemoryInit"); assert(theMemoryInit.size === size); assert(theMemoryInit.type === binaryen.none); + var info = binaryen.getExpressionInfo(theMemoryInit); + assert(info.id === theMemoryInit.id); + assert(info.type === theMemoryInit.type); + assert(info.segment === theMemoryInit.segment); + assert(info.dest === theMemoryInit.dest); + assert(info.offset === theMemoryInit.offset); + assert(info.size === theMemoryInit.size); + theMemoryInit.segment = segment = "5"; assert(theMemoryInit.segment === "5"); theMemoryInit.dest = dest = module.i32.const(6); @@ -1301,6 +1747,13 @@ console.log("# MemoryInit"); theMemoryInit.finalize(); assert(theMemoryInit.type === binaryen.none); + info = binaryen.getExpressionInfo(theMemoryInit); + assert(info.type === theMemoryInit.type); + assert(info.segment === theMemoryInit.segment); + assert(info.dest === theMemoryInit.dest); + assert(info.offset === theMemoryInit.offset); + assert(info.size === theMemoryInit.size); + console.log(theMemoryInit.toText()); assert( theMemoryInit.toText() @@ -1322,12 +1775,21 @@ console.log("# DataDrop"); assert(theDataDrop.segment === segment); assert(theDataDrop.type === binaryen.none); + var info = binaryen.getExpressionInfo(theDataDrop); + assert(info.id === theDataDrop.id); + assert(info.type === theDataDrop.type); + assert(info.segment === theDataDrop.segment); + theDataDrop.segment = segment = "2"; assert(theDataDrop.segment === "2"); theDataDrop.type = binaryen.f64; theDataDrop.finalize(); assert(theDataDrop.type === binaryen.none); + info = binaryen.getExpressionInfo(theDataDrop); + assert(info.type === theDataDrop.type); + assert(info.segment === theDataDrop.segment); + console.log(theDataDrop.toText()); assert( theDataDrop.toText() @@ -1354,6 +1816,13 @@ console.log("# MemoryCopy"); assert(theMemoryCopy.size === size); assert(theMemoryCopy.type === binaryen.none); + var info = binaryen.getExpressionInfo(theMemoryCopy); + assert(info.id === theMemoryCopy.id); + assert(info.type === theMemoryCopy.type); + assert(info.dest === theMemoryCopy.dest); + assert(info.source === theMemoryCopy.source); + assert(info.size === theMemoryCopy.size); + theMemoryCopy.dest = dest = module.i32.const(4); assert(theMemoryCopy.dest === dest); theMemoryCopy.source = source = module.i32.const(5); @@ -1364,6 +1833,12 @@ console.log("# MemoryCopy"); theMemoryCopy.finalize(); assert(theMemoryCopy.type === binaryen.none); + info = binaryen.getExpressionInfo(theMemoryCopy); + assert(info.type === theMemoryCopy.type); + assert(info.dest === theMemoryCopy.dest); + assert(info.source === theMemoryCopy.source); + assert(info.size === theMemoryCopy.size); + console.log(theMemoryCopy.toText()); assert( theMemoryCopy.toText() @@ -1390,6 +1865,13 @@ console.log("# MemoryFill"); assert(theMemoryFill.size === size); assert(theMemoryFill.type === binaryen.none); + var info = binaryen.getExpressionInfo(theMemoryFill); + assert(info.id === theMemoryFill.id); + assert(info.type === theMemoryFill.type); + assert(info.dest === theMemoryFill.dest); + assert(info.value === theMemoryFill.value); + assert(info.size === theMemoryFill.size); + theMemoryFill.dest = dest = module.i32.const(4); assert(theMemoryFill.dest === dest); theMemoryFill.value = value = module.i32.const(5); @@ -1400,6 +1882,12 @@ console.log("# MemoryFill"); theMemoryFill.finalize(); assert(theMemoryFill.type === binaryen.none); + info = binaryen.getExpressionInfo(theMemoryFill); + assert(info.type === theMemoryFill.type); + assert(info.dest === theMemoryFill.dest); + assert(info.value === theMemoryFill.value); + assert(info.size === theMemoryFill.size); + console.log(theMemoryFill.toText()); assert( theMemoryFill.toText() @@ -1421,12 +1909,21 @@ console.log("# RefIsNull"); assert(theRefIsNull.value === value); assert(theRefIsNull.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theRefIsNull); + assert(info.id === theRefIsNull.id); + assert(info.type === theRefIsNull.type); + assert(info.value === theRefIsNull.value); + theRefIsNull.value = value = module.local.get(2, binaryen.externref); assert(theRefIsNull.value === value); theRefIsNull.type = binaryen.f64; theRefIsNull.finalize(); assert(theRefIsNull.type === binaryen.i32); + info = binaryen.getExpressionInfo(theRefIsNull); + assert(info.type === theRefIsNull.type); + assert(info.value === theRefIsNull.value); + console.log(theRefIsNull.toText()); assert( theRefIsNull.toText() @@ -1451,6 +1948,12 @@ console.log("# RefAs"); assert(theRefAs.value === value); assert(theRefAs.type !== binaryen.i32); // TODO: === (ref any) + var info = binaryen.getExpressionInfo(theRefAs); + assert(info.id === theRefAs.id); + assert(info.type === theRefAs.type); + assert(info.op === theRefAs.op); + assert(info.value === theRefAs.value); + theRefAs.op = op = binaryen.Operations.RefAsExternConvertAny; assert(theRefAs.op === op); theRefAs.op = op = binaryen.Operations.RefAsNonNull; @@ -1460,6 +1963,11 @@ console.log("# RefAs"); theRefAs.finalize(); assert(theRefAs.type !== binaryen.f64); // TODO: === (ref any) + info = binaryen.getExpressionInfo(theRefAs); + assert(info.type === theRefAs.type); + assert(info.op === theRefAs.op); + assert(info.value === theRefAs.value); + console.log(theRefAs.toText()); assert( theRefAs.toText() @@ -1485,11 +1993,20 @@ console.log("# RefFunc"); assert(theRefFunc.func === func); assert(theRefFunc.type === type); + var info = binaryen.getExpressionInfo(theRefFunc); + assert(info.id === theRefFunc.id); + assert(info.type === theRefFunc.type); + assert(info.func === theRefFunc.func); + theRefFunc.func = func = "b"; assert(theRefFunc.func === func); theRefFunc.finalize(); assert(theRefFunc.type === type); + info = binaryen.getExpressionInfo(theRefFunc); + assert(info.type === theRefFunc.type); + assert(info.func === theRefFunc.func); + console.log(theRefFunc.toText()); assert( theRefFunc.toText() @@ -1513,6 +2030,12 @@ console.log("# RefEq"); assert(theRefEq.right === right); assert(theRefEq.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theRefEq); + assert(info.id === theRefEq.id); + assert(info.type === theRefEq.type); + assert(info.left === theRefEq.left); + assert(info.right === theRefEq.right); + theRefEq.left = left = module.local.get(2, binaryen.eqref); assert(theRefEq.left === left); theRefEq.right = right = module.local.get(3, binaryen.eqref); @@ -1521,6 +2044,11 @@ console.log("# RefEq"); theRefEq.finalize(); assert(theRefEq.type === binaryen.i32); + info = binaryen.getExpressionInfo(theRefEq); + assert(info.type === theRefEq.type); + assert(info.left === theRefEq.left); + assert(info.right === theRefEq.right); + console.log(theRefEq.toText()); assert( theRefEq.toText() @@ -1544,6 +2072,12 @@ console.log("# RefTest"); assert(theRefTest.castType === castType); assert(theRefTest.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theRefTest); + assert(info.id === theRefTest.id); + assert(info.type === theRefTest.type); + assert(info.ref === theRefTest.ref); + assert(info.castType === theRefTest.castType); + theRefTest.ref = ref = module.local.get(2, binaryen.externref); assert(theRefTest.ref === ref); theRefTest.castType = castType = binaryen.externref; @@ -1552,6 +2086,11 @@ console.log("# RefTest"); theRefTest.finalize(); assert(theRefTest.type === binaryen.i32); + info = binaryen.getExpressionInfo(theRefTest); + assert(info.type === theRefTest.type); + assert(info.ref === theRefTest.ref); + assert(info.castType === theRefTest.castType); + console.log(theRefTest.toText()); assert( theRefTest.toText() @@ -1574,12 +2113,21 @@ console.log("# RefCast"); assert(theRefCast.ref === ref); assert(theRefCast.type === type); + var info = binaryen.getExpressionInfo(theRefCast); + assert(info.id === theRefCast.id); + assert(info.type === theRefCast.type); + assert(info.ref === theRefCast.ref); + theRefCast.ref = ref = module.local.get(2, binaryen.externref); assert(theRefCast.ref === ref); theRefCast.type = type = binaryen.externref; theRefCast.finalize(); assert(theRefCast.type === type); + info = binaryen.getExpressionInfo(theRefCast); + assert(info.type === theRefCast.type); + assert(info.ref === theRefCast.ref); + console.log(theRefCast.toText()); assert( theRefCast.toText() @@ -1608,6 +2156,14 @@ console.log("# BrOn"); // TODO: What should theBrOn.type be equal to? + var info = binaryen.getExpressionInfo(theBrOn); + assert(info.id === theBrOn.id); + assert(info.type === theBrOn.type); + assert(info.name === theBrOn.name); + assert(info.ref === theBrOn.ref); + assert(info.op === theBrOn.op); + assert(info.castType === theBrOn.castType); + theBrOn.name = name = "br2"; assert(theBrOn.name === name); theBrOn.ref = ref = module.local.get(1, binaryen.anyref); @@ -1618,6 +2174,12 @@ console.log("# BrOn"); assert(theBrOn.castType === castType); theBrOn.finalize(); + info = binaryen.getExpressionInfo(theBrOn); + assert(info.name === theBrOn.name); + assert(info.ref === theBrOn.ref); + assert(info.op === theBrOn.op); + assert(info.castType === theBrOn.castType); + console.log(theBrOn.toText()); assert( theBrOn.toText() @@ -1657,6 +2219,11 @@ console.log("# StructNew"); assertDeepEqual(theStructNew.getOperands(), operands); assert(theStructNew.type === type); + var info = binaryen.getExpressionInfo(theStructNew); + assert(info.id === theStructNew.id); + assert(info.type === theStructNew.type); + assertDeepEqual(info.operands, theStructNew.operands); + theStructNew.operands = operands = [ module.i32.const(3), // set module.i32.const(4), // set @@ -1675,6 +2242,10 @@ console.log("# StructNew"); theStructNew.finalize(); assert(theStructNew.type === type); + info = binaryen.getExpressionInfo(theStructNew); + assert(info.type === theStructNew.type); + assertDeepEqual(info.operands, theStructNew.operands); + console.log(theStructNew.toText()); assert( theStructNew.toText() @@ -1714,6 +2285,13 @@ console.log("# StructGet"); assert(theStructGet.signed === signed); assert(theStructGet.type === type); + var info = binaryen.getExpressionInfo(theStructGet); + assert(info.id === theStructGet.id); + assert(info.type === theStructGet.type); + assert(info.index === theStructGet.index); + assert(info.ref === theStructGet.ref); + assert(info.isSigned === theStructGet.signed); + theStructGet.index = index = 1; assert(theStructGet.index === index); theStructGet.ref = ref = module.local.get(1, struct1Type); @@ -1724,6 +2302,12 @@ console.log("# StructGet"); theStructGet.finalize(); assert(theStructGet.type === type); + info = binaryen.getExpressionInfo(theStructGet); + assert(info.type === theStructGet.type); + assert(info.index === theStructGet.index); + assert(info.ref === theStructGet.ref); + assert(info.isSigned === theStructGet.signed); + console.log(theStructGet.toText()); assert( theStructGet.toText() @@ -1762,6 +2346,13 @@ console.log("# StructSet"); assert(theStructSet.value === value); assert(theStructSet.type === binaryen.none); + var info = binaryen.getExpressionInfo(theStructSet); + assert(info.id === theStructSet.id); + assert(info.type === theStructSet.type); + assert(info.index === theStructSet.index); + assert(info.ref === theStructSet.ref); + assert(info.value === theStructSet.value); + theStructSet.index = index = 1; assert(theStructSet.index === index); theStructSet.ref = ref = module.local.get(2, struct1Type); @@ -1772,6 +2363,12 @@ console.log("# StructSet"); theStructSet.finalize(); assert(theStructSet.type === binaryen.none); + info = binaryen.getExpressionInfo(theStructSet); + assert(info.type === theStructSet.type); + assert(info.index === theStructSet.index); + assert(info.ref === theStructSet.ref); + assert(info.value === theStructSet.value); + console.log(theStructSet.toText()); assert( theStructSet.toText() @@ -1804,6 +2401,12 @@ console.log("# ArrayNew"); assert(theArrayNew.init === init); assert(theArrayNew.type === type); + var info = binaryen.getExpressionInfo(theArrayNew); + assert(info.id === theArrayNew.id); + assert(info.type === theArrayNew.type); + assert(info.size === theArrayNew.size); + assert(info.init === theArrayNew.init); + theArrayNew.size = size = module.i32.const(4); assert(theArrayNew.size === size); theArrayNew.init = init = module.i32.const(3); @@ -1812,6 +2415,11 @@ console.log("# ArrayNew"); theArrayNew.finalize(); assert(theArrayNew.type === type); + info = binaryen.getExpressionInfo(theArrayNew); + assert(info.type === theArrayNew.type); + assert(info.size === theArrayNew.size); + assert(info.init === theArrayNew.init); + console.log(theArrayNew.toText()); assert( theArrayNew.toText() @@ -1846,6 +2454,11 @@ console.log("# ArrayNewFixed"); assertDeepEqual(theArrayNewFixed.getValues(), values); assert(theArrayNewFixed.type === type); + var info = binaryen.getExpressionInfo(theArrayNewFixed); + assert(info.id === theArrayNewFixed.id); + assert(info.type === theArrayNewFixed.type); + assertDeepEqual(info.values, theArrayNewFixed.values); + theArrayNewFixed.values = values = [ module.i32.const(3), // set module.i32.const(4), // set @@ -1864,6 +2477,10 @@ console.log("# ArrayNewFixed"); theArrayNewFixed.finalize(); assert(theArrayNewFixed.type === type); + info = binaryen.getExpressionInfo(theArrayNewFixed); + assert(info.type === theArrayNewFixed.type); + assertDeepEqual(info.values, theArrayNewFixed.values); + console.log(theArrayNewFixed.toText()); assert( theArrayNewFixed.toText() @@ -1898,6 +2515,13 @@ console.log("# ArrayNewData"); assert(theArrayNewData.size === size); assert(theArrayNewData.type === type); + var info = binaryen.getExpressionInfo(theArrayNewData); + assert(info.id === theArrayNewData.id); + assert(info.type === theArrayNewData.type); + assert(info.segment === theArrayNewData.segment); + assert(info.offset === theArrayNewData.offset); + assert(info.size === theArrayNewData.size); + theArrayNewData.segment = segment = "3"; assert(theArrayNewData.segment === segment); theArrayNewData.offset = offset = module.i32.const(4); @@ -1908,6 +2532,12 @@ console.log("# ArrayNewData"); theArrayNewData.finalize(); assert(theArrayNewData.type === type); + info = binaryen.getExpressionInfo(theArrayNewData); + assert(info.type === theArrayNewData.type); + assert(info.segment === theArrayNewData.segment); + assert(info.offset === theArrayNewData.offset); + assert(info.size === theArrayNewData.size); + console.log(theArrayNewData.toText()); assert( theArrayNewData.toText() @@ -1942,6 +2572,13 @@ console.log("# ArrayNewElem"); assert(theArrayNewElem.size === size); assert(theArrayNewElem.type === type); + var info = binaryen.getExpressionInfo(theArrayNewElem); + assert(info.id === theArrayNewElem.id); + assert(info.type === theArrayNewElem.type); + assert(info.segment === theArrayNewElem.segment); + assert(info.offset === theArrayNewElem.offset); + assert(info.size === theArrayNewElem.size); + theArrayNewElem.segment = segment = "3"; assert(theArrayNewElem.segment === segment); theArrayNewElem.offset = offset = module.i32.const(4); @@ -1952,6 +2589,13 @@ console.log("# ArrayNewElem"); theArrayNewElem.finalize(); assert(theArrayNewElem.type === type); + info = binaryen.getExpressionInfo(theArrayNewElem); + assert(info.id === theArrayNewElem.id); + assert(info.type === theArrayNewElem.type); + assert(info.segment === theArrayNewElem.segment); + assert(info.offset === theArrayNewElem.offset); + assert(info.size === theArrayNewElem.size); + console.log(theArrayNewElem.toText()); assert( theArrayNewElem.toText() @@ -1986,6 +2630,13 @@ console.log("# ArrayGet"); assert(theArrayGet.signed === signed); assert(theArrayGet.type === type); + var info = binaryen.getExpressionInfo(theArrayGet); + assert(info.id === theArrayGet.id); + assert(info.type === theArrayGet.type); + assert(info.ref === theArrayGet.ref); + assert(info.index === theArrayGet.index); + assert(info.isSigned === theArrayGet.signed); + theArrayGet.ref = ref = module.local.get(1, array1Type); assert(theArrayGet.ref === ref); theArrayGet.index = index = module.i32.const(1); @@ -1996,6 +2647,12 @@ console.log("# ArrayGet"); theArrayGet.finalize(); assert(theArrayGet.type === type); + info = binaryen.getExpressionInfo(theArrayGet); + assert(info.type === theArrayGet.type); + assert(info.ref === theArrayGet.ref); + assert(info.index === theArrayGet.index); + assert(info.isSigned === theArrayGet.signed); + console.log(theArrayGet.toText()); assert( theArrayGet.toText() @@ -2029,6 +2686,13 @@ console.log("# ArraySet"); assert(theArraySet.value === value); assert(theArraySet.type === binaryen.none); + var info = binaryen.getExpressionInfo(theArraySet); + assert(info.id === theArraySet.id); + assert(info.type === theArraySet.type); + assert(info.ref === theArraySet.ref); + assert(info.index === theArraySet.index); + assert(info.value === theArraySet.value); + theArraySet.ref = ref = module.local.get(2, array1Type); assert(theArraySet.ref === ref); theArraySet.index = index = module.i32.const(1); @@ -2039,6 +2703,12 @@ console.log("# ArraySet"); theArraySet.finalize(); assert(theArraySet.type === binaryen.none); + info = binaryen.getExpressionInfo(theArraySet); + assert(info.type === theArraySet.type); + assert(info.ref === theArraySet.ref); + assert(info.index === theArraySet.index); + assert(info.value === theArraySet.value); + console.log(theArraySet.toText()); assert( theArraySet.toText() @@ -2068,12 +2738,21 @@ console.log("# ArrayLen"); assert(theArrayLen.ref === ref); assert(theArrayLen.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theArrayLen); + assert(info.id === theArrayLen.id); + assert(info.type === theArrayLen.type); + assert(info.ref === theArrayLen.ref); + theArrayLen.ref = ref = module.local.get(1, array1Type); assert(theArrayLen.ref === ref); theArrayLen.type = binaryen.i64; theArrayLen.finalize(); assert(theArrayLen.type === binaryen.i32); + info = binaryen.getExpressionInfo(theArrayLen); + assert(info.type === theArrayLen.type); + assert(info.ref === theArrayLen.ref); + console.log(theArrayLen.toText()); assert( theArrayLen.toText() @@ -2109,6 +2788,14 @@ console.log("# ArrayFill"); assert(theArrayFill.size === size); assert(theArrayFill.type === binaryen.none); + var info = binaryen.getExpressionInfo(theArrayFill); + assert(info.id === theArrayFill.id); + assert(info.type === theArrayFill.type); + assert(info.ref === theArrayFill.ref); + assert(info.index === theArrayFill.index); + assert(info.value === theArrayFill.value); + assert(info.size === theArrayFill.size); + theArrayFill.ref = ref = module.local.get(2, array1Type); assert(theArrayFill.ref === ref); theArrayFill.index = index = module.i32.const(2); @@ -2121,6 +2808,13 @@ console.log("# ArrayFill"); theArrayFill.finalize(); assert(theArrayFill.type === binaryen.none); + info = binaryen.getExpressionInfo(theArrayFill); + assert(info.type === theArrayFill.type); + assert(info.ref === theArrayFill.ref); + assert(info.index === theArrayFill.index); + assert(info.value === theArrayFill.value); + assert(info.size === theArrayFill.size); + console.log(theArrayFill.toText()); assert( theArrayFill.toText() @@ -2158,6 +2852,15 @@ console.log("# ArrayCopy"); assert(theArrayCopy.length === length); assert(theArrayCopy.type === binaryen.none); + var info = binaryen.getExpressionInfo(theArrayCopy); + assert(info.id === theArrayCopy.id); + assert(info.type === theArrayCopy.type); + assert(info.destRef === theArrayCopy.destRef); + assert(info.destIndex === theArrayCopy.destIndex); + assert(info.srcRef === theArrayCopy.srcRef); + assert(info.srcIndex === theArrayCopy.srcIndex); + assert(info.length === theArrayCopy.length); + theArrayCopy.destRef = destRef = module.local.get(2, array1Type); assert(theArrayCopy.destRef === destRef); theArrayCopy.destIndex = destIndex = module.i32.const(2); @@ -2172,6 +2875,14 @@ console.log("# ArrayCopy"); theArrayCopy.finalize(); assert(theArrayCopy.type === binaryen.none); + info = binaryen.getExpressionInfo(theArrayCopy); + assert(info.type === theArrayCopy.type); + assert(info.destRef === theArrayCopy.destRef); + assert(info.destIndex === theArrayCopy.destIndex); + assert(info.srcRef === theArrayCopy.srcRef); + assert(info.srcIndex === theArrayCopy.srcIndex); + assert(info.length === theArrayCopy.length); + console.log(theArrayCopy.toText()); assert( theArrayCopy.toText() @@ -2208,6 +2919,15 @@ console.log("# ArrayInitData"); assert(theArrayInitData.offset === offset); assert(theArrayInitData.size === size); assert(theArrayInitData.type === binaryen.none); + + var info = binaryen.getExpressionInfo(theArrayInitData); + assert(info.id === theArrayInitData.id); + assert(info.type === theArrayInitData.type); + assert(info.segment === theArrayInitData.segment); + assert(info.ref === theArrayInitData.ref); + assert(info.index === theArrayInitData.index); + assert(info.offset === theArrayInitData.offset); + assert(info.size === theArrayInitData.size); theArrayInitData.segment = segment = "1"; assert(theArrayInitData.segment === segment); @@ -2223,6 +2943,14 @@ console.log("# ArrayInitData"); theArrayInitData.finalize(); assert(theArrayInitData.type === binaryen.none); + info = binaryen.getExpressionInfo(theArrayInitData); + assert(info.type === theArrayInitData.type); + assert(info.segment === theArrayInitData.segment); + assert(info.ref === theArrayInitData.ref); + assert(info.index === theArrayInitData.index); + assert(info.offset === theArrayInitData.offset); + assert(info.size === theArrayInitData.size); + console.log(theArrayInitData.toText()); assert( theArrayInitData.toText() @@ -2260,6 +2988,15 @@ console.log("# ArrayInitElem"); assert(theArrayInitElem.size === size); assert(theArrayInitElem.type === binaryen.none); + var info = binaryen.getExpressionInfo(theArrayInitElem); + assert(info.id === theArrayInitElem.id); + assert(info.type === theArrayInitElem.type); + assert(info.segment === theArrayInitElem.segment); + assert(info.ref === theArrayInitElem.ref); + assert(info.index === theArrayInitElem.index); + assert(info.offset === theArrayInitElem.offset); + assert(info.size === theArrayInitElem.size); + theArrayInitElem.segment = segment = "1"; assert(theArrayInitElem.segment === segment); theArrayInitElem.ref = ref = module.local.get(1, array1Type); @@ -2274,6 +3011,14 @@ console.log("# ArrayInitElem"); theArrayInitElem.finalize(); assert(theArrayInitElem.type === binaryen.none); + info = binaryen.getExpressionInfo(theArrayInitElem); + assert(info.type === theArrayInitElem.type); + assert(info.segment === theArrayInitElem.segment); + assert(info.ref === theArrayInitElem.ref); + assert(info.index === theArrayInitElem.index); + assert(info.offset === theArrayInitElem.offset); + assert(info.size === theArrayInitElem.size); + console.log(theArrayInitElem.toText()); assert( theArrayInitElem.toText() @@ -2307,6 +3052,17 @@ console.log("# Try"); assert(theTry.hasCatchAll() == 1); console.log(theTry.toText()); + var info = binaryen.getExpressionInfo(theTry); + assert(info.id === theTry.id); + assert(info.type === theTry.type); + assert(info.name === theTry.name); + assert(info.body === theTry.body); + assertDeepEqual(info.catchTags, theTry.catchTags); + assertDeepEqual(info.catchBodies, theTry.catchBodies); + assert(info.hasCatchAll === theTry.hasCatchAll()); + assert(info.delegateTarget === theTry.delegateTarget); + assert(info.isDelegate === theTry.delegate); + theTry.body = body = module.i32.const(4); assert(theTry.body === body); catchBodies = [ @@ -2349,6 +3105,17 @@ console.log("# Try"); theTry.finalize(); assert(theTry.type === binaryen.i32); + info = binaryen.getExpressionInfo(theTry); + assert(info.id === theTry.id); + assert(info.type === theTry.type); + assert(info.name === theTry.name); + assert(info.body === theTry.body); + assertDeepEqual(info.catchTags, theTry.catchTags); + assertDeepEqual(info.catchBodies, theTry.catchBodies); + assert(info.hasCatchAll === theTry.hasCatchAll()); + assert(info.delegateTarget === theTry.delegateTarget); + assert(info.isDelegate === theTry.delegate); + console.log(theTry.toText()); const tryDelegate = binaryen.Try(module.try('', body, [], [], "try_blah")); @@ -2356,6 +3123,11 @@ console.log("# Try"); assert(tryDelegate.getDelegateTarget() == "try_blah"); tryDelegate.setDelegateTarget("try_outer"); assert(tryDelegate.getDelegateTarget() == "try_outer"); + + info = binaryen.getExpressionInfo(tryDelegate); + assert(info.delegateTarget === tryDelegate.delegateTarget); + assert(info.isDelegate === tryDelegate.delegate); + console.log(tryDelegate.toText()); module.dispose(); @@ -2377,6 +3149,12 @@ console.log("# Throw"); assertDeepEqual(theThrow.operands, operands); assert(theThrow.type === binaryen.unreachable); + var info = binaryen.getExpressionInfo(theThrow); + assert(info.id === theThrow.id); + assert(info.type === theThrow.type); + assert(info.tag === theThrow.tag); + assertDeepEqual(info.operands, theThrow.operands); + theThrow.tag = "bar"; assert(theThrow.tag === "bar"); theThrow.operands = operands = [ @@ -2398,6 +3176,11 @@ console.log("# Throw"); theThrow.finalize(); assert(theThrow.type === binaryen.unreachable); + info = binaryen.getExpressionInfo(theThrow); + assert(info.type === theThrow.type); + assert(info.tag === theThrow.tag); + assertDeepEqual(info.operands, theThrow.operands); + console.log(theThrow.toText()); assert( theThrow.toText() @@ -2418,12 +3201,21 @@ console.log("# Rethrow"); assert(theRethrow.target === "l0"); assert(theRethrow.type === binaryen.unreachable); + var info = binaryen.getExpressionInfo(theRethrow); + assert(info.id === theRethrow.id); + assert(info.type === theRethrow.type); + assert(info.target === theRethrow.target); + theRethrow.target = "l1"; assert(theRethrow.target === "l1"); theRethrow.type = binaryen.f64; theRethrow.finalize(); assert(theRethrow.type === binaryen.unreachable); + info = binaryen.getExpressionInfo(theRethrow); + assert(info.type === theRethrow.type); + assert(info.target === theRethrow.target); + console.log(theRethrow.toText()); assert( theRethrow.toText() @@ -2449,6 +3241,11 @@ console.log("# TupleMake"); assertDeepEqual(theTupleMake.operands, operands); assert(theTupleMake.type === type); + var info = binaryen.getExpressionInfo(theTupleMake); + assert(info.id === theTupleMake.id); + assert(info.type === theTupleMake.type); + assertDeepEqual(info.operands, theTupleMake.operands); + theTupleMake.operands = operands = [ module.i32.const(3), // set module.i32.const(4), // set @@ -2468,6 +3265,10 @@ console.log("# TupleMake"); theTupleMake.finalize(); assert(theTupleMake.type === type); + info = binaryen.getExpressionInfo(theTupleMake); + assert(info.type === theTupleMake.type); + assertDeepEqual(info.operands, theTupleMake.operands); + console.log(theTupleMake.toText()); assert( theTupleMake.toText() @@ -2494,6 +3295,12 @@ console.log("# TupleExtract"); assert(theTupleExtract.index === index); assert(theTupleExtract.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theTupleExtract); + assert(info.id === theTupleExtract.id); + assert(info.type === theTupleExtract.type); + assert(info.tuple === theTupleExtract.tuple); + assert(info.index === theTupleExtract.index); + theTupleExtract.tuple = tuple = module.tuple.make([ module.f64.const(3), module.f64.const(4) @@ -2505,6 +3312,11 @@ console.log("# TupleExtract"); theTupleExtract.finalize(); assert(theTupleExtract.type === binaryen.f64); + info = binaryen.getExpressionInfo(theTupleExtract); + assert(info.type === theTupleExtract.type); + assert(info.tuple === theTupleExtract.tuple); + assert(info.index === theTupleExtract.index); + console.log(theTupleExtract.toText()); assert( theTupleExtract.toText() @@ -2526,9 +3338,17 @@ console.log("# RefI31"); assert(theRefI31.value === value); // assert(theRefI31.type === binaryen.?); // TODO: (ref i31) + var info = binaryen.getExpressionInfo(theRefI31); + assert(info.id === theRefI31.id); + assert(info.type === theRefI31.type); + assert(info.value === theRefI31.value); + theRefI31.value = value = module.local.get(2, binaryen.i32); assert(theRefI31.value === value); + info = binaryen.getExpressionInfo(theRefI31); + assert(info.value === theRefI31.value); + console.log(theRefI31.toText()); assert( theRefI31.toText() @@ -2551,6 +3371,12 @@ console.log("# I31Get"); assert(theI31Get.signed === true); assert(theI31Get.type === binaryen.i32); + var info = binaryen.getExpressionInfo(theI31Get); + assert(info.id === theI31Get.id); + assert(info.type === theI31Get.type); + assert(info.i31 === theI31Get.i31); + assert(info.isSigned === theI31Get.signed); + theI31Get.i31 = i31 = module.local.get(2, binaryen.i31ref); assert(theI31Get.i31 === i31); theI31Get.signed = false; @@ -2559,6 +3385,11 @@ console.log("# I31Get"); theI31Get.finalize(); assert(theI31Get.type === binaryen.i32); + info = binaryen.getExpressionInfo(theI31Get); + assert(info.type === theI31Get.type); + assert(info.i31 === theI31Get.i31); + assert(info.isSigned === theI31Get.signed); + console.log(theI31Get.toText()); assert( theI31Get.toText() From 19e887e28b3998438e6032db58ca7ceacf5dc88d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 18 Apr 2025 07:33:22 -0700 Subject: [PATCH 441/622] Fuzzer: Avoid constantly growing the wasm each time (#7511) Imagine we start with a fuzz file, then mutate it (using it as initial content and letting the fuzzer make changes, perhaps with `--preserve-imports-and-exports`). We can mutate it again and again, which is what harnesses like Fuzzilli do. It is somewhat bad if we keep growing the file, because then when we explore the space of wasm files, the further we travel, the more we focus on big files for no good reason. Running `--fuzz-passes` is one way to trim the wasm (it might end up running `-O3`), but not reliable. This PR goes through places that always add code and tries to at least give a chance to not do so. As long as there is a chance to not grow, then a harness can see two testcases of equal coverage and pick the smaller one. Specific changes: * If a data segment exists already, do not always add others. * If an exnref table exists already, reuse it (like we do with funcref). * Don't always add hashMemory support (which is large). * Avoid errors in the fuzzer on wasm files without exports. That is now possible, since we no longer always add at least 1 export. --- scripts/fuzz_opt.py | 26 ++++-- src/tools/fuzzing.h | 1 + src/tools/fuzzing/fuzzing.cpp | 88 ++++++++++-------- test/passes/fuzz_metrics_noprint.bin.txt | 60 ++++++------ .../fuzz_metrics_passes_noprint.bin.txt | 60 ++++++------ ...e-to-fuzz_all-features_metrics_noprint.txt | 92 +++++++++---------- 6 files changed, 179 insertions(+), 148 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 80052431da7..0cdc976ef86 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1627,7 +1627,11 @@ def handle(self, wasm): class ClusterFuzz(TestCaseHandler): frequency = 0.1 - def handle(self, wasm): + # Use handle_pair over handle because we don't use these wasm files anyhow, + # we generate our own using run.py. If we used handle, we'd be called twice + # for each iteration (once for each of the wasm files we ignore), which is + # confusing. + def handle_pair(self, input, before_wasm, after_wasm, opts): self.ensure() # run.py() should emit these two files. Delete them to make sure they @@ -1651,6 +1655,9 @@ def handle(self, wasm): assert os.path.exists(fuzz_file) assert os.path.exists(flags_file) + # We'll use the fuzz file a few times below in commands. + fuzz_file = os.path.abspath(fuzz_file) + # Run the testcase in V8, similarly to how ClusterFuzz does. cmd = [shared.V8] # The flags are given in the flags file - we do *not* use our normal @@ -1664,7 +1671,7 @@ def handle(self, wasm): cmd += get_v8_extra_flags() # Run the fuzz file, which contains a modified fuzz_shell.js - we do # *not* run fuzz_shell.js normally. - cmd.append(os.path.abspath(fuzz_file)) + cmd.append(fuzz_file) # No wasm file needs to be provided: it is hardcoded into the JS. Note # that we use run_vm(), which will ignore known issues in our output and # in V8. Those issues may cause V8 to e.g. reject a binary we emit that @@ -1672,12 +1679,19 @@ def handle(self, wasm): # a crash). output = run_vm(cmd) - # Verify that we called something. The fuzzer should always emit at - # least one exported function (unless we've decided to ignore the entire + # Verify that we called something, if the fuzzer emitted a func export + # (rarely, none might exist), unless we've decided to ignore the entire # run, or if the wasm errored during instantiation, which can happen due - # to a testcase with a segment out of bounds, say). + # to a testcase with a segment out of bounds, say. if output != IGNORE and not output.startswith(INSTANTIATE_ERROR): - assert FUZZ_EXEC_CALL_PREFIX in output + # Do the work to find if there were function exports: extract the + # wasm from the JS, and process it. + run([sys.executable, + in_binaryen('scripts', 'clusterfuzz', 'extract_wasms.py'), + fuzz_file, + 'extracted']) + if get_exports('extracted.0.wasm', ['func']): + assert FUZZ_EXEC_CALL_PREFIX in output def ensure(self): # The first time we actually run, set things up: make a bundle like the diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index bb43fa39fa2..6cf09d164bb 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -174,6 +174,7 @@ class TranslateToFuzzReader { Name exnrefTableName; std::unordered_map logImportNames; + Name hashMemoryName; Name throwImportName; Name tableGetImportName; Name tableSetImportName; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 864705cea83..26cc41e6d73 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -26,10 +26,6 @@ namespace wasm { -namespace { - -} // anonymous namespace - TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, std::vector&& input, bool closedWorld) @@ -395,9 +391,12 @@ void TranslateToFuzzReader::setupMemory() { auto& memory = wasm.memories[0]; if (wasm.features.hasBulkMemory()) { - size_t memCovered = 0; + size_t numSegments = upTo(8); // need at least one segment for memory.inits - size_t numSegments = upTo(8) + 1; + if (wasm.dataSegments.empty() && !numSegments) { + numSegments = 1; + } + size_t memCovered = 0; for (size_t i = 0; i < numSegments; i++) { auto segment = builder.makeDataSegment(); segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(i)), @@ -417,19 +416,21 @@ void TranslateToFuzzReader::setupMemory() { wasm.addDataSegment(std::move(segment)); } } else { - // init some data - auto segment = builder.makeDataSegment(); - segment->memory = memory->name; - segment->offset = - builder.makeConst(Literal::makeFromInt32(0, memory->addressType)); - segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(0)), - false); - auto num = upTo(fuzzParams->USABLE_MEMORY * 2); - for (size_t i = 0; i < num; i++) { - auto value = upTo(512); - segment->data.push_back(value >= 256 ? 0 : (value & 0xff)); + // init some data, especially if none exists before + if (!oneIn(wasm.dataSegments.empty() ? 10 : 2)) { + auto segment = builder.makeDataSegment(); + segment->memory = memory->name; + segment->offset = + builder.makeConst(Literal::makeFromInt32(0, memory->addressType)); + segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(0)), + false); + auto num = upTo(fuzzParams->USABLE_MEMORY * 2); + for (size_t i = 0; i < num; i++) { + auto value = upTo(512); + segment->data.push_back(value >= 256 ? 0 : (value & 0xff)); + } + wasm.addDataSegment(std::move(segment)); } - wasm.addDataSegment(std::move(segment)); } } @@ -588,17 +589,27 @@ void TranslateToFuzzReader::setupTables() { // When EH is enabled, set up an exnref table. if (wasm.features.hasExceptionHandling()) { Type exnref = Type(HeapType::exn, Nullable); - Address initial = upTo(10); - Address max = oneIn(2) ? initial + upTo(4) : Memory::kUnlimitedSize; - auto tablePtr = - builder.makeTable(Names::getValidTableName(wasm, "exnref_table"), - exnref, - initial, - max, - Type::i32); // TODO: wasm64 - tablePtr->hasExplicitName = true; - table = wasm.addTable(std::move(tablePtr)); - exnrefTableName = table->name; + auto iter = + std::find_if(wasm.tables.begin(), wasm.tables.end(), [&](auto& table) { + return table->type == exnref; + }); + if (iter != wasm.tables.end()) { + // Use the existing one. + exnrefTableName = iter->get()->name; + } else { + // Create a new exnref table. + Address initial = upTo(10); + Address max = oneIn(2) ? initial + upTo(4) : Memory::kUnlimitedSize; + auto tablePtr = + builder.makeTable(Names::getValidTableName(wasm, "exnref_table"), + exnref, + initial, + max, + Type::i32); // TODO: wasm64 + tablePtr->hasExplicitName = true; + table = wasm.addTable(std::move(tablePtr)); + exnrefTableName = table->name; + } } } @@ -1073,6 +1084,11 @@ void TranslateToFuzzReader::addImportSleepSupport() { } void TranslateToFuzzReader::addHashMemorySupport() { + // Don't always add this. + if (oneIn(2)) { + return; + } + // Add memory hasher helper (for the hash, see hash.h). The function looks // like: // function hashMemory() { @@ -1107,13 +1123,13 @@ void TranslateToFuzzReader::addHashMemorySupport() { } contents.push_back(builder.makeLocalGet(0, Type::i32)); auto* body = builder.makeBlock(contents); - auto name = Names::getValidFunctionName(wasm, "hashMemory"); + hashMemoryName = Names::getValidFunctionName(wasm, "hashMemory"); auto* hasher = wasm.addFunction(builder.makeFunction( - name, Signature(Type::none, Type::i32), {Type::i32}, body)); + hashMemoryName, Signature(Type::none, Type::i32), {Type::i32}, body)); - if (!preserveImportsAndExports) { + if (!preserveImportsAndExports && !wasm.getExportOrNull("hashMemory")) { wasm.addExport( - builder.makeExport(hasher->name, hasher->name, ExternalKind::Function)); + builder.makeExport("hashMemory", hasher->name, ExternalKind::Function)); // Export memory so JS fuzzing can use it if (!wasm.getExportOrNull("memory")) { wasm.addExport(builder.makeExport( @@ -1321,7 +1337,7 @@ Expression* TranslateToFuzzReader::makeImportSleep(Type type) { } Expression* TranslateToFuzzReader::makeMemoryHashLogging() { - auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32); + auto* hash = builder.makeCall(hashMemoryName, {}, Type::i32); return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none); } @@ -2019,7 +2035,7 @@ void TranslateToFuzzReader::addInvocations(Function* func) { } invocations.push_back(invoke); // log out memory in some cases - if (oneIn(2)) { + if (hashMemoryName && oneIn(2)) { invocations.push_back(makeMemoryHashLogging()); } } @@ -2177,7 +2193,7 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { Expression* TranslateToFuzzReader::_makenone() { auto choice = upTo(100); if (choice < LOGGING_PERCENT) { - if (choice < LOGGING_PERCENT / 2) { + if (!hashMemoryName || choice < LOGGING_PERCENT / 2) { return makeImportLogging(); } else { return makeMemoryHashLogging(); diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index 1d46c87763d..c23a9ac4eb7 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 77 - [funcs] : 111 - [globals] : 21 - [imports] : 5 + [exports] : 65 + [funcs] : 93 + [globals] : 7 + [imports] : 4 [memories] : 1 - [memory-data] : 5 - [table-data] : 45 + [memory-data] : 23 + [table-data] : 25 [tables] : 1 [tags] : 0 - [total] : 9163 - [vars] : 296 - Binary : 620 - Block : 1503 - Break : 288 - Call : 580 - CallIndirect : 101 - Const : 1500 - Drop : 136 - GlobalGet : 772 - GlobalSet : 562 - If : 478 - Load : 126 - LocalGet : 630 - LocalSet : 487 - Loop : 166 - Nop : 78 - RefFunc : 45 - Return : 87 - Select : 75 - Store : 60 - Switch : 2 - Unary : 588 - Unreachable : 279 + [total] : 6800 + [vars] : 256 + Binary : 454 + Block : 1201 + Break : 196 + Call : 205 + CallIndirect : 61 + Const : 1131 + Drop : 88 + GlobalGet : 635 + GlobalSet : 487 + If : 378 + Load : 88 + LocalGet : 406 + LocalSet : 341 + Loop : 148 + Nop : 107 + RefFunc : 25 + Return : 58 + Select : 52 + Store : 41 + Switch : 1 + Unary : 451 + Unreachable : 246 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index 301f7d664dd..934a25abe09 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 36 - [funcs] : 64 - [globals] : 7 - [imports] : 4 + [exports] : 37 + [funcs] : 59 + [globals] : 4 + [imports] : 6 [memories] : 1 - [memory-data] : 29 - [table-data] : 19 + [memory-data] : 20 + [table-data] : 28 [tables] : 1 [tags] : 0 - [total] : 10813 - [vars] : 208 - Binary : 791 - Block : 1709 - Break : 412 - Call : 382 - CallIndirect : 73 - Const : 1793 - Drop : 77 - GlobalGet : 898 - GlobalSet : 637 - If : 561 - Load : 199 - LocalGet : 908 - LocalSet : 616 - Loop : 248 - Nop : 169 - RefFunc : 19 - Return : 89 - Select : 91 - Store : 80 - Switch : 2 - Unary : 747 - Unreachable : 312 + [total] : 9402 + [vars] : 189 + Binary : 651 + Block : 1534 + Break : 332 + Call : 296 + CallIndirect : 91 + Const : 1666 + Drop : 64 + GlobalGet : 650 + GlobalSet : 582 + If : 506 + Load : 149 + LocalGet : 827 + LocalSet : 497 + Loop : 232 + Nop : 114 + RefFunc : 28 + Return : 81 + Select : 75 + Store : 71 + Switch : 7 + Unary : 657 + Unreachable : 292 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 6aa6117a0f8..c949e40bb85 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,57 +1,57 @@ Metrics total - [exports] : 17 - [funcs] : 19 - [globals] : 4 + [exports] : 16 + [funcs] : 21 + [globals] : 26 [imports] : 12 [memories] : 1 - [memory-data] : 17 - [table-data] : 5 + [memory-data] : 16 + [table-data] : 6 [tables] : 2 - [tags] : 3 - [total] : 921 - [vars] : 46 - ArrayNewFixed : 1 + [tags] : 1 + [total] : 887 + [vars] : 47 + ArrayNewFixed : 6 AtomicCmpxchg : 1 - AtomicNotify : 1 + AtomicFence : 1 + AtomicNotify : 2 AtomicRMW : 1 - Binary : 97 - Block : 134 + Binary : 93 + Block : 116 BrOn : 1 - Break : 6 - Call : 37 - CallIndirect : 1 - CallRef : 1 - Const : 177 - DataDrop : 2 - Drop : 18 - GlobalGet : 62 - GlobalSet : 54 - I31Get : 2 - If : 35 - Load : 25 - LocalGet : 58 - LocalSet : 43 - Loop : 9 - MemoryCopy : 3 + Break : 11 + Call : 27 + CallRef : 2 + Const : 178 + Drop : 15 + GlobalGet : 66 + GlobalSet : 50 + If : 32 + Load : 21 + LocalGet : 54 + LocalSet : 29 + Loop : 5 + MemoryCopy : 1 MemoryFill : 1 - Nop : 12 - Pop : 5 - RefEq : 5 - RefFunc : 7 - RefI31 : 8 - RefNull : 1 - Return : 12 + MemoryInit : 1 + Nop : 14 + RefEq : 3 + RefFunc : 11 + RefI31 : 11 + RefNull : 11 + RefTest : 1 + Return : 6 SIMDExtract : 2 - Select : 7 - Store : 1 - StringConst : 7 - StringEq : 3 - StructNew : 6 - TableSet : 1 + Select : 3 + Store : 3 + StringConst : 12 + StringEq : 2 + StringWTF16Get : 1 + StructNew : 14 Throw : 1 - Try : 4 - TryTable : 3 - TupleMake : 1 - Unary : 38 - Unreachable : 27 + Try : 5 + TryTable : 6 + TupleExtract : 1 + TupleMake : 8 + Unary : 33 + Unreachable : 25 From 60cc9c5548951f9dd64cccef88fff313c823654f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 18 Apr 2025 11:24:46 -0700 Subject: [PATCH 442/622] Allow null-checking exact casts without custom descriptors (#7520) We previously allowed trivial exact casts (i.e. those where the input and target types are the same) to validate even without custom descriptors enabled to ensure that finalization always produces valid casts. But validation can also produce casts where the cast target is a non-nullable exact reference and the input type is a nullable exact reference to the same heap type. Update validation to allow these casts. This is safe because erasing the exactness of the input and cast types does not change the results of these casts. They succeed iff the input is a non-null value. --- src/wasm/wasm-validator.cpp | 19 ++- test/lit/validation/exact-casts-trivial.wast | 162 +++++++++++++++++-- 2 files changed, 164 insertions(+), 17 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index c344080958c..62a970bbb12 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2904,13 +2904,14 @@ void FunctionValidator::visitRefTest(RefTest* curr) { curr, "ref.test target type and ref type must have a common supertype"); - // If custom descriptors is not enabled, only trivial exact casts are allowed, - // i.e. those where the operand has the same exact type. The result of these - // trivial exact casts does not change when the types are made inexact during - // binary writing. + // If custom descriptors is not enabled, only trivial and null-checking exact + // casts are allowed, i.e. those where the operand is also exact and has the + // same heap type, but may differ in nullability. The result of these trivial + // exact casts does not change when the types are made inexact during binary + // writing. if (!getModule()->features.hasCustomDescriptors()) { - shouldBeTrue(curr->castType.isInexact() || - curr->castType == curr->ref->type, + shouldBeTrue(curr->castType.isInexact() || curr->castType.with(Nullable) == + curr->ref->type.with(Nullable), curr, "ref.test of exact type requires custom descriptors " "[--enable-custom-descriptors]"); @@ -2956,7 +2957,8 @@ void FunctionValidator::visitRefCast(RefCast* curr) { // See comment about exactness on visitRefTest. if (!getModule()->features.hasCustomDescriptors()) { - shouldBeTrue(curr->type.isInexact() || curr->type == curr->ref->type, + shouldBeTrue(curr->type.isInexact() || + curr->type.with(Nullable) == curr->ref->type.with(Nullable), curr, "ref.cast to exact type requires custom descriptors " "[--enable-custom-descriptors]"); @@ -2993,7 +2995,8 @@ void FunctionValidator::visitBrOn(BrOn* curr) { // See comment about exactness on visitRefTest. if (!getModule()->features.hasCustomDescriptors()) { shouldBeTrue(curr->castType.isInexact() || - curr->castType == curr->ref->type, + curr->castType.with(Nullable) == + curr->ref->type.with(Nullable), curr, "br_on_cast* to exact type requires custom descriptors " "[--enable-custom-descriptors]"); diff --git a/test/lit/validation/exact-casts-trivial.wast b/test/lit/validation/exact-casts-trivial.wast index 637c0c016bb..171b303bfe6 100644 --- a/test/lit/validation/exact-casts-trivial.wast +++ b/test/lit/validation/exact-casts-trivial.wast @@ -9,37 +9,97 @@ ;; CHECK: (type $foo (struct)) (type $foo (struct)) - ;; CHECK: (func $ref.cast (type $1) (param $0 (ref null (exact $foo))) + ;; CHECK: (func $ref.cast (type $1) (param $0 (ref (exact $foo))) (param $1 (ref null (exact $foo))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref null (exact $foo)) + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref null (exact $foo)) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $ref.cast (param (ref null (exact $foo))) + (func $ref.cast (param (ref (exact $foo)) (ref null (exact $foo))) (drop (ref.cast anyref (local.get 0) ) ) + (drop + (ref.cast (ref any) + (local.get 0) + ) + ) + (drop + (ref.cast anyref + (local.get 1) + ) + ) + (drop + (ref.cast (ref any) + (local.get 1) + ) + ) ) - ;; CHECK: (func $ref.test (type $2) (param $0 (ref (exact $foo))) + ;; CHECK: (func $ref.test (type $1) (param $0 (ref (exact $foo))) (param $1 (ref null (exact $foo))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref (exact $foo)) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test (ref null (exact $foo)) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $ref.test (param (ref (exact $foo))) + (func $ref.test (param (ref (exact $foo)) (ref null (exact $foo))) (drop (ref.test anyref (local.get 0) ) ) + (drop + (ref.test (ref any) + (local.get 0) + ) + ) + (drop + (ref.test anyref + (local.get 1) + ) + ) + (drop + (ref.test (ref any) + (local.get 1) + ) + ) ) - ;; CHECK: (func $br_on_cast (type $1) (param $0 (ref null (exact $foo))) + ;; CHECK: (func $br_on_cast (type $2) (param $0 (ref null (exact $foo))) (param $1 (ref null (exact $foo))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result anyref) ;; CHECK-NEXT: (br_on_cast $block (ref null (exact $foo)) (ref null (exact $foo)) @@ -47,8 +107,29 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block1 (result anyref) + ;; CHECK-NEXT: (br_on_cast $block1 (ref null (exact $foo)) (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block2 (result anyref) + ;; CHECK-NEXT: (br_on_cast $block2 (ref null (exact $foo)) (ref null (exact $foo)) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block3 (result anyref) + ;; CHECK-NEXT: (br_on_cast $block3 (ref null (exact $foo)) (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $br_on_cast (param (ref null (exact $foo))) + (func $br_on_cast (param (ref null (exact $foo)) (ref null (exact $foo))) (drop (block (result anyref) (br_on_cast 0 anyref anyref @@ -56,9 +137,30 @@ ) ) ) + (drop + (block (result anyref) + (br_on_cast 0 anyref (ref any) + (local.get 0) + ) + ) + ) + (drop + (block (result anyref) + (br_on_cast 0 anyref anyref + (local.get 1) + ) + ) + ) + (drop + (block (result anyref) + (br_on_cast 0 anyref (ref any) + (local.get 1) + ) + ) + ) ) - ;; CHECK: (func $br_on_cast_fail (type $2) (param $0 (ref (exact $foo))) + ;; CHECK: (func $br_on_cast_fail (type $1) (param $0 (ref (exact $foo))) (param $1 (ref null (exact $foo))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result anyref) ;; CHECK-NEXT: (br_on_cast_fail $block (ref (exact $foo)) (ref (exact $foo)) @@ -66,8 +168,29 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block1 (result anyref) + ;; CHECK-NEXT: (br_on_cast_fail $block1 (ref (exact $foo)) (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block2 (result anyref) + ;; CHECK-NEXT: (br_on_cast_fail $block2 (ref null (exact $foo)) (ref null (exact $foo)) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block3 (result anyref) + ;; CHECK-NEXT: (br_on_cast_fail $block3 (ref null (exact $foo)) (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $br_on_cast_fail (param (ref (exact $foo))) + (func $br_on_cast_fail (param (ref (exact $foo)) (ref null (exact $foo))) (drop (block (result anyref) (br_on_cast_fail 0 anyref anyref @@ -75,5 +198,26 @@ ) ) ) + (drop + (block (result anyref) + (br_on_cast_fail 0 anyref (ref any) + (local.get 0) + ) + ) + ) + (drop + (block (result anyref) + (br_on_cast_fail 0 anyref anyref + (local.get 1) + ) + ) + ) + (drop + (block (result anyref) + (br_on_cast_fail 0 anyref (ref any) + (local.get 1) + ) + ) + ) ) ) From ffa65f012b95be169a9e40db87c9d35e3b57275c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 18 Apr 2025 12:43:15 -0700 Subject: [PATCH 443/622] Update TypeMerging for exact types (#7521) Do not merge types that differ in exactness. --- scripts/test/fuzzing.py | 1 + src/passes/TypeMerging.cpp | 4 ++++ test/lit/passes/type-merging-exact.wast | 21 +++++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 test/lit/passes/type-merging-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 4935f8b52ec..9a3e4da97b1 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -123,6 +123,7 @@ 'remove-unused-brs-exact.wast', 'signature-refining-exact.wast', 'gufa-cast-all-exact.wast', + 'type-merging-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index e7a25cf372c..566e25f0135 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -667,6 +667,9 @@ bool shapeEq(Type a, Type b) { if (a.getNullability() != b.getNullability()) { return false; } + if (a.getExactness() != b.getExactness()) { + return false; + } return true; } @@ -688,6 +691,7 @@ size_t shapeHash(Type a) { } rehash(digest, 4); rehash(digest, (int)a.getNullability()); + rehash(digest, (int)a.getExactness()); return digest; } diff --git a/test/lit/passes/type-merging-exact.wast b/test/lit/passes/type-merging-exact.wast new file mode 100644 index 00000000000..d56da52338b --- /dev/null +++ b/test/lit/passes/type-merging-exact.wast @@ -0,0 +1,21 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that types that differ only in exactness are not merged. + +;; RUN: wasm-opt %s -all --closed-world --preserve-type-order \ +;; RUN: --type-merging --remove-unused-types -S -o - | filecheck %s + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $foo (struct)) + (type $foo (struct)) + ;; CHECK: (type $A (struct (field (ref (exact $foo))))) + (type $A (struct (field (ref (exact $foo))))) + ;; CHECK: (type $B (struct (field (ref $foo)))) + (type $B (struct (field (ref $foo)))) + + ;; CHECK: (global $a (ref null $A) (ref.null none)) + (global $a (ref null $A) (ref.null none)) + ;; CHECK: (global $b (ref null $B) (ref.null none)) + (global $b (ref null $B) (ref.null none)) +) From e185ff94852efdab9d6d75fa542a7b5b5c785d27 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 18 Apr 2025 12:47:24 -0700 Subject: [PATCH 444/622] Avoid invalid exact casts in RemoveUnusedBrs (#7522) RemoveUnusedBrs can introduce new casts when optimizing out br_on_cast and br_on_cast_fail instructions. This happens when the pass finds a more precise type for the fallthrough value reaching the casting branch, allowing it to improve the cast target type. When the branch is subsequently optimized out, this precise type information is recovered with an inserted cast. Update the pass to avoid creating invalid casts to exact types when custom descriptors is not enabled. --- src/passes/RemoveUnusedBrs.cpp | 6 + test/lit/passes/remove-unused-brs-exact.wast | 127 ++++++++++++++----- 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 6fa69e70cd7..fc2effeb898 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -906,6 +906,12 @@ struct RemoveUnusedBrs : public WalkerPass> { // further optimizations after this, and those optimizations might even // benefit from this improvement. auto glb = Type::getGreatestLowerBound(curr->castType, refType); + if (!getModule()->features.hasCustomDescriptors() && glb.isExact() && + !curr->castType.isExact()) { + // When custom descriptors is not enabled, nontrivial exact casts are + // not allowed. + glb = glb.with(Inexact); + } if (curr->op == BrOnCastFail) { // BrOnCastFail sends the input type, with adjusted nullability. The // input heap type makes sense for the branch target, and we will not diff --git a/test/lit/passes/remove-unused-brs-exact.wast b/test/lit/passes/remove-unused-brs-exact.wast index 09d56d71c3d..32d0bf103c1 100644 --- a/test/lit/passes/remove-unused-brs-exact.wast +++ b/test/lit/passes/remove-unused-brs-exact.wast @@ -1,42 +1,105 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s -all --remove-unused-brs -S -o - | filecheck %s - ;; Check that we optimize the cast correctly when the fallthrough has exact -;; type. In particular, we should not insert a ref.as_non_null, which would -;; trap. +;; type, whether or not custom descriptors is enabled. + +;; RUN: wasm-opt %s -all --remove-unused-brs -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --disable-custom-descriptors --remove-unused-brs -S -o - | filecheck %s --check-prefix=NO_CD (module - ;; CHECK: (type $struct (struct)) - (type $struct (struct)) - ;; CHECK: (func $br_on_cast_fail (type $1) (param $0 (ref null (exact $struct))) - ;; CHECK-NEXT: (local $1 (ref null $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref null (exact $struct)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $br_on_cast_fail (param (ref null (exact $struct))) - (local $1 (ref null $struct)) - (drop - (block $block (result (ref $struct)) + ;; CHECK: (type $foo (struct)) + ;; NO_CD: (type $foo (struct)) + (type $foo (struct)) + + ;; CHECK: (func $br_on_cast (type $1) (param $0 (ref (exact $foo))) + ;; CHECK-NEXT: (local $inexact (ref $foo)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (local.tee $inexact + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $br_on_cast (type $1) (param $0 (ref (exact $foo))) + ;; NO_CD-NEXT: (local $inexact (ref $foo)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (block $block (result (ref $foo)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (br $block + ;; NO_CD-NEXT: (local.tee $inexact + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $br_on_cast (param (ref (exact $foo))) + (local $inexact (ref $foo)) + (drop + (block (result (ref $foo)) + (drop + (br_on_cast 0 anyref (ref $foo) + (local.tee $inexact + (local.get 0) + ) + ) + ) + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $br_on_cast_fail (type $1) (param $0 (ref (exact $foo))) + ;; CHECK-NEXT: (local $inexact (ref $foo)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (local.tee $inexact + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $br_on_cast_fail (type $1) (param $0 (ref (exact $foo))) + ;; NO_CD-NEXT: (local $inexact (ref $foo)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (block $block (result (ref (exact $foo))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $inexact + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $br_on_cast_fail (param (ref (exact $foo))) + (local $inexact (ref $foo)) (drop - (br_on_cast_fail $block (ref null $struct) (ref null $struct) - (local.tee $1 - (local.get 0) + (block (result (ref $foo)) + (drop + (br_on_cast_fail 0 anyref (ref $foo) + (local.tee $inexact + (local.get 0) + ) + ) + ) + (local.get 0) ) - ) ) - (return) - ) ) - ) ) From 90ad796de7cb43d9a6a0d891f2ceea14b880526a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 21 Apr 2025 08:37:44 -0700 Subject: [PATCH 445/622] [SIMD] Add i32x4.dot_i8x16_i7x16_add_s in interpreter (#7527) --- scripts/test/fuzzing.py | 2 - src/literal.h | 1 + src/wasm-interpreter.h | 9 ++-- src/wasm/literal.cpp | 15 ++++++ test/lit/exec/relaxed.wast | 44 ++++++++++++++++ test/spec/dot_product.wast | 104 +++++++++++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 test/lit/exec/relaxed.wast create mode 100644 test/spec/dot_product.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 9a3e4da97b1..3f8b7c05e28 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -19,8 +19,6 @@ unfuzzable = [ # Float16 is still experimental. 'f16.wast', - # not all relaxed SIMD instructions are implemented in the interpreter - 'relaxed-simd.wast', # TODO: fuzzer and interpreter support for strings 'strings.wast', 'simplify-locals-strings.wast', diff --git a/src/literal.h b/src/literal.h index c3cf2c15f70..aba8d974c07 100644 --- a/src/literal.h +++ b/src/literal.h @@ -607,6 +607,7 @@ class Literal { Literal dotSI8x16toI16x8(const Literal& other) const; Literal dotUI8x16toI16x8(const Literal& other) const; Literal dotSI16x8toI32x4(const Literal& other) const; + Literal dotSI8x16toI16x8Add(const Literal& left, const Literal& right) const; Literal extMulLowSI32x4(const Literal& other) const; Literal extMulHighSI32x4(const Literal& other) const; Literal extMulLowUI32x4(const Literal& other) const; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 8cfae4e5009..4d62e88c1c6 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1306,10 +1306,13 @@ class ExpressionRunner : public OverriddenVisitor { return NONCONSTANT_FLOW; } return a.relaxedNmaddF64x2(b, c); - default: - // TODO: implement signselect and dot_add - WASM_UNREACHABLE("not implemented"); + case DotI8x16I7x16AddSToVecI32x4: + if (relaxedBehavior == RelaxedBehavior::NonConstant) { + return NONCONSTANT_FLOW; + } + return a.dotSI8x16toI16x8Add(b, c); } + WASM_UNREACHABLE("invalid op"); } Flow visitSIMDShift(SIMDShift* curr) { NOTE_ENTER("SIMDShift"); diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 3a67526dad2..6069757a062 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -2604,6 +2604,21 @@ Literal Literal::dotSI16x8toI32x4(const Literal& other) const { return dot<4, 2, &Literal::getLanesSI16x8>(*this, other); } +Literal Literal::dotSI8x16toI16x8Add(const Literal& left, + const Literal& right) const { + auto temp = dotSI8x16toI16x8(left); + + auto tempLanes = temp.getLanesSI16x8(); + LaneArray<4> dest; + // TODO: the index on dest may be wrong, see + // https://github.com/WebAssembly/relaxed-simd/issues/162 + for (size_t i = 0; i < 4; i++) { + dest[i] = tempLanes[i * 2].add(tempLanes[i * 2 + 1]); + } + + return Literal(dest).addI32x4(right); +} + Literal Literal::bitselectV128(const Literal& left, const Literal& right) const { return andV128(left).orV128(notV128().andV128(right)); diff --git a/test/lit/exec/relaxed.wast b/test/lit/exec/relaxed.wast new file mode 100644 index 00000000000..bf88835b7d4 --- /dev/null +++ b/test/lit/exec/relaxed.wast @@ -0,0 +1,44 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s + +(module + (import "fuzzing-support" "log-i32" (func $log (param i32))) + + ;; CHECK: [fuzz-exec] calling i32x4.dot_i8x16_i7x16_add_s + ;; CHECK-NEXT: [LoggingExternalInterface logging 8] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 22] + ;; CHECK-NEXT: [LoggingExternalInterface logging 32] + (func $i32x4.dot_i8x16_i7x16_add_s (export "i32x4.dot_i8x16_i7x16_add_s") + (local $v v128) + (local.set $v + (i32x4.dot_i8x16_i7x16_add_s + (v128.const i32x4 0 1 2 3) + (v128.const i32x4 4 5 6 7) + (v128.const i32x4 8 9 10 11) + ) + ) + (call $log + (i32x4.extract_lane 0 + (local.get $v) + ) + ) + (call $log + (i32x4.extract_lane 1 + (local.get $v) + ) + ) + (call $log + (i32x4.extract_lane 2 + (local.get $v) + ) + ) + (call $log + (i32x4.extract_lane 3 + (local.get $v) + ) + ) + ) +) + diff --git a/test/spec/dot_product.wast b/test/spec/dot_product.wast new file mode 100644 index 00000000000..ff512bb855b --- /dev/null +++ b/test/spec/dot_product.wast @@ -0,0 +1,104 @@ +;; Tests for dot products. +;; +;; This is the same as the upstream relaxed_dot_product.wast test in +;; relaxed-simd, but with the non-relaxed versions, and with picking the proper +;; outcome in the multiple-choice questions (which use either() in the original +;; test). + +(module + (func (export "i16x8.dot_i8x16_i7x16_s") (param v128 v128) (result v128) (i16x8.dot_i8x16_i7x16_s (local.get 0) (local.get 1))) + (func (export "i32x4.dot_i8x16_i7x16_add_s") (param v128 v128 v128) (result v128) (i32x4.dot_i8x16_i7x16_add_s (local.get 0) (local.get 1) (local.get 2))) + + (func (export "i16x8.dot_i8x16_i7x16_s_cmp") (param v128 v128) (result v128) + (i16x8.eq + (i16x8.dot_i8x16_i7x16_s (local.get 0) (local.get 1)) + (i16x8.dot_i8x16_i7x16_s (local.get 0) (local.get 1)))) + (func (export "i32x4.dot_i8x16_i7x16_add_s_cmp") (param v128 v128 v128) (result v128) + (i16x8.eq + (i32x4.dot_i8x16_i7x16_add_s (local.get 0) (local.get 1) (local.get 2)) + (i32x4.dot_i8x16_i7x16_add_s (local.get 0) (local.get 1) (local.get 2)))) +) + +;; Simple values to ensure things are functional. +(assert_return (invoke "i16x8.dot_i8x16_i7x16_s" + (v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) + (v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)) + (v128.const i16x8 1 13 41 85 145 221 313 421)) + +;; Test max and min i8 values; +(assert_return (invoke "i16x8.dot_i8x16_i7x16_s" + (v128.const i8x16 -128 -128 127 127 0 0 0 0 0 0 0 0 0 0 0 0) + (v128.const i8x16 127 127 127 127 0 0 0 0 0 0 0 0 0 0 0 0)) + (v128.const i16x8 -32512 32258 0 0 0 0 0 0)) + +;; signed * unsigned : -128 * 129 * 2 = -33,024 saturated to -32,768 +;; signed * signed : -128 * -127 * 2 = 32,512 +;; unsigned * unsigned : 128 * 129 * 2 = 33,024 +(assert_return (invoke "i16x8.dot_i8x16_i7x16_s" + (v128.const i8x16 -128 -128 0 0 0 0 0 0 0 0 0 0 0 0 0 0) + (v128.const i8x16 -127 -127 0 0 0 0 0 0 0 0 0 0 0 0 0 0)) + (v128.const i16x8 32512 0 0 0 0 0 0 0)) + +;; Simple values to ensure things are functional. +(assert_return (invoke "i32x4.dot_i8x16_i7x16_add_s" + (v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) + (v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) + (v128.const i32x4 0 1 2 3)) + ;; intermediate result is [14, 126, 366, 734] + (v128.const i32x4 14 127 368 737)) + +;; Test max and min i8 values; +(assert_return (invoke "i32x4.dot_i8x16_i7x16_add_s" + (v128.const i8x16 -128 -128 -128 -128 127 127 127 127 0 0 0 0 0 0 0 0) + (v128.const i8x16 127 127 127 127 127 127 127 127 0 0 0 0 0 0 0 0) + (v128.const i32x4 1 2 3 4)) + ;; intermediate result is [-65024, 64516, 0, 0] + (v128.const i32x4 -65023 64518 3 4)) + +;; signed * unsigned : -128 * 129 * 4 = -66,048 (+ 1) VPDPBUSD AVX2-VNNI or AVX512-VNNI +;; signed * unsigned with intermediate saturation : +;; (-128 * 129) + (-128 * 129) = -33024 saturated to -32768 (PMADDUBSW) +;; -32768 + -32768 = -65536 (+ 1) +;; signed * signed : -128 * -127 * 4 = 65,024 (+ 1) +;; unsigned * unsigned : 128 * 129 * 2 = 66,048 (+ 1) +(assert_return (invoke "i32x4.dot_i8x16_i7x16_add_s" + (v128.const i8x16 -128 -128 -128 -128 0 0 0 0 0 0 0 0 0 0 0 0) + (v128.const i8x16 -127 -127 -127 -127 0 0 0 0 0 0 0 0 0 0 0 0) + (v128.const i32x4 1 2 3 4)) + (v128.const i32x4 65025 2 3 4)) + +;; Check that multiple calls to the relaxed instruction with same inputs returns same results. + +;; Test max and min i8 values; +(assert_return (invoke "i16x8.dot_i8x16_i7x16_s_cmp" + (v128.const i8x16 -128 -128 127 127 0 0 0 0 0 0 0 0 0 0 0 0) + (v128.const i8x16 127 127 127 127 0 0 0 0 0 0 0 0 0 0 0 0)) + (v128.const i16x8 -1 -1 -1 -1 -1 -1 -1 -1)) + +;; Test max and min i8 values; +(assert_return (invoke "i32x4.dot_i8x16_i7x16_add_s_cmp" + (v128.const i8x16 -128 -128 -128 -128 127 127 127 127 0 0 0 0 0 0 0 0) + (v128.const i8x16 127 127 127 127 127 127 127 127 0 0 0 0 0 0 0 0) + (v128.const i32x4 1 2 3 4)) + ;; intermediate result is [-65024, 64516, 0, 0] + (v128.const i32x4 -1 -1 -1 -1)) + +;; signed * unsigned : -128 * 129 * 2 = -33,024 saturated to -32,768 +;; signed * signed : -128 * -127 * 2 = 32,512 +;; unsigned * unsigned : 128 * 129 * 2 = 33,024 +(assert_return (invoke "i16x8.dot_i8x16_i7x16_s_cmp" + (v128.const i8x16 -128 -128 0 0 0 0 0 0 0 0 0 0 0 0 0 0) + (v128.const i8x16 -127 -127 0 0 0 0 0 0 0 0 0 0 0 0 0 0)) + (v128.const i16x8 -1 -1 -1 -1 -1 -1 -1 -1)) + +;; signed * unsigned : -128 * 129 * 4 = -66,048 (+ 1) VPDPBUSD AVX2-VNNI or AVX512-VNNI +;; signed * unsigned with intermediate saturation : +;; (-128 * 129) + (-128 * 129) = -33024 saturated to -32768 (PMADDUBSW) +;; -32768 + -32768 = -65536 (+ 1) +;; signed * signed : -128 * -127 * 4 = 65,024 (+ 1) +;; unsigned * unsigned : 128 * 129 * 2 = 66,048 (+ 1) +(assert_return (invoke "i32x4.dot_i8x16_i7x16_add_s_cmp" + (v128.const i8x16 -128 -128 -128 -128 0 0 0 0 0 0 0 0 0 0 0 0) + (v128.const i8x16 -127 -127 -127 -127 0 0 0 0 0 0 0 0 0 0 0 0) + (v128.const i32x4 1 2 3 4)) + (v128.const i32x4 -1 -1 -1 -1)) From 526986b28ab856c359345d958586df1efe89b154 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Mon, 21 Apr 2025 10:23:56 -0700 Subject: [PATCH 446/622] Allow emcc-tests.sh to be invoked from outside the Binaryen source directory (#7533) This makes it easier to run the tests while keeping the source tree clean. --- scripts/emcc-tests.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/emcc-tests.sh b/scripts/emcc-tests.sh index 409ba256ed4..16c55e31153 100755 --- a/scripts/emcc-tests.sh +++ b/scripts/emcc-tests.sh @@ -3,16 +3,18 @@ set -o errexit set -o pipefail +SRCDIR="$(dirname $(dirname ${BASH_SOURCE[0]}))" + mkdir -p emcc-build echo "emcc-tests: build:wasm" -emcmake cmake -B emcc-build -DCMAKE_BUILD_TYPE=Release -G Ninja +emcmake cmake -S $SRCDIR -B emcc-build -DCMAKE_BUILD_TYPE=Release -G Ninja ninja -C emcc-build binaryen_wasm echo "emcc-tests: test:wasm" -./check.py --binaryen-bin=emcc-build/bin binaryenjs_wasm +$SRCDIR/check.py --binaryen-bin=emcc-build/bin binaryenjs_wasm echo "emcc-tests: done:wasm" echo "emcc-tests: build:js" ninja -C emcc-build binaryen_js echo "emcc-tests: test:js" -./check.py --binaryen-bin=emcc-build/bin binaryenjs +$SRCDIR/check.py --binaryen-bin=emcc-build/bin binaryenjs echo "emcc-tests: done:js" From fb3c6e2311e793ffbac85c2ac5c07c6d2ca1ed8b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 21 Apr 2025 11:18:26 -0700 Subject: [PATCH 447/622] Read call-indirect-overlong and custom-descriptors features (#7524) These features were previously missed when reading target features sections. --- src/wasm/wasm-binary.cpp | 5 +++++ test/unit/test_features.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 12b123c880e..88fb43b345a 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4979,6 +4979,9 @@ void WasmBinaryReader::readFeatures(size_t payloadLen) { } } else if (name == BinaryConsts::CustomSections::BulkMemoryOptFeature) { feature = FeatureSet::BulkMemoryOpt; + } else if (name == + BinaryConsts::CustomSections::CallIndirectOverlongFeature) { + feature = FeatureSet::CallIndirectOverlong; } else if (name == BinaryConsts::CustomSections::ExceptionHandlingFeature) { feature = FeatureSet::ExceptionHandling; } else if (name == BinaryConsts::CustomSections::MutableGlobalsFeature) { @@ -5013,6 +5016,8 @@ void WasmBinaryReader::readFeatures(size_t payloadLen) { feature = FeatureSet::SharedEverything; } else if (name == BinaryConsts::CustomSections::FP16Feature) { feature = FeatureSet::FP16; + } else if (name == BinaryConsts::CustomSections::CustomDescriptorsFeature) { + feature = FeatureSet::CustomDescriptors; } else { // Silently ignore unknown features (this may be and old binaryen running // on a new wasm). diff --git a/test/unit/test_features.py b/test/unit/test_features.py index 0a232da0fb4..2647ff84717 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -451,4 +451,6 @@ def test_emit_all_features(self): '--enable-shared-everything', '--enable-fp16', '--enable-bulk-memory-opt', + '--enable-call-indirect-overlong', + '--enable-custom-descriptors', ], p2.stdout.splitlines()) From 27fbd3175d1bf7623fd9e519c4fa8bc1bda41905 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 21 Apr 2025 12:05:10 -0700 Subject: [PATCH 448/622] [test] Avoid reusing a.mjs for all js tests. (#7534) Instead create `.mjs` named after each test. Test output now looks like this: ``` $ ./check.py --binaryen-bin=emcc-build/bin binaryenjs_wasm warning: Binaryen not found (or has not been successfully built to bin/ ? executing: /usr/bin/node -e process.stdout.write(typeof WebAssembly) [ checking binaryen.js testcases (/usr/local/google/home/sbc/dev/wasm/binaryen/emcc-build/bin/binaryen_wasm.js)... ] executing: /usr/bin/node atomics.mjs executing: /usr/bin/node closed-world.mjs executing: /usr/bin/node copy-expression.mjs executing: /usr/bin/node custom-section.mjs ... ``` Previously: ``` $ ./check.py --binaryen-bin=emcc-build/bin binaryenjs_wasm warning: Binaryen not found (or has not been successfully built to bin/ ? executing: /usr/bin/node -e process.stdout.write(typeof WebAssembly) [ checking binaryen.js testcases (/usr/local/google/home/sbc/dev/wasm/binaryen/emcc-build/bin/binaryen_wasm.js)... ] atomics.js executing: /usr/bin/node a.mjs closed-world.js executing: /usr/bin/node a.mjs copy-expression.js executing: /usr/bin/node a.mjs custom-section.js executing: /usr/bin/node a.mjs debug-info.js executing: /usr/bin/node a.mjs debug-names.js executing: /usr/bin/node a.mjs emit_asmjs.js ... ``` --- scripts/test/binaryenjs.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/scripts/test/binaryenjs.py b/scripts/test/binaryenjs.py index 93fbe532f88..842f34c5e34 100644 --- a/scripts/test/binaryenjs.py +++ b/scripts/test/binaryenjs.py @@ -29,19 +29,15 @@ def do_test_binaryen_js_with(which): print('\n[ checking binaryen.js testcases (' + which + ')... ]\n') - for s in sorted(os.listdir(os.path.join(shared.options.binaryen_test, 'binaryen.js'))): - if not s.endswith('.js'): - continue - print(s) - f = open('a.mjs', 'w') + for s in shared.get_tests(shared.get_test_dir('binaryen.js'), ['.js']): + basename = os.path.basename(s) + outname = os.path.splitext(basename)[0] + '.mjs' + f = open(outname, 'w') # avoid stdout/stderr ordering issues in some js shells - use just stdout - f.write(''' - console.warn = console.error = console.log; - ''') + f.write('console.warn = console.error = console.log;') binaryen_js = open(which).read() f.write(binaryen_js) - test_path = os.path.join(shared.options.binaryen_test, 'binaryen.js', s) - test_src = open(test_path).read() + test_src = open(s).read() f.write(support.js_test_wrap().replace('%TEST%', test_src)) f.close() @@ -57,12 +53,12 @@ def test(cmd): # run in all possible shells if shared.MOZJS: - test([shared.MOZJS, '-m', 'a.mjs']) + test([shared.MOZJS, '-m', outname]) if shared.NODEJS: if node_has_wasm or 'WebAssembly.' not in test_src: - test([shared.NODEJS, 'a.mjs']) + test([shared.NODEJS, outname]) else: - print('Skipping ' + test_path + ' because WebAssembly might not be supported') + print('Skipping ' + basename + ' because WebAssembly might not be supported') def update_binaryen_js_tests(): @@ -78,12 +74,10 @@ def update_binaryen_js_tests(): node_has_wasm = shared.NODEJS and support.node_has_webassembly(shared.NODEJS) for s in shared.get_tests(shared.get_test_dir('binaryen.js'), ['.js']): basename = os.path.basename(s) - print(basename) - f = open('a.mjs', 'w') + outname = os.path.splitext(basename)[0] + '.mjs' + f = open(outname, 'w') # avoid stdout/stderr ordering issues in some js shells - use just stdout - f.write(''' - console.warn = console.error = console.log; - ''') + f.write('console.warn = console.error = console.log;') f.write(open(shared.BINARYEN_JS).read()) test_src = open(s).read() f.write(support.js_test_wrap().replace('%TEST%', test_src)) @@ -100,9 +94,9 @@ def update(cmd): # run in available shell if shared.MOZJS: - update([shared.MOZJS, '-m', 'a.mjs']) + update([shared.MOZJS, '-m', outname]) elif node_has_wasm or 'WebAssembly.' not in test_src: - update([shared.NODEJS, 'a.mjs']) + update([shared.NODEJS, outname]) else: print('Skipping ' + basename + ' because WebAssembly might not be supported') From f1cb4665f51bd26d11fac7ee0a9b0be715459cf3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 21 Apr 2025 13:18:57 -0700 Subject: [PATCH 449/622] [NFC] Add withInexactIfNoCustomDescs helper (#7528) There are several places in the code where we need to make a type inexact only if custom descriptors is disabled. Add a helper to slightly simplify those places. --- src/passes/GUFA.cpp | 6 ++---- src/passes/RemoveUnusedBrs.cpp | 5 ++--- src/wasm-type.h | 11 ++++++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 35380c952f6..4ad72b857de 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -375,10 +375,8 @@ struct GUFAOptimizer auto oracleType = parent.getContents(curr).getType(); // Exact casts are only allowed when custom descriptors is enabled. - if (oracleType.isExact() && - !getModule()->features.hasCustomDescriptors()) { - oracleType = oracleType.with(Inexact); - } + oracleType = + oracleType.withInexactIfNoCustomDescs(getModule()->features); if (oracleType.isRef() && oracleType != curr->type && Type::isSubType(oracleType, curr->type)) { replaceCurrent(Builder(*getModule()).makeRefCast(curr, oracleType)); diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index fc2effeb898..add46585269 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -906,11 +906,10 @@ struct RemoveUnusedBrs : public WalkerPass> { // further optimizations after this, and those optimizations might even // benefit from this improvement. auto glb = Type::getGreatestLowerBound(curr->castType, refType); - if (!getModule()->features.hasCustomDescriptors() && glb.isExact() && - !curr->castType.isExact()) { + if (!curr->castType.isExact()) { // When custom descriptors is not enabled, nontrivial exact casts are // not allowed. - glb = glb.with(Inexact); + glb = glb.withInexactIfNoCustomDescs(getModule()->features); } if (curr->op == BrOnCastFail) { // BrOnCastFail sends the input type, with adjusted nullability. The diff --git a/src/wasm-type.h b/src/wasm-type.h index 7f0cde2c48f..4ecbf6ab379 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -418,16 +418,21 @@ class Type { } // Return a new reference type with some part updated to the specified value. - Type with(HeapType heapType) { + Type with(HeapType heapType) const { return Type(heapType, getNullability(), getExactness()); } - Type with(Nullability nullability) { + Type with(Nullability nullability) const { return Type(getHeapType(), nullability, getExactness()); } - Type with(Exactness exactness) { + Type with(Exactness exactness) const { return Type(getHeapType(), getNullability(), exactness); } + // Make the type inexact if custom descriptors is not enabled. + Type withInexactIfNoCustomDescs(FeatureSet feats) const { + return !isExact() || feats.hasCustomDescriptors() ? *this : with(Inexact); + } + private: template bool hasPredicate() { for (const auto& type : *this) { From 8c22aa51b87a3c6700e1cfb231f9fc8baf9d3006 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 21 Apr 2025 13:23:39 -0700 Subject: [PATCH 450/622] [test] Extract function in scripts/test/binaryenjs.py. NFC (#7535) Also, move `js_test_wrap` into binaryenjs.py which is its only callsite. --- scripts/test/binaryenjs.py | 54 ++++++++++++++++++++++---------------- scripts/test/support.py | 12 --------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/scripts/test/binaryenjs.py b/scripts/test/binaryenjs.py index 842f34c5e34..81acff79856 100644 --- a/scripts/test/binaryenjs.py +++ b/scripts/test/binaryenjs.py @@ -19,6 +19,31 @@ from . import support +def js_test_wrap(): + # common wrapper code for JS tests, waiting for binaryen.js to become ready + # and providing common utility used by all tests: + return ''' + (async function __in_test_code__() { + var binaryen = await Binaryen() + function assert(x) { if (!x) throw Error('Test assertion failed'); } + %TEST% + })(); + ''' + + +def make_js_test(input_js_file, binaryen_js): + basename = os.path.basename(input_js_file) + outname = os.path.splitext(basename)[0] + '.mjs' + with open(outname, 'w') as f: + # avoid stdout/stderr ordering issues in some js shells - use just stdout + f.write('console.warn = console.error = console.log;') + binaryen_js = open(binaryen_js).read() + f.write(binaryen_js) + test_src = open(input_js_file).read() + f.write(js_test_wrap().replace('%TEST%', test_src)) + return outname + + def do_test_binaryen_js_with(which): if not (shared.MOZJS or shared.NODEJS): shared.fail_with_error('no vm to run binaryen.js tests') @@ -30,16 +55,7 @@ def do_test_binaryen_js_with(which): print('\n[ checking binaryen.js testcases (' + which + ')... ]\n') for s in shared.get_tests(shared.get_test_dir('binaryen.js'), ['.js']): - basename = os.path.basename(s) - outname = os.path.splitext(basename)[0] + '.mjs' - f = open(outname, 'w') - # avoid stdout/stderr ordering issues in some js shells - use just stdout - f.write('console.warn = console.error = console.log;') - binaryen_js = open(which).read() - f.write(binaryen_js) - test_src = open(s).read() - f.write(support.js_test_wrap().replace('%TEST%', test_src)) - f.close() + outname = make_js_test(s, which) def test(cmd): if 'fatal' not in s: @@ -55,10 +71,11 @@ def test(cmd): if shared.MOZJS: test([shared.MOZJS, '-m', outname]) if shared.NODEJS: + test_src = open(s).read() if node_has_wasm or 'WebAssembly.' not in test_src: test([shared.NODEJS, outname]) else: - print('Skipping ' + basename + ' because WebAssembly might not be supported') + print('Skipping ' + s + ' because WebAssembly might not be supported') def update_binaryen_js_tests(): @@ -73,18 +90,10 @@ def update_binaryen_js_tests(): print('\n[ checking binaryen.js testcases... ]\n') node_has_wasm = shared.NODEJS and support.node_has_webassembly(shared.NODEJS) for s in shared.get_tests(shared.get_test_dir('binaryen.js'), ['.js']): - basename = os.path.basename(s) - outname = os.path.splitext(basename)[0] + '.mjs' - f = open(outname, 'w') - # avoid stdout/stderr ordering issues in some js shells - use just stdout - f.write('console.warn = console.error = console.log;') - f.write(open(shared.BINARYEN_JS).read()) - test_src = open(s).read() - f.write(support.js_test_wrap().replace('%TEST%', test_src)) - f.close() + outname = make_js_test(s, shared.BINARYEN_JS) def update(cmd): - if 'fatal' not in basename: + if 'fatal' not in outname: out = support.run_command(cmd, stderr=subprocess.STDOUT) else: # expect an error - the specific error code will depend on the vm @@ -93,12 +102,13 @@ def update(cmd): o.write(out) # run in available shell + test_src = open(s).read() if shared.MOZJS: update([shared.MOZJS, '-m', outname]) elif node_has_wasm or 'WebAssembly.' not in test_src: update([shared.NODEJS, outname]) else: - print('Skipping ' + basename + ' because WebAssembly might not be supported') + print('Skipping ' + s + ' because WebAssembly might not be supported') def test_binaryen_js(): diff --git a/scripts/test/support.py b/scripts/test/support.py index a41fee22793..c109c98b654 100644 --- a/scripts/test/support.py +++ b/scripts/test/support.py @@ -204,15 +204,3 @@ def run_command(cmd, expected_status=0, stderr=None, def node_has_webassembly(cmd): cmd = [cmd, '-e', 'process.stdout.write(typeof WebAssembly)'] return run_command(cmd) == 'object' - - -def js_test_wrap(): - # common wrapper code for JS tests, waiting for binaryen.js to become ready - # and providing common utility used by all tests: - return ''' - (async function __in_test_code__() { - var binaryen = await Binaryen() - function assert(x) { if (!x) throw Error('Test assertion failed'); } - %TEST% - })(); - ''' From eb00324e8ee75dd0ec53096a53b7f92da4ce01b0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 21 Apr 2025 14:29:59 -0700 Subject: [PATCH 451/622] Avoid invalid exact casts in TypeRefining (#7529) TypeRefining (both the normal and GUFA variants) is able to infer that a field can be more precise than is immediately apparent from the type of an expression set to that field. In these cases, the pass inserts a cast to recover the more precise type information. When custom descriptors are not enabled, casts to exact types invalid, so avoid introducing new exactness that might need such invalid casts in that case. --- scripts/test/fuzzing.py | 2 + src/passes/TypeRefining.cpp | 13 ++- test/lit/passes/type-refining-exact.wast | 105 ++++++++++++++++++ test/lit/passes/type-refining-gufa-exact.wast | 80 +++++++++++++ 4 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/type-refining-exact.wast create mode 100644 test/lit/passes/type-refining-gufa-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 3f8b7c05e28..971a5c18e4d 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -122,6 +122,8 @@ 'signature-refining-exact.wast', 'gufa-cast-all-exact.wast', 'type-merging-exact.wast', + 'type-refining-exact.wast', + 'type-refining-gufa-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index d934709a256..da113808ec9 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -61,7 +61,13 @@ struct FieldInfoScanner HeapType type, Index index, FieldInfo& info) { - info.note(expr->type); + auto noted = expr->type; + // Do not introduce new exact fields that might requires invalid + // casts. Keep any existing exact fields, though. + if (type.getStruct().fields[index].type.isInexact()) { + noted = noted.withInexactIfNoCustomDescs(getModule()->features); + } + info.note(noted); } void @@ -185,6 +191,11 @@ struct TypeRefining : public Pass { auto& infos = finalInfos[type]; for (Index i = 0; i < fields.size(); i++) { auto gufaType = oracle.getContents(DataLocation{type, i}).getType(); + // Do not introduce new exact fields that might requires invalid + // casts. Keep any existing exact fields, though. + if (!fields[i].type.isExact()) { + gufaType = gufaType.withInexactIfNoCustomDescs(module->features); + } infos[i] = LUBFinder(gufaType); } } diff --git a/test/lit/passes/type-refining-exact.wast b/test/lit/passes/type-refining-exact.wast new file mode 100644 index 00000000000..d8d2441ba31 --- /dev/null +++ b/test/lit/passes/type-refining-exact.wast @@ -0,0 +1,105 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that we don't refine in ways that might require invalid exact casts +;; when custom descriptors is disabled. + +;; RUN: wasm-opt %s -all --closed-world --preserve-type-order \ +;; RUN: --type-refining -S -o - | filecheck %s + +;; RUN: wasm-opt %s -all --disable-custom-descriptors --closed-world --preserve-type-order \ +;; RUN: --type-refining -S -o - | filecheck %s --check-prefix=NO_CD + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $foo (struct)) + ;; NO_CD: (rec + ;; NO_CD-NEXT: (type $foo (struct)) + (type $foo (struct)) + ;; CHECK: (type $bar (struct (field (mut (ref (exact $foo)))))) + ;; NO_CD: (type $bar (struct (field (mut (ref $foo))))) + (type $bar (struct (field (mut (ref null $foo))))) + + ;; CHECK: (type $already-exact (struct (field (ref (exact $foo))))) + ;; NO_CD: (type $already-exact (struct (field (ref (exact $foo))))) + (type $already-exact (struct (field (ref (exact $foo))))) + + ;; CHECK: (tag $e (type $3)) + ;; NO_CD: (tag $e (type $3)) + (tag $e) + + ;; CHECK: (func $struct.new (type $4) (param $inexact (ref null $foo)) (param $exact (ref (exact $foo))) (result anyref) + ;; CHECK-NEXT: (struct.new $bar + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (try (result (ref null $foo)) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (struct.get $bar 0 + ;; CHECK-NEXT: (struct.new $bar + ;; CHECK-NEXT: (local.get $exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e + ;; CHECK-NEXT: (local.get $inexact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $struct.new (type $4) (param $inexact (ref null $foo)) (param $exact (ref (exact $foo))) (result anyref) + ;; NO_CD-NEXT: (struct.new $bar + ;; NO_CD-NEXT: (ref.cast (ref $foo) + ;; NO_CD-NEXT: (try (result (ref null $foo)) + ;; NO_CD-NEXT: (do + ;; NO_CD-NEXT: (struct.get $bar 0 + ;; NO_CD-NEXT: (struct.new $bar + ;; NO_CD-NEXT: (local.get $exact) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (catch $e + ;; NO_CD-NEXT: (local.get $inexact) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $struct.new (param $inexact (ref null $foo)) (param $exact (ref (exact $foo))) (result anyref) + (struct.new $bar + (try (result (ref null $foo)) + (do + (struct.get $bar 0 + (struct.new $bar + (local.get $exact) + ) + ) + ) + (catch $e + (local.get $inexact) + ) + ) + ) + ) + + ;; CHECK: (func $make-already-exact (type $5) (param $0 (ref (exact $foo))) (result (ref (exact $foo))) + ;; CHECK-NEXT: (struct.get $already-exact 0 + ;; CHECK-NEXT: (struct.new $already-exact + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $make-already-exact (type $5) (param $0 (ref (exact $foo))) (result (ref (exact $foo))) + ;; NO_CD-NEXT: (struct.get $already-exact 0 + ;; NO_CD-NEXT: (struct.new $already-exact + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $make-already-exact (param (ref (exact $foo))) (result (ref (exact $foo))) + ;; We should not accidentally remove exactness from a field that is already exact. + (struct.get $already-exact 0 + (struct.new $already-exact + (local.get 0) + ) + ) + ) +) diff --git a/test/lit/passes/type-refining-gufa-exact.wast b/test/lit/passes/type-refining-gufa-exact.wast new file mode 100644 index 00000000000..3222a541783 --- /dev/null +++ b/test/lit/passes/type-refining-gufa-exact.wast @@ -0,0 +1,80 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that we don't refine in ways that might require invalid exact casts +;; when custom descriptors is disabled. + +;; RUN: wasm-opt %s -all --closed-world --preserve-type-order \ +;; RUN: --type-refining-gufa -S -o - | filecheck %s + +;; RUN: wasm-opt %s -all --disable-custom-descriptors --closed-world --preserve-type-order \ +;; RUN: --type-refining-gufa -S -o - | filecheck %s --check-prefix=NO_CD + +(module + ;; CHECK: (type $foo (struct)) + ;; NO_CD: (type $foo (struct)) + (type $foo (struct)) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $bar (struct (field (ref (exact $foo))))) + ;; NO_CD: (rec + ;; NO_CD-NEXT: (type $bar (struct (field (ref $foo)))) + (type $bar (struct (field (ref null $foo)))) + + ;; CHECK: (type $already-exact (struct (field (ref (exact $foo))))) + ;; NO_CD: (type $already-exact (struct (field (ref (exact $foo))))) + (type $already-exact (struct (field (ref (exact $foo))))) + + ;; CHECK: (import "" "" (global $exact (ref (exact $foo)))) + ;; NO_CD: (import "" "" (global $exact (ref (exact $foo)))) + (import "" "" (global $exact (ref (exact $foo)))) + + ;; CHECK: (global $g (ref $foo) (global.get $exact)) + ;; NO_CD: (global $g (ref $foo) (global.get $exact)) + (global $g (ref $foo) (global.get $exact)) + + ;; CHECK: (func $make-bar (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $bar + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $make-bar (type $3) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (struct.new $bar + ;; NO_CD-NEXT: (global.get $g) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $make-bar + (drop + (struct.new $bar + (global.get $g) + ) + ) + ) + + ;; CHECK: (func $make-already-exact (type $4) (result (ref (exact $foo))) + ;; CHECK-NEXT: (struct.get $already-exact 0 + ;; CHECK-NEXT: (struct.new $already-exact + ;; CHECK-NEXT: (global.get $exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $make-already-exact (type $4) (result (ref (exact $foo))) + ;; NO_CD-NEXT: (struct.get $already-exact 0 + ;; NO_CD-NEXT: (struct.new $already-exact + ;; NO_CD-NEXT: (global.get $exact) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $make-already-exact (result (ref (exact $foo))) + ;; We should not accidentally remove exactness from a field that is already exact. + (struct.get $already-exact 0 + (struct.new $already-exact + (global.get $exact) + ) + ) + ) +) From aee3229cf45657aa67f58e765ba5310ff2525d48 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 21 Apr 2025 15:47:15 -0700 Subject: [PATCH 452/622] Do not let GUFA refine casts to exact when invalid (#7530) We already prevented GUFA from introduce new exact casts when custom descriptors is not enabled, but now prevent it from refining existing casts to be exact as well. --- src/passes/GUFA.cpp | 3 ++ test/lit/passes/gufa-cast-all-exact.wast | 61 +++++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 4ad72b857de..480e4cfd857 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -266,6 +266,9 @@ struct GUFAOptimizer void visitRefCast(RefCast* curr) { auto currType = curr->type; auto inferredType = getContents(curr).getType(); + // Do not refine to an invalid exact cast. + inferredType = + inferredType.withInexactIfNoCustomDescs(getModule()->features); if (inferredType.isRef() && inferredType != currType && Type::isSubType(inferredType, currType)) { // We have inferred that this will only contain something of a more diff --git a/test/lit/passes/gufa-cast-all-exact.wast b/test/lit/passes/gufa-cast-all-exact.wast index a938c357397..26c34863e7f 100644 --- a/test/lit/passes/gufa-cast-all-exact.wast +++ b/test/lit/passes/gufa-cast-all-exact.wast @@ -2,8 +2,11 @@ ;; Check that exact casts are only added when custom descriptors are enabled. -;; RUN: wasm-opt %s -all --gufa-cast-all -S -o - | filecheck %s -;; RUN: wasm-opt %s -all --disable-custom-descriptors --gufa-cast-all -S -o - | filecheck %s --check-prefix=NO_CD +;; RUN: foreach %s %t wasm-opt -all --gufa-cast-all -S -o - \ +;; RUN: | filecheck %s + +;; RUN: foreach %s %t wasm-opt -all --disable-custom-descriptors --gufa-cast-all -S -o - \ +;; RUN: | filecheck %s --check-prefix=NO_CD (module ;; CHECK: (type $foo (struct)) @@ -52,3 +55,57 @@ ) ) ) + +(module + ;; CHECK: (type $foo (struct)) + ;; NO_CD: (type $foo (struct)) + (type $foo (struct)) + + ;; CHECK: (import "" "a" (global $exact-a (ref (exact $foo)))) + ;; NO_CD: (import "" "a" (global $exact-a (ref (exact $foo)))) + (import "" "a" (global $exact-a (ref (exact $foo)))) + ;; CHECK: (import "" "b" (global $exact-b (ref (exact $foo)))) + ;; NO_CD: (import "" "b" (global $exact-b (ref (exact $foo)))) + (import "" "b" (global $exact-b (ref (exact $foo)))) + + ;; CHECK: (global $g (mut (ref $foo)) (global.get $exact-a)) + ;; NO_CD: (global $g (mut (ref $foo)) (global.get $exact-a)) + (global $g (mut (ref $foo)) (global.get $exact-a)) + + ;; CHECK: (func $set (type $1) + ;; CHECK-NEXT: (global.set $g + ;; CHECK-NEXT: (global.get $exact-b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $set (type $1) + ;; NO_CD-NEXT: (global.set $g + ;; NO_CD-NEXT: (global.get $exact-b) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $set + ;; $g can now hold two different exact $foo references. + (global.set $g + (global.get $exact-b) + ) + ) + + ;; CHECK: (func $get (type $2) (result (ref $foo)) + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $get (type $2) (result (ref $foo)) + ;; NO_CD-NEXT: (ref.cast (ref $foo) + ;; NO_CD-NEXT: (global.get $g) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $get (result (ref $foo)) + ;; We can only refine this cast target to be exact if custom descriptors are + ;; allowed. + (ref.cast (ref $foo) + (global.get $g) + ) + ) +) From 18077057a3a6f3a738a9e3ded6e2820a5b3499eb Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 21 Apr 2025 15:50:49 -0700 Subject: [PATCH 453/622] Use ES6 import to run JS tests. NFC (#7536) Previously each test would take binaryen.js can modify a copy of it. Now they import it, as is (hopefully) more common in practice. --- scripts/test/binaryenjs.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/scripts/test/binaryenjs.py b/scripts/test/binaryenjs.py index 81acff79856..21600de26c2 100644 --- a/scripts/test/binaryenjs.py +++ b/scripts/test/binaryenjs.py @@ -13,34 +13,41 @@ # limitations under the License. import os +import shutil import subprocess from . import shared from . import support -def js_test_wrap(): +def make_js_test_header(binaryen_js): # common wrapper code for JS tests, waiting for binaryen.js to become ready # and providing common utility used by all tests: return ''' - (async function __in_test_code__() { - var binaryen = await Binaryen() - function assert(x) { if (!x) throw Error('Test assertion failed'); } - %TEST% - })(); - ''' +import Binaryen from "%s"; +var binaryen = await Binaryen() + +// avoid stdout/stderr ordering issues in some js shells - use just stdout +console.warn = console.error = console.log; + +function assert(x) { + if (!x) throw Error('Test assertion failed'); +} +''' % binaryen_js def make_js_test(input_js_file, binaryen_js): + # Copy the binaryen.js file to binaryen.mjs for now since file + # extensions matter under node. + # TODO(sbc): Should binaryen build as a `.mjs` file itself? + shutil.copyfile(binaryen_js, 'binaryen.mjs') + basename = os.path.basename(input_js_file) outname = os.path.splitext(basename)[0] + '.mjs' with open(outname, 'w') as f: - # avoid stdout/stderr ordering issues in some js shells - use just stdout - f.write('console.warn = console.error = console.log;') - binaryen_js = open(binaryen_js).read() - f.write(binaryen_js) + f.write(make_js_test_header('./binaryen.mjs')) test_src = open(input_js_file).read() - f.write(js_test_wrap().replace('%TEST%', test_src)) + f.write(test_src) return outname From 647a2e12da8cad6ce8ffcbce90c157ade2166d17 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 22 Apr 2025 08:35:20 -0700 Subject: [PATCH 454/622] Propagate exactness when combining GUFA possible contents (#7537) The logic for joining nullability into a PossibleContents cone value did not previously preserve the exactness of the type in the cone value, causing assertion failures. --- src/ir/possible-contents.cpp | 2 +- test/lit/passes/gufa-cast-all-exact.wast | 29 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index c422dc3d5be..625136506f0 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -102,7 +102,7 @@ PossibleContents PossibleContents::combine(const PossibleContents& a, // just add in nullability. For example, a literal of type T and a null // becomes an exact type of T that allows nulls, and so forth. auto mixInNull = [](ConeType cone) { - cone.type = Type(cone.type.getHeapType(), Nullable); + cone.type = cone.type.with(Nullable); return cone; }; if (!a.isNull()) { diff --git a/test/lit/passes/gufa-cast-all-exact.wast b/test/lit/passes/gufa-cast-all-exact.wast index 26c34863e7f..5d181d3bb21 100644 --- a/test/lit/passes/gufa-cast-all-exact.wast +++ b/test/lit/passes/gufa-cast-all-exact.wast @@ -109,3 +109,32 @@ ) ) ) + +(module + ;; CHECK: (type $foo (struct (field i32))) + ;; NO_CD: (type $foo (struct (field i32))) + (type $foo (struct (field i32))) + + ;; CHECK: (import "" "" (global $exact (ref (exact $foo)))) + ;; NO_CD: (import "" "" (global $exact (ref (exact $foo)))) + (import "" "" (global $exact (ref (exact $foo)))) + + ;; CHECK: (func $get (type $1) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $get (type $1) (result i32) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $get (result i32) + ;; Regression test for a bug where exactness was not preserved when combining + ;; nullness into cone types, resulting in a later assertion failure on the + ;; StructGet. + (struct.get $foo 0 + (select (result (ref null (exact $foo))) + (global.get $exact) + (ref.null none) + (i32.const 0) + ) + ) + ) +) From e2b41f265c957bdd43f6040ad3efe16b33d7da3b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 22 Apr 2025 21:04:37 -0700 Subject: [PATCH 455/622] [NFC] Simplify Array2Struct type replacement (#7539) Array2Struct has to update the types of every expression that interacts with and produces a reference to the optimized array type. It previously did this by separately checking whether a nullable or non-nullable reference to the array was a subtype of the expression's type. Simplify this logic by doing only a single check that considers only the heap types of the references. Also remove some unnecessary variables in which various reference types were cached since it is extremely cheap to materialize a reference type now. These simplifications will also make it easier to update the pass to handle exact reference types once `array.new` instructions are typed as exact. --- src/passes/Heap2Local.cpp | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index c95fc5d485a..d71d27bafd6 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -1029,8 +1029,7 @@ struct Array2Struct : PostWalker { // The type of the struct we are changing to (nullable and non-nullable // variations). - Type nullStruct; - Type nonNullStruct; + HeapType structType; Array2Struct(Expression* allocation, EscapeAnalyzer& analyzer, @@ -1048,7 +1047,7 @@ struct Array2Struct : PostWalker { for (Index i = 0; i < numFields; i++) { fields.push_back(element); } - HeapType structType = Struct(fields); + structType = Struct(fields); // Generate a StructNew to replace the ArrayNew*. if (auto* arrayNew = allocation->dynCast()) { @@ -1097,10 +1096,6 @@ struct Array2Struct : PostWalker { // the array type (which can be the case of an array of arrays). But that is // fine to do as the array.get is rewritten to a struct.get which is then // lowered away to locals anyhow. - auto nullArray = Type(arrayType, Nullable); - auto nonNullArray = Type(arrayType, NonNullable); - nullStruct = Type(structType, Nullable); - nonNullStruct = Type(structType, NonNullable); for (auto& [reached, _] : analyzer.reachedInteractions) { if (reached->is()) { // Casts must be handled later: We need to see the old type, and to @@ -1108,19 +1103,18 @@ struct Array2Struct : PostWalker { continue; } - // We must check subtyping here because the allocation may be upcast as it - // flows around. If we do see such upcasting then we are refining here and - // must refinalize. - if (Type::isSubType(nullArray, reached->type)) { - if (nullArray != reached->type) { - refinalize = true; - } - reached->type = nullStruct; - } else if (Type::isSubType(nonNullArray, reached->type)) { - if (nonNullArray != reached->type) { + if (!reached->type.isRef()) { + continue; + } + + // The allocation type may be generalized as it flows around. If we do see + // such generalizing, then we are refining here and must refinalize. + auto reachedHeapType = reached->type.getHeapType(); + if (HeapType::isSubType(arrayType, reachedHeapType)) { + if (arrayType != reachedHeapType) { refinalize = true; } - reached->type = nonNullStruct; + reached->type = Type(structType, reached->type.getNullability()); } } @@ -1248,7 +1242,7 @@ struct Array2Struct : PostWalker { // type here unconditionally, since we know the allocation flows through // here, and anyhow we will be removing the reference during Struct2Local, // later.) - curr->type = nonNullStruct; + curr->type = Type(structType, NonNullable); } // Regardless of how we altered the type here, refinalize. From c679a593436f66612819a3176c21e6bd00c3f888 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:51:08 +0800 Subject: [PATCH 456/622] Improve getMaxBits for 0 / 0 (#7532) The result of unsigned integer division cannot have more bits than the dividend. Fixes #7471 --- src/ir/bits.h | 16 +++-- .../lit/passes/optimize-instructions-mvp.wast | 64 +++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/ir/bits.h b/src/ir/bits.h index 15168ca664a..e8f2b4f50cc 100644 --- a/src/ir/bits.h +++ b/src/ir/bits.h @@ -187,7 +187,9 @@ Index getMaxBits(Expression* curr, return 32; } int32_t bitsRight = getMaxBits(c); - return std::max(0, maxBitsLeft - bitsRight + 1); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); } return 32; } @@ -195,7 +197,9 @@ Index getMaxBits(Expression* curr, int32_t maxBitsLeft = getMaxBits(binary->left, localInfoProvider); if (auto* c = binary->right->dynCast()) { int32_t bitsRight = getMaxBits(c); - return std::max(0, maxBitsLeft - bitsRight + 1); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); } return maxBitsLeft; } @@ -282,7 +286,9 @@ Index getMaxBits(Expression* curr, return 64; } int32_t bitsRight = getMaxBits(c); - return std::max(0, maxBitsLeft - bitsRight + 1); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); } return 64; } @@ -290,7 +296,9 @@ Index getMaxBits(Expression* curr, int32_t maxBitsLeft = getMaxBits(binary->left, localInfoProvider); if (auto* c = binary->right->dynCast()) { int32_t bitsRight = getMaxBits(c); - return std::max(0, maxBitsLeft - bitsRight + 1); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); } return maxBitsLeft; } diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 9f505a7586f..aea7b391c77 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -11395,6 +11395,17 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.div_u + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.extend_i32_u ;; CHECK-NEXT: (i64.eq ;; CHECK-NEXT: (local.get $y) @@ -11409,6 +11420,17 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.div_u + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ge_u ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const -2) @@ -11433,6 +11455,17 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.div_u + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.extend_i32_u ;; CHECK-NEXT: (i64.eq ;; CHECK-NEXT: (local.get $y) @@ -11447,6 +11480,17 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.div_u + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -11683,6 +11727,11 @@ (local.get $x) (i32.const -2147483648) )) + ;; i32(0) / i32(0) => i32(0) but still traps + (drop (i32.div_s + (i32.const 0) + (i32.const 0) + )) ;; i64(x) / -9223372036854775808 -> x == -9223372036854775808 (drop (i64.div_s (local.get $y) @@ -11693,6 +11742,11 @@ (local.get $y) (i64.const -2147483648) )) + ;; i64(0) / i64(0) => i64(0) but still traps + (drop (i64.div_s + (i64.const 0) + (i64.const 0) + )) ;; unsigned divs ;; u32(x) / -2 => x >= -2 @@ -11715,6 +11769,11 @@ (local.get $x) (i32.const -2147483648) )) + ;; i32(0) / i32(0) => i32(0) but still traps + (drop (i32.div_u + (i32.const 0) + (i32.const 0) + )) ;; u64(x) / -1 => u64(x == -1) (drop (i64.div_u (local.get $y) @@ -11725,6 +11784,11 @@ (local.get $y) (i64.const -9223372036854775808) )) + ;; i64(0) / i64(0) => i64(0) but still traps + (drop (i64.div_u + (i64.const 0) + (i64.const 0) + )) ;; bool(x) | 1 ==> 1 (drop (i32.or From 637326b143c3e49d67fdba7a31674ba0a405eb80 Mon Sep 17 00:00:00 2001 From: Gulg <65360617+GulgDev@users.noreply.github.com> Date: Wed, 23 Apr 2025 22:05:25 +0500 Subject: [PATCH 457/622] [JS API] Simplify getExpressionInfo to use expression wrappers (#7525) This simplifies the internals greatly, and moves us towards deprecating getExpressionInfo. Expression(ptr) (without "new") now returns a wrapper for the specific class, which is more useful than getExpressionInfo. --- src/js/binaryen.js-post.js | 747 ++++----------------------- test/binaryen.js/expressions.js | 10 +- test/binaryen.js/kitchen-sink.js.txt | 2 +- 3 files changed, 112 insertions(+), 647 deletions(-) diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 4695a5a4d85..1743c97388e 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -3058,600 +3058,48 @@ Module['getExpressionType'] = function(expr) { Module['getExpressionInfo'] = function(expr) { const id = Module['_BinaryenExpressionGetId'](expr); const type = Module['_BinaryenExpressionGetType'](expr); + const info = { id, type }; switch (id) { - case Module['BlockId']: { - const name = Module['_BinaryenBlockGetName'](expr); - return { - 'id': id, - 'type': type, - 'name': name ? UTF8ToString(name) : null, - 'children': getAllNested(expr, Module['_BinaryenBlockGetNumChildren'], Module['_BinaryenBlockGetChildAt']) - }; - } - case Module['IfId']: - return { - 'id': id, - 'type': type, - 'condition': Module['_BinaryenIfGetCondition'](expr), - 'ifTrue': Module['_BinaryenIfGetIfTrue'](expr), - 'ifFalse': Module['_BinaryenIfGetIfFalse'](expr) - }; - case Module['LoopId']: { - const name = Module['_BinaryenLoopGetName'](expr); - return { - 'id': id, - 'type': type, - 'name': name ? UTF8ToString(name) : null, - 'body': Module['_BinaryenLoopGetBody'](expr) - }; - } - case Module['BreakId']: { - const name = Module['_BinaryenBreakGetName'](expr); - return { - 'id': id, - 'type': type, - 'name': name ? UTF8ToString(name) : null, - 'condition': Module['_BinaryenBreakGetCondition'](expr), - 'value': Module['_BinaryenBreakGetValue'](expr) - }; - } - case Module['SwitchId']: - return { - 'id': id, - 'type': type, - // Do not pass the index as the second parameter to UTF8ToString as that will cut off the string. - 'names': getAllNested(expr, Module['_BinaryenSwitchGetNumNames'], Module['_BinaryenSwitchGetNameAt']).map(p => UTF8ToString(p)), - 'defaultName': UTF8ToString(Module['_BinaryenSwitchGetDefaultName'](expr)), - 'condition': Module['_BinaryenSwitchGetCondition'](expr), - 'value': Module['_BinaryenSwitchGetValue'](expr) - }; - case Module['CallId']: - return { - 'id': id, - 'type': type, - 'isReturn': Boolean(Module['_BinaryenCallIsReturn'](expr)), - 'target': UTF8ToString(Module['_BinaryenCallGetTarget'](expr)), - 'operands': getAllNested(expr, Module[ '_BinaryenCallGetNumOperands'], Module['_BinaryenCallGetOperandAt']) - }; - case Module['CallIndirectId']: - return { - 'id': id, - 'type': type, - 'isReturn': Boolean(Module['_BinaryenCallIndirectIsReturn'](expr)), - 'target': Module['_BinaryenCallIndirectGetTarget'](expr), - 'table': UTF8ToString(Module['_BinaryenCallIndirectGetTable'](expr)), - 'operands': getAllNested(expr, Module['_BinaryenCallIndirectGetNumOperands'], Module['_BinaryenCallIndirectGetOperandAt']), - 'params': Module['_BinaryenCallIndirectGetParams'](expr), - 'results': Module['_BinaryenCallIndirectGetResults'](expr) - }; - case Module['LocalGetId']: - return { - 'id': id, - 'type': type, - 'index': Module['_BinaryenLocalGetGetIndex'](expr) - }; - case Module['LocalSetId']: - return { - 'id': id, - 'type': type, - 'isTee': Boolean(Module['_BinaryenLocalSetIsTee'](expr)), - 'index': Module['_BinaryenLocalSetGetIndex'](expr), - 'value': Module['_BinaryenLocalSetGetValue'](expr) - }; - case Module['GlobalGetId']: - return { - 'id': id, - 'type': type, - 'name': UTF8ToString(Module['_BinaryenGlobalGetGetName'](expr)) - }; - case Module['GlobalSetId']: - return { - 'id': id, - 'type': type, - 'name': UTF8ToString(Module['_BinaryenGlobalSetGetName'](expr)), - 'value': Module['_BinaryenGlobalSetGetValue'](expr) - }; - case Module['TableGetId']: - return { - 'id': id, - 'type': type, - 'table': UTF8ToString(Module['_BinaryenTableGetGetTable'](expr)), - 'index': Module['_BinaryenTableGetGetIndex'](expr) - }; - case Module['TableSetId']: - return { - 'id': id, - 'type': type, - 'table': UTF8ToString(Module['_BinaryenTableSetGetTable'](expr)), - 'index': Module['_BinaryenTableSetGetIndex'](expr), - 'value': Module['_BinaryenTableSetGetValue'](expr) - }; - case Module['TableSizeId']: - return { - 'id': id, - 'type': type, - 'table': UTF8ToString(Module['_BinaryenTableSizeGetTable'](expr)), - }; - case Module['TableGrowId']: - return { - 'id': id, - 'type': type, - 'table': UTF8ToString(Module['_BinaryenTableGrowGetTable'](expr)), - 'value': Module['_BinaryenTableGrowGetValue'](expr), - 'delta': Module['_BinaryenTableGrowGetDelta'](expr), - }; - case Module['LoadId']: - return { - 'id': id, - 'type': type, - 'isAtomic': Boolean(Module['_BinaryenLoadIsAtomic'](expr)), - 'isSigned': Boolean(Module['_BinaryenLoadIsSigned'](expr)), - 'offset': Module['_BinaryenLoadGetOffset'](expr), - 'bytes': Module['_BinaryenLoadGetBytes'](expr), - 'align': Module['_BinaryenLoadGetAlign'](expr), - 'ptr': Module['_BinaryenLoadGetPtr'](expr) - }; - case Module['StoreId']: - return { - 'id': id, - 'type': type, - 'isAtomic': Boolean(Module['_BinaryenStoreIsAtomic'](expr)), - 'offset': Module['_BinaryenStoreGetOffset'](expr), - 'bytes': Module['_BinaryenStoreGetBytes'](expr), - 'align': Module['_BinaryenStoreGetAlign'](expr), - 'ptr': Module['_BinaryenStoreGetPtr'](expr), - 'value': Module['_BinaryenStoreGetValue'](expr), - 'valueType': Module['_BinaryenStoreGetValueType'](expr) - }; - case Module['ConstId']: { - let value; + case Module['ConstId']: switch (type) { - case Module['i32']: value = Module['_BinaryenConstGetValueI32'](expr); break; - case Module['i64']: value = { + case Module['i32']: info.value = Module['_BinaryenConstGetValueI32'](expr); break; + case Module['i64']: info.value = { 'low': Module['_BinaryenConstGetValueI64Low'](expr), 'high': Module['_BinaryenConstGetValueI64High'](expr) }; break; - case Module['f32']: value = Module['_BinaryenConstGetValueF32'](expr); break; - case Module['f64']: value = Module['_BinaryenConstGetValueF64'](expr); break; + case Module['f32']: info.value = Module['_BinaryenConstGetValueF32'](expr); break; + case Module['f64']: info.value = Module['_BinaryenConstGetValueF64'](expr); break; case Module['v128']: { preserveStack(() => { const tempBuffer = stackAlloc(16); Module['_BinaryenConstGetValueV128'](expr, tempBuffer); - value = new Array(16); + info.value = new Array(16); for (let i = 0; i < 16; i++) { - value[i] = HEAPU8[tempBuffer + i]; + info.value[i] = HEAPU8[tempBuffer + i]; } }); break; } default: throw Error('unexpected type: ' + type); } - return { - 'id': id, - 'type': type, - 'value': value - }; - } - case Module['UnaryId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenUnaryGetOp'](expr), - 'value': Module['_BinaryenUnaryGetValue'](expr) - }; - case Module['BinaryId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenBinaryGetOp'](expr), - 'left': Module['_BinaryenBinaryGetLeft'](expr), - 'right': Module['_BinaryenBinaryGetRight'](expr) - }; - case Module['SelectId']: - return { - 'id': id, - 'type': type, - 'ifTrue': Module['_BinaryenSelectGetIfTrue'](expr), - 'ifFalse': Module['_BinaryenSelectGetIfFalse'](expr), - 'condition': Module['_BinaryenSelectGetCondition'](expr) - }; - case Module['DropId']: - return { - 'id': id, - 'type': type, - 'value': Module['_BinaryenDropGetValue'](expr) - }; - case Module['ReturnId']: - return { - 'id': id, - 'type': type, - 'value': Module['_BinaryenReturnGetValue'](expr) - }; - case Module['NopId']: - case Module['UnreachableId']: - case Module['PopId']: - return { - 'id': id, - 'type': type - }; - case Module['MemorySizeId']: - return { - 'id': id, - 'type': type - }; - case Module['MemoryGrowId']: - return { - 'id': id, - 'type': type, - 'delta': Module['_BinaryenMemoryGrowGetDelta'](expr) - } - case Module['AtomicRMWId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenAtomicRMWGetOp'](expr), - 'bytes': Module['_BinaryenAtomicRMWGetBytes'](expr), - 'offset': Module['_BinaryenAtomicRMWGetOffset'](expr), - 'ptr': Module['_BinaryenAtomicRMWGetPtr'](expr), - 'value': Module['_BinaryenAtomicRMWGetValue'](expr) - }; - case Module['AtomicCmpxchgId']: - return { - 'id': id, - 'type': type, - 'bytes': Module['_BinaryenAtomicCmpxchgGetBytes'](expr), - 'offset': Module['_BinaryenAtomicCmpxchgGetOffset'](expr), - 'ptr': Module['_BinaryenAtomicCmpxchgGetPtr'](expr), - 'expected': Module['_BinaryenAtomicCmpxchgGetExpected'](expr), - 'replacement': Module['_BinaryenAtomicCmpxchgGetReplacement'](expr) - }; - case Module['AtomicWaitId']: - return { - 'id': id, - 'type': type, - 'ptr': Module['_BinaryenAtomicWaitGetPtr'](expr), - 'expected': Module['_BinaryenAtomicWaitGetExpected'](expr), - 'timeout': Module['_BinaryenAtomicWaitGetTimeout'](expr), - 'expectedType': Module['_BinaryenAtomicWaitGetExpectedType'](expr) - }; - case Module['AtomicNotifyId']: - return { - 'id': id, - 'type': type, - 'ptr': Module['_BinaryenAtomicNotifyGetPtr'](expr), - 'notifyCount': Module['_BinaryenAtomicNotifyGetNotifyCount'](expr) - }; - case Module['AtomicFenceId']: - return { - 'id': id, - 'type': type, - 'order': Module['_BinaryenAtomicFenceGetOrder'](expr) - }; - case Module['SIMDExtractId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenSIMDExtractGetOp'](expr), - 'vec': Module['_BinaryenSIMDExtractGetVec'](expr), - 'index': Module['_BinaryenSIMDExtractGetIndex'](expr) - }; - case Module['SIMDReplaceId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenSIMDReplaceGetOp'](expr), - 'vec': Module['_BinaryenSIMDReplaceGetVec'](expr), - 'index': Module['_BinaryenSIMDReplaceGetIndex'](expr), - 'value': Module['_BinaryenSIMDReplaceGetValue'](expr) - }; - case Module['SIMDShuffleId']: - return preserveStack(() => { - const tempBuffer = stackAlloc(16); - Module['_BinaryenSIMDShuffleGetMask'](expr, tempBuffer); - const mask = new Array(16); - for (let i = 0; i < 16; i++) { - mask[i] = HEAPU8[tempBuffer + i]; + break; + default: { + const staticMembers = expressionWrappers[id]; + Object.keys(staticMembers).forEach(memberName => { + const member = staticMembers[memberName]; + if (typeof member === "function") { + let match; + if (member.length === 1 && (match = memberName.match(/(^get|^(?=is|has))/))) { + const index = match[1].length; + const propertyName = memberName.charAt(index).toLowerCase() + memberName.substring(index + 1); + info[propertyName] = member(expr); + } } - return { - 'id': id, - 'type': type, - 'left': Module['_BinaryenSIMDShuffleGetLeft'](expr), - 'right': Module['_BinaryenSIMDShuffleGetRight'](expr), - 'mask': mask - }; }); - case Module['SIMDTernaryId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenSIMDTernaryGetOp'](expr), - 'a': Module['_BinaryenSIMDTernaryGetA'](expr), - 'b': Module['_BinaryenSIMDTernaryGetB'](expr), - 'c': Module['_BinaryenSIMDTernaryGetC'](expr) - }; - case Module['SIMDShiftId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenSIMDShiftGetOp'](expr), - 'vec': Module['_BinaryenSIMDShiftGetVec'](expr), - 'shift': Module['_BinaryenSIMDShiftGetShift'](expr) - }; - case Module['SIMDLoadId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenSIMDLoadGetOp'](expr), - 'offset': Module['_BinaryenSIMDLoadGetOffset'](expr), - 'align': Module['_BinaryenSIMDLoadGetAlign'](expr), - 'ptr': Module['_BinaryenSIMDLoadGetPtr'](expr) - }; - case Module['SIMDLoadStoreLaneId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenSIMDLoadStoreLaneGetOp'](expr), - 'offset': Module['_BinaryenSIMDLoadStoreLaneGetOffset'](expr), - 'align': Module['_BinaryenSIMDLoadStoreLaneGetAlign'](expr), - 'index': Module['_BinaryenSIMDLoadStoreLaneGetIndex'](expr), - 'ptr': Module['_BinaryenSIMDLoadStoreLaneGetPtr'](expr), - 'vec': Module['_BinaryenSIMDLoadStoreLaneGetVec'](expr), - 'isStore': Boolean(Module['_BinaryenSIMDLoadStoreLaneIsStore'](expr)) - }; - case Module['MemoryInitId']: - return { - 'id': id, - 'type': type, - 'segment': UTF8ToString(Module['_BinaryenMemoryInitGetSegment'](expr)), - 'dest': Module['_BinaryenMemoryInitGetDest'](expr), - 'offset': Module['_BinaryenMemoryInitGetOffset'](expr), - 'size': Module['_BinaryenMemoryInitGetSize'](expr) - }; - case Module['DataDropId']: - return { - 'id': id, - 'type': type, - 'segment': UTF8ToString(Module['_BinaryenDataDropGetSegment'](expr)), - }; - case Module['MemoryCopyId']: - return { - 'id': id, - 'type': type, - 'dest': Module['_BinaryenMemoryCopyGetDest'](expr), - 'source': Module['_BinaryenMemoryCopyGetSource'](expr), - 'size': Module['_BinaryenMemoryCopyGetSize'](expr) - }; - case Module['MemoryFillId']: - return { - 'id': id, - 'type': type, - 'dest': Module['_BinaryenMemoryFillGetDest'](expr), - 'value': Module['_BinaryenMemoryFillGetValue'](expr), - 'size': Module['_BinaryenMemoryFillGetSize'](expr) - }; - case Module['RefNullId']: - return { - 'id': id, - 'type': type - }; - case Module['RefIsNullId']: - return { - 'id': id, - 'type': type, - 'value': Module['_BinaryenRefIsNullGetValue'](expr) - }; - case Module['RefAsId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenRefAsGetOp'](expr), - 'value': Module['_BinaryenRefAsGetValue'](expr) - }; - case Module['RefFuncId']: - return { - 'id': id, - 'type': type, - 'func': UTF8ToString(Module['_BinaryenRefFuncGetFunc'](expr)), - }; - case Module['RefEqId']: - return { - 'id': id, - 'type': type, - 'left': Module['_BinaryenRefEqGetLeft'](expr), - 'right': Module['_BinaryenRefEqGetRight'](expr) - }; - case Module['RefTestId']: - return { - 'id': id, - 'type': type, - 'ref': Module['_BinaryenRefTestGetRef'](expr), - 'castType': Module['_BinaryenRefTestGetCastType'](expr) - }; - case Module['RefCastId']: - return { - 'id': id, - 'type': type, - 'ref': Module['_BinaryenRefCastGetRef'](expr) - }; - case Module['BrOnId']: - return { - 'id': id, - 'type': type, - 'op': Module['_BinaryenBrOnGetOp'](expr), - 'name': UTF8ToString(Module['_BinaryenBrOnGetName'](expr)), - 'ref': Module['_BinaryenBrOnGetRef'](expr), - 'castType': Module['_BinaryenBrOnGetCastType'](expr) - }; - case Module['StructNewId']: - return { - 'id': id, - 'type': type, - 'operands': getAllNested(expr, Module['_BinaryenStructNewGetNumOperands'], Module['_BinaryenStructNewGetOperandAt']), - }; - case Module['StructGetId']: - return { - 'id': id, - 'type': type, - 'index': Module['_BinaryenStructGetGetIndex'](expr), - 'ref': Module['_BinaryenStructGetGetRef'](expr), - 'isSigned': Boolean(Module['_BinaryenStructGetIsSigned'](expr)) - }; - case Module['StructSetId']: - return { - 'id': id, - 'type': type, - 'index': Module['_BinaryenStructSetGetIndex'](expr), - 'ref': Module['_BinaryenStructSetGetRef'](expr), - 'value': Module['_BinaryenStructSetGetValue'](expr) - }; - case Module['ArrayNewId']: - return { - 'id': id, - 'type': type, - 'init': Module['_BinaryenArrayNewGetInit'](expr), - 'size': Module['_BinaryenArrayNewGetSize'](expr) - }; - case Module['ArrayNewFixedId']: - return { - 'id': id, - 'type': type, - 'values': getAllNested(expr, Module['_BinaryenArrayNewFixedGetNumValues'], Module['_BinaryenArrayNewFixedGetValueAt']) - }; - case Module['ArrayNewDataId']: - return { - 'id': id, - 'type': type, - 'segment': UTF8ToString(Module['_BinaryenArrayNewDataGetSegment'](expr)), - 'offset': Module['_BinaryenArrayNewDataGetOffset'](expr), - 'size': Module['_BinaryenArrayNewDataGetSize'](expr) - }; - case Module['ArrayNewElemId']: - return { - 'id': id, - 'type': type, - 'segment': UTF8ToString(Module['_BinaryenArrayNewElemGetSegment'](expr)), - 'offset': Module['_BinaryenArrayNewElemGetOffset'](expr), - 'size': Module['_BinaryenArrayNewElemGetSize'](expr) - }; - case Module['ArrayGetId']: - return { - 'id': id, - 'type': type, - 'ref': Module['_BinaryenArrayGetGetRef'](expr), - 'index': Module['_BinaryenArrayGetGetIndex'](expr), - 'isSigned': Boolean(Module['_BinaryenArrayGetIsSigned'](expr)) - }; - case Module['ArraySetId']: - return { - 'id': id, - 'type': type, - 'ref': Module['_BinaryenArraySetGetRef'](expr), - 'index': Module['_BinaryenArraySetGetIndex'](expr), - 'value': Module['_BinaryenArraySetGetValue'](expr) - }; - case Module['ArrayLenId']: - return { - 'id': id, - 'type': type, - 'ref': Module['_BinaryenArrayLenGetRef'](expr) - }; - case Module['ArrayFillId']: - return { - 'id': id, - 'type': type, - 'ref': Module['_BinaryenArrayFillGetRef'](expr), - 'index': Module['_BinaryenArrayFillGetIndex'](expr), - 'value': Module['_BinaryenArrayFillGetValue'](expr), - 'size': Module['_BinaryenArrayFillGetSize'](expr) - }; - case Module['ArrayCopyId']: - return { - 'id': id, - 'type': type, - 'destRef': Module['_BinaryenArrayCopyGetDestRef'](expr), - 'destIndex': Module['_BinaryenArrayCopyGetDestIndex'](expr), - 'srcRef': Module['_BinaryenArrayCopyGetSrcRef'](expr), - 'srcIndex': Module['_BinaryenArrayCopyGetSrcIndex'](expr), - 'length': Module['_BinaryenArrayCopyGetLength'](expr) - }; - case Module['ArrayInitDataId']: - return { - 'id': id, - 'type': type, - 'segment': UTF8ToString(Module['_BinaryenArrayInitDataGetSegment'](expr)), - 'ref': Module['_BinaryenArrayInitDataGetRef'](expr), - 'index': Module['_BinaryenArrayInitDataGetIndex'](expr), - 'offset': Module['_BinaryenArrayInitDataGetOffset'](expr), - 'size': Module['_BinaryenArrayInitDataGetSize'](expr) - }; - case Module['ArrayInitElemId']: - return { - 'id': id, - 'type': type, - 'segment': UTF8ToString(Module['_BinaryenArrayInitElemGetSegment'](expr)), - 'ref': Module['_BinaryenArrayInitElemGetRef'](expr), - 'index': Module['_BinaryenArrayInitElemGetIndex'](expr), - 'offset': Module['_BinaryenArrayInitElemGetOffset'](expr), - 'size': Module['_BinaryenArrayInitElemGetSize'](expr) - }; - case Module['TryId']: { - const name = Module['_BinaryenTryGetName'](expr); - const delegateTarget = Module['_BinaryenTryGetDelegateTarget'](expr); - return { - 'id': id, - 'type': type, - 'name': name ? UTF8ToString(name) : null, - 'body': Module['_BinaryenTryGetBody'](expr), - 'catchTags': getAllNested(expr, Module['_BinaryenTryGetNumCatchTags'], Module['_BinaryenTryGetCatchTagAt']).map(p => UTF8ToString(p)), - 'catchBodies': getAllNested(expr, Module['_BinaryenTryGetNumCatchBodies'], Module['_BinaryenTryGetCatchBodyAt']), - 'hasCatchAll': Boolean(Module['_BinaryenTryHasCatchAll'](expr)), - 'delegateTarget': delegateTarget ? UTF8ToString(delegateTarget) : null, - 'isDelegate': Boolean(Module['_BinaryenTryIsDelegate'](expr)) - }; + break; } - case Module['ThrowId']: - return { - 'id': id, - 'type': type, - 'tag': UTF8ToString(Module['_BinaryenThrowGetTag'](expr)), - 'operands': getAllNested(expr, Module['_BinaryenThrowGetNumOperands'], Module['_BinaryenThrowGetOperandAt']) - }; - case Module['RethrowId']: - return { - 'id': id, - 'type': type, - 'target': UTF8ToString(Module['_BinaryenRethrowGetTarget'](expr)) - }; - case Module['TupleMakeId']: - return { - 'id': id, - 'type': type, - 'operands': getAllNested(expr, Module['_BinaryenTupleMakeGetNumOperands'], Module['_BinaryenTupleMakeGetOperandAt']) - }; - case Module['TupleExtractId']: - return { - 'id': id, - 'type': type, - 'tuple': Module['_BinaryenTupleExtractGetTuple'](expr), - 'index': Module['_BinaryenTupleExtractGetIndex'](expr) - }; - case Module['RefI31Id']: - return { - 'id': id, - 'type': type, - 'value': Module['_BinaryenRefI31GetValue'](expr) - }; - case Module['I31GetId']: - return { - 'id': id, - 'type': type, - 'i31': Module['_BinaryenI31GetGetI31'](expr), - 'isSigned': Boolean(Module['_BinaryenI31GetIsSigned'](expr)) - }; - - default: - throw Error('unexpected id: ' + id); } + return info; }; // Gets the side effects of the specified expression @@ -3979,12 +3427,15 @@ Module['setAllowInliningFunctionsWithLoops'] = function(value) { // Expression wrappers +// Expression ID-to-wrapper map +let expressionWrappers = {}; + // Private symbol used to store the underlying C-API pointer of a wrapped object. const thisPtr = Symbol(); // Makes a specific expression wrapper class with the specified static members // while automatically deriving instance methods and accessors. -function makeExpressionWrapper(ownStaticMembers) { +function makeExpressionWrapper(expressionId, ownStaticMembers) { /** * @constructor * @extends Expression @@ -4005,6 +3456,8 @@ function makeExpressionWrapper(ownStaticMembers) { (SpecificExpression.prototype = Object.create(Expression.prototype)).constructor = SpecificExpression; // derive own instance members deriveWrapperInstanceMembers(SpecificExpression.prototype, ownStaticMembers); + // register the expression wrapper + expressionWrappers[expressionId] = SpecificExpression; return SpecificExpression; } @@ -4052,6 +3505,12 @@ function deriveWrapperInstanceMembers(prototype, staticMembers) { // Base class of all expression wrappers /** @constructor */ function Expression(expr) { + // Returns the specific wrapper if called without `new` + if (!(this instanceof Expression)) { + if (!expr) return null; + const id = Module['_BinaryenExpressionGetId'](expr); + return expressionWrappers[id](expr); + } if (!expr) throw Error("expression reference must not be null"); this[thisPtr] = expr; } @@ -4077,7 +3536,7 @@ Expression.prototype['valueOf'] = function() { Module['Expression'] = Expression; -Module['Block'] = makeExpressionWrapper({ +Module['Block'] = makeExpressionWrapper(Module['_BinaryenBlockId'](), { 'getName'(expr) { const name = Module['_BinaryenBlockGetName'](expr); return name ? UTF8ToString(name) : null; @@ -4111,7 +3570,7 @@ Module['Block'] = makeExpressionWrapper({ } }); -Module['If'] = makeExpressionWrapper({ +Module['If'] = makeExpressionWrapper(Module['_BinaryenIfId'](), { 'getCondition'(expr) { return Module['_BinaryenIfGetCondition'](expr); }, @@ -4132,7 +3591,7 @@ Module['If'] = makeExpressionWrapper({ } }); -Module['Loop'] = makeExpressionWrapper({ +Module['Loop'] = makeExpressionWrapper(Module['_BinaryenLoopId'](), { 'getName'(expr) { const name = Module['_BinaryenLoopGetName'](expr); return name ? UTF8ToString(name) : null; @@ -4148,7 +3607,7 @@ Module['Loop'] = makeExpressionWrapper({ } }); -Module['Break'] = makeExpressionWrapper({ +Module['Break'] = makeExpressionWrapper(Module['_BinaryenBreakId'](), { 'getName'(expr) { const name = Module['_BinaryenBreakGetName'](expr); return name ? UTF8ToString(name) : null; @@ -4170,7 +3629,7 @@ Module['Break'] = makeExpressionWrapper({ } }); -Module['Switch'] = makeExpressionWrapper({ +Module['Switch'] = makeExpressionWrapper(Module['_BinaryenSwitchId'](), { 'getNumNames'(expr) { return Module['_BinaryenSwitchGetNumNames'](expr); }, @@ -4218,7 +3677,7 @@ Module['Switch'] = makeExpressionWrapper({ }, }); -Module['Call'] = makeExpressionWrapper({ +Module['Call'] = makeExpressionWrapper(Module['_BinaryenCallId'](), { 'getTarget'(expr) { return UTF8ToString(Module['_BinaryenCallGetTarget'](expr)); }, @@ -4257,7 +3716,7 @@ Module['Call'] = makeExpressionWrapper({ } }); -Module['CallIndirect'] = makeExpressionWrapper({ +Module['CallIndirect'] = makeExpressionWrapper(Module['_BinaryenCallIndirectId'](), { 'getTarget'(expr) { return Module['_BinaryenCallIndirectGetTarget'](expr); }, @@ -4314,7 +3773,7 @@ Module['CallIndirect'] = makeExpressionWrapper({ } }); -Module['LocalGet'] = makeExpressionWrapper({ +Module['LocalGet'] = makeExpressionWrapper(Module['_BinaryenLocalGetId'](), { 'getIndex'(expr) { return Module['_BinaryenLocalGetGetIndex'](expr); }, @@ -4323,7 +3782,7 @@ Module['LocalGet'] = makeExpressionWrapper({ } }); -Module['LocalSet'] = makeExpressionWrapper({ +Module['LocalSet'] = makeExpressionWrapper(Module['_BinaryenLocalSetId'](), { 'getIndex'(expr) { return Module['_BinaryenLocalSetGetIndex'](expr); }, @@ -4341,7 +3800,7 @@ Module['LocalSet'] = makeExpressionWrapper({ } }); -Module['GlobalGet'] = makeExpressionWrapper({ +Module['GlobalGet'] = makeExpressionWrapper(Module['_BinaryenGlobalGetId'](), { 'getName'(expr) { return UTF8ToString(Module['_BinaryenGlobalGetGetName'](expr)); }, @@ -4350,7 +3809,7 @@ Module['GlobalGet'] = makeExpressionWrapper({ } }); -Module['GlobalSet'] = makeExpressionWrapper({ +Module['GlobalSet'] = makeExpressionWrapper(Module['_BinaryenGlobalSetId'](), { 'getName'(expr) { return UTF8ToString(Module['_BinaryenGlobalSetGetName'](expr)); }, @@ -4365,7 +3824,7 @@ Module['GlobalSet'] = makeExpressionWrapper({ } }); -Module['TableGet'] = makeExpressionWrapper({ +Module['TableGet'] = makeExpressionWrapper(Module['_BinaryenTableGetId'](), { 'getTable'(expr) { return UTF8ToString(Module['_BinaryenTableGetGetTable'](expr)); }, @@ -4380,7 +3839,7 @@ Module['TableGet'] = makeExpressionWrapper({ } }); -Module['TableSet'] = makeExpressionWrapper({ +Module['TableSet'] = makeExpressionWrapper(Module['_BinaryenTableSetId'](), { 'getTable'(expr) { return UTF8ToString(Module['_BinaryenTableSetGetTable'](expr)); }, @@ -4401,7 +3860,7 @@ Module['TableSet'] = makeExpressionWrapper({ } }); -Module['TableSize'] = makeExpressionWrapper({ +Module['TableSize'] = makeExpressionWrapper(Module['_BinaryenTableSizeId'](), { 'getTable'(expr) { return UTF8ToString(Module['_BinaryenTableSizeGetTable'](expr)); }, @@ -4410,7 +3869,7 @@ Module['TableSize'] = makeExpressionWrapper({ }, }); -Module['TableGrow'] = makeExpressionWrapper({ +Module['TableGrow'] = makeExpressionWrapper(Module['_BinaryenTableGrowId'](), { 'getTable'(expr) { return UTF8ToString(Module['_BinaryenTableGrowGetTable'](expr)); }, @@ -4431,9 +3890,9 @@ Module['TableGrow'] = makeExpressionWrapper({ } }); -Module['MemorySize'] = makeExpressionWrapper({}); +Module['MemorySize'] = makeExpressionWrapper(Module['_BinaryenMemorySizeId'](), {}); -Module['MemoryGrow'] = makeExpressionWrapper({ +Module['MemoryGrow'] = makeExpressionWrapper(Module['_BinaryenMemoryGrowId'](), { 'getDelta'(expr) { return Module['_BinaryenMemoryGrowGetDelta'](expr); }, @@ -4442,7 +3901,7 @@ Module['MemoryGrow'] = makeExpressionWrapper({ } }); -Module['Load'] = makeExpressionWrapper({ +Module['Load'] = makeExpressionWrapper(Module['_BinaryenLoadId'](), { 'isAtomic'(expr) { return Boolean(Module['_BinaryenLoadIsAtomic'](expr)); }, @@ -4481,7 +3940,7 @@ Module['Load'] = makeExpressionWrapper({ } }); -Module['Store'] = makeExpressionWrapper({ +Module['Store'] = makeExpressionWrapper(Module['_BinaryenStoreId'](), { 'isAtomic'(expr) { return Boolean(Module['_BinaryenStoreIsAtomic'](expr)); }, @@ -4526,7 +3985,7 @@ Module['Store'] = makeExpressionWrapper({ } }); -Module['Const'] = makeExpressionWrapper({ +Module['Const'] = makeExpressionWrapper(Module['_BinaryenConstId'](), { 'getValueI32'(expr) { return Module['_BinaryenConstGetValueI32'](expr); }, @@ -4580,7 +4039,7 @@ Module['Const'] = makeExpressionWrapper({ } }); -Module['Unary'] = makeExpressionWrapper({ +Module['Unary'] = makeExpressionWrapper(Module['_BinaryenUnaryId'](), { 'getOp'(expr) { return Module['_BinaryenUnaryGetOp'](expr); }, @@ -4595,7 +4054,7 @@ Module['Unary'] = makeExpressionWrapper({ } }); -Module['Binary'] = makeExpressionWrapper({ +Module['Binary'] = makeExpressionWrapper(Module['_BinaryenBinaryId'](), { 'getOp'(expr) { return Module['_BinaryenBinaryGetOp'](expr); }, @@ -4616,7 +4075,7 @@ Module['Binary'] = makeExpressionWrapper({ } }); -Module['Select'] = makeExpressionWrapper({ +Module['Select'] = makeExpressionWrapper(Module['_BinaryenSelectId'](), { 'getIfTrue'(expr) { return Module['_BinaryenSelectGetIfTrue'](expr); }, @@ -4637,7 +4096,7 @@ Module['Select'] = makeExpressionWrapper({ } }); -Module['Drop'] = makeExpressionWrapper({ +Module['Drop'] = makeExpressionWrapper(Module['_BinaryenDropId'](), { 'getValue'(expr) { return Module['_BinaryenDropGetValue'](expr); }, @@ -4646,7 +4105,7 @@ Module['Drop'] = makeExpressionWrapper({ } }); -Module['Return'] = makeExpressionWrapper({ +Module['Return'] = makeExpressionWrapper(Module['_BinaryenReturnId'](), { 'getValue'(expr) { return Module['_BinaryenReturnGetValue'](expr); }, @@ -4655,7 +4114,7 @@ Module['Return'] = makeExpressionWrapper({ } }); -Module['AtomicRMW'] = makeExpressionWrapper({ +Module['AtomicRMW'] = makeExpressionWrapper(Module['_BinaryenAtomicRMWId'](), { 'getOp'(expr) { return Module['_BinaryenAtomicRMWGetOp'](expr); }, @@ -4688,7 +4147,7 @@ Module['AtomicRMW'] = makeExpressionWrapper({ } }); -Module['AtomicCmpxchg'] = makeExpressionWrapper({ +Module['AtomicCmpxchg'] = makeExpressionWrapper(Module['_BinaryenAtomicCmpxchgId'](), { 'getBytes'(expr) { return Module['_BinaryenAtomicCmpxchgGetBytes'](expr); }, @@ -4721,7 +4180,7 @@ Module['AtomicCmpxchg'] = makeExpressionWrapper({ } }); -Module['AtomicWait'] = makeExpressionWrapper({ +Module['AtomicWait'] = makeExpressionWrapper(Module['_BinaryenAtomicWaitId'](), { 'getPtr'(expr) { return Module['_BinaryenAtomicWaitGetPtr'](expr); }, @@ -4748,7 +4207,7 @@ Module['AtomicWait'] = makeExpressionWrapper({ } }); -Module['AtomicNotify'] = makeExpressionWrapper({ +Module['AtomicNotify'] = makeExpressionWrapper(Module['_BinaryenAtomicNotifyId'](), { 'getPtr'(expr) { return Module['_BinaryenAtomicNotifyGetPtr'](expr); }, @@ -4763,7 +4222,7 @@ Module['AtomicNotify'] = makeExpressionWrapper({ } }); -Module['AtomicFence'] = makeExpressionWrapper({ +Module['AtomicFence'] = makeExpressionWrapper(Module['_BinaryenAtomicFenceId'](), { 'getOrder'(expr) { return Module['_BinaryenAtomicFenceGetOrder'](expr); }, @@ -4772,7 +4231,7 @@ Module['AtomicFence'] = makeExpressionWrapper({ } }); -Module['SIMDExtract'] = makeExpressionWrapper({ +Module['SIMDExtract'] = makeExpressionWrapper(Module['_BinaryenSIMDExtractId'](), { 'getOp'(expr) { return Module['_BinaryenSIMDExtractGetOp'](expr); }, @@ -4793,7 +4252,7 @@ Module['SIMDExtract'] = makeExpressionWrapper({ } }); -Module['SIMDReplace'] = makeExpressionWrapper({ +Module['SIMDReplace'] = makeExpressionWrapper(Module['_BinaryenSIMDReplaceId'](), { 'getOp'(expr) { return Module['_BinaryenSIMDReplaceGetOp'](expr); }, @@ -4820,7 +4279,7 @@ Module['SIMDReplace'] = makeExpressionWrapper({ } }); -Module['SIMDShuffle'] = makeExpressionWrapper({ +Module['SIMDShuffle'] = makeExpressionWrapper(Module['_BinaryenSIMDShuffleId'](), { 'getLeft'(expr) { return Module['_BinaryenSIMDShuffleGetLeft'](expr); }, @@ -4856,7 +4315,7 @@ Module['SIMDShuffle'] = makeExpressionWrapper({ } }); -Module['SIMDTernary'] = makeExpressionWrapper({ +Module['SIMDTernary'] = makeExpressionWrapper(Module['_BinaryenSIMDTernaryId'](), { 'getOp'(expr) { return Module['_BinaryenSIMDTernaryGetOp'](expr); }, @@ -4883,7 +4342,7 @@ Module['SIMDTernary'] = makeExpressionWrapper({ } }); -Module['SIMDShift'] = makeExpressionWrapper({ +Module['SIMDShift'] = makeExpressionWrapper(Module['_BinaryenSIMDShiftId'](), { 'getOp'(expr) { return Module['_BinaryenSIMDShiftGetOp'](expr); }, @@ -4904,7 +4363,7 @@ Module['SIMDShift'] = makeExpressionWrapper({ } }); -Module['SIMDLoad'] = makeExpressionWrapper({ +Module['SIMDLoad'] = makeExpressionWrapper(Module['_BinaryenSIMDLoadId'](), { 'getOp'(expr) { return Module['_BinaryenSIMDLoadGetOp'](expr); }, @@ -4931,7 +4390,7 @@ Module['SIMDLoad'] = makeExpressionWrapper({ } }); -Module['SIMDLoadStoreLane'] = makeExpressionWrapper({ +Module['SIMDLoadStoreLane'] = makeExpressionWrapper(Module['_BinaryenSIMDLoadStoreLaneId'](), { 'getOp'(expr) { return Module['_BinaryenSIMDLoadStoreLaneGetOp'](expr); }, @@ -4973,7 +4432,7 @@ Module['SIMDLoadStoreLane'] = makeExpressionWrapper({ } }); -Module['MemoryInit'] = makeExpressionWrapper({ +Module['MemoryInit'] = makeExpressionWrapper(Module['_BinaryenMemoryInitId'](), { 'getSegment'(expr) { return UTF8ToString(Module['_BinaryenMemoryInitGetSegment'](expr)); }, @@ -5000,7 +4459,7 @@ Module['MemoryInit'] = makeExpressionWrapper({ } }); -Module['DataDrop'] = makeExpressionWrapper({ +Module['DataDrop'] = makeExpressionWrapper(Module['_BinaryenDataDropId'](), { 'getSegment'(expr) { return UTF8ToString(Module['_BinaryenDataDropGetSegment'](expr)); }, @@ -5009,7 +4468,7 @@ Module['DataDrop'] = makeExpressionWrapper({ } }); -Module['MemoryCopy'] = makeExpressionWrapper({ +Module['MemoryCopy'] = makeExpressionWrapper(Module['_BinaryenMemoryCopyId'](), { 'getDest'(expr) { return Module['_BinaryenMemoryCopyGetDest'](expr); }, @@ -5030,7 +4489,7 @@ Module['MemoryCopy'] = makeExpressionWrapper({ } }); -Module['MemoryFill'] = makeExpressionWrapper({ +Module['MemoryFill'] = makeExpressionWrapper(Module['_BinaryenMemoryFillId'](), { 'getDest'(expr) { return Module['_BinaryenMemoryFillGetDest'](expr); }, @@ -5051,7 +4510,7 @@ Module['MemoryFill'] = makeExpressionWrapper({ } }); -Module['RefIsNull'] = makeExpressionWrapper({ +Module['RefIsNull'] = makeExpressionWrapper(Module['_BinaryenRefIsNullId'](), { 'getValue'(expr) { return Module['_BinaryenRefIsNullGetValue'](expr); }, @@ -5060,7 +4519,7 @@ Module['RefIsNull'] = makeExpressionWrapper({ } }); -Module['RefAs'] = makeExpressionWrapper({ +Module['RefAs'] = makeExpressionWrapper(Module['_BinaryenRefAsId'](), { 'getOp'(expr) { return Module['_BinaryenRefAsGetOp'](expr); }, @@ -5075,7 +4534,7 @@ Module['RefAs'] = makeExpressionWrapper({ } }); -Module['RefFunc'] = makeExpressionWrapper({ +Module['RefFunc'] = makeExpressionWrapper(Module['_BinaryenRefFuncId'](), { 'getFunc'(expr) { return UTF8ToString(Module['_BinaryenRefFuncGetFunc'](expr)); }, @@ -5084,7 +4543,7 @@ Module['RefFunc'] = makeExpressionWrapper({ } }); -Module['RefEq'] = makeExpressionWrapper({ +Module['RefEq'] = makeExpressionWrapper(Module['_BinaryenRefEqId'](), { 'getLeft'(expr) { return Module['_BinaryenRefEqGetLeft'](expr); }, @@ -5099,7 +4558,7 @@ Module['RefEq'] = makeExpressionWrapper({ } }); -Module['RefTest'] = makeExpressionWrapper({ +Module['RefTest'] = makeExpressionWrapper(Module['_BinaryenRefTestId'](), { 'getRef'(expr) { return Module['_BinaryenRefTestGetRef'](expr); }, @@ -5114,7 +4573,7 @@ Module['RefTest'] = makeExpressionWrapper({ } }); -Module['RefCast'] = makeExpressionWrapper({ +Module['RefCast'] = makeExpressionWrapper(Module['_BinaryenRefCastId'](), { 'getRef'(expr) { return Module['_BinaryenRefCastGetRef'](expr); }, @@ -5126,7 +4585,7 @@ Module['RefCast'] = makeExpressionWrapper({ // TODO: any.convert_extern // TODO: extern.convert_any -Module['BrOn'] = makeExpressionWrapper({ +Module['BrOn'] = makeExpressionWrapper(Module['_BinaryenBrOnId'](), { 'getOp'(expr) { return Module['_BinaryenBrOnGetOp'](expr); }, @@ -5153,7 +4612,7 @@ Module['BrOn'] = makeExpressionWrapper({ } }); -Module['StructNew'] = makeExpressionWrapper({ +Module['StructNew'] = makeExpressionWrapper(Module['_BinaryenStructNewId'](), { 'getNumOperands'(expr) { return Module['_BinaryenStructNewGetNumOperands'](expr); }, @@ -5187,7 +4646,7 @@ Module['StructNew'] = makeExpressionWrapper({ } }); -Module['StructGet'] = makeExpressionWrapper({ +Module['StructGet'] = makeExpressionWrapper(Module['_BinaryenStructGetId'](), { 'getIndex'(expr) { return Module['_BinaryenStructGetGetIndex'](expr); }, @@ -5208,7 +4667,7 @@ Module['StructGet'] = makeExpressionWrapper({ } }); -Module['StructSet'] = makeExpressionWrapper({ +Module['StructSet'] = makeExpressionWrapper(Module['_BinaryenStructSetId'](), { 'getIndex'(expr) { return Module['_BinaryenStructSetGetIndex'](expr); }, @@ -5229,7 +4688,7 @@ Module['StructSet'] = makeExpressionWrapper({ } }); -Module['ArrayNew'] = makeExpressionWrapper({ +Module['ArrayNew'] = makeExpressionWrapper(Module['_BinaryenArrayNewId'](), { 'getInit'(expr) { return Module['_BinaryenArrayNewGetInit'](expr); }, @@ -5244,7 +4703,7 @@ Module['ArrayNew'] = makeExpressionWrapper({ } }); -Module['ArrayNewFixed'] = makeExpressionWrapper({ +Module['ArrayNewFixed'] = makeExpressionWrapper(Module['_BinaryenArrayNewFixedId'](), { 'getNumValues'(expr) { return Module['_BinaryenArrayNewFixedGetNumValues'](expr); }, @@ -5280,7 +4739,7 @@ Module['ArrayNewFixed'] = makeExpressionWrapper({ } }); -Module['ArrayNewData'] = makeExpressionWrapper({ +Module['ArrayNewData'] = makeExpressionWrapper(Module['_BinaryenArrayNewDataId'](), { 'getSegment'(expr) { return UTF8ToString(Module['_BinaryenArrayNewDataGetSegment'](expr)); }, @@ -5301,7 +4760,7 @@ Module['ArrayNewData'] = makeExpressionWrapper({ } }); -Module['ArrayNewElem'] = makeExpressionWrapper({ +Module['ArrayNewElem'] = makeExpressionWrapper(Module['_BinaryenArrayNewElemId'](), { 'getSegment'(expr) { return UTF8ToString(Module['_BinaryenArrayNewElemGetSegment'](expr)); }, @@ -5322,7 +4781,7 @@ Module['ArrayNewElem'] = makeExpressionWrapper({ } }); -Module['ArrayGet'] = makeExpressionWrapper({ +Module['ArrayGet'] = makeExpressionWrapper(Module['_BinaryenArrayGetId'](), { 'getRef'(expr) { return Module['_BinaryenArrayGetGetRef'](expr); }, @@ -5343,7 +4802,7 @@ Module['ArrayGet'] = makeExpressionWrapper({ } }); -Module['ArraySet'] = makeExpressionWrapper({ +Module['ArraySet'] = makeExpressionWrapper(Module['_BinaryenArraySetId'](), { 'getRef'(expr) { return Module['_BinaryenArraySetGetRef'](expr); }, @@ -5364,7 +4823,7 @@ Module['ArraySet'] = makeExpressionWrapper({ } }); -Module['ArrayLen'] = makeExpressionWrapper({ +Module['ArrayLen'] = makeExpressionWrapper(Module['_BinaryenArrayLenId'](), { 'getRef'(expr) { return Module['_BinaryenArrayLenGetRef'](expr); }, @@ -5373,7 +4832,7 @@ Module['ArrayLen'] = makeExpressionWrapper({ } }); -Module['ArrayFill'] = makeExpressionWrapper({ +Module['ArrayFill'] = makeExpressionWrapper(Module['_BinaryenArrayFillId'](), { 'getRef'(expr) { return Module['_BinaryenArrayFillGetRef'](expr); }, @@ -5400,7 +4859,7 @@ Module['ArrayFill'] = makeExpressionWrapper({ } }); -Module['ArrayCopy'] = makeExpressionWrapper({ +Module['ArrayCopy'] = makeExpressionWrapper(Module['_BinaryenArrayCopyId'](), { 'getDestRef'(expr) { return Module['_BinaryenArrayCopyGetDestRef'](expr); }, @@ -5433,7 +4892,7 @@ Module['ArrayCopy'] = makeExpressionWrapper({ } }); -Module['ArrayInitData'] = makeExpressionWrapper({ +Module['ArrayInitData'] = makeExpressionWrapper(Module['_BinaryenArrayInitDataId'](), { 'getSegment'(expr) { return UTF8ToString(Module['_BinaryenArrayInitDataGetSegment'](expr)); }, @@ -5466,7 +4925,7 @@ Module['ArrayInitData'] = makeExpressionWrapper({ } }); -Module['ArrayInitElem'] = makeExpressionWrapper({ +Module['ArrayInitElem'] = makeExpressionWrapper(Module['_BinaryenArrayInitElemId'](), { 'getSegment'(expr) { return UTF8ToString(Module['_BinaryenArrayInitElemGetSegment'](expr)); }, @@ -5499,7 +4958,7 @@ Module['ArrayInitElem'] = makeExpressionWrapper({ } }); -Module['Try'] = makeExpressionWrapper({ +Module['Try'] = makeExpressionWrapper(Module['_BinaryenTryId'](), { 'getName'(expr) { const name = Module['_BinaryenTryGetName'](expr); return name ? UTF8ToString(name) : null; @@ -5578,7 +5037,7 @@ Module['Try'] = makeExpressionWrapper({ } }); -Module['Throw'] = makeExpressionWrapper({ +Module['Throw'] = makeExpressionWrapper(Module['_BinaryenThrowId'](), { 'getTag'(expr) { return UTF8ToString(Module['_BinaryenThrowGetTag'](expr)); }, @@ -5611,7 +5070,7 @@ Module['Throw'] = makeExpressionWrapper({ }, }); -Module['Rethrow'] = makeExpressionWrapper({ +Module['Rethrow'] = makeExpressionWrapper(Module['_BinaryenRethrowId'](), { 'getTarget'(expr) { const target = Module['_BinaryenRethrowGetTarget'](expr); return target ? UTF8ToString(target) : null; @@ -5621,7 +5080,7 @@ Module['Rethrow'] = makeExpressionWrapper({ } }); -Module['TupleMake'] = makeExpressionWrapper({ +Module['TupleMake'] = makeExpressionWrapper(Module['_BinaryenTupleMakeId'](), { 'getNumOperands'(expr) { return Module['_BinaryenTupleMakeGetNumOperands'](expr); }, @@ -5648,7 +5107,7 @@ Module['TupleMake'] = makeExpressionWrapper({ } }); -Module['TupleExtract'] = makeExpressionWrapper({ +Module['TupleExtract'] = makeExpressionWrapper(Module['_BinaryenTupleExtractId'](), { 'getTuple'(expr) { return Module['_BinaryenTupleExtractGetTuple'](expr); }, @@ -5663,7 +5122,7 @@ Module['TupleExtract'] = makeExpressionWrapper({ } }); -Module['RefI31'] = makeExpressionWrapper({ +Module['RefI31'] = makeExpressionWrapper(Module['_BinaryenRefI31Id'](), { 'getValue'(expr) { return Module['_BinaryenRefI31GetValue'](expr); }, @@ -5672,7 +5131,7 @@ Module['RefI31'] = makeExpressionWrapper({ } }); -Module['I31Get'] = makeExpressionWrapper({ +Module['I31Get'] = makeExpressionWrapper(Module['_BinaryenI31GetId'](), { 'getI31'(expr) { return Module['_BinaryenI31GetGetI31'](expr); }, diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index 3cbe7e59448..f249f95493c 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -9,7 +9,11 @@ function assertDeepEqual(x, y) { console.log("# Expression"); (function testWrapper() { - var theExpression = binaryen.Block(42); // works without new + const module = new binaryen.Module(); + + const ptr = module.block(null, []); + + var theExpression = binaryen.Expression(ptr); // works without new assert(theExpression instanceof binaryen.Block); assert(theExpression instanceof binaryen.Expression); assert(theExpression.constructor === binaryen.Block); @@ -17,7 +21,9 @@ console.log("# Expression"); assert(typeof binaryen.Block.getName === "function"); // own assert(typeof theExpression.getId === "function"); // proto assert(typeof theExpression.getName === "function"); // own - assert((theExpression | 0) === 42); // via valueOf + assert((theExpression | 0) === ptr); // via valueOf + + module.dispose(); })(); console.log("# Block"); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index bee1d298937..be7a06f7ad8 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -2821,4 +2821,4 @@ sizeof Literal: 24 ) getExpressionInfo(memory.grow)={"id":21,"type":2,"delta":1} -getExpressionInfo(switch)={"id":5,"type":1,"names":["label"],"defaultName":"label","condition":0,"value":0} +getExpressionInfo(switch)={"id":5,"type":1,"numNames":1,"names":["label"],"defaultName":"label","condition":0,"value":0} From e43be974554b8338faac2a027d7da6a26feb29d9 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 23 Apr 2025 12:06:11 -0700 Subject: [PATCH 458/622] Update evaluateCastCheck for exact types (#7541) Additionally use finality of heap types to infer that a value being cast must have exactly its static type, which allows us to infer that more casts to exact types will be successful. Add exhaustive unit tests for `evaluateCastCheck` for various interesting heap type relationships. --- src/ir/gc-type-utils.h | 18 +++- test/gtest/CMakeLists.txt | 3 +- test/gtest/cast-check.cpp | 212 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 test/gtest/cast-check.cpp diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h index 6ac6db3ba06..f46b1b97179 100644 --- a/src/ir/gc-type-utils.h +++ b/src/ir/gc-type-utils.h @@ -104,7 +104,21 @@ inline EvaluationResult evaluateCastCheck(Type refType, Type castType) { } auto castHeapType = castType.getHeapType(); - auto refIsHeapSubType = HeapType::isSubType(refHeapType, castHeapType); + + // Check whether a value of type `a` is known to also have type `b`, assuming + // it is non-null. + auto isHeapSubtype = [](Type a, Type b) { + // If the heap type of `a` has no subtypes, then we know its value must be + // exactly `a`. + // TODO: Use information from a subtypes analysis, if available. + if (!a.getHeapType().isBasic() && !a.getHeapType().isOpen()) { + a = a.with(Exact); + } + // Ignore nullability. + return Type::isSubType(a.with(NonNullable), b.with(NonNullable)); + }; + + auto refIsHeapSubType = isHeapSubtype(refType, castType); if (refIsHeapSubType) { // The heap type is a subtype. All we need is for nullability to work out as @@ -121,7 +135,7 @@ inline EvaluationResult evaluateCastCheck(Type refType, Type castType) { return SuccessOnlyIfNonNull; } - auto castIsHeapSubType = HeapType::isSubType(castHeapType, refHeapType); + auto castIsHeapSubType = isHeapSubtype(castType, refType); bool heapTypesCompatible = refIsHeapSubType || castIsHeapSubType; if (!heapTypesCompatible || castHeapType.isBottom()) { diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 396403b3080..04e8d587e13 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -6,7 +6,7 @@ endif() set(unittest_SOURCES arena.cpp - source-map.cpp + cast-check.cpp cfg.cpp dfa_minimization.cpp disjoint_sets.cpp @@ -18,6 +18,7 @@ set(unittest_SOURCES possible-contents.cpp printing.cpp scc.cpp + source-map.cpp stringify.cpp suffix_tree.cpp topological-sort.cpp diff --git a/test/gtest/cast-check.cpp b/test/gtest/cast-check.cpp new file mode 100644 index 00000000000..22825b7cce4 --- /dev/null +++ b/test/gtest/cast-check.cpp @@ -0,0 +1,212 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ir/gc-type-utils.h" +#include "type-test.h" +#include "wasm-type.h" +#include "gtest/gtest.h" + +namespace wasm { + +using namespace GCTypeUtils; + +class CastCheckTest : public TypeTest { +protected: + HeapType super, sub, subFinal; + + void SetUp() override { + TypeBuilder builder(3); + builder[0] = Struct(); + builder[0].setOpen(); + builder[1] = Struct(); + builder[1].subTypeOf(builder[0]).setOpen(); + builder[2] = Struct(); + builder[2].subTypeOf(builder[0]); + + auto built = builder.build(); + ASSERT_TRUE(built); + + super = (*built)[0]; + sub = (*built)[1]; + subFinal = (*built)[2]; + } +}; + +TEST_F(CastCheckTest, CastToSelfNonFinal) { +#define EXPECT_CAST( \ + srcNullability, srcExactness, castNullability, castExactness, result) \ + EXPECT_EQ(evaluateCastCheck(Type(super, srcNullability, srcExactness), \ + Type(super, castNullability, castExactness)), \ + result); + + EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, Success); + EXPECT_CAST(Nullable, Inexact, Nullable, Exact, Unknown); + EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, SuccessOnlyIfNonNull); + EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, Unknown); + EXPECT_CAST(Nullable, Exact, Nullable, Inexact, Success); + EXPECT_CAST(Nullable, Exact, Nullable, Exact, Success); + EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, SuccessOnlyIfNonNull); + EXPECT_CAST(Nullable, Exact, NonNullable, Exact, SuccessOnlyIfNonNull); + EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Success); + EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Unknown); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Success); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Unknown); + EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Success); + EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Success); + EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Success); + EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Success); +#undef EXPECT_CAST +} + +TEST_F(CastCheckTest, CastToSelfFinal) { +#define EXPECT_CAST( \ + srcNullability, srcExactness, castNullability, castExactness, result) \ + EXPECT_EQ(evaluateCastCheck(Type(subFinal, srcNullability, srcExactness), \ + Type(subFinal, castNullability, castExactness)), \ + result); + + EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, Success); + EXPECT_CAST(Nullable, Inexact, Nullable, Exact, Success); + EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, SuccessOnlyIfNonNull); + EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, SuccessOnlyIfNonNull); + EXPECT_CAST(Nullable, Exact, Nullable, Inexact, Success); + EXPECT_CAST(Nullable, Exact, Nullable, Exact, Success); + EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, SuccessOnlyIfNonNull); + EXPECT_CAST(Nullable, Exact, NonNullable, Exact, SuccessOnlyIfNonNull); + EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Success); + EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Success); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Success); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Success); + EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Success); + EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Success); + EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Success); + EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Success); +#undef EXPECT_CAST +} + +TEST_F(CastCheckTest, CastToSuper) { +#define EXPECT_CAST( \ + srcNullability, srcExactness, castNullability, castExactness, result) \ + EXPECT_EQ(evaluateCastCheck(Type(sub, srcNullability, srcExactness), \ + Type(super, castNullability, castExactness)), \ + result); + + EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, Success); + EXPECT_CAST(Nullable, Inexact, Nullable, Exact, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, SuccessOnlyIfNonNull); + EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, Failure); + EXPECT_CAST(Nullable, Exact, Nullable, Inexact, Success); + EXPECT_CAST(Nullable, Exact, Nullable, Exact, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, SuccessOnlyIfNonNull); + EXPECT_CAST(Nullable, Exact, NonNullable, Exact, Failure); + EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Success); + EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Failure); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Success); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Failure); + EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Success); + EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Failure); + EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Success); + EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Failure); +#undef EXPECT_CAST +} + +TEST_F(CastCheckTest, CastToSub) { +#define EXPECT_CAST( \ + srcNullability, srcExactness, castNullability, castExactness, result) \ + EXPECT_EQ(evaluateCastCheck(Type(super, srcNullability, srcExactness), \ + Type(sub, castNullability, castExactness)), \ + result); + + EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, Unknown); + EXPECT_CAST(Nullable, Inexact, Nullable, Exact, Unknown); + EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, Unknown); + EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, Unknown); + EXPECT_CAST(Nullable, Exact, Nullable, Inexact, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Exact, Nullable, Exact, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, Failure); + EXPECT_CAST(Nullable, Exact, NonNullable, Exact, Failure); + EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Unknown); + EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Unknown); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Unknown); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Unknown); + EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Failure); + EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Failure); + EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Failure); + EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Failure); +#undef EXPECT_CAST +} + +TEST_F(CastCheckTest, CastToSibling) { +#define EXPECT_CAST( \ + srcNullability, srcExactness, castNullability, castExactness, result) \ + EXPECT_EQ(evaluateCastCheck(Type(sub, srcNullability, srcExactness), \ + Type(subFinal, castNullability, castExactness)), \ + result); + + EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Inexact, Nullable, Exact, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, Failure); + EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, Failure); + EXPECT_CAST(Nullable, Exact, Nullable, Inexact, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Exact, Nullable, Exact, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, Failure); + EXPECT_CAST(Nullable, Exact, NonNullable, Exact, Failure); + EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Failure); + EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Failure); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Failure); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Failure); + EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Failure); + EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Failure); + EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Failure); + EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Failure); +#undef EXPECT_CAST +} + +TEST_F(CastCheckTest, CastToBottom) { +#define EXPECT_CAST(srcNullability, srcExactness, castNullability, result) \ + EXPECT_EQ(evaluateCastCheck(Type(super, srcNullability, srcExactness), \ + Type(HeapType::none, castNullability, Inexact)), \ + result); + + EXPECT_CAST(Nullable, Inexact, Nullable, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Inexact, NonNullable, Failure); + EXPECT_CAST(Nullable, Exact, Nullable, SuccessOnlyIfNull); + EXPECT_CAST(Nullable, Exact, NonNullable, Failure); + EXPECT_CAST(NonNullable, Inexact, Nullable, Failure); + EXPECT_CAST(NonNullable, Inexact, NonNullable, Failure); + EXPECT_CAST(NonNullable, Exact, Nullable, Failure); + EXPECT_CAST(NonNullable, Exact, NonNullable, Failure); +#undef EXPECT_CAST +} + +TEST_F(CastCheckTest, CastFromBottom) { +#define EXPECT_CAST(srcNullability, castNullability, castExactness, result) \ + EXPECT_EQ(evaluateCastCheck(Type(HeapType::none, srcNullability, Inexact), \ + Type(super, castNullability, castExactness)), \ + result); + + EXPECT_CAST(Nullable, Nullable, Inexact, Success); + EXPECT_CAST(Nullable, Nullable, Exact, Success); + EXPECT_CAST(Nullable, NonNullable, Inexact, Failure); + EXPECT_CAST(Nullable, NonNullable, Exact, Failure); + EXPECT_CAST(NonNullable, Nullable, Inexact, GCTypeUtils::Unreachable); + EXPECT_CAST(NonNullable, Nullable, Exact, GCTypeUtils::Unreachable); + EXPECT_CAST(NonNullable, NonNullable, Inexact, GCTypeUtils::Unreachable); + EXPECT_CAST(NonNullable, NonNullable, Exact, GCTypeUtils::Unreachable); +#undef EXPECT_CAST +} + +} // namespace wasm From 2d978562dbc0377bc4e097821fda5110ca60522d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 23 Apr 2025 13:28:42 -0700 Subject: [PATCH 459/622] Exhaustively test OptimizeInstructions on ref.cast (#7542) Write a script to systematically generate test cases for all possible interesting cast patterns. Update the code to correctly handle the case where the cast is known to succeed but still requires a cast to recover exactness. --- scripts/test/fuzzing.py | 2 +- scripts/test/gen-cast-test.py | 96 + src/passes/OptimizeInstructions.cpp | 9 + .../optimize-instructions-all-casts.wast | 1830 +++++++++++++++++ .../passes/optimize-instructions-exact.wast | 22 - 5 files changed, 1936 insertions(+), 23 deletions(-) create mode 100755 scripts/test/gen-cast-test.py create mode 100644 test/lit/passes/optimize-instructions-all-casts.wast delete mode 100644 test/lit/passes/optimize-instructions-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 971a5c18e4d..ab2f3539f78 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -114,7 +114,7 @@ 'exact-references-lowering.wast', 'exact-casts.wast', 'exact-casts-trivial.wast', - 'optimize-instructions-exact.wast', + 'optimize-instructions-all-casts.wast', 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', 'coalesce-locals-exact.wast', diff --git a/scripts/test/gen-cast-test.py b/scripts/test/gen-cast-test.py new file mode 100755 index 00000000000..39fd63da8c1 --- /dev/null +++ b/scripts/test/gen-cast-test.py @@ -0,0 +1,96 @@ +#! /usr/bin/python3 + +''' +This script is used to generate +test/lit/passes/optimize-instructions-all-casts.wast +''' + +import itertools + +interesting_pairs = [('$super', '$super', 'cast-to-self-nonfinal'), + ('$sub-final', '$sub-final', 'cast-to-self-final'), + ('$sub', '$super', 'cast-to-super'), + ('$super', '$sub', 'cast-to-sub'), + ('$sub-final', '$sub', 'cast-to-sibling'), + ('$super', 'none', 'cast-to-bottom'), + ('none', '$super', 'cast-from-bottom')] + + +def gen_test_configs(): + for src_heap, cast_heap, heap_name in interesting_pairs: + for src_nullable, src_exact, cast_nullable, cast_exact in \ + itertools.product([True, False], repeat=4): + if src_exact and src_heap == 'none': + continue + if cast_exact and cast_heap == 'none': + continue + yield heap_name, src_heap, cast_heap, src_nullable, cast_nullable, \ + src_exact, cast_exact + + +def print_test(config): + heap_name, src_heap, cast_heap, src_nullable, cast_nullable, src_exact, \ + cast_exact = config + + src_nullable_name = 'null' if src_nullable else 'non-null' + cast_nullable_name = 'null' if cast_nullable else 'non-null' + + src_exact_name = 'exact' if src_exact else 'inexact' + cast_exact_name = 'exact' if cast_exact else 'inexact' + + test_name = f'{heap_name}-{src_nullable_name}-{src_exact_name}-to-' + \ + f'{cast_nullable_name}-{cast_exact_name}' + + src_nullable_type = ' null' if src_nullable else '' + cast_nullable_type = ' null' if cast_nullable else '' + + src_heap_type = f'(exact {src_heap})' if src_exact else src_heap + cast_heap_type = f'(exact {cast_heap})' if cast_exact else cast_heap + + src_type = f'(ref{src_nullable_type} {src_heap_type})' + cast_type = f'(ref{cast_nullable_type} {cast_heap_type})' + + test = f''' + (func ${test_name} (param {src_type}) (result {cast_type}) + (local anyref) + (ref.cast {cast_type} + (local.tee 1 + (local.get 0) + ) + ) + )''' + print(test) + + +def print_tests(): + for config in gen_test_configs(): + print_test(config) + + +def print_header(): + header = ''';; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; NOTE: Test has been generated by scripts/test/gen-cast-test.py. Do not edit manually. + +;; Exhaustively test optimization of all interesting casts. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + (type $super (sub (struct))) + (type $sub (sub $super (struct))) + (type $sub-final (sub final $super (struct)))''' + print(header) + + +def print_footer(): + print(')') + + +def main(): + print_header() + print_tests() + print_footer() + + +if __name__ == '__main__': + main() diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 8c13663deba..59209e9c4d3 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2333,6 +2333,15 @@ struct OptimizeInstructions builder.makeRefNull(nullType))); return; } + + // At this point we know the cast will succeed as long as nullability + // works out, but we still need the cast to recover the exactness that + // is not present in the value's static type, so there's nothing we + // can do. + if (needsExactCast) { + return; + } + // We need to use a tee to return the value since we can't materialize // it directly. auto scratch = builder.addVar(getFunction(), ref->type); diff --git a/test/lit/passes/optimize-instructions-all-casts.wast b/test/lit/passes/optimize-instructions-all-casts.wast new file mode 100644 index 00000000000..cba7b7d7649 --- /dev/null +++ b/test/lit/passes/optimize-instructions-all-casts.wast @@ -0,0 +1,1830 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; NOTE: Test has been generated by scripts/test/gen-cast-test.py. Do not edit manually. + +;; Exhaustively test optimization of all interesting casts. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub-final (sub final $super (struct))) + + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + (type $sub-final (sub final $super (struct))) + + ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-null-exact (type $3) (param $0 (ref null (exact $super))) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-exact-to-null-exact (param (ref null (exact $super))) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-null-inexact (type $4) (param $0 (ref null (exact $super))) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-exact-to-null-inexact (param (ref null (exact $super))) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-non-null-exact (type $5) (param $0 (ref null (exact $super))) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-exact-to-non-null-exact (param (ref null (exact $super))) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-non-null-inexact (type $6) (param $0 (ref null (exact $super))) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-exact-to-non-null-inexact (param (ref null (exact $super))) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-null-exact (type $7) (param $0 (ref null $super)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref null (exact $super)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-inexact-to-null-exact (param (ref null $super)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-null-inexact (type $8) (param $0 (ref null $super)) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-inexact-to-null-inexact (param (ref null $super)) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-non-null-exact (type $9) (param $0 (ref null $super)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $super)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-inexact-to-non-null-exact (param (ref null $super)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-non-null-inexact (type $10) (param $0 (ref null $super)) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-inexact-to-non-null-inexact (param (ref null $super)) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-null-exact (type $11) (param $0 (ref (exact $super))) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-exact-to-null-exact (param (ref (exact $super))) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-null-inexact (type $12) (param $0 (ref (exact $super))) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-exact-to-null-inexact (param (ref (exact $super))) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-non-null-exact (type $13) (param $0 (ref (exact $super))) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-exact-to-non-null-exact (param (ref (exact $super))) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-non-null-inexact (type $14) (param $0 (ref (exact $super))) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-exact-to-non-null-inexact (param (ref (exact $super))) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-null-exact (type $15) (param $0 (ref $super)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $super)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-inexact-to-null-exact (param (ref $super)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-null-inexact (type $16) (param $0 (ref $super)) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-inexact-to-null-inexact (param (ref $super)) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-exact (type $17) (param $0 (ref $super)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $super)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-exact (param (ref $super)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-inexact (type $18) (param $0 (ref $super)) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-inexact (param (ref $super)) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-exact-to-null-exact (type $19) (param $0 (ref null (exact $sub-final))) (result (ref null (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-exact-to-null-exact (param (ref null (exact $sub-final))) (result (ref null (exact $sub-final))) + (local anyref) + (ref.cast (ref null (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-exact-to-null-inexact (type $20) (param $0 (ref null (exact $sub-final))) (result (ref null $sub-final)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-exact-to-null-inexact (param (ref null (exact $sub-final))) (result (ref null $sub-final)) + (local anyref) + (ref.cast (ref null $sub-final) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-exact-to-non-null-exact (type $21) (param $0 (ref null (exact $sub-final))) (result (ref (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-exact-to-non-null-exact (param (ref null (exact $sub-final))) (result (ref (exact $sub-final))) + (local anyref) + (ref.cast (ref (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-exact-to-non-null-inexact (type $22) (param $0 (ref null (exact $sub-final))) (result (ref $sub-final)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-exact-to-non-null-inexact (param (ref null (exact $sub-final))) (result (ref $sub-final)) + (local anyref) + (ref.cast (ref $sub-final) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-inexact-to-null-exact (type $23) (param $0 (ref null $sub-final)) (result (ref null (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref null (exact $sub-final)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-inexact-to-null-exact (param (ref null $sub-final)) (result (ref null (exact $sub-final))) + (local anyref) + (ref.cast (ref null (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-inexact-to-null-inexact (type $24) (param $0 (ref null $sub-final)) (result (ref null $sub-final)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null $sub-final)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-inexact-to-null-inexact (param (ref null $sub-final)) (result (ref null $sub-final)) + (local anyref) + (ref.cast (ref null $sub-final) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-inexact-to-non-null-exact (type $25) (param $0 (ref null $sub-final)) (result (ref (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-inexact-to-non-null-exact (param (ref null $sub-final)) (result (ref (exact $sub-final))) + (local anyref) + (ref.cast (ref (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-inexact-to-non-null-inexact (type $26) (param $0 (ref null $sub-final)) (result (ref $sub-final)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null $sub-final)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-inexact-to-non-null-inexact (param (ref null $sub-final)) (result (ref $sub-final)) + (local anyref) + (ref.cast (ref $sub-final) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-exact-to-null-exact (type $27) (param $0 (ref (exact $sub-final))) (result (ref null (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-exact-to-null-exact (param (ref (exact $sub-final))) (result (ref null (exact $sub-final))) + (local anyref) + (ref.cast (ref null (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-exact-to-null-inexact (type $28) (param $0 (ref (exact $sub-final))) (result (ref null $sub-final)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-exact-to-null-inexact (param (ref (exact $sub-final))) (result (ref null $sub-final)) + (local anyref) + (ref.cast (ref null $sub-final) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-exact-to-non-null-exact (type $29) (param $0 (ref (exact $sub-final))) (result (ref (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-exact-to-non-null-exact (param (ref (exact $sub-final))) (result (ref (exact $sub-final))) + (local anyref) + (ref.cast (ref (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-exact-to-non-null-inexact (type $30) (param $0 (ref (exact $sub-final))) (result (ref $sub-final)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-exact-to-non-null-inexact (param (ref (exact $sub-final))) (result (ref $sub-final)) + (local anyref) + (ref.cast (ref $sub-final) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-null-exact (type $31) (param $0 (ref $sub-final)) (result (ref null (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-inexact-to-null-exact (param (ref $sub-final)) (result (ref null (exact $sub-final))) + (local anyref) + (ref.cast (ref null (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-null-inexact (type $32) (param $0 (ref $sub-final)) (result (ref null $sub-final)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref $sub-final)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-inexact-to-null-inexact (param (ref $sub-final)) (result (ref null $sub-final)) + (local anyref) + (ref.cast (ref null $sub-final) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-non-null-exact (type $33) (param $0 (ref $sub-final)) (result (ref (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-inexact-to-non-null-exact (param (ref $sub-final)) (result (ref (exact $sub-final))) + (local anyref) + (ref.cast (ref (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-non-null-inexact (type $34) (param $0 (ref $sub-final)) (result (ref $sub-final)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref $sub-final)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-inexact-to-non-null-inexact (param (ref $sub-final)) (result (ref $sub-final)) + (local anyref) + (ref.cast (ref $sub-final) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-exact-to-null-exact (type $35) (param $0 (ref null (exact $sub))) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-exact-to-null-exact (param (ref null (exact $sub))) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-exact-to-null-inexact (type $36) (param $0 (ref null (exact $sub))) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-exact-to-null-inexact (param (ref null (exact $sub))) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-exact-to-non-null-exact (type $37) (param $0 (ref null (exact $sub))) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-exact-to-non-null-exact (param (ref null (exact $sub))) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-exact-to-non-null-inexact (type $38) (param $0 (ref null (exact $sub))) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-exact-to-non-null-inexact (param (ref null (exact $sub))) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-inexact-to-null-exact (type $39) (param $0 (ref null $sub)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-inexact-to-null-exact (param (ref null $sub)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-inexact-to-null-inexact (type $40) (param $0 (ref null $sub)) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-inexact-to-null-inexact (param (ref null $sub)) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-inexact-to-non-null-exact (type $41) (param $0 (ref null $sub)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-inexact-to-non-null-exact (param (ref null $sub)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-inexact-to-non-null-inexact (type $42) (param $0 (ref null $sub)) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-inexact-to-non-null-inexact (param (ref null $sub)) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-exact-to-null-exact (type $43) (param $0 (ref (exact $sub))) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-exact-to-null-exact (param (ref (exact $sub))) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-exact-to-null-inexact (type $44) (param $0 (ref (exact $sub))) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-exact-to-null-inexact (param (ref (exact $sub))) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-exact-to-non-null-exact (type $45) (param $0 (ref (exact $sub))) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-exact-to-non-null-exact (param (ref (exact $sub))) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-exact-to-non-null-inexact (type $46) (param $0 (ref (exact $sub))) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-exact-to-non-null-inexact (param (ref (exact $sub))) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-inexact-to-null-exact (type $47) (param $0 (ref $sub)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-inexact-to-null-exact (param (ref $sub)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-inexact-to-null-inexact (type $48) (param $0 (ref $sub)) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-inexact-to-null-inexact (param (ref $sub)) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-inexact-to-non-null-exact (type $49) (param $0 (ref $sub)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-inexact-to-non-null-exact (param (ref $sub)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-inexact-to-non-null-inexact (type $50) (param $0 (ref $sub)) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-inexact-to-non-null-inexact (param (ref $sub)) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-exact-to-null-exact (type $51) (param $0 (ref null (exact $super))) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-exact-to-null-exact (param (ref null (exact $super))) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-exact-to-null-inexact (type $52) (param $0 (ref null (exact $super))) (result (ref null $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-exact-to-null-inexact (param (ref null (exact $super))) (result (ref null $sub)) + (local anyref) + (ref.cast (ref null $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-exact-to-non-null-exact (type $53) (param $0 (ref null (exact $super))) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-exact-to-non-null-exact (param (ref null (exact $super))) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-exact-to-non-null-inexact (type $54) (param $0 (ref null (exact $super))) (result (ref $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-exact-to-non-null-inexact (param (ref null (exact $super))) (result (ref $sub)) + (local anyref) + (ref.cast (ref $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-inexact-to-null-exact (type $55) (param $0 (ref null $super)) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref null (exact $sub)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-inexact-to-null-exact (param (ref null $super)) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-inexact-to-null-inexact (type $56) (param $0 (ref null $super)) (result (ref null $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref null $sub) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-inexact-to-null-inexact (param (ref null $super)) (result (ref null $sub)) + (local anyref) + (ref.cast (ref null $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-inexact-to-non-null-exact (type $57) (param $0 (ref null $super)) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-inexact-to-non-null-exact (param (ref null $super)) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-inexact-to-non-null-inexact (type $58) (param $0 (ref null $super)) (result (ref $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-inexact-to-non-null-inexact (param (ref null $super)) (result (ref $sub)) + (local anyref) + (ref.cast (ref $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-exact-to-null-exact (type $59) (param $0 (ref (exact $super))) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-exact-to-null-exact (param (ref (exact $super))) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-exact-to-null-inexact (type $60) (param $0 (ref (exact $super))) (result (ref null $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-exact-to-null-inexact (param (ref (exact $super))) (result (ref null $sub)) + (local anyref) + (ref.cast (ref null $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-exact-to-non-null-exact (type $61) (param $0 (ref (exact $super))) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-exact-to-non-null-exact (param (ref (exact $super))) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-exact-to-non-null-inexact (type $62) (param $0 (ref (exact $super))) (result (ref $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-exact-to-non-null-inexact (param (ref (exact $super))) (result (ref $sub)) + (local anyref) + (ref.cast (ref $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-inexact-to-null-exact (type $63) (param $0 (ref $super)) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-inexact-to-null-exact (param (ref $super)) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-inexact-to-null-inexact (type $64) (param $0 (ref $super)) (result (ref null $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-inexact-to-null-inexact (param (ref $super)) (result (ref null $sub)) + (local anyref) + (ref.cast (ref null $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-inexact-to-non-null-exact (type $65) (param $0 (ref $super)) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-inexact-to-non-null-exact (param (ref $super)) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-inexact-to-non-null-inexact (type $66) (param $0 (ref $super)) (result (ref $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-inexact-to-non-null-inexact (param (ref $super)) (result (ref $sub)) + (local anyref) + (ref.cast (ref $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-exact-to-null-exact (type $67) (param $0 (ref null (exact $sub-final))) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-exact-to-null-exact (param (ref null (exact $sub-final))) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-exact-to-null-inexact (type $68) (param $0 (ref null (exact $sub-final))) (result (ref null $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-exact-to-null-inexact (param (ref null (exact $sub-final))) (result (ref null $sub)) + (local anyref) + (ref.cast (ref null $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-exact-to-non-null-exact (type $69) (param $0 (ref null (exact $sub-final))) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-exact-to-non-null-exact (param (ref null (exact $sub-final))) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-exact-to-non-null-inexact (type $70) (param $0 (ref null (exact $sub-final))) (result (ref $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-exact-to-non-null-inexact (param (ref null (exact $sub-final))) (result (ref $sub)) + (local anyref) + (ref.cast (ref $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-inexact-to-null-exact (type $71) (param $0 (ref null $sub-final)) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-inexact-to-null-exact (param (ref null $sub-final)) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-inexact-to-null-inexact (type $72) (param $0 (ref null $sub-final)) (result (ref null $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-inexact-to-null-inexact (param (ref null $sub-final)) (result (ref null $sub)) + (local anyref) + (ref.cast (ref null $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-inexact-to-non-null-exact (type $73) (param $0 (ref null $sub-final)) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-inexact-to-non-null-exact (param (ref null $sub-final)) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-inexact-to-non-null-inexact (type $74) (param $0 (ref null $sub-final)) (result (ref $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-inexact-to-non-null-inexact (param (ref null $sub-final)) (result (ref $sub)) + (local anyref) + (ref.cast (ref $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-exact-to-null-exact (type $75) (param $0 (ref (exact $sub-final))) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-exact-to-null-exact (param (ref (exact $sub-final))) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-exact-to-null-inexact (type $76) (param $0 (ref (exact $sub-final))) (result (ref null $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-exact-to-null-inexact (param (ref (exact $sub-final))) (result (ref null $sub)) + (local anyref) + (ref.cast (ref null $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-exact-to-non-null-exact (type $77) (param $0 (ref (exact $sub-final))) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-exact-to-non-null-exact (param (ref (exact $sub-final))) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-exact-to-non-null-inexact (type $78) (param $0 (ref (exact $sub-final))) (result (ref $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-exact-to-non-null-inexact (param (ref (exact $sub-final))) (result (ref $sub)) + (local anyref) + (ref.cast (ref $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-null-exact (type $79) (param $0 (ref $sub-final)) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-inexact-to-null-exact (param (ref $sub-final)) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-null-inexact (type $80) (param $0 (ref $sub-final)) (result (ref null $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-inexact-to-null-inexact (param (ref $sub-final)) (result (ref null $sub)) + (local anyref) + (ref.cast (ref null $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-non-null-exact (type $81) (param $0 (ref $sub-final)) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-inexact-to-non-null-exact (param (ref $sub-final)) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-non-null-inexact (type $82) (param $0 (ref $sub-final)) (result (ref $sub)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-inexact-to-non-null-inexact (param (ref $sub-final)) (result (ref $sub)) + (local anyref) + (ref.cast (ref $sub) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-bottom-null-exact-to-null-inexact (type $83) (param $0 (ref null (exact $super))) (result nullref) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-bottom-null-exact-to-null-inexact (param (ref null (exact $super))) (result (ref null none)) + (local anyref) + (ref.cast (ref null none) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-bottom-null-exact-to-non-null-inexact (type $84) (param $0 (ref null (exact $super))) (result (ref none)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-bottom-null-exact-to-non-null-inexact (param (ref null (exact $super))) (result (ref none)) + (local anyref) + (ref.cast (ref none) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-bottom-null-inexact-to-null-inexact (type $85) (param $0 (ref null $super)) (result nullref) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-bottom-null-inexact-to-null-inexact (param (ref null $super)) (result (ref null none)) + (local anyref) + (ref.cast (ref null none) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-bottom-null-inexact-to-non-null-inexact (type $86) (param $0 (ref null $super)) (result (ref none)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-bottom-null-inexact-to-non-null-inexact (param (ref null $super)) (result (ref none)) + (local anyref) + (ref.cast (ref none) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-bottom-non-null-exact-to-null-inexact (type $87) (param $0 (ref (exact $super))) (result nullref) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-bottom-non-null-exact-to-null-inexact (param (ref (exact $super))) (result (ref null none)) + (local anyref) + (ref.cast (ref null none) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-bottom-non-null-exact-to-non-null-inexact (type $88) (param $0 (ref (exact $super))) (result (ref none)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-bottom-non-null-exact-to-non-null-inexact (param (ref (exact $super))) (result (ref none)) + (local anyref) + (ref.cast (ref none) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-bottom-non-null-inexact-to-null-inexact (type $89) (param $0 (ref $super)) (result nullref) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-bottom-non-null-inexact-to-null-inexact (param (ref $super)) (result (ref null none)) + (local anyref) + (ref.cast (ref null none) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-bottom-non-null-inexact-to-non-null-inexact (type $90) (param $0 (ref $super)) (result (ref none)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-bottom-non-null-inexact-to-non-null-inexact (param (ref $super)) (result (ref none)) + (local anyref) + (ref.cast (ref none) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-null-inexact-to-null-exact (type $91) (param $0 nullref) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-null-inexact-to-null-exact (param (ref null none)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-null-inexact-to-null-inexact (type $92) (param $0 nullref) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-null-inexact-to-null-inexact (param (ref null none)) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-null-inexact-to-non-null-exact (type $93) (param $0 nullref) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-null-inexact-to-non-null-exact (param (ref null none)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-null-inexact-to-non-null-inexact (type $94) (param $0 nullref) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-null-inexact-to-non-null-inexact (param (ref null none)) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-null-exact (type $95) (param $0 (ref none)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-non-null-inexact-to-null-exact (param (ref none)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-null-inexact (type $96) (param $0 (ref none)) (result (ref null $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-non-null-inexact-to-null-inexact (param (ref none)) (result (ref null $super)) + (local anyref) + (ref.cast (ref null $super) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-non-null-exact (type $97) (param $0 (ref none)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-non-null-inexact-to-non-null-exact (param (ref none)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-non-null-inexact (type $98) (param $0 (ref none)) (result (ref $super)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-non-null-inexact-to-non-null-inexact (param (ref none)) (result (ref $super)) + (local anyref) + (ref.cast (ref $super) + (local.tee 1 + (local.get 0) + ) + ) + ) +) diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast deleted file mode 100644 index cb603da0e68..00000000000 --- a/test/lit/passes/optimize-instructions-exact.wast +++ /dev/null @@ -1,22 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; Check that optimizations on casts involving exact reference types work -;; correctly. - -;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s - -(module - ;; CHECK: (type $struct (struct)) - (type $struct (struct)) - ;; CHECK: (func $cast-to-exact (type $1) (param $0 anyref) (result (ref (exact $struct))) - ;; CHECK-NEXT: (ref.cast (ref (exact $struct)) - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-exact (param anyref) (result (ref (exact $struct))) - ;; This will not be changed, but should not trigger an assertion. - (ref.cast (ref (exact $struct)) - (local.get 0) - ) - ) -) From 3bcf77b5bde072a2cdcb14116a41e70170592814 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 23 Apr 2025 16:00:06 -0700 Subject: [PATCH 460/622] Split generated casts test for more coverage (#7543) Split the automatically generated optimize-instructions-all-casts.wast into two tests: one that tests casts to exact types and another that tests casts to inexact types. For the test file that does not cast to exact types, additionally test with custom descriptors disabled to ensure that the results validate. --- scripts/test/fuzzing.py | 2 +- scripts/test/gen-cast-test.py | 39 +- ...optimize-instructions-all-casts-exact.wast | 819 ++++++++ .../optimize-instructions-all-casts.wast | 1733 +++++++---------- 4 files changed, 1587 insertions(+), 1006 deletions(-) create mode 100644 test/lit/passes/optimize-instructions-all-casts-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index ab2f3539f78..e854309ebf0 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -114,7 +114,7 @@ 'exact-references-lowering.wast', 'exact-casts.wast', 'exact-casts-trivial.wast', - 'optimize-instructions-all-casts.wast', + 'optimize-instructions-all-casts-exact.wast', 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', 'coalesce-locals-exact.wast', diff --git a/scripts/test/gen-cast-test.py b/scripts/test/gen-cast-test.py index 39fd63da8c1..21e469b9858 100755 --- a/scripts/test/gen-cast-test.py +++ b/scripts/test/gen-cast-test.py @@ -1,10 +1,10 @@ #! /usr/bin/python3 ''' -This script is used to generate -test/lit/passes/optimize-instructions-all-casts.wast +Generate test modules with all interesting casts ''' +import argparse import itertools interesting_pairs = [('$super', '$super', 'cast-to-self-nonfinal'), @@ -16,7 +16,7 @@ ('none', '$super', 'cast-from-bottom')] -def gen_test_configs(): +def gen_test_configs(args): for src_heap, cast_heap, heap_name in interesting_pairs: for src_nullable, src_exact, cast_nullable, cast_exact in \ itertools.product([True, False], repeat=4): @@ -24,6 +24,8 @@ def gen_test_configs(): continue if cast_exact and cast_heap == 'none': continue + if args.enable_descs != cast_exact: + continue yield heap_name, src_heap, cast_heap, src_nullable, cast_nullable, \ src_exact, cast_exact @@ -62,23 +64,37 @@ def print_test(config): print(test) -def print_tests(): - for config in gen_test_configs(): +def print_tests(args): + for config in gen_test_configs(args): print_test(config) -def print_header(): - header = ''';; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; NOTE: Test has been generated by scripts/test/gen-cast-test.py. Do not edit manually. +def print_header(args): + flags = '' + if args.enable_descs: + flags = ' --enable-custom-descs' + header = f''';; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; NOTE: Test has been generated by scripts/test/gen-cast-test.py{flags}. Do not edit manually. ;; Exhaustively test optimization of all interesting casts. +''' + if args.enable_descs: + header += ''' ;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s +''' + else: + header += ''' +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --disable-custom-descriptors --optimize-instructions -S -o - | filecheck %s --check-prefix=NO_CD +''' + header += ''' (module (type $super (sub (struct))) (type $sub (sub $super (struct))) (type $sub-final (sub final $super (struct)))''' + print(header) @@ -87,8 +103,11 @@ def print_footer(): def main(): - print_header() - print_tests() + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--enable-custom-descs', action='store_true', dest='enable_descs') + args = parser.parse_args() + print_header(args) + print_tests(args) print_footer() diff --git a/test/lit/passes/optimize-instructions-all-casts-exact.wast b/test/lit/passes/optimize-instructions-all-casts-exact.wast new file mode 100644 index 00000000000..27794f855cd --- /dev/null +++ b/test/lit/passes/optimize-instructions-all-casts-exact.wast @@ -0,0 +1,819 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; NOTE: Test has been generated by scripts/test/gen-cast-test.py --enable-custom-descs. Do not edit manually. + +;; Exhaustively test optimization of all interesting casts. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub-final (sub final $super (struct))) + + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + (type $sub-final (sub final $super (struct))) + + ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-null-exact (type $3) (param $0 (ref null (exact $super))) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-exact-to-null-exact (param (ref null (exact $super))) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-non-null-exact (type $4) (param $0 (ref null (exact $super))) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-exact-to-non-null-exact (param (ref null (exact $super))) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-null-exact (type $5) (param $0 (ref null $super)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref null (exact $super)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-inexact-to-null-exact (param (ref null $super)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-non-null-exact (type $6) (param $0 (ref null $super)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $super)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-null-inexact-to-non-null-exact (param (ref null $super)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-null-exact (type $7) (param $0 (ref (exact $super))) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-exact-to-null-exact (param (ref (exact $super))) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-non-null-exact (type $8) (param $0 (ref (exact $super))) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-exact-to-non-null-exact (param (ref (exact $super))) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-null-exact (type $9) (param $0 (ref $super)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $super)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-inexact-to-null-exact (param (ref $super)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-exact (type $10) (param $0 (ref $super)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $super)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-exact (param (ref $super)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-exact-to-null-exact (type $11) (param $0 (ref null (exact $sub-final))) (result (ref null (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-exact-to-null-exact (param (ref null (exact $sub-final))) (result (ref null (exact $sub-final))) + (local anyref) + (ref.cast (ref null (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-exact-to-non-null-exact (type $12) (param $0 (ref null (exact $sub-final))) (result (ref (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-exact-to-non-null-exact (param (ref null (exact $sub-final))) (result (ref (exact $sub-final))) + (local anyref) + (ref.cast (ref (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-inexact-to-null-exact (type $13) (param $0 (ref null $sub-final)) (result (ref null (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref null (exact $sub-final)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-inexact-to-null-exact (param (ref null $sub-final)) (result (ref null (exact $sub-final))) + (local anyref) + (ref.cast (ref null (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-null-inexact-to-non-null-exact (type $14) (param $0 (ref null $sub-final)) (result (ref (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-null-inexact-to-non-null-exact (param (ref null $sub-final)) (result (ref (exact $sub-final))) + (local anyref) + (ref.cast (ref (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-exact-to-null-exact (type $15) (param $0 (ref (exact $sub-final))) (result (ref null (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-exact-to-null-exact (param (ref (exact $sub-final))) (result (ref null (exact $sub-final))) + (local anyref) + (ref.cast (ref null (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-exact-to-non-null-exact (type $16) (param $0 (ref (exact $sub-final))) (result (ref (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-exact-to-non-null-exact (param (ref (exact $sub-final))) (result (ref (exact $sub-final))) + (local anyref) + (ref.cast (ref (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-null-exact (type $17) (param $0 (ref $sub-final)) (result (ref null (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-inexact-to-null-exact (param (ref $sub-final)) (result (ref null (exact $sub-final))) + (local anyref) + (ref.cast (ref null (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-non-null-exact (type $18) (param $0 (ref $sub-final)) (result (ref (exact $sub-final))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-self-final-non-null-inexact-to-non-null-exact (param (ref $sub-final)) (result (ref (exact $sub-final))) + (local anyref) + (ref.cast (ref (exact $sub-final)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-exact-to-null-exact (type $19) (param $0 (ref null (exact $sub))) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-exact-to-null-exact (param (ref null (exact $sub))) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-exact-to-non-null-exact (type $20) (param $0 (ref null (exact $sub))) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-exact-to-non-null-exact (param (ref null (exact $sub))) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-inexact-to-null-exact (type $21) (param $0 (ref null $sub)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-inexact-to-null-exact (param (ref null $sub)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-null-inexact-to-non-null-exact (type $22) (param $0 (ref null $sub)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-null-inexact-to-non-null-exact (param (ref null $sub)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-exact-to-null-exact (type $23) (param $0 (ref (exact $sub))) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-exact-to-null-exact (param (ref (exact $sub))) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-exact-to-non-null-exact (type $24) (param $0 (ref (exact $sub))) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-exact-to-non-null-exact (param (ref (exact $sub))) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-inexact-to-null-exact (type $25) (param $0 (ref $sub)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-inexact-to-null-exact (param (ref $sub)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-super-non-null-inexact-to-non-null-exact (type $26) (param $0 (ref $sub)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-super-non-null-inexact-to-non-null-exact (param (ref $sub)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-exact-to-null-exact (type $27) (param $0 (ref null (exact $super))) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-exact-to-null-exact (param (ref null (exact $super))) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-exact-to-non-null-exact (type $28) (param $0 (ref null (exact $super))) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-exact-to-non-null-exact (param (ref null (exact $super))) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-inexact-to-null-exact (type $29) (param $0 (ref null $super)) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref null (exact $sub)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-inexact-to-null-exact (param (ref null $super)) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-null-inexact-to-non-null-exact (type $30) (param $0 (ref null $super)) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-null-inexact-to-non-null-exact (param (ref null $super)) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-exact-to-null-exact (type $31) (param $0 (ref (exact $super))) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-exact-to-null-exact (param (ref (exact $super))) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-exact-to-non-null-exact (type $32) (param $0 (ref (exact $super))) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-exact-to-non-null-exact (param (ref (exact $super))) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-inexact-to-null-exact (type $33) (param $0 (ref $super)) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-inexact-to-null-exact (param (ref $super)) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sub-non-null-inexact-to-non-null-exact (type $34) (param $0 (ref $super)) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sub-non-null-inexact-to-non-null-exact (param (ref $super)) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-exact-to-null-exact (type $35) (param $0 (ref null (exact $sub-final))) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-exact-to-null-exact (param (ref null (exact $sub-final))) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-exact-to-non-null-exact (type $36) (param $0 (ref null (exact $sub-final))) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-exact-to-non-null-exact (param (ref null (exact $sub-final))) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-inexact-to-null-exact (type $37) (param $0 (ref null $sub-final)) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-inexact-to-null-exact (param (ref null $sub-final)) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-null-inexact-to-non-null-exact (type $38) (param $0 (ref null $sub-final)) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-null-inexact-to-non-null-exact (param (ref null $sub-final)) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-exact-to-null-exact (type $39) (param $0 (ref (exact $sub-final))) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-exact-to-null-exact (param (ref (exact $sub-final))) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-exact-to-non-null-exact (type $40) (param $0 (ref (exact $sub-final))) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-exact-to-non-null-exact (param (ref (exact $sub-final))) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-null-exact (type $41) (param $0 (ref $sub-final)) (result (ref null (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-inexact-to-null-exact (param (ref $sub-final)) (result (ref null (exact $sub))) + (local anyref) + (ref.cast (ref null (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-non-null-exact (type $42) (param $0 (ref $sub-final)) (result (ref (exact $sub))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-to-sibling-non-null-inexact-to-non-null-exact (param (ref $sub-final)) (result (ref (exact $sub))) + (local anyref) + (ref.cast (ref (exact $sub)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-null-inexact-to-null-exact (type $43) (param $0 nullref) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-null-inexact-to-null-exact (param (ref null none)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-null-inexact-to-non-null-exact (type $44) (param $0 nullref) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-null-inexact-to-non-null-exact (param (ref null none)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-null-exact (type $45) (param $0 (ref none)) (result (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-non-null-inexact-to-null-exact (param (ref none)) (result (ref null (exact $super))) + (local anyref) + (ref.cast (ref null (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) + + ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-non-null-exact (type $46) (param $0 (ref none)) (result (ref (exact $super))) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-from-bottom-non-null-inexact-to-non-null-exact (param (ref none)) (result (ref (exact $super))) + (local anyref) + (ref.cast (ref (exact $super)) + (local.tee 1 + (local.get 0) + ) + ) + ) +) diff --git a/test/lit/passes/optimize-instructions-all-casts.wast b/test/lit/passes/optimize-instructions-all-casts.wast index cba7b7d7649..11d710e58af 100644 --- a/test/lit/passes/optimize-instructions-all-casts.wast +++ b/test/lit/passes/optimize-instructions-all-casts.wast @@ -3,39 +3,21 @@ ;; Exhaustively test optimization of all interesting casts. -;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --disable-custom-descriptors --optimize-instructions -S -o - | filecheck %s --check-prefix=NO_CD (module ;; CHECK: (type $super (sub (struct))) + ;; NO_CD: (type $super (sub (struct))) (type $super (sub (struct))) - ;; CHECK: (type $sub-final (sub final $super (struct))) - ;; CHECK: (type $sub (sub $super (struct))) + ;; NO_CD: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) + ;; CHECK: (type $sub-final (sub final $super (struct))) + ;; NO_CD: (type $sub-final (sub final $super (struct))) (type $sub-final (sub final $super (struct))) - ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-null-exact (type $3) (param $0 (ref null (exact $super))) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref null (exact $super))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-nonfinal-null-exact-to-null-exact (param (ref null (exact $super))) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-null-inexact (type $4) (param $0 (ref null (exact $super))) (result (ref null $super)) + ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-null-inexact (type $3) (param $0 (ref null (exact $super))) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref null (exact $super))) ;; CHECK-NEXT: (drop @@ -47,6 +29,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-nonfinal-null-exact-to-null-inexact (type $3) (param $0 (ref null (exact $super))) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null (exact $super))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) (func $cast-to-self-nonfinal-null-exact-to-null-inexact (param (ref null (exact $super))) (result (ref null $super)) (local anyref) (ref.cast (ref null $super) @@ -56,30 +50,7 @@ ) ) - ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-non-null-exact (type $5) (param $0 (ref null (exact $super))) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref null (exact $super))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-nonfinal-null-exact-to-non-null-exact (param (ref null (exact $super))) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-non-null-inexact (type $6) (param $0 (ref null (exact $super))) (result (ref $super)) + ;; CHECK: (func $cast-to-self-nonfinal-null-exact-to-non-null-inexact (type $4) (param $0 (ref null (exact $super))) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref null (exact $super))) ;; CHECK-NEXT: (drop @@ -93,6 +64,20 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-nonfinal-null-exact-to-non-null-inexact (type $4) (param $0 (ref null (exact $super))) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null (exact $super))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (ref.as_non_null + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) (func $cast-to-self-nonfinal-null-exact-to-non-null-inexact (param (ref null (exact $super))) (result (ref $super)) (local anyref) (ref.cast (ref $super) @@ -102,24 +87,7 @@ ) ) - ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-null-exact (type $7) (param $0 (ref null $super)) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref null (exact $super)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-nonfinal-null-inexact-to-null-exact (param (ref null $super)) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-null-inexact (type $8) (param $0 (ref null $super)) (result (ref null $super)) + ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-null-inexact (type $5) (param $0 (ref null $super)) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref null $super)) ;; CHECK-NEXT: (drop @@ -131,6 +99,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-nonfinal-null-inexact-to-null-inexact (type $5) (param $0 (ref null $super)) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null $super)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) (func $cast-to-self-nonfinal-null-inexact-to-null-inexact (param (ref null $super)) (result (ref null $super)) (local anyref) (ref.cast (ref null $super) @@ -140,24 +120,7 @@ ) ) - ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-non-null-exact (type $9) (param $0 (ref null $super)) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $super)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-nonfinal-null-inexact-to-non-null-exact (param (ref null $super)) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-non-null-inexact (type $10) (param $0 (ref null $super)) (result (ref $super)) + ;; CHECK: (func $cast-to-self-nonfinal-null-inexact-to-non-null-inexact (type $6) (param $0 (ref null $super)) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref null $super)) ;; CHECK-NEXT: (drop @@ -171,6 +134,20 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-nonfinal-null-inexact-to-non-null-inexact (type $6) (param $0 (ref null $super)) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null $super)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (ref.as_non_null + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) (func $cast-to-self-nonfinal-null-inexact-to-non-null-inexact (param (ref null $super)) (result (ref $super)) (local anyref) (ref.cast (ref $super) @@ -180,28 +157,7 @@ ) ) - ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-null-exact (type $11) (param $0 (ref (exact $super))) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref (exact $super))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-nonfinal-non-null-exact-to-null-exact (param (ref (exact $super))) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-null-inexact (type $12) (param $0 (ref (exact $super))) (result (ref null $super)) + ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-null-inexact (type $7) (param $0 (ref (exact $super))) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref (exact $super))) ;; CHECK-NEXT: (drop @@ -213,6 +169,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-nonfinal-non-null-exact-to-null-inexact (type $7) (param $0 (ref (exact $super))) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref (exact $super))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) (func $cast-to-self-nonfinal-non-null-exact-to-null-inexact (param (ref (exact $super))) (result (ref null $super)) (local anyref) (ref.cast (ref null $super) @@ -222,28 +190,7 @@ ) ) - ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-non-null-exact (type $13) (param $0 (ref (exact $super))) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref (exact $super))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-nonfinal-non-null-exact-to-non-null-exact (param (ref (exact $super))) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-non-null-inexact (type $14) (param $0 (ref (exact $super))) (result (ref $super)) + ;; CHECK: (func $cast-to-self-nonfinal-non-null-exact-to-non-null-inexact (type $8) (param $0 (ref (exact $super))) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref (exact $super))) ;; CHECK-NEXT: (drop @@ -255,6 +202,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-nonfinal-non-null-exact-to-non-null-inexact (type $8) (param $0 (ref (exact $super))) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref (exact $super))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) (func $cast-to-self-nonfinal-non-null-exact-to-non-null-inexact (param (ref (exact $super))) (result (ref $super)) (local anyref) (ref.cast (ref $super) @@ -264,24 +223,7 @@ ) ) - ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-null-exact (type $15) (param $0 (ref $super)) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $super)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-nonfinal-non-null-inexact-to-null-exact (param (ref $super)) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-null-inexact (type $16) (param $0 (ref $super)) (result (ref null $super)) + ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-null-inexact (type $9) (param $0 (ref $super)) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref $super)) ;; CHECK-NEXT: (drop @@ -293,6 +235,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-nonfinal-non-null-inexact-to-null-inexact (type $9) (param $0 (ref $super)) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref $super)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) (func $cast-to-self-nonfinal-non-null-inexact-to-null-inexact (param (ref $super)) (result (ref null $super)) (local anyref) (ref.cast (ref null $super) @@ -302,24 +256,7 @@ ) ) - ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-exact (type $17) (param $0 (ref $super)) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $super)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-exact (param (ref $super)) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-inexact (type $18) (param $0 (ref $super)) (result (ref $super)) + ;; CHECK: (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-inexact (type $10) (param $0 (ref $super)) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref $super)) ;; CHECK-NEXT: (drop @@ -331,6 +268,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-inexact (type $10) (param $0 (ref $super)) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref $super)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) (func $cast-to-self-nonfinal-non-null-inexact-to-non-null-inexact (param (ref $super)) (result (ref $super)) (local anyref) (ref.cast (ref $super) @@ -340,28 +289,7 @@ ) ) - ;; CHECK: (func $cast-to-self-final-null-exact-to-null-exact (type $19) (param $0 (ref null (exact $sub-final))) (result (ref null (exact $sub-final))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-null-exact-to-null-exact (param (ref null (exact $sub-final))) (result (ref null (exact $sub-final))) - (local anyref) - (ref.cast (ref null (exact $sub-final)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-null-exact-to-null-inexact (type $20) (param $0 (ref null (exact $sub-final))) (result (ref null $sub-final)) + ;; CHECK: (func $cast-to-self-final-null-exact-to-null-inexact (type $11) (param $0 (ref null (exact $sub-final))) (result (ref null $sub-final)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) ;; CHECK-NEXT: (drop @@ -373,6 +301,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-final-null-exact-to-null-inexact (type $11) (param $0 (ref null (exact $sub-final))) (result (ref null $sub-final)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null (exact $sub-final))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) (func $cast-to-self-final-null-exact-to-null-inexact (param (ref null (exact $sub-final))) (result (ref null $sub-final)) (local anyref) (ref.cast (ref null $sub-final) @@ -382,30 +322,7 @@ ) ) - ;; CHECK: (func $cast-to-self-final-null-exact-to-non-null-exact (type $21) (param $0 (ref null (exact $sub-final))) (result (ref (exact $sub-final))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-null-exact-to-non-null-exact (param (ref null (exact $sub-final))) (result (ref (exact $sub-final))) - (local anyref) - (ref.cast (ref (exact $sub-final)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-null-exact-to-non-null-inexact (type $22) (param $0 (ref null (exact $sub-final))) (result (ref $sub-final)) + ;; CHECK: (func $cast-to-self-final-null-exact-to-non-null-inexact (type $12) (param $0 (ref null (exact $sub-final))) (result (ref $sub-final)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref null (exact $sub-final))) ;; CHECK-NEXT: (drop @@ -419,6 +336,20 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-final-null-exact-to-non-null-inexact (type $12) (param $0 (ref null (exact $sub-final))) (result (ref $sub-final)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null (exact $sub-final))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (ref.as_non_null + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) (func $cast-to-self-final-null-exact-to-non-null-inexact (param (ref null (exact $sub-final))) (result (ref $sub-final)) (local anyref) (ref.cast (ref $sub-final) @@ -428,24 +359,7 @@ ) ) - ;; CHECK: (func $cast-to-self-final-null-inexact-to-null-exact (type $23) (param $0 (ref null $sub-final)) (result (ref null (exact $sub-final))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref null (exact $sub-final)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-null-inexact-to-null-exact (param (ref null $sub-final)) (result (ref null (exact $sub-final))) - (local anyref) - (ref.cast (ref null (exact $sub-final)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-null-inexact-to-null-inexact (type $24) (param $0 (ref null $sub-final)) (result (ref null $sub-final)) + ;; CHECK: (func $cast-to-self-final-null-inexact-to-null-inexact (type $13) (param $0 (ref null $sub-final)) (result (ref null $sub-final)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref null $sub-final)) ;; CHECK-NEXT: (drop @@ -457,6 +371,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-self-final-null-inexact-to-null-inexact (type $13) (param $0 (ref null $sub-final)) (result (ref null $sub-final)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null $sub-final)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) (func $cast-to-self-final-null-inexact-to-null-inexact (param (ref null $sub-final)) (result (ref null $sub-final)) (local anyref) (ref.cast (ref null $sub-final) @@ -466,24 +392,7 @@ ) ) - ;; CHECK: (func $cast-to-self-final-null-inexact-to-non-null-exact (type $25) (param $0 (ref null $sub-final)) (result (ref (exact $sub-final))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-null-inexact-to-non-null-exact (param (ref null $sub-final)) (result (ref (exact $sub-final))) - (local anyref) - (ref.cast (ref (exact $sub-final)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-null-inexact-to-non-null-inexact (type $26) (param $0 (ref null $sub-final)) (result (ref $sub-final)) + ;; CHECK: (func $cast-to-self-final-null-inexact-to-non-null-inexact (type $14) (param $0 (ref null $sub-final)) (result (ref $sub-final)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local $2 (ref null $sub-final)) ;; CHECK-NEXT: (drop @@ -497,768 +406,434 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast-to-self-final-null-inexact-to-non-null-inexact (param (ref null $sub-final)) (result (ref $sub-final)) - (local anyref) - (ref.cast (ref $sub-final) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-non-null-exact-to-null-exact (type $27) (param $0 (ref (exact $sub-final))) (result (ref null (exact $sub-final))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-non-null-exact-to-null-exact (param (ref (exact $sub-final))) (result (ref null (exact $sub-final))) - (local anyref) - (ref.cast (ref null (exact $sub-final)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-non-null-exact-to-null-inexact (type $28) (param $0 (ref (exact $sub-final))) (result (ref null $sub-final)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-non-null-exact-to-null-inexact (param (ref (exact $sub-final))) (result (ref null $sub-final)) - (local anyref) - (ref.cast (ref null $sub-final) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-non-null-exact-to-non-null-exact (type $29) (param $0 (ref (exact $sub-final))) (result (ref (exact $sub-final))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-non-null-exact-to-non-null-exact (param (ref (exact $sub-final))) (result (ref (exact $sub-final))) - (local anyref) - (ref.cast (ref (exact $sub-final)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-non-null-exact-to-non-null-inexact (type $30) (param $0 (ref (exact $sub-final))) (result (ref $sub-final)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-non-null-exact-to-non-null-inexact (param (ref (exact $sub-final))) (result (ref $sub-final)) - (local anyref) - (ref.cast (ref $sub-final) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-null-exact (type $31) (param $0 (ref $sub-final)) (result (ref null (exact $sub-final))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-non-null-inexact-to-null-exact (param (ref $sub-final)) (result (ref null (exact $sub-final))) - (local anyref) - (ref.cast (ref null (exact $sub-final)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-null-inexact (type $32) (param $0 (ref $sub-final)) (result (ref null $sub-final)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref $sub-final)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-non-null-inexact-to-null-inexact (param (ref $sub-final)) (result (ref null $sub-final)) - (local anyref) - (ref.cast (ref null $sub-final) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-non-null-exact (type $33) (param $0 (ref $sub-final)) (result (ref (exact $sub-final))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $sub-final)) - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-non-null-inexact-to-non-null-exact (param (ref $sub-final)) (result (ref (exact $sub-final))) - (local anyref) - (ref.cast (ref (exact $sub-final)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-non-null-inexact (type $34) (param $0 (ref $sub-final)) (result (ref $sub-final)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref $sub-final)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-self-final-non-null-inexact-to-non-null-inexact (param (ref $sub-final)) (result (ref $sub-final)) - (local anyref) - (ref.cast (ref $sub-final) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-null-exact-to-null-exact (type $35) (param $0 (ref null (exact $sub))) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-super-null-exact-to-null-exact (param (ref null (exact $sub))) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-null-exact-to-null-inexact (type $36) (param $0 (ref null (exact $sub))) (result (ref null $super)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref null (exact $sub))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-super-null-exact-to-null-inexact (param (ref null (exact $sub))) (result (ref null $super)) - (local anyref) - (ref.cast (ref null $super) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-null-exact-to-non-null-exact (type $37) (param $0 (ref null (exact $sub))) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-to-super-null-exact-to-non-null-exact (param (ref null (exact $sub))) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-null-exact-to-non-null-inexact (type $38) (param $0 (ref null (exact $sub))) (result (ref $super)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref null (exact $sub))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-super-null-exact-to-non-null-inexact (param (ref null (exact $sub))) (result (ref $super)) - (local anyref) - (ref.cast (ref $super) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-null-inexact-to-null-exact (type $39) (param $0 (ref null $sub)) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-super-null-inexact-to-null-exact (param (ref null $sub)) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-null-inexact-to-null-inexact (type $40) (param $0 (ref null $sub)) (result (ref null $super)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref null $sub)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-super-null-inexact-to-null-inexact (param (ref null $sub)) (result (ref null $super)) - (local anyref) - (ref.cast (ref null $super) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-null-inexact-to-non-null-exact (type $41) (param $0 (ref null $sub)) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-to-super-null-inexact-to-non-null-exact (param (ref null $sub)) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-null-inexact-to-non-null-inexact (type $42) (param $0 (ref null $sub)) (result (ref $super)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref null $sub)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-super-null-inexact-to-non-null-inexact (param (ref null $sub)) (result (ref $super)) - (local anyref) - (ref.cast (ref $super) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-non-null-exact-to-null-exact (type $43) (param $0 (ref (exact $sub))) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-to-super-non-null-exact-to-null-exact (param (ref (exact $sub))) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-non-null-exact-to-null-inexact (type $44) (param $0 (ref (exact $sub))) (result (ref null $super)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref (exact $sub))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-super-non-null-exact-to-null-inexact (param (ref (exact $sub))) (result (ref null $super)) - (local anyref) - (ref.cast (ref null $super) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-non-null-exact-to-non-null-exact (type $45) (param $0 (ref (exact $sub))) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-to-super-non-null-exact-to-non-null-exact (param (ref (exact $sub))) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-non-null-exact-to-non-null-inexact (type $46) (param $0 (ref (exact $sub))) (result (ref $super)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref (exact $sub))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-super-non-null-exact-to-non-null-inexact (param (ref (exact $sub))) (result (ref $super)) - (local anyref) - (ref.cast (ref $super) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-non-null-inexact-to-null-exact (type $47) (param $0 (ref $sub)) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-to-super-non-null-inexact-to-null-exact (param (ref $sub)) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-non-null-inexact-to-null-inexact (type $48) (param $0 (ref $sub)) (result (ref null $super)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref $sub)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-super-non-null-inexact-to-null-inexact (param (ref $sub)) (result (ref null $super)) - (local anyref) - (ref.cast (ref null $super) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-non-null-inexact-to-non-null-exact (type $49) (param $0 (ref $sub)) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-to-super-non-null-inexact-to-non-null-exact (param (ref $sub)) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-super-non-null-inexact-to-non-null-inexact (type $50) (param $0 (ref $sub)) (result (ref $super)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 (ref $sub)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.tee $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - (func $cast-to-super-non-null-inexact-to-non-null-inexact (param (ref $sub)) (result (ref $super)) - (local anyref) - (ref.cast (ref $super) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-sub-null-exact-to-null-exact (type $51) (param $0 (ref null (exact $super))) (result (ref null (exact $sub))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-sub-null-exact-to-null-exact (param (ref null (exact $super))) (result (ref null (exact $sub))) - (local anyref) - (ref.cast (ref null (exact $sub)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-sub-null-exact-to-null-inexact (type $52) (param $0 (ref null (exact $super))) (result (ref null $sub)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast-to-sub-null-exact-to-null-inexact (param (ref null (exact $super))) (result (ref null $sub)) - (local anyref) - (ref.cast (ref null $sub) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-sub-null-exact-to-non-null-exact (type $53) (param $0 (ref null (exact $super))) (result (ref (exact $sub))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-to-sub-null-exact-to-non-null-exact (param (ref null (exact $super))) (result (ref (exact $sub))) - (local anyref) - (ref.cast (ref (exact $sub)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-to-sub-null-exact-to-non-null-inexact (type $54) (param $0 (ref null (exact $super))) (result (ref $sub)) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-to-sub-null-exact-to-non-null-inexact (param (ref null (exact $super))) (result (ref $sub)) + ;; NO_CD: (func $cast-to-self-final-null-inexact-to-non-null-inexact (type $14) (param $0 (ref null $sub-final)) (result (ref $sub-final)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null $sub-final)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (ref.as_non_null + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-self-final-null-inexact-to-non-null-inexact (param (ref null $sub-final)) (result (ref $sub-final)) (local anyref) - (ref.cast (ref $sub) + (ref.cast (ref $sub-final) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-null-inexact-to-null-exact (type $55) (param $0 (ref null $super)) (result (ref null (exact $sub))) + ;; CHECK: (func $cast-to-self-final-non-null-exact-to-null-inexact (type $15) (param $0 (ref (exact $sub-final))) (result (ref null $sub-final)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref null (exact $sub)) + ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-null-inexact-to-null-exact (param (ref null $super)) (result (ref null (exact $sub))) + ;; NO_CD: (func $cast-to-self-final-non-null-exact-to-null-inexact (type $15) (param $0 (ref (exact $sub-final))) (result (ref null $sub-final)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref (exact $sub-final))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-self-final-non-null-exact-to-null-inexact (param (ref (exact $sub-final))) (result (ref null $sub-final)) (local anyref) - (ref.cast (ref null (exact $sub)) + (ref.cast (ref null $sub-final) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-null-inexact-to-null-inexact (type $56) (param $0 (ref null $super)) (result (ref null $sub)) + ;; CHECK: (func $cast-to-self-final-non-null-exact-to-non-null-inexact (type $16) (param $0 (ref (exact $sub-final))) (result (ref $sub-final)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref null $sub) + ;; CHECK-NEXT: (local $2 (ref (exact $sub-final))) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-null-inexact-to-null-inexact (param (ref null $super)) (result (ref null $sub)) + ;; NO_CD: (func $cast-to-self-final-non-null-exact-to-non-null-inexact (type $16) (param $0 (ref (exact $sub-final))) (result (ref $sub-final)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref (exact $sub-final))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-self-final-non-null-exact-to-non-null-inexact (param (ref (exact $sub-final))) (result (ref $sub-final)) (local anyref) - (ref.cast (ref null $sub) + (ref.cast (ref $sub-final) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-null-inexact-to-non-null-exact (type $57) (param $0 (ref null $super)) (result (ref (exact $sub))) + ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-null-inexact (type $17) (param $0 (ref $sub-final)) (result (ref null $sub-final)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local $2 (ref $sub-final)) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-null-inexact-to-non-null-exact (param (ref null $super)) (result (ref (exact $sub))) + ;; NO_CD: (func $cast-to-self-final-non-null-inexact-to-null-inexact (type $17) (param $0 (ref $sub-final)) (result (ref null $sub-final)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref $sub-final)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-self-final-non-null-inexact-to-null-inexact (param (ref $sub-final)) (result (ref null $sub-final)) (local anyref) - (ref.cast (ref (exact $sub)) + (ref.cast (ref null $sub-final) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-null-inexact-to-non-null-inexact (type $58) (param $0 (ref null $super)) (result (ref $sub)) + ;; CHECK: (func $cast-to-self-final-non-null-inexact-to-non-null-inexact (type $18) (param $0 (ref $sub-final)) (result (ref $sub-final)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (local $2 (ref $sub-final)) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-null-inexact-to-non-null-inexact (param (ref null $super)) (result (ref $sub)) + ;; NO_CD: (func $cast-to-self-final-non-null-inexact-to-non-null-inexact (type $18) (param $0 (ref $sub-final)) (result (ref $sub-final)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref $sub-final)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-self-final-non-null-inexact-to-non-null-inexact (param (ref $sub-final)) (result (ref $sub-final)) (local anyref) - (ref.cast (ref $sub) + (ref.cast (ref $sub-final) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-non-null-exact-to-null-exact (type $59) (param $0 (ref (exact $super))) (result (ref null (exact $sub))) + ;; CHECK: (func $cast-to-super-null-exact-to-null-inexact (type $19) (param $0 (ref null (exact $sub))) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-non-null-exact-to-null-exact (param (ref (exact $super))) (result (ref null (exact $sub))) + ;; NO_CD: (func $cast-to-super-null-exact-to-null-inexact (type $19) (param $0 (ref null (exact $sub))) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null (exact $sub))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-super-null-exact-to-null-inexact (param (ref null (exact $sub))) (result (ref null $super)) (local anyref) - (ref.cast (ref null (exact $sub)) + (ref.cast (ref null $super) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-non-null-exact-to-null-inexact (type $60) (param $0 (ref (exact $super))) (result (ref null $sub)) + ;; CHECK: (func $cast-to-super-null-exact-to-non-null-inexact (type $20) (param $0 (ref null (exact $sub))) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null (exact $sub))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast-to-sub-non-null-exact-to-null-inexact (param (ref (exact $super))) (result (ref null $sub)) + ;; NO_CD: (func $cast-to-super-null-exact-to-non-null-inexact (type $20) (param $0 (ref null (exact $sub))) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null (exact $sub))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (ref.as_non_null + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-super-null-exact-to-non-null-inexact (param (ref null (exact $sub))) (result (ref $super)) (local anyref) - (ref.cast (ref null $sub) + (ref.cast (ref $super) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-non-null-exact-to-non-null-exact (type $61) (param $0 (ref (exact $super))) (result (ref (exact $sub))) + ;; CHECK: (func $cast-to-super-null-inexact-to-null-inexact (type $21) (param $0 (ref null $sub)) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null $sub)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-non-null-exact-to-non-null-exact (param (ref (exact $super))) (result (ref (exact $sub))) + ;; NO_CD: (func $cast-to-super-null-inexact-to-null-inexact (type $21) (param $0 (ref null $sub)) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null $sub)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-super-null-inexact-to-null-inexact (param (ref null $sub)) (result (ref null $super)) (local anyref) - (ref.cast (ref (exact $sub)) + (ref.cast (ref null $super) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-non-null-exact-to-non-null-inexact (type $62) (param $0 (ref (exact $super))) (result (ref $sub)) + ;; CHECK: (func $cast-to-super-null-inexact-to-non-null-inexact (type $22) (param $0 (ref null $sub)) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $2 (ref null $sub)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast-to-sub-non-null-exact-to-non-null-inexact (param (ref (exact $super))) (result (ref $sub)) + ;; NO_CD: (func $cast-to-super-null-inexact-to-non-null-inexact (type $22) (param $0 (ref null $sub)) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref null $sub)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (ref.as_non_null + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-super-null-inexact-to-non-null-inexact (param (ref null $sub)) (result (ref $super)) (local anyref) - (ref.cast (ref $sub) + (ref.cast (ref $super) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-non-null-inexact-to-null-exact (type $63) (param $0 (ref $super)) (result (ref null (exact $sub))) + ;; CHECK: (func $cast-to-super-non-null-exact-to-null-inexact (type $23) (param $0 (ref (exact $sub))) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local $2 (ref (exact $sub))) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-non-null-inexact-to-null-exact (param (ref $super)) (result (ref null (exact $sub))) + ;; NO_CD: (func $cast-to-super-non-null-exact-to-null-inexact (type $23) (param $0 (ref (exact $sub))) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref (exact $sub))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-super-non-null-exact-to-null-inexact (param (ref (exact $sub))) (result (ref null $super)) (local anyref) - (ref.cast (ref null (exact $sub)) + (ref.cast (ref null $super) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-non-null-inexact-to-null-inexact (type $64) (param $0 (ref $super)) (result (ref null $sub)) + ;; CHECK: (func $cast-to-super-non-null-exact-to-non-null-inexact (type $24) (param $0 (ref (exact $sub))) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (local $2 (ref (exact $sub))) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-non-null-inexact-to-null-inexact (param (ref $super)) (result (ref null $sub)) + ;; NO_CD: (func $cast-to-super-non-null-exact-to-non-null-inexact (type $24) (param $0 (ref (exact $sub))) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref (exact $sub))) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-super-non-null-exact-to-non-null-inexact (param (ref (exact $sub))) (result (ref $super)) (local anyref) - (ref.cast (ref null $sub) + (ref.cast (ref $super) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-non-null-inexact-to-non-null-exact (type $65) (param $0 (ref $super)) (result (ref (exact $sub))) + ;; CHECK: (func $cast-to-super-non-null-inexact-to-null-inexact (type $25) (param $0 (ref $sub)) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref (exact $sub)) + ;; CHECK-NEXT: (local $2 (ref $sub)) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-non-null-inexact-to-non-null-exact (param (ref $super)) (result (ref (exact $sub))) + ;; NO_CD: (func $cast-to-super-non-null-inexact-to-null-inexact (type $25) (param $0 (ref $sub)) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref $sub)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-super-non-null-inexact-to-null-inexact (param (ref $sub)) (result (ref null $super)) (local anyref) - (ref.cast (ref (exact $sub)) + (ref.cast (ref null $super) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sub-non-null-inexact-to-non-null-inexact (type $66) (param $0 (ref $super)) (result (ref $sub)) + ;; CHECK: (func $cast-to-super-non-null-inexact-to-non-null-inexact (type $26) (param $0 (ref $sub)) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (local $2 (ref $sub)) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - (func $cast-to-sub-non-null-inexact-to-non-null-inexact (param (ref $super)) (result (ref $sub)) + ;; NO_CD: (func $cast-to-super-non-null-inexact-to-non-null-inexact (type $26) (param $0 (ref $sub)) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (local $2 (ref $sub)) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.tee $2 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (local.get $2) + ;; NO_CD-NEXT: ) + (func $cast-to-super-non-null-inexact-to-non-null-inexact (param (ref $sub)) (result (ref $super)) (local anyref) - (ref.cast (ref $sub) + (ref.cast (ref $super) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-null-exact-to-null-exact (type $67) (param $0 (ref null (exact $sub-final))) (result (ref null (exact $sub))) + ;; CHECK: (func $cast-to-sub-null-exact-to-null-inexact (type $27) (param $0 (ref null (exact $super))) (result (ref null $sub)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (local.tee $1 @@ -1266,60 +841,92 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast-to-sibling-null-exact-to-null-exact (param (ref null (exact $sub-final))) (result (ref null (exact $sub))) + ;; NO_CD: (func $cast-to-sub-null-exact-to-null-inexact (type $27) (param $0 (ref null (exact $super))) (result (ref null $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast nullref + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-sub-null-exact-to-null-inexact (param (ref null (exact $super))) (result (ref null $sub)) (local anyref) - (ref.cast (ref null (exact $sub)) + (ref.cast (ref null $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-null-exact-to-null-inexact (type $68) (param $0 (ref null (exact $sub-final))) (result (ref null $sub)) + ;; CHECK: (func $cast-to-sub-null-exact-to-non-null-inexact (type $28) (param $0 (ref null (exact $super))) (result (ref $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-null-exact-to-null-inexact (param (ref null (exact $sub-final))) (result (ref null $sub)) + ;; NO_CD: (func $cast-to-sub-null-exact-to-non-null-inexact (type $28) (param $0 (ref null (exact $super))) (result (ref $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $cast-to-sub-null-exact-to-non-null-inexact (param (ref null (exact $super))) (result (ref $sub)) (local anyref) - (ref.cast (ref null $sub) + (ref.cast (ref $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-null-exact-to-non-null-exact (type $69) (param $0 (ref null (exact $sub-final))) (result (ref (exact $sub))) + ;; CHECK: (func $cast-to-sub-null-inexact-to-null-inexact (type $29) (param $0 (ref null $super)) (result (ref null $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref null $sub) ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-null-exact-to-non-null-exact (param (ref null (exact $sub-final))) (result (ref (exact $sub))) + ;; NO_CD: (func $cast-to-sub-null-inexact-to-null-inexact (type $29) (param $0 (ref null $super)) (result (ref null $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast (ref null $sub) + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-sub-null-inexact-to-null-inexact (param (ref null $super)) (result (ref null $sub)) (local anyref) - (ref.cast (ref (exact $sub)) + (ref.cast (ref null $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-null-exact-to-non-null-inexact (type $70) (param $0 (ref null (exact $sub-final))) (result (ref $sub)) + ;; CHECK: (func $cast-to-sub-null-inexact-to-non-null-inexact (type $30) (param $0 (ref null $super)) (result (ref $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $sub) ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-null-exact-to-non-null-inexact (param (ref null (exact $sub-final))) (result (ref $sub)) + ;; NO_CD: (func $cast-to-sub-null-inexact-to-non-null-inexact (type $30) (param $0 (ref null $super)) (result (ref $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast (ref $sub) + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-sub-null-inexact-to-non-null-inexact (param (ref null $super)) (result (ref $sub)) (local anyref) (ref.cast (ref $sub) (local.tee 1 @@ -1328,68 +935,102 @@ ) ) - ;; CHECK: (func $cast-to-sibling-null-inexact-to-null-exact (type $71) (param $0 (ref null $sub-final)) (result (ref null (exact $sub))) + ;; CHECK: (func $cast-to-sub-non-null-exact-to-null-inexact (type $31) (param $0 (ref (exact $super))) (result (ref null $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-null-inexact-to-null-exact (param (ref null $sub-final)) (result (ref null (exact $sub))) + ;; NO_CD: (func $cast-to-sub-non-null-exact-to-null-inexact (type $31) (param $0 (ref (exact $super))) (result (ref null $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $cast-to-sub-non-null-exact-to-null-inexact (param (ref (exact $super))) (result (ref null $sub)) (local anyref) - (ref.cast (ref null (exact $sub)) + (ref.cast (ref null $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-null-inexact-to-null-inexact (type $72) (param $0 (ref null $sub-final)) (result (ref null $sub)) + ;; CHECK: (func $cast-to-sub-non-null-exact-to-non-null-inexact (type $32) (param $0 (ref (exact $super))) (result (ref $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-null-inexact-to-null-inexact (param (ref null $sub-final)) (result (ref null $sub)) + ;; NO_CD: (func $cast-to-sub-non-null-exact-to-non-null-inexact (type $32) (param $0 (ref (exact $super))) (result (ref $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $cast-to-sub-non-null-exact-to-non-null-inexact (param (ref (exact $super))) (result (ref $sub)) (local anyref) - (ref.cast (ref null $sub) + (ref.cast (ref $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-null-inexact-to-non-null-exact (type $73) (param $0 (ref null $sub-final)) (result (ref (exact $sub))) + ;; CHECK: (func $cast-to-sub-non-null-inexact-to-null-inexact (type $33) (param $0 (ref $super)) (result (ref null $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $sub) ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-null-inexact-to-non-null-exact (param (ref null $sub-final)) (result (ref (exact $sub))) + ;; NO_CD: (func $cast-to-sub-non-null-inexact-to-null-inexact (type $33) (param $0 (ref $super)) (result (ref null $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast (ref $sub) + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-sub-non-null-inexact-to-null-inexact (param (ref $super)) (result (ref null $sub)) (local anyref) - (ref.cast (ref (exact $sub)) + (ref.cast (ref null $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-null-inexact-to-non-null-inexact (type $74) (param $0 (ref null $sub-final)) (result (ref $sub)) + ;; CHECK: (func $cast-to-sub-non-null-inexact-to-non-null-inexact (type $34) (param $0 (ref $super)) (result (ref $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $sub) ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-null-inexact-to-non-null-inexact (param (ref null $sub-final)) (result (ref $sub)) + ;; NO_CD: (func $cast-to-sub-non-null-inexact-to-non-null-inexact (type $34) (param $0 (ref $super)) (result (ref $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast (ref $sub) + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-sub-non-null-inexact-to-non-null-inexact (param (ref $super)) (result (ref $sub)) (local anyref) (ref.cast (ref $sub) (local.tee 1 @@ -1398,25 +1039,32 @@ ) ) - ;; CHECK: (func $cast-to-sibling-non-null-exact-to-null-exact (type $75) (param $0 (ref (exact $sub-final))) (result (ref null (exact $sub))) + ;; CHECK: (func $cast-to-sibling-null-exact-to-null-inexact (type $35) (param $0 (ref null (exact $sub-final))) (result (ref null $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-non-null-exact-to-null-exact (param (ref (exact $sub-final))) (result (ref null (exact $sub))) + ;; NO_CD: (func $cast-to-sibling-null-exact-to-null-inexact (type $35) (param $0 (ref null (exact $sub-final))) (result (ref null $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast nullref + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-sibling-null-exact-to-null-inexact (param (ref null (exact $sub-final))) (result (ref null $sub)) (local anyref) - (ref.cast (ref null (exact $sub)) + (ref.cast (ref null $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-non-null-exact-to-null-inexact (type $76) (param $0 (ref (exact $sub-final))) (result (ref null $sub)) + ;; CHECK: (func $cast-to-sibling-null-exact-to-non-null-inexact (type $36) (param $0 (ref null (exact $sub-final))) (result (ref $sub)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1425,34 +1073,50 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-non-null-exact-to-null-inexact (param (ref (exact $sub-final))) (result (ref null $sub)) + ;; NO_CD: (func $cast-to-sibling-null-exact-to-non-null-inexact (type $36) (param $0 (ref null (exact $sub-final))) (result (ref $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $cast-to-sibling-null-exact-to-non-null-inexact (param (ref null (exact $sub-final))) (result (ref $sub)) (local anyref) - (ref.cast (ref null $sub) + (ref.cast (ref $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-non-null-exact-to-non-null-exact (type $77) (param $0 (ref (exact $sub-final))) (result (ref (exact $sub))) + ;; CHECK: (func $cast-to-sibling-null-inexact-to-null-inexact (type $37) (param $0 (ref null $sub-final)) (result (ref null $sub)) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-non-null-exact-to-non-null-exact (param (ref (exact $sub-final))) (result (ref (exact $sub))) + ;; NO_CD: (func $cast-to-sibling-null-inexact-to-null-inexact (type $37) (param $0 (ref null $sub-final)) (result (ref null $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast nullref + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-to-sibling-null-inexact-to-null-inexact (param (ref null $sub-final)) (result (ref null $sub)) (local anyref) - (ref.cast (ref (exact $sub)) + (ref.cast (ref null $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-non-null-exact-to-non-null-inexact (type $78) (param $0 (ref (exact $sub-final))) (result (ref $sub)) + ;; CHECK: (func $cast-to-sibling-null-inexact-to-non-null-inexact (type $38) (param $0 (ref null $sub-final)) (result (ref $sub)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1461,7 +1125,16 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-non-null-exact-to-non-null-inexact (param (ref (exact $sub-final))) (result (ref $sub)) + ;; NO_CD: (func $cast-to-sibling-null-inexact-to-non-null-inexact (type $38) (param $0 (ref null $sub-final)) (result (ref $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $cast-to-sibling-null-inexact-to-non-null-inexact (param (ref null $sub-final)) (result (ref $sub)) (local anyref) (ref.cast (ref $sub) (local.tee 1 @@ -1470,7 +1143,7 @@ ) ) - ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-null-exact (type $79) (param $0 (ref $sub-final)) (result (ref null (exact $sub))) + ;; CHECK: (func $cast-to-sibling-non-null-exact-to-null-inexact (type $39) (param $0 (ref (exact $sub-final))) (result (ref null $sub)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1479,16 +1152,25 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-non-null-inexact-to-null-exact (param (ref $sub-final)) (result (ref null (exact $sub))) + ;; NO_CD: (func $cast-to-sibling-non-null-exact-to-null-inexact (type $39) (param $0 (ref (exact $sub-final))) (result (ref null $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $cast-to-sibling-non-null-exact-to-null-inexact (param (ref (exact $sub-final))) (result (ref null $sub)) (local anyref) - (ref.cast (ref null (exact $sub)) + (ref.cast (ref null $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-null-inexact (type $80) (param $0 (ref $sub-final)) (result (ref null $sub)) + ;; CHECK: (func $cast-to-sibling-non-null-exact-to-non-null-inexact (type $40) (param $0 (ref (exact $sub-final))) (result (ref $sub)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1497,16 +1179,25 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-non-null-inexact-to-null-inexact (param (ref $sub-final)) (result (ref null $sub)) + ;; NO_CD: (func $cast-to-sibling-non-null-exact-to-non-null-inexact (type $40) (param $0 (ref (exact $sub-final))) (result (ref $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $cast-to-sibling-non-null-exact-to-non-null-inexact (param (ref (exact $sub-final))) (result (ref $sub)) (local anyref) - (ref.cast (ref null $sub) + (ref.cast (ref $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-non-null-exact (type $81) (param $0 (ref $sub-final)) (result (ref (exact $sub))) + ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-null-inexact (type $41) (param $0 (ref $sub-final)) (result (ref null $sub)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1515,16 +1206,25 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $cast-to-sibling-non-null-inexact-to-non-null-exact (param (ref $sub-final)) (result (ref (exact $sub))) + ;; NO_CD: (func $cast-to-sibling-non-null-inexact-to-null-inexact (type $41) (param $0 (ref $sub-final)) (result (ref null $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $cast-to-sibling-non-null-inexact-to-null-inexact (param (ref $sub-final)) (result (ref null $sub)) (local anyref) - (ref.cast (ref (exact $sub)) + (ref.cast (ref null $sub) (local.tee 1 (local.get 0) ) ) ) - ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-non-null-inexact (type $82) (param $0 (ref $sub-final)) (result (ref $sub)) + ;; CHECK: (func $cast-to-sibling-non-null-inexact-to-non-null-inexact (type $42) (param $0 (ref $sub-final)) (result (ref $sub)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1533,6 +1233,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-sibling-non-null-inexact-to-non-null-inexact (type $42) (param $0 (ref $sub-final)) (result (ref $sub)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-to-sibling-non-null-inexact-to-non-null-inexact (param (ref $sub-final)) (result (ref $sub)) (local anyref) (ref.cast (ref $sub) @@ -1542,7 +1251,7 @@ ) ) - ;; CHECK: (func $cast-to-bottom-null-exact-to-null-inexact (type $83) (param $0 (ref null (exact $super))) (result nullref) + ;; CHECK: (func $cast-to-bottom-null-exact-to-null-inexact (type $43) (param $0 (ref null (exact $super))) (result nullref) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (local.tee $1 @@ -1550,6 +1259,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-bottom-null-exact-to-null-inexact (type $43) (param $0 (ref null (exact $super))) (result nullref) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast nullref + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) (func $cast-to-bottom-null-exact-to-null-inexact (param (ref null (exact $super))) (result (ref null none)) (local anyref) (ref.cast (ref null none) @@ -1559,7 +1276,7 @@ ) ) - ;; CHECK: (func $cast-to-bottom-null-exact-to-non-null-inexact (type $84) (param $0 (ref null (exact $super))) (result (ref none)) + ;; CHECK: (func $cast-to-bottom-null-exact-to-non-null-inexact (type $44) (param $0 (ref null (exact $super))) (result (ref none)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1568,6 +1285,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-bottom-null-exact-to-non-null-inexact (type $44) (param $0 (ref null (exact $super))) (result (ref none)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-to-bottom-null-exact-to-non-null-inexact (param (ref null (exact $super))) (result (ref none)) (local anyref) (ref.cast (ref none) @@ -1577,7 +1303,7 @@ ) ) - ;; CHECK: (func $cast-to-bottom-null-inexact-to-null-inexact (type $85) (param $0 (ref null $super)) (result nullref) + ;; CHECK: (func $cast-to-bottom-null-inexact-to-null-inexact (type $45) (param $0 (ref null $super)) (result nullref) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (local.tee $1 @@ -1585,6 +1311,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-bottom-null-inexact-to-null-inexact (type $45) (param $0 (ref null $super)) (result nullref) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (ref.cast nullref + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) (func $cast-to-bottom-null-inexact-to-null-inexact (param (ref null $super)) (result (ref null none)) (local anyref) (ref.cast (ref null none) @@ -1594,7 +1328,7 @@ ) ) - ;; CHECK: (func $cast-to-bottom-null-inexact-to-non-null-inexact (type $86) (param $0 (ref null $super)) (result (ref none)) + ;; CHECK: (func $cast-to-bottom-null-inexact-to-non-null-inexact (type $46) (param $0 (ref null $super)) (result (ref none)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1603,6 +1337,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-bottom-null-inexact-to-non-null-inexact (type $46) (param $0 (ref null $super)) (result (ref none)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-to-bottom-null-inexact-to-non-null-inexact (param (ref null $super)) (result (ref none)) (local anyref) (ref.cast (ref none) @@ -1612,7 +1355,7 @@ ) ) - ;; CHECK: (func $cast-to-bottom-non-null-exact-to-null-inexact (type $87) (param $0 (ref (exact $super))) (result nullref) + ;; CHECK: (func $cast-to-bottom-non-null-exact-to-null-inexact (type $47) (param $0 (ref (exact $super))) (result nullref) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1621,6 +1364,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-bottom-non-null-exact-to-null-inexact (type $47) (param $0 (ref (exact $super))) (result nullref) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-to-bottom-non-null-exact-to-null-inexact (param (ref (exact $super))) (result (ref null none)) (local anyref) (ref.cast (ref null none) @@ -1630,7 +1382,7 @@ ) ) - ;; CHECK: (func $cast-to-bottom-non-null-exact-to-non-null-inexact (type $88) (param $0 (ref (exact $super))) (result (ref none)) + ;; CHECK: (func $cast-to-bottom-non-null-exact-to-non-null-inexact (type $48) (param $0 (ref (exact $super))) (result (ref none)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1639,6 +1391,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-bottom-non-null-exact-to-non-null-inexact (type $48) (param $0 (ref (exact $super))) (result (ref none)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-to-bottom-non-null-exact-to-non-null-inexact (param (ref (exact $super))) (result (ref none)) (local anyref) (ref.cast (ref none) @@ -1648,7 +1409,7 @@ ) ) - ;; CHECK: (func $cast-to-bottom-non-null-inexact-to-null-inexact (type $89) (param $0 (ref $super)) (result nullref) + ;; CHECK: (func $cast-to-bottom-non-null-inexact-to-null-inexact (type $49) (param $0 (ref $super)) (result nullref) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1657,6 +1418,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-bottom-non-null-inexact-to-null-inexact (type $49) (param $0 (ref $super)) (result nullref) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-to-bottom-non-null-inexact-to-null-inexact (param (ref $super)) (result (ref null none)) (local anyref) (ref.cast (ref null none) @@ -1666,7 +1436,7 @@ ) ) - ;; CHECK: (func $cast-to-bottom-non-null-inexact-to-non-null-inexact (type $90) (param $0 (ref $super)) (result (ref none)) + ;; CHECK: (func $cast-to-bottom-non-null-inexact-to-non-null-inexact (type $50) (param $0 (ref $super)) (result (ref none)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1675,6 +1445,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-to-bottom-non-null-inexact-to-non-null-inexact (type $50) (param $0 (ref $super)) (result (ref none)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-to-bottom-non-null-inexact-to-non-null-inexact (param (ref $super)) (result (ref none)) (local anyref) (ref.cast (ref none) @@ -1684,25 +1463,7 @@ ) ) - ;; CHECK: (func $cast-from-bottom-null-inexact-to-null-exact (type $91) (param $0 nullref) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - (func $cast-from-bottom-null-inexact-to-null-exact (param (ref null none)) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-from-bottom-null-inexact-to-null-inexact (type $92) (param $0 nullref) (result (ref null $super)) + ;; CHECK: (func $cast-from-bottom-null-inexact-to-null-inexact (type $51) (param $0 nullref) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1711,6 +1472,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-from-bottom-null-inexact-to-null-inexact (type $51) (param $0 nullref) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (ref.null none) + ;; NO_CD-NEXT: ) (func $cast-from-bottom-null-inexact-to-null-inexact (param (ref null none)) (result (ref null $super)) (local anyref) (ref.cast (ref null $super) @@ -1720,25 +1490,7 @@ ) ) - ;; CHECK: (func $cast-from-bottom-null-inexact-to-non-null-exact (type $93) (param $0 nullref) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-from-bottom-null-inexact-to-non-null-exact (param (ref null none)) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-from-bottom-null-inexact-to-non-null-inexact (type $94) (param $0 nullref) (result (ref $super)) + ;; CHECK: (func $cast-from-bottom-null-inexact-to-non-null-inexact (type $52) (param $0 nullref) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1747,6 +1499,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-from-bottom-null-inexact-to-non-null-inexact (type $52) (param $0 nullref) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-from-bottom-null-inexact-to-non-null-inexact (param (ref null none)) (result (ref $super)) (local anyref) (ref.cast (ref $super) @@ -1756,25 +1517,7 @@ ) ) - ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-null-exact (type $95) (param $0 (ref none)) (result (ref null (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-from-bottom-non-null-inexact-to-null-exact (param (ref none)) (result (ref null (exact $super))) - (local anyref) - (ref.cast (ref null (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-null-inexact (type $96) (param $0 (ref none)) (result (ref null $super)) + ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-null-inexact (type $53) (param $0 (ref none)) (result (ref null $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1783,6 +1526,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-from-bottom-non-null-inexact-to-null-inexact (type $53) (param $0 (ref none)) (result (ref null $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-from-bottom-non-null-inexact-to-null-inexact (param (ref none)) (result (ref null $super)) (local anyref) (ref.cast (ref null $super) @@ -1792,25 +1544,7 @@ ) ) - ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-non-null-exact (type $97) (param $0 (ref none)) (result (ref (exact $super))) - ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $1 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $cast-from-bottom-non-null-inexact-to-non-null-exact (param (ref none)) (result (ref (exact $super))) - (local anyref) - (ref.cast (ref (exact $super)) - (local.tee 1 - (local.get 0) - ) - ) - ) - - ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-non-null-inexact (type $98) (param $0 (ref none)) (result (ref $super)) + ;; CHECK: (func $cast-from-bottom-non-null-inexact-to-non-null-inexact (type $54) (param $0 (ref none)) (result (ref $super)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $1 @@ -1819,6 +1553,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-from-bottom-non-null-inexact-to-non-null-inexact (type $54) (param $0 (ref none)) (result (ref $super)) + ;; NO_CD-NEXT: (local $1 anyref) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (local.tee $1 + ;; NO_CD-NEXT: (local.get $0) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) (func $cast-from-bottom-non-null-inexact-to-non-null-inexact (param (ref none)) (result (ref $super)) (local anyref) (ref.cast (ref $super) From ce2abc2e23a524763dd11e82513b04ad6c023723 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 23 Apr 2025 18:48:40 -0700 Subject: [PATCH 461/622] Account for exactness when finding the most refined fallthrough (#7544) Treat exactness as part of the heap type and prefer to refine it over refining nullability. This is a somewhat arbitrary choice, and as shown by the tests, it is possible to construct a situation in which preferring to refine nullability would be preferable. --- scripts/test/fuzzing.py | 1 + src/ir/properties.h | 20 +++--- .../passes/optimize-instructions-exact.wast | 66 +++++++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 test/lit/passes/optimize-instructions-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index e854309ebf0..32f00766ba3 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -114,6 +114,7 @@ 'exact-references-lowering.wast', 'exact-casts.wast', 'exact-casts-trivial.wast', + 'optimize-instructions-exact.wast', 'optimize-instructions-all-casts-exact.wast', 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', diff --git a/src/ir/properties.h b/src/ir/properties.h index 70f18c2762f..8d3266b76f8 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -400,6 +400,7 @@ inline Expression** getMostRefinedFallthrough(Expression** currp, return currp; } auto bestType = curr->type.getHeapType(); + auto bestExactness = curr->type.getExactness(); auto bestNullability = curr->type.getNullability(); auto** bestp = currp; while (1) { @@ -412,20 +413,23 @@ inline Expression** getMostRefinedFallthrough(Expression** currp, } assert(next->type.isRef()); auto nextType = next->type.getHeapType(); + auto nextExactness = next->type.getExactness(); auto nextNullability = next->type.getNullability(); - if (nextType == bestType) { + if (nextType == bestType && nextExactness == bestExactness) { // Heap types match: refine nullability if possible. if (bestNullability == Nullable && nextNullability == NonNullable) { bestp = nextp; bestNullability = NonNullable; } - } else { - // Refine heap type if possible, resetting nullability. - if (HeapType::isSubType(nextType, bestType)) { - bestp = nextp; - bestNullability = nextNullability; - bestType = nextType; - } + } else if ((nextType != bestType && + HeapType::isSubType(nextType, bestType)) || + (nextType == bestType && nextExactness == Exact && + bestExactness == Inexact)) { + // Refine heap, resetting nullability. + bestp = nextp; + bestType = nextType; + bestExactness = nextExactness; + bestNullability = nextNullability; } currp = nextp; } diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast new file mode 100644 index 00000000000..cf11805ef52 --- /dev/null +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -0,0 +1,66 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --disable-custom-descriptors --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (type $foo (sub (struct))) + (type $foo (sub (struct))) + + ;; CHECK: (func $ref-cast-exact-fallthrough (type $1) (param $exact (ref (exact $foo))) (result (ref $foo)) + ;; CHECK-NEXT: (local $inexact (ref $foo)) + ;; CHECK-NEXT: (local $2 (ref (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $inexact + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.get $exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $ref-cast-exact-fallthrough (param $exact (ref (exact $foo))) (result (ref $foo)) + (local $inexact (ref $foo)) + ;; We should find that the local.get is the most precise fallthrough value and + ;; hoist it to eliminate the cast. + (ref.cast (ref $foo) + (local.tee $inexact + (local.get $exact) + ) + ) + ) + + ;; CHECK: (func $prefer-exactness (type $2) (param $exact-null (ref null (exact $foo))) (result (ref $foo)) + ;; CHECK-NEXT: (local $inexact-nn (ref $foo)) + ;; CHECK-NEXT: (local $inexact-null (ref null $foo)) + ;; CHECK-NEXT: (local $3 (ref null (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $inexact-nn + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.tee $inexact-null + ;; CHECK-NEXT: (local.tee $3 + ;; CHECK-NEXT: (local.get $exact-null) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $prefer-exactness (param $exact-null (ref null (exact $foo))) (result (ref $foo)) + (local $inexact-nn (ref $foo)) + (local $inexact-null (ref null $foo)) + ;; We should prefer to hoist the exact expression and introduce another null + ;; check rather than hoisting the non-null, inexact expression. + (ref.cast (ref $foo) + (local.tee $inexact-nn + (ref.as_non_null + (local.tee $inexact-null + (local.get $exact-null) + ) + ) + ) + ) + ) +) From 5f04581f7c4a6c90a1311763215c74fd12b2d8cc Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 24 Apr 2025 09:43:51 -0700 Subject: [PATCH 462/622] Convert JSON parsing failures from assertions to exceptions (#7531) This allows some useful error reporting with bad source maps. The JSON exception is converted to the existing map parse exception by the source map parser, allowing a unified interface. Also add several tests of bogus source maps. --- src/source-map.h | 7 +++--- src/support/json.h | 30 ++++++++++++++++------- src/wasm/source-map.cpp | 14 +++++++++-- test/gtest/CMakeLists.txt | 10 +++++++- test/gtest/source-map.cpp | 50 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 93 insertions(+), 18 deletions(-) diff --git a/src/source-map.h b/src/source-map.h index 88058d29da9..a7d1fb9a98e 100644 --- a/src/source-map.h +++ b/src/source-map.h @@ -19,14 +19,15 @@ #include +#include "support/json.h" #include "wasm.h" namespace wasm { struct MapParseException { - std::string text; - - MapParseException(std::string text) : text(text) {} + std::string errorText; + MapParseException(std::string errorText) : errorText(errorText){}; + MapParseException(json::JsonParseException ex) : errorText(ex.errorText){}; void dump(std::ostream& o) const; }; diff --git a/src/support/json.h b/src/support/json.h index 1c8f2bf180f..ff9f8eb942d 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -45,6 +45,18 @@ namespace json { using IString = wasm::IString; +struct JsonParseException { + std::string errorText; + + JsonParseException(std::string errorText) : errorText(errorText) {} + void dump(std::ostream& o) const { o << "JSON parse error: " << errorText; } +}; + +#define THROW_IF(expr, message) \ + if (expr) { \ + throw JsonParseException(message); \ + } + // Main value type struct Value { struct Ref : public std::shared_ptr { @@ -277,7 +289,7 @@ struct Value { do { close = strchr(close + 1, '"'); } while (*(close - 1) == '\\'); - assert(close); + THROW_IF(!close, "malformed JSON string"); *close = 0; // end this string, and reuse it straight from the input char* raw = curr + 1; if (stringEncoding == ASCII) { @@ -301,24 +313,24 @@ struct Value { if (*curr == ']') { break; } - assert(*curr == ','); + THROW_IF(*curr != ',', "malformed JSON array"); curr++; skip(); } curr++; } else if (*curr == 'n') { // Null - assert(strncmp(curr, "null", 4) == 0); + THROW_IF(strncmp(curr, "null", 4) != 0, "unexpected JSON literal"); setNull(); curr += 4; } else if (*curr == 't') { // Bool true - assert(strncmp(curr, "true", 4) == 0); + THROW_IF(strncmp(curr, "true", 4) != 0, "unexpected JSON literal"); setBool(true); curr += 4; } else if (*curr == 'f') { // Bool false - assert(strncmp(curr, "false", 5) == 0); + THROW_IF(strncmp(curr, "false", 5) != 0, "unexpected JSON literal"); setBool(false); curr += 5; } else if (*curr == '{') { @@ -327,15 +339,15 @@ struct Value { skip(); setObject(); while (*curr != '}') { - assert(*curr == '"'); + THROW_IF(*curr != '"', "malformed key in JSON object"); curr++; char* close = strchr(curr, '"'); - assert(close); + THROW_IF(!close, "malformed key in JSON object"); *close = 0; // end this string, and reuse it straight from the input IString key(curr); curr = close + 1; skip(); - assert(*curr == ':'); + THROW_IF(*curr != ':', "missing ':', in JSON object"); curr++; skip(); Ref value = Ref(new Value()); @@ -345,7 +357,7 @@ struct Value { if (*curr == '}') { break; } - assert(*curr == ','); + THROW_IF(*curr != ',', "malformed value in JSON object"); curr++; skip(); } diff --git a/src/wasm/source-map.cpp b/src/wasm/source-map.cpp index cce8a5967f1..ecb7e96512c 100644 --- a/src/wasm/source-map.cpp +++ b/src/wasm/source-map.cpp @@ -28,7 +28,7 @@ void MapParseException::dump(std::ostream& o) const { Colors::red(o); o << "map parse exception: "; Colors::green(o); - o << text; + o << errorText; Colors::magenta(o); o << "]"; Colors::normal(o); @@ -39,7 +39,11 @@ void SourceMapReader::parse(Module& wasm) { return; } json::Value json; - json.parse(buffer.data(), json::Value::ASCII); + try { + json.parse(buffer.data(), json::Value::ASCII); + } catch (json::JsonParseException jx) { + throw MapParseException(jx); + } if (!json.isObject()) { throw MapParseException("Source map is not valid JSON"); } @@ -135,6 +139,12 @@ SourceMapReader::readDebugLocationAt(size_t currLocation) { col += readBase64VLQ(); next = peek(); + if (next == ';') { + // Generated JS files can have multiple lines, and mappings for each + // line are separated by ';'. Wasm files do not have lines, so there + // should be only one generated "line". + throw MapParseException("Unexpected mapping for 2nd generated line"); + } if (next == ',' || next == '\"') { hasSymbol = false; break; diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 04e8d587e13..223dbddf79b 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -2,6 +2,7 @@ if(BUILD_FUZZTEST) include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third_party/fuzztest) else() include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third_party/googletest/googletest/include) + include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third_party/googletest/googlemock/include) endif() set(unittest_SOURCES @@ -18,7 +19,6 @@ set(unittest_SOURCES possible-contents.cpp printing.cpp scc.cpp - source-map.cpp stringify.cpp suffix_tree.cpp topological-sort.cpp @@ -29,6 +29,14 @@ set(unittest_SOURCES if(BUILD_FUZZTEST) set(unittest_SOURCES ${unittest_SOURCES} type-domains.cpp) +else() + # source-map.cpp uses the gmock-matchers.h header, which is included with the + # "standard" upstream gtest library, but not with the one bundled into the + # fuzztest library (and the gmock included with upstream gtest seems + # incompatible with the one in fuzztest). For now we work around this by just + # excluding source-map.cpp from the fuzztest build, but if we start using + # gmock more we should figure out what the right way to hande this is. + set(unittest_SOURCES ${unittest_SOURCES} source-map.cpp) endif() # suffix_tree.cpp includes LLVM header using std::iterator (deprecated in C++17) diff --git a/test/gtest/source-map.cpp b/test/gtest/source-map.cpp index 657034befcb..a01d064eda6 100644 --- a/test/gtest/source-map.cpp +++ b/test/gtest/source-map.cpp @@ -16,6 +16,7 @@ #include "source-map.h" #include "print-test.h" +#include "gmock/gmock-matchers.h" #include "gtest/gtest.h" using namespace wasm; @@ -53,6 +54,16 @@ class SourceMapTest : public PrintTest { EXPECT_EQ(loc->columnNumber, col); EXPECT_EQ(loc->symbolNameIndex, sym); } + + void ExpectParseError(std::string& mapString, const char* expectedError) { + SCOPED_TRACE(mapString); + EXPECT_THROW(parseMap(mapString), MapParseException); + try { + parseMap(mapString); + } catch (MapParseException ex) { + EXPECT_THAT(ex.errorText, ::testing::HasSubstr(expectedError)); + } + } }; // Check that debug location parsers can handle single-segment mappings. @@ -73,16 +84,49 @@ TEST_F(SourceMapTest, SourceMappingSingleSegment) { EXPECT_FALSE(loc.has_value()); } -TEST_F(SourceMapTest, BadSourceMap) { - // This source map is missing the version field. +TEST_F(SourceMapTest, BadSourceMaps) { + // Test that a malformed JSON string throws rather than asserting. std::string sourceMap = R"( + { + "version": 3, + "sources": ["foo.c"], + "mappings": "" + malformed + } + )"; + ExpectParseError(sourceMap, "malformed value in JSON object"); + + // Valid JSON, but missing the version field. + sourceMap = R"( { "sources": [], "names": [], "mappings": "A" } )"; - EXPECT_THROW(parseMap(sourceMap), MapParseException); + ExpectParseError(sourceMap, "Source map version missing"); + + // Valid JSON, but a bad "sources" field. + sourceMap = R"( + { + "version": 3, + "sources": 123, + "mappings": "" + } + )"; + ExpectParseError(sourceMap, "Source map sources missing or not an array"); + + sourceMap = R"( + { + "version": 3, + "sources": ["foo.c"], + "mappings": "C;A" + } + )"; + parseMap(sourceMap); + // Mapping strings are parsed incrementally, so errors don't show up until a + // sufficiently far-advanced location is requested to reach the problem. + EXPECT_THROW(reader->readDebugLocationAt(1), MapParseException); } TEST_F(SourceMapTest, SourcesAndNames) { From c87a7da97964d7eb1161da714df2f22177dd1920 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Apr 2025 10:09:11 -0700 Subject: [PATCH 463/622] [Strings] Erase the strings section after StringLifting (#7546) After we lift the strings section into stringref, we don't need the section any more, and leaving it around could cause problems with repeated lifting/ lowering operations (which would be very much possible with #7540). In particular, without erasing it, we'd accumulate such sections over time. --- src/passes/StringLifting.cpp | 66 ++++++++++--------- .../passes/string-lifting-section-erase.wast | 23 +++++++ 2 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 test/lit/passes/string-lifting-section-erase.wast diff --git a/src/passes/StringLifting.cpp b/src/passes/StringLifting.cpp index 58e2d28882e..07b7fdd7bcd 100644 --- a/src/passes/StringLifting.cpp +++ b/src/passes/StringLifting.cpp @@ -79,41 +79,45 @@ struct StringLifting : public Pass { } // Imported strings may also be found in the string section. - for (auto& section : module->customSections) { - if (section.name == "string.consts") { - // We found the string consts section. Parse it. - auto copy = section.data; - json::Value array; - array.parse(copy.data(), json::Value::WTF16); - if (!array.isArray()) { + auto stringSectionIter = std::find_if( + module->customSections.begin(), + module->customSections.end(), + [&](CustomSection& section) { return section.name == "string.consts"; }); + if (stringSectionIter != module->customSections.end()) { + // We found the string consts section. Parse it. + auto& section = *stringSectionIter; + auto copy = section.data; + json::Value array; + array.parse(copy.data(), json::Value::WTF16); + if (!array.isArray()) { + Fatal() << "StringLifting: string.const section should be a JSON array"; + } + + // We have the array of constants from the section. Find globals that + // refer to it. + for (auto& global : module->globals) { + if (!global->imported() || global->module != "string.const") { + continue; + } + // The index in the array is the basename. + Index index = std::stoi(std::string(global->base.str)); + if (index >= array.size()) { + Fatal() << "StringLifting: bad index in string.const section"; + } + auto item = array[index]; + if (!item->isString()) { Fatal() - << "StringLifting: string.const section should be a JSON array"; + << "StringLifting: string.const section entry is not a string"; } - - // We have the array of constants from the section. Find globals that - // refer to it. - for (auto& global : module->globals) { - if (!global->imported() || global->module != "string.const") { - continue; - } - // The index in the array is the basename. - Index index = std::stoi(std::string(global->base.str)); - if (index >= array.size()) { - Fatal() << "StringLifting: bad index in string.const section"; - } - auto item = array[index]; - if (!item->isString()) { - Fatal() - << "StringLifting: string.const section entry is not a string"; - } - if (importedStrings.count(global->name)) { - Fatal() - << "StringLifting: string.const section tramples other const"; - } - importedStrings[global->name] = item->getIString(); + if (importedStrings.count(global->name)) { + Fatal() << "StringLifting: string.const section tramples other const"; } - break; + importedStrings[global->name] = item->getIString(); } + + // Remove the custom section: After lifting it has no purpose (and could + // cause problems with repeated lifting/lowering). + module->customSections.erase(stringSectionIter); } auto array16 = Type(Array(Field(Field::i16, Mutable)), Nullable); diff --git a/test/lit/passes/string-lifting-section-erase.wast b/test/lit/passes/string-lifting-section-erase.wast new file mode 100644 index 00000000000..70e59e7c3c7 --- /dev/null +++ b/test/lit/passes/string-lifting-section-erase.wast @@ -0,0 +1,23 @@ +;; Test that the section vanishes after lifting. + +(module + (func $strings + ;; These strings cannot appear as magic imports, and will definitely go in + ;; the strings section. + (drop + (string.const "unpaired high surrogate \ED\A0\80 ") + ) + (drop + (string.const "unpaired low surrogate \ED\BD\88 ") + ) + ) +) + +;; Lower into the section. We should see the section. +;; RUN: wasm-opt %s -all --string-lowering -S -o - | filecheck %s --check-prefix=LOWER +;; LOWER: custom section + +;; Also lift. Now no section should appear. +;; RUN: wasm-opt %s -all --string-lowering --string-lifting -S -o - | filecheck %s --check-prefix=AND_LIFT +;; AND_LIFT-NOT: custom section + From 2f90ad17dfec56567f78915c1b282334d6132c7f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 24 Apr 2025 10:17:54 -0700 Subject: [PATCH 464/622] Add test with exact types to fuzzer ignore list (#7548) --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 32f00766ba3..df317f6314d 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -115,6 +115,7 @@ 'exact-casts.wast', 'exact-casts-trivial.wast', 'optimize-instructions-exact.wast', + 'optimize-instructions-all-casts.wast', 'optimize-instructions-all-casts-exact.wast', 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', From bba8a103271d5a184486d79632d30ac8c4ef2149 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Apr 2025 11:03:30 -0700 Subject: [PATCH 465/622] Fix JSON parsing of escaped strings (#7545) To find the end of a string, we must be more careful of escaping - we assumed any \" was an escaped double-quote, but it might be part of \\", that is, where there is an escaped \ before us, and the double-quote is not escaped itself. --- scripts/test/fuzzing.py | 3 +++ src/support/json.h | 20 +++++++++++++------- test/lit/passes/string-lifting-section.wast | 11 ++++++++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index df317f6314d..8ddc4982563 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -128,6 +128,9 @@ 'type-refining-gufa-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', + # TODO: fix split_wast() on tricky escaping situations like a string ending + # in \\" (the " is not escaped - there is an escaped \ before it) + 'string-lifting-section.wast', ] diff --git a/src/support/json.h b/src/support/json.h index ff9f8eb942d..0ddc4865467 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -282,14 +282,20 @@ struct Value { skip(); if (*curr == '"') { // String - // Start |close| at the opening ", and in the loop below we will always + // Start |close| after the opening ", and in the loop below we will always // begin looking at the first character after. - char* close = curr; - // Skip escaped " - do { - close = strchr(close + 1, '"'); - } while (*(close - 1) == '\\'); - THROW_IF(!close, "malformed JSON string"); + char* close = curr + 1; + // Skip escaped ", which appears as \". We need to be careful though, as + // \" might also be \\" which would be an escaped \ and an *un*escaped ". + while (*close && *close != '"') { + if (*close == '\\') { + // Skip the \ and the character after it, which it escapes. + close++; + THROW_IF(!*close, "unexpected end of JSON string (quoting)"); + } + close++; + } + THROW_IF(!close, "unexpected end of JSON string"); *close = 0; // end this string, and reuse it straight from the input char* raw = curr + 1; if (stringEncoding == ASCII) { diff --git a/test/lit/passes/string-lifting-section.wast b/test/lit/passes/string-lifting-section.wast index 247fc39a48e..815af9fc646 100644 --- a/test/lit/passes/string-lifting-section.wast +++ b/test/lit/passes/string-lifting-section.wast @@ -2,7 +2,7 @@ ;; Lower first to generate the string.consts custom section, then lift it back. -;; RUN: foreach %s %t wasm-opt -all --string-lowering --string-lifting -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --string-lowering --string-lifting -S -o - | filecheck %s (module ;; CHECK: (type $0 (array (mut i16))) @@ -37,6 +37,8 @@ ;; CHECK: (import "string.const" "5" (global $"string.const_\"unpaired low surrogate \\ed\\bd\\88 \"" (ref extern))) + ;; CHECK: (import "string.const" "6" (global $"string.const_\"z\\\\\"" (ref extern))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) @@ -94,6 +96,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (string.const "unpaired low surrogate \ed\bd\88 ") ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.const "z\\") + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $tricky-consts ;; These tricky strings should remain exactly the same after lowering and @@ -110,6 +115,10 @@ (drop (string.const "unpaired low surrogate \ED\BD\88 ") ) + ;; A string with \", but the " is not escaped, as the \ is part of \\. + (drop + (string.const "z\\") + ) ) ) From 456e57352ebe7d52eb4db2b2cd62f1314ec215b3 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Fri, 25 Apr 2025 03:07:44 +0800 Subject: [PATCH 466/622] OptimizeInstructions: Use fallthrough for (unsigned)x < 0 ==> i32(0) (#7480) Fixes: #7455 --- src/passes/OptimizeInstructions.cpp | 10 +-- .../lit/passes/optimize-instructions-mvp.wast | 66 +++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 59209e9c4d3..a8a2c6bf5bc 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -570,11 +570,13 @@ struct OptimizeInstructions return replaceCurrent(getDroppedChildrenAndAppend(curr, c)); } // unsigned(x) < 0 => i32(0) - if (matches(curr, binary(LtU, any(&x), ival(&c))) && + if (curr->op == Abstract::getBinary(curr->left->type, Abstract::LtU) && + (c = getFallthrough(curr->right)->dynCast()) && c->value.isZero()) { - c->value = Literal::makeZero(Type::i32); - c->type = Type::i32; - return replaceCurrent(getDroppedChildrenAndAppend(curr, c)); + // We could reuse c here, if we checked it had no more uses + auto zero = + Builder(*getModule()).makeConst(Literal::makeZero(Type::i32)); + return replaceCurrent(getDroppedChildrenAndAppend(curr, zero)); } } } diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index aea7b391c77..7b5551a67a9 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -175,6 +175,7 @@ ) ) ) + ;; CHECK: (func $eqz-gt_s (result i32) ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (i32.const 0) @@ -11575,6 +11576,44 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (i64.store + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ne ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 0) @@ -11867,6 +11906,33 @@ ) (i64.const 0) )) + (drop (i32.lt_u + (i32.load + (i32.const 0) + ) + (block (result i32) + (i32.store + (i32.const 0) + (i32.const 0) + ) + (i32.const 0) + ) + ) + ) + (drop (i64.lt_u + (i64.load + (i32.const 0) + ) + (block (result i64) + (i64.store + (i32.const 0) + (i64.const 0) + ) + (i64.const 0) + ) + ) + ) + ;; (unsigned)x > 0 => x != 0 (drop (i32.gt_u From acd3ed320f1767d056ceab8c2b86cbe72dcbb0d5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 24 Apr 2025 14:34:25 -0700 Subject: [PATCH 467/622] [NFC] Simplify interpretation of casts (#7549) The interpreter previously inspected subtyping for nullability and heap types separately when executing casts. Simplify this to just check subtyping on the combined type. This is NFC for now, but will have the happy side effect of handling exactness correctly once we type allocations as exact and interpret exact casts. --- src/wasm-interpreter.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 4d62e88c1c6..a92aa09f4b4 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1642,14 +1642,7 @@ class ExpressionRunner : public OverriddenVisitor { } Literal val = ref.getSingleValue(); Type castType = curr->getCastType(); - if (val.isNull()) { - if (castType.isNullable()) { - return typename Cast::Success{val}; - } else { - return typename Cast::Failure{val}; - } - } - if (HeapType::isSubType(val.type.getHeapType(), castType.getHeapType())) { + if (Type::isSubType(val.type, castType)) { return typename Cast::Success{val}; } else { return typename Cast::Failure{val}; From e6d02fa1018cee8ab8c181de5d0d558ac3373ffb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Apr 2025 14:58:37 -0700 Subject: [PATCH 468/622] [NFC] Improve wasm-reduce help text (#7293) --- src/tools/wasm-reduce.cpp | 58 ++++++++++++++++++++++++++++++++-- test/lit/help/wasm-reduce.test | 55 ++++++++++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 6aac43653a5..43c188bda82 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -1250,9 +1250,61 @@ int main(int argc, const char* argv[]) { const std::string WasmReduceOption = "wasm-reduce options"; - ToolOptions options("wasm-reduce", - "Reduce a wasm file to a smaller one that has the same " - "behavior on a given command"); + ToolOptions options( + "wasm-reduce", + R"(Reduce a wasm file to a smaller one with the same behavior on a given command. + +Typical usage: + + wasm-reduce orig.wasm '--command=bash a.sh' --test t.wasm --working w.wasm + +The original file orig.wasm is where we begin. We then repeatedly test a small +reduction of it by writing that modification to the 'test file' (specified by +'--test'), and we run the command, in this example 'bash a.sh'. That command +should use the test file (and not the original file or any other one). Whenever +the reduction works, we write that new smaller file to the 'working file' +(specified by '--working'). The reduction 'works' if it correctly preserves the +behavior of the command on the original input, specifically, that it has the +same stdout and the result return code. Each time reduction works we continue to +reduce from that point (and each time it fails, we go back and try something +else). + +As mentioned above, the command should run on the test file. That is, the first +thing that wasm-reduce does on the example above is, effectively, + + cp orig.wasm t.wasm + bash a.sh + +In other words, it copies the original to the test file, and runs the command. +Whatever the command does, we will preserve as we copy progressively smaller +files to t.wasm. As we make progress, the smallest file will be written to +the working file, w.wasm, and when reduction is done you will find the final +result there. + +Comparison to creduce: + +1. creduce requires the command to return 0. wasm-reduce is often used to reduce + crashes, which have non-zero return codes, so it is natural to allow any + return code. As mentioned above, we preserve the return code as we reduce. +2. creduce ignores stdout. wasm-reduce preserves stdout as it reduces, as part + of the principle of preserving the original behavior of the command (if your + stdout varies in uninteresting ways, your command can be a script that runs + the real command and captures stdout to /dev/null, or filters it). +3. creduce tramples the original input file as it reduces. wasm-reduce never + modifies the input (to avoid mistakes that cause data loss). Instead, + when reductions work we write to the 'working file' as mentioned above, and + the final reduction will be there. +4. creduce runs the command in a temp directory. That is safer in general, but + it is not how the original command ran, and in particular forces additional + work if you have multiple files (which, for wasm-reduce, is common, e.g. if + the testcase is a combination of JavaScript and wasm). wasm-reduce runs the + command in the current directory (of course, your command can be a script + that changes directory to anywhere else). + +More documentation can be found at + + https://github.com/WebAssembly/binaryen/wiki/Fuzzing#reducing + )"); options .add("--command", "-cmd", diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 1a04f2f5878..2739394e3ed 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -2,8 +2,59 @@ ;; CHECK: ================================================================================ ;; CHECK-NEXT: wasm-reduce INFILE ;; CHECK-NEXT: -;; CHECK-NEXT: Reduce a wasm file to a smaller one that has the same behavior on a given -;; CHECK-NEXT: command +;; CHECK-NEXT: Reduce a wasm file to a smaller one with the same behavior on a given command. +;; CHECK-NEXT: +;; CHECK-NEXT: Typical usage: +;; CHECK-NEXT: +;; CHECK-NEXT: wasm-reduce orig.wasm '--command=bash a.sh' --test t.wasm --working w.wasm +;; CHECK-NEXT: +;; CHECK-NEXT: The original file orig.wasm is where we begin. We then repeatedly test a small +;; CHECK-NEXT: reduction of it by writing that modification to the 'test file' (specified by +;; CHECK-NEXT: '--test'), and we run the command, in this example 'bash a.sh'. That command +;; CHECK-NEXT: should use the test file (and not the original file or any other one). Whenever +;; CHECK-NEXT: the reduction works, we write that new smaller file to the 'working file' +;; CHECK-NEXT: (specified by '--working'). The reduction 'works' if it correctly preserves the +;; CHECK-NEXT: behavior of the command on the original input, specifically, that it has the +;; CHECK-NEXT: same stdout and the result return code. Each time reduction works we continue to +;; CHECK-NEXT: reduce from that point (and each time it fails, we go back and try something +;; CHECK-NEXT: else). +;; CHECK-NEXT: +;; CHECK-NEXT: As mentioned above, the command should run on the test file. That is, the first +;; CHECK-NEXT: thing that wasm-reduce does on the example above is, effectively, +;; CHECK-NEXT: +;; CHECK-NEXT: cp orig.wasm t.wasm +;; CHECK-NEXT: bash a.sh +;; CHECK-NEXT: +;; CHECK-NEXT: In other words, it copies the original to the test file, and runs the command. +;; CHECK-NEXT: Whatever the command does, we will preserve as we copy progressively smaller +;; CHECK-NEXT: files to t.wasm. As we make progress, the smallest file will be written to +;; CHECK-NEXT: the working file, w.wasm, and when reduction is done you will find the final +;; CHECK-NEXT: result there. +;; CHECK-NEXT: +;; CHECK-NEXT: Comparison to creduce: +;; CHECK-NEXT: +;; CHECK-NEXT: 1. creduce requires the command to return 0. wasm-reduce is often used to reduce +;; CHECK-NEXT: crashes, which have non-zero return codes, so it is natural to allow any +;; CHECK-NEXT: return code. As mentioned above, we preserve the return code as we reduce. +;; CHECK-NEXT: 2. creduce ignores stdout. wasm-reduce preserves stdout as it reduces, as part +;; CHECK-NEXT: of the principle of preserving the original behavior of the command (if your +;; CHECK-NEXT: stdout varies in uninteresting ways, your command can be a script that runs +;; CHECK-NEXT: the real command and captures stdout to /dev/null, or filters it). +;; CHECK-NEXT: 3. creduce tramples the original input file as it reduces. wasm-reduce never +;; CHECK-NEXT: modifies the input (to avoid mistakes that cause data loss). Instead, +;; CHECK-NEXT: when reductions work we write to the 'working file' as mentioned above, and +;; CHECK-NEXT: the final reduction will be there. +;; CHECK-NEXT: 4. creduce runs the command in a temp directory. That is safer in general, but +;; CHECK-NEXT: it is not how the original command ran, and in particular forces additional +;; CHECK-NEXT: work if you have multiple files (which, for wasm-reduce, is common, e.g. if +;; CHECK-NEXT: the testcase is a combination of JavaScript and wasm). wasm-reduce runs the +;; CHECK-NEXT: command in the current directory (of course, your command can be a script +;; CHECK-NEXT: that changes directory to anywhere else). +;; CHECK-NEXT: +;; CHECK-NEXT: More documentation can be found at +;; CHECK-NEXT: +;; CHECK-NEXT: https://github.com/WebAssembly/binaryen/wiki/Fuzzing#reducing +;; CHECK-NEXT: ;; CHECK-NEXT: ================================================================================ ;; CHECK-NEXT: ;; CHECK-NEXT: From d758b00068e82d9d318e16d25c886334a4f05ffa Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 28 Apr 2025 15:13:44 -0700 Subject: [PATCH 469/622] Use GLB in PossibleContents::intersect (#7550) Use the standard utility rather than reimplementing type intersection. The new code is simpler, shorter, and properly supports exactness, avoiding an assertion failure in the added test case. The other functional change is that when one of the intersected heap types is bottom and the type GLB is a non-nullable reference to bottom, the result of the intersection is `None` where it was previously a `Cone`. --- src/ir/possible-contents.cpp | 35 ++++------------------- test/lit/passes/gufa-cast-all-exact.wast | 36 ++++++++++++++++++++++++ test/lit/passes/gufa-refs.wast | 9 ++++-- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 625136506f0..58298383f89 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -182,29 +182,19 @@ void PossibleContents::intersect(const PossibleContents& other) { auto heapType = type.getHeapType(); auto otherHeapType = otherType.getHeapType(); - // If both inputs are nullable then the intersection is nullable as well. - auto nullability = - type.isNullable() && otherType.isNullable() ? Nullable : NonNullable; + // Intersect the types. + auto newType = Type::getGreatestLowerBound(type, otherType); auto setNoneOrNull = [&]() { - if (nullability == Nullable) { + if (newType.isNullable()) { value = Literal::makeNull(heapType); } else { value = None(); } }; - // If the heap types are not compatible then they are in separate hierarchies - // and there is no intersection, aside from possibly a null of the bottom - // type. - auto isSubType = HeapType::isSubType(heapType, otherHeapType); - auto otherIsSubType = HeapType::isSubType(otherHeapType, heapType); - if (!isSubType && !otherIsSubType) { - if (heapType.getBottom() == otherHeapType.getBottom()) { - setNoneOrNull(); - } else { - value = None(); - } + if (newType == Type::unreachable || newType.isNull()) { + setNoneOrNull(); return; } @@ -212,17 +202,6 @@ void PossibleContents::intersect(const PossibleContents& other) { auto depthFromRoot = heapType.getDepth(); auto otherDepthFromRoot = otherHeapType.getDepth(); - // To compute the new cone, find the new heap type for it, and to compute its - // depth, consider the adjustments to the existing depths that stem from the - // choice of new heap type. - HeapType newHeapType; - - if (depthFromRoot < otherDepthFromRoot) { - newHeapType = otherHeapType; - } else { - newHeapType = heapType; - } - // Note the global's information, if we started as a global. In that case, the // code below will refine our type but we can remain a global, which we will // accomplish by restoring our global status at the end. @@ -231,8 +210,6 @@ void PossibleContents::intersect(const PossibleContents& other) { globalName = getGlobal(); } - auto newType = Type(newHeapType, nullability); - // By assumption |other| has full depth. Consider the other cone in |this|. if (hasFullCone()) { // Both are full cones, so the result is as well. @@ -252,7 +229,7 @@ void PossibleContents::intersect(const PossibleContents& other) { // E.g. if |this| is a cone of depth 10, and |otherHeapType| is an immediate // subtype of |this|, then the new cone must be of depth 9. auto newDepth = getCone().depth; - if (newHeapType == otherHeapType) { + if (newType.getHeapType() == otherHeapType) { assert(depthFromRoot <= otherDepthFromRoot); auto reduction = otherDepthFromRoot - depthFromRoot; if (reduction > newDepth) { diff --git a/test/lit/passes/gufa-cast-all-exact.wast b/test/lit/passes/gufa-cast-all-exact.wast index 5d181d3bb21..1b40f9ee266 100644 --- a/test/lit/passes/gufa-cast-all-exact.wast +++ b/test/lit/passes/gufa-cast-all-exact.wast @@ -138,3 +138,39 @@ ) ) ) + +(module + ;; CHECK: (type $foo (sub (struct (field i32)))) + ;; NO_CD: (type $foo (sub (struct (field i32)))) + (type $foo (sub (struct (field i32)))) + + ;; CHECK: (import "" "" (global $null-exact (ref null (exact $foo)))) + ;; NO_CD: (import "" "" (global $null-exact (ref null (exact $foo)))) + (import "" "" (global $null-exact (ref null (exact $foo)))) + + ;; CHECK: (func $as-non-null (type $1) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $null-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $as-non-null (type $1) (result i32) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (ref.as_non_null + ;; NO_CD-NEXT: (global.get $null-exact) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $as-non-null (result i32) + (struct.get $foo 0 + ;; Regression test for an assertion failure when the intersection here + ;; dropped exactness as well as nullness. + (ref.as_non_null + (global.get $null-exact) + ) + ) + ) +) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 6d48d651a5f..2178f142383 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -6069,8 +6069,13 @@ ;; CHECK: (func $test-set-bottom (type $2) ;; CHECK-NEXT: (block ;; (replaces unreachable ArraySet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop From ad4672e4c1938f4872ab49ff9d2224a790a47f62 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 29 Apr 2025 12:41:33 -0700 Subject: [PATCH 470/622] Avoid errors in Type::with(HeapType) (#7551) Previously doing e.g. `type.with(HeapType::none)` would cause an assertion failure if `type` was exact because `.with()` would only replace the heap type and exact references to basic heap types are disallowed. Rather than checking for and avoiding this error in all the callers, simply drop exactness when `.with()` is called with a basic heap type. This is reasonable behavior because the only alternative is never correct. Add a test that hits an assertion failure without this fix. AbstractTypeRefining replaces a defined type with `none` and the type updating utility does not check whether the new heap type is basic before doing the replacement. --- scripts/test/fuzzing.py | 1 + src/wasm-type.h | 6 +++++- .../passes/abstract-type-refining-exact.wast | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/abstract-type-refining-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 8ddc4982563..96121d4649e 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -114,6 +114,7 @@ 'exact-references-lowering.wast', 'exact-casts.wast', 'exact-casts-trivial.wast', + 'abstract-type-refining-exact.wast', 'optimize-instructions-exact.wast', 'optimize-instructions-all-casts.wast', 'optimize-instructions-all-casts-exact.wast', diff --git a/src/wasm-type.h b/src/wasm-type.h index 4ecbf6ab379..579d65e727a 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -418,8 +418,12 @@ class Type { } // Return a new reference type with some part updated to the specified value. + // Always clear exactness when replacing the referenced type with a basic heap + // type to avoid creating an invalid type. Type with(HeapType heapType) const { - return Type(heapType, getNullability(), getExactness()); + return Type(heapType, + getNullability(), + heapType.isBasic() ? Inexact : getExactness()); } Type with(Nullability nullability) const { return Type(getHeapType(), nullability, getExactness()); diff --git a/test/lit/passes/abstract-type-refining-exact.wast b/test/lit/passes/abstract-type-refining-exact.wast new file mode 100644 index 00000000000..dec05e0f328 --- /dev/null +++ b/test/lit/passes/abstract-type-refining-exact.wast @@ -0,0 +1,18 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --closed-world --abstract-type-refining -S -o - | filecheck %s + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $foo (struct)) + (type $foo (struct)) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (local $0 (ref none)) + ;; CHECK-NEXT: ) + (func $test + ;; $foo will be replaced with none` and the exactness should be dropped + ;; without errors. + (local (ref (exact $foo))) + ) +) From 3b0ab2cfcad2c342a68a1125d69ca2dcf929c926 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 29 Apr 2025 13:28:14 -0700 Subject: [PATCH 471/622] [NFC] Make public type collection more efficient (#7561) Previously public type collection worked by collecting all types in the module, classifying their visibility, then picking out all the public types to return. However, visibility classification requires directly finding the public types from the imports and exports in the first place, so the initial step of collecting all the types was unnecessary. Refactor the code so `getPublicHeapTypes` calculates the public types directly from the imports and exports and `classifyTypes` uses those results to classify visibility only when collecting all types from the module anyway. --- src/ir/module-utils.cpp | 85 +++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 7f2dfcc089c..c19ae369eb9 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -590,11 +590,46 @@ namespace { void classifyTypeVisibility(Module& wasm, InsertOrderedMap& types) { - // We will need to traverse the types used by public types and mark them - // public as well. + for (auto type : getPublicHeapTypes(wasm)) { + if (auto it = types.find(type); it != types.end()) { + it->second.visibility = Visibility::Public; + } + } + for (auto& [type, info] : types) { + if (info.visibility != Visibility::Public) { + info.visibility = Visibility::Private; + } + } +} + +void setIndices(IndexedHeapTypes& indexedTypes) { + for (Index i = 0; i < indexedTypes.types.size(); i++) { + indexedTypes.indices[indexedTypes.types[i]] = i; + } +} + +} // anonymous namespace + +std::vector collectHeapTypes(Module& wasm) { + auto info = collectHeapTypeInfo(wasm); + std::vector types; + types.reserve(info.size()); + for (auto& [type, _] : info) { + types.push_back(type); + } + return types; +} + +std::vector getPublicHeapTypes(Module& wasm) { + // Look at the types of imports as exports to get an initial set of public + // types, then traverse the types used by public types and collect the + // transitively reachable public types as well. std::vector workList; std::unordered_set publicGroups; + // The collected types. + std::vector publicTypes; + auto notePublic = [&](HeapType type) { if (type.isBasic()) { return; @@ -604,12 +639,8 @@ void classifyTypeVisibility(Module& wasm, // The groups in this type have already been marked public. return; } - for (auto member : type.getRecGroup()) { - if (auto it = types.find(member); it != types.end()) { - it->second.visibility = Visibility::Public; - } - workList.push_back(member); - } + publicTypes.insert(publicTypes.end(), group.begin(), group.end()); + workList.insert(workList.end(), group.begin(), group.end()); }; ModuleUtils::iterImportedTags(wasm, [&](Tag* tag) { notePublic(tag->type); }); @@ -675,46 +706,10 @@ void classifyTypeVisibility(Module& wasm, } } - for (auto& [_, info] : types) { - if (info.visibility != Visibility::Public) { - info.visibility = Visibility::Private; - } - } - // TODO: In an open world, we need to consider subtypes of public types public // as well, or potentially even consider all types to be public unless // otherwise annotated. -} - -void setIndices(IndexedHeapTypes& indexedTypes) { - for (Index i = 0; i < indexedTypes.types.size(); i++) { - indexedTypes.indices[indexedTypes.types[i]] = i; - } -} - -} // anonymous namespace - -std::vector collectHeapTypes(Module& wasm) { - auto info = collectHeapTypeInfo(wasm); - std::vector types; - types.reserve(info.size()); - for (auto& [type, _] : info) { - types.push_back(type); - } - return types; -} - -std::vector getPublicHeapTypes(Module& wasm) { - auto info = collectHeapTypeInfo( - wasm, TypeInclusion::BinaryTypes, VisibilityHandling::FindVisibility); - std::vector types; - types.reserve(info.size()); - for (auto& [type, typeInfo] : info) { - if (typeInfo.visibility == Visibility::Public) { - types.push_back(type); - } - } - return types; + return publicTypes; } std::vector getPrivateHeapTypes(Module& wasm) { From d48529ddbda51af6290f03f98d53b1276b2544e8 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Tue, 29 Apr 2025 13:41:41 -0700 Subject: [PATCH 472/622] [Outlining] Add Try/Catch/CatchAll (#7472) Supports try/catch/catchAll in the stringify of the module. Its contents can now be outlined. --- src/passes/Outlining.cpp | 14 ++++++ src/passes/stringify-walker-impl.h | 16 +++--- src/passes/stringify-walker.h | 45 ++++++++++------- src/wasm-ir-builder.h | 23 +++++++-- src/wasm/wasm-ir-builder.cpp | 29 +++++++++-- test/gtest/stringify.cpp | 13 ++--- test/lit/passes/outlining.wast | 80 ++++++++++++++++++++++++++++++ 7 files changed, 182 insertions(+), 38 deletions(-) diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index 85bc12e5869..0ac2e424b55 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -137,6 +137,20 @@ struct ReconstructStringifyWalker } else if (auto curr = reason.getLoopStart()) { ODBG(desc = "Loop Start at "); ASSERT_OK(existingBuilder.visitLoopStart(curr->loop)); + } else if (auto curr = reason.getTryStart()) { + // We preserve the name of the tryy because IRBuilder expects + // visitTryStart() to be called on an empty Try, during the normal case of + // parsing. TODO: Fix this. + auto name = curr->tryy->name; + ASSERT_OK(existingBuilder.visitTryStart(curr->tryy, Name())); + ODBG(desc = "Try Start at "); + curr->tryy->name = name; + } else if (auto curr = reason.getCatchStart()) { + ASSERT_OK(existingBuilder.visitCatch(curr->tag)); + ODBG(desc = "Catch Start at "); + } else if (reason.getCatchAllStart()) { + ASSERT_OK(existingBuilder.visitCatchAll()); + ODBG(desc = "Catch All Start at"); } else if (reason.getEnd()) { ODBG(desc = "End at "); ASSERT_OK(existingBuilder.visitEnd()); diff --git a/src/passes/stringify-walker-impl.h b/src/passes/stringify-walker-impl.h index 018d851dd25..6befc8e80c1 100644 --- a/src/passes/stringify-walker-impl.h +++ b/src/passes/stringify-walker-impl.h @@ -97,14 +97,18 @@ template void StringifyWalker::dequeueControlFlow() { } case Expression::Id::TryId: { auto* tryy = curr->cast(); - addUniqueSymbol(SeparatorReason::makeTryBodyStart()); + + addUniqueSymbol(SeparatorReason::makeTryStart(tryy)); Super::walk(tryy->body); - addUniqueSymbol(SeparatorReason::makeEnd()); - for (auto& child : tryy->catchBodies) { - addUniqueSymbol(SeparatorReason::makeTryCatchStart()); - Super::walk(child); - addUniqueSymbol(SeparatorReason::makeEnd()); + for (size_t i = 0; i < tryy->catchBodies.size(); i++) { + if (tryy->hasCatchAll() && i == tryy->catchBodies.size() - 1) { + addUniqueSymbol(SeparatorReason::makeCatchAllStart()); + } else { + addUniqueSymbol(SeparatorReason::makeCatchStart(tryy->catchTags[i])); + } + Super::walk(tryy->catchBodies[i]); } + addUniqueSymbol(SeparatorReason::makeEnd()); break; } case Expression::Id::LoopId: { diff --git a/src/passes/stringify-walker.h b/src/passes/stringify-walker.h index d46334d3b84..ef71f0cf9be 100644 --- a/src/passes/stringify-walker.h +++ b/src/passes/stringify-walker.h @@ -89,9 +89,15 @@ struct StringifyWalker Loop* loop; }; - struct TryBodyStart {}; + struct TryStart { + Try* tryy; + }; + + struct CatchStart { + Name tag; + }; - struct TryCatchStart {}; + struct CatchAllStart {}; struct End { Expression* curr; @@ -101,8 +107,9 @@ struct StringifyWalker IfStart, ElseStart, LoopStart, - TryBodyStart, - TryCatchStart, + TryStart, + CatchStart, + CatchAllStart, End>; Separator reason; @@ -124,11 +131,14 @@ struct StringifyWalker static SeparatorReason makeLoopStart(Loop* loop) { return SeparatorReason(LoopStart{loop}); } - static SeparatorReason makeTryCatchStart() { - return SeparatorReason(TryCatchStart{}); + static SeparatorReason makeTryStart(Try* tryy) { + return SeparatorReason(TryStart{tryy}); } - static SeparatorReason makeTryBodyStart() { - return SeparatorReason(TryBodyStart{}); + static SeparatorReason makeCatchStart(Name tag) { + return SeparatorReason(CatchStart{tag}); + } + static SeparatorReason makeCatchAllStart() { + return SeparatorReason(CatchAllStart{}); } static SeparatorReason makeEnd() { return SeparatorReason(End{}); } FuncStart* getFuncStart() { return std::get_if(&reason); } @@ -136,11 +146,10 @@ struct StringifyWalker IfStart* getIfStart() { return std::get_if(&reason); } ElseStart* getElseStart() { return std::get_if(&reason); } LoopStart* getLoopStart() { return std::get_if(&reason); } - TryBodyStart* getTryBodyStart() { - return std::get_if(&reason); - } - TryCatchStart* getTryCatchStart() { - return std::get_if(&reason); + TryStart* getTryStart() { return std::get_if(&reason); } + CatchStart* getCatchStart() { return std::get_if(&reason); } + CatchAllStart* getCatchAllStart() { + return std::get_if(&reason); } End* getEnd() { return std::get_if(&reason); } }; @@ -158,10 +167,12 @@ struct StringifyWalker return o << "Else Start"; } else if (reason.getLoopStart()) { return o << "Loop Start"; - } else if (reason.getTryBodyStart()) { - return o << "Try Body Start"; - } else if (reason.getTryCatchStart()) { - return o << "Try Catch Start"; + } else if (reason.getTryStart()) { + return o << "Try Start"; + } else if (reason.getCatchStart()) { + return o << "Catch Start"; + } else if (reason.getCatchAllStart()) { + return o << "Catch All Start"; } else if (reason.getEnd()) { return o << "End"; } diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index fb2727bf94c..7fbe089c127 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -306,14 +306,17 @@ class IRBuilder : public UnifiedExpressionVisitor> { struct TryScope { Try* tryy; Name originalLabel; + Index index; }; struct CatchScope { Try* tryy; Name originalLabel; + Index index; }; struct CatchAllScope { Try* tryy; Name originalLabel; + Index index; }; struct TryTableScope { TryTable* trytable; @@ -396,15 +399,17 @@ class IRBuilder : public UnifiedExpressionVisitor> { return ScopeCtx(LoopScope{loop}, inputType); } static ScopeCtx makeTry(Try* tryy, Name originalLabel, Type inputType) { - return ScopeCtx(TryScope{tryy, originalLabel}, inputType); + return ScopeCtx(TryScope{tryy, originalLabel, 0}, inputType); } static ScopeCtx makeCatch(ScopeCtx&& scope, Try* tryy) { - scope.scope = CatchScope{tryy, scope.getOriginalLabel()}; + scope.scope = + CatchScope{tryy, scope.getOriginalLabel(), scope.getIndex() + 1}; scope.resetForDelimiter(/*keepInput=*/false); return scope; } static ScopeCtx makeCatchAll(ScopeCtx&& scope, Try* tryy) { - scope.scope = CatchAllScope{tryy, scope.getOriginalLabel()}; + scope.scope = + CatchAllScope{tryy, scope.getOriginalLabel(), scope.getIndex() + 1}; scope.resetForDelimiter(/*keepInput=*/false); return scope; } @@ -530,6 +535,18 @@ class IRBuilder : public UnifiedExpressionVisitor> { } WASM_UNREACHABLE("unexpected scope kind"); } + Index getIndex() { + if (auto* tryScope = std::get_if(&scope)) { + return tryScope->index; + } + if (auto* catchScope = std::get_if(&scope)) { + return catchScope->index; + } + if (auto* catchAllScope = std::get_if(&scope)) { + return catchAllScope->index; + } + WASM_UNREACHABLE("unexpected scope kind"); + } Type getLabelType() { // Loops receive their input type rather than their output type. return getLoop() ? inputType : getResultType(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 1ea7b86afdd..0928c99c927 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -931,6 +931,21 @@ Result<> IRBuilder::visitElse() { return pushScope(ScopeCtx::makeElse(std::move(scope))); } +void setCatchBody(Try* tryy, Expression* expr, Index index) { + // Indexes are managed manually to support Outlining. + // Its prepopulated try catchBodies and catchTags vectors + // cannot be appended to, as in the case of the empty try + // used during parsing. + if (tryy->catchBodies.size() < index) { + tryy->catchBodies.resize(tryy->catchBodies.size() + 1); + } + // The first time visitCatch is called: the body of the + // try is set and catchBodies is not appended to, but the tag + // for the following catch is appended. So, catchTags uses + // index as-is, but catchBodies uses index-1. + tryy->catchBodies[index - 1] = expr; +} + Result<> IRBuilder::visitCatch(Name tag) { auto scope = getScope(); bool wasTry = true; @@ -942,14 +957,18 @@ Result<> IRBuilder::visitCatch(Name tag) { if (!tryy) { return Err{"unexpected catch"}; } + auto index = scope.getIndex(); auto expr = finishScope(); CHECK_ERR(expr); if (wasTry) { tryy->body = *expr; } else { - tryy->catchBodies.push_back(*expr); + setCatchBody(tryy, *expr, index); + } + if (tryy->catchTags.size() == index) { + tryy->catchTags.resize(tryy->catchTags.size() + 1); } - tryy->catchTags.push_back(tag); + tryy->catchTags[index] = tag; if (binaryPos && func) { auto& delimiterLocs = func->delimiterLocations[tryy]; @@ -980,12 +999,13 @@ Result<> IRBuilder::visitCatchAll() { if (!tryy) { return Err{"unexpected catch"}; } + auto index = scope.getIndex(); auto expr = finishScope(); CHECK_ERR(expr); if (wasTry) { tryy->body = *expr; } else { - tryy->catchBodies.push_back(*expr); + setCatchBody(tryy, *expr, index); } if (binaryPos && func) { @@ -1123,7 +1143,8 @@ Result<> IRBuilder::visitEnd() { push(maybeWrapForLabel(tryy)); } else if (Try * tryy; (tryy = scope.getCatch()) || (tryy = scope.getCatchAll())) { - tryy->catchBodies.push_back(*expr); + auto index = scope.getIndex(); + setCatchBody(tryy, *expr, index); tryy->name = scope.label; tryy->finalize(tryy->type); push(maybeWrapForLabel(tryy)); diff --git a/test/gtest/stringify.cpp b/test/gtest/stringify.cpp index cad0fd37d9d..6028d4aabf6 100644 --- a/test/gtest/stringify.cpp +++ b/test/gtest/stringify.cpp @@ -98,19 +98,16 @@ adding unique symbol for End adding unique symbol for If Start in visitExpression for i32.const 30 adding unique symbol for End -adding unique symbol for Try Body Start +adding unique symbol for Try Start in visitExpression for nop -adding unique symbol for End -adding unique symbol for Try Catch Start +adding unique symbol for Catch Start in visitExpression for block -adding unique symbol for End -adding unique symbol for Try Catch Start +adding unique symbol for Catch Start in visitExpression for block adding unique symbol for End -adding unique symbol for Try Body Start +adding unique symbol for Try Start in visitExpression for nop -adding unique symbol for End -adding unique symbol for Try Catch Start +adding unique symbol for Catch Start in visitExpression for block adding unique symbol for End adding unique symbol for Block Start diff --git a/test/lit/passes/outlining.wast b/test/lit/passes/outlining.wast index c608ec7d7d9..6de0990cbcd 100644 --- a/test/lit/passes/outlining.wast +++ b/test/lit/passes/outlining.wast @@ -1164,3 +1164,83 @@ ) ) ) + +;; Tests that the contents of Catch are outlined +(module + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (type $0 (func)) + (type $0 (func)) + (type $1 (func (result i32))) + ;; CHECK: (tag $eimport$1 (type $0)) + (tag $eimport$1 (type $0)) + ;; CHECK: (func $outline$ (type $1) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -2147483647) + ;; CHECK-NEXT: ) + + ;; CHECK: (func $a (type $1) (result i32) + ;; CHECK-NEXT: (local $0 externref) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const -20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $eimport$1 + ;; CHECK-NEXT: (call $outline$) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const -15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (result i32) + (local $0 externref) + (try (result i32) + (do + (i32.const -20) + ) + (catch $eimport$1 + (drop + (i32.const -12) + ) + (i32.const -2147483647) + ) + (catch_all + (i32.const -15) + ) + ) + ) + ;; CHECK: (func $b (type $1) (result i32) + ;; CHECK-NEXT: (local $0 externref) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const -20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $eimport$1 + ;; CHECK-NEXT: (call $outline$) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const -15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $b (result i32) + (local $0 externref) + (try (result i32) + (do + (i32.const -20) + ) + (catch $eimport$1 + (drop + (i32.const -12) + ) + (i32.const -2147483647) + ) + (catch_all + (i32.const -15) + ) + ) + ) +) From 20e7058347821490690c426adb8935ec1dada9e0 Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Tue, 29 Apr 2025 14:41:25 -0700 Subject: [PATCH 473/622] [Outlining] Add TryTable (#7504) Supports try_table in the stringify of the module, and filters out try_table outlining sequences. --- src/passes/Outlining.cpp | 9 +++-- src/passes/hash-stringify-walker.cpp | 3 +- src/passes/stringify-walker-impl.h | 7 ++++ src/passes/stringify-walker.h | 13 ++++++ test/lit/passes/outlining.wast | 60 ++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index 0ac2e424b55..80bbcd05071 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -151,6 +151,9 @@ struct ReconstructStringifyWalker } else if (reason.getCatchAllStart()) { ASSERT_OK(existingBuilder.visitCatchAll()); ODBG(desc = "Catch All Start at"); + } else if (auto curr = reason.getTryTableStart()) { + ODBG(desc = "Try Table Start at "); + ASSERT_OK(existingBuilder.visitTryTableStart(curr->tryt)); } else if (reason.getEnd()) { ODBG(desc = "End at "); ASSERT_OK(existingBuilder.visitEnd()); @@ -346,9 +349,9 @@ struct Outlining : public Pass { substrings = StringifyProcessor::dedupe(substrings); // Remove substrings with overlapping indices. substrings = StringifyProcessor::filterOverlaps(substrings); - // Remove substrings with branch and return instructions until an analysis - // is performed to see if the intended destination of the branch is included - // in the substring to be outlined. + // Remove substrings with branch, return, and try_table instructions until + // an analysis is performed to see if the intended destination of the branch + // is included in the substring to be outlined. substrings = StringifyProcessor::filterBranches(substrings, stringify.exprs); // Remove substrings with local.set instructions until Outlining is extended diff --git a/src/passes/hash-stringify-walker.cpp b/src/passes/hash-stringify-walker.cpp index 0f65ccc3f46..76a840d4ffd 100644 --- a/src/passes/hash-stringify-walker.cpp +++ b/src/passes/hash-stringify-walker.cpp @@ -269,7 +269,8 @@ std::vector StringifyProcessor::filterBranches( const std::vector& exprs) { return StringifyProcessor::filter( substrings, exprs, [](const Expression* curr) { - return Properties::isBranch(curr) || curr->is(); + return Properties::isBranch(curr) || curr->is() || + curr->is(); }); } diff --git a/src/passes/stringify-walker-impl.h b/src/passes/stringify-walker-impl.h index 6befc8e80c1..5829f29df90 100644 --- a/src/passes/stringify-walker-impl.h +++ b/src/passes/stringify-walker-impl.h @@ -118,6 +118,13 @@ template void StringifyWalker::dequeueControlFlow() { addUniqueSymbol(SeparatorReason::makeEnd()); break; } + case Expression::Id::TryTableId: { + auto* tryt = curr->cast(); + addUniqueSymbol(SeparatorReason::makeTryTableStart(tryt)); + Super::walk(tryt->body); + addUniqueSymbol(SeparatorReason::makeEnd()); + break; + } default: { assert(Properties::isControlFlowStructure(curr)); WASM_UNREACHABLE("unexpected expression"); diff --git a/src/passes/stringify-walker.h b/src/passes/stringify-walker.h index ef71f0cf9be..75fa668109f 100644 --- a/src/passes/stringify-walker.h +++ b/src/passes/stringify-walker.h @@ -99,6 +99,10 @@ struct StringifyWalker struct CatchAllStart {}; + struct TryTableStart { + TryTable* tryt; + }; + struct End { Expression* curr; }; @@ -110,6 +114,7 @@ struct StringifyWalker TryStart, CatchStart, CatchAllStart, + TryTableStart, End>; Separator reason; @@ -140,6 +145,9 @@ struct StringifyWalker static SeparatorReason makeCatchAllStart() { return SeparatorReason(CatchAllStart{}); } + static SeparatorReason makeTryTableStart(TryTable* tryt) { + return SeparatorReason(TryTableStart{tryt}); + } static SeparatorReason makeEnd() { return SeparatorReason(End{}); } FuncStart* getFuncStart() { return std::get_if(&reason); } BlockStart* getBlockStart() { return std::get_if(&reason); } @@ -151,6 +159,9 @@ struct StringifyWalker CatchAllStart* getCatchAllStart() { return std::get_if(&reason); } + TryTableStart* getTryTableStart() { + return std::get_if(&reason); + } End* getEnd() { return std::get_if(&reason); } }; @@ -173,6 +184,8 @@ struct StringifyWalker return o << "Catch Start"; } else if (reason.getCatchAllStart()) { return o << "Catch All Start"; + } else if (reason.getTryTableStart()) { + return o << "Try Table Start"; } else if (reason.getEnd()) { return o << "End"; } diff --git a/test/lit/passes/outlining.wast b/test/lit/passes/outlining.wast index 6de0990cbcd..3dafbf8fe28 100644 --- a/test/lit/passes/outlining.wast +++ b/test/lit/passes/outlining.wast @@ -1244,3 +1244,63 @@ ) ) ) + +;; Tests TryTable instructions are correctly filtered from being outlined. +;; The (drop (i32.const 0)) instructions were added to form an outlineable +;; sequence with the block that contains the try_table. +(module + ;; CHECK: (type $1 (func (result (ref exn)))) + + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (tag $tag$0 (type $0)) + (tag $tag$0 (type $0)) + ;; CHECK: (func $a (type $1) (result (ref exn)) + ;; CHECK-NEXT: (loop $label1 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (try_table (catch_all $label1) + ;; CHECK-NEXT: (throw $tag$0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (result (ref exn)) + (loop $label1 (result (ref exn)) + (drop + (i32.const 0) + ) + (block (result (ref exn)) + (try_table (catch_all $label1) + (throw $tag$0) + ) + ) + ) + ) + ;; CHECK: (func $b (type $1) (result (ref exn)) + ;; CHECK-NEXT: (loop $label1 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (try_table (catch_all $label1) + ;; CHECK-NEXT: (throw $tag$0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $b (result (ref exn)) + (loop $label1 (result (ref exn)) + (drop + (i32.const 0) + ) + (block (result (ref exn)) + (try_table (catch_all $label1) + (throw $tag$0) + ) + ) + ) + ) +) From f4a0b18618a9553e9283a92db2df5d436b572a2e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 29 Apr 2025 21:20:29 -0700 Subject: [PATCH 474/622] Disallow exact references in public types (#7554) When custom descriptors are disabled, validate that public types do not contain exact references. If they did, we would drop the exactness and change the identity of the public type during binary writing, which would be incorrect. This still allows internal usage of exact types without custom descriptors enabled, and it is up to the individual passes to ensure that the eventual erasing of exactness does not cause any problems. --- src/wasm/wasm-validator.cpp | 29 +++++++++++++++++++++++++++ test/lit/validation/public-exact.wast | 23 +++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 test/lit/validation/public-exact.wast diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 62a970bbb12..03446d51efe 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3970,6 +3970,34 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { // Main validator class +static void validateTypes(Module& module, ValidationInfo& info) { + // Check that public types do not contain any exact references if custom + // descriptors is not enabled. If they did, we would erase the exactness + // during binary writing and change the public type identities. + if (module.features.hasCustomDescriptors()) { + return; + } + + for (auto type : ModuleUtils::getPublicHeapTypes(module)) { + for (auto child : type.getTypeChildren()) { + if (child.isExact()) { + std::string typeName; + if (auto it = module.typeNames.find(type); + it != module.typeNames.end()) { + typeName = '$' + it->second.name.toString(); + } else { + typeName = type.toString(); + } + info.fail("Exact reference in public type not allowed without custom " + "descriptors [--enable-custom-descriptors]", + typeName, + nullptr); + break; + } + } + } +} + static void validateImports(Module& module, ValidationInfo& info) { ModuleUtils::iterImportedFunctions(module, [&](Function* curr) { if (curr->getResults().isTuple()) { @@ -4439,6 +4467,7 @@ bool WasmValidator::validate(Module& module, Flags flags) { // Validate globally. if (info.validateGlobally) { + validateTypes(module, info); validateImports(module, info); validateExports(module, info); validateGlobals(module, info); diff --git a/test/lit/validation/public-exact.wast b/test/lit/validation/public-exact.wast new file mode 100644 index 00000000000..9c1c76a58ce --- /dev/null +++ b/test/lit/validation/public-exact.wast @@ -0,0 +1,23 @@ +;; Test that exact references in public types are disallowed without custom descriptors + +;; RUN: not wasm-opt %s -all --disable-custom-descriptors 2>&1 | filecheck %s +;; RUN: wasm-opt %s -all -S -o - | filecheck %s --check-prefix=NOERR + +;; CHECK: [wasm-validator error in module] Exact reference in public type not allowed without custom descriptors [--enable-custom-descriptors], on +;; CHECK-NEXT: $struct +;; CHECK-NEXT: [wasm-validator error in module] Exact reference in public type not allowed without custom descriptors [--enable-custom-descriptors], on +;; CHECK-NEXT: $array +;; CHECK-NEXT: [wasm-validator error in module] Exact reference in public type not allowed without custom descriptors [--enable-custom-descriptors], on +;; CHECK-NEXT: $func + +;; NOERR: (module + +(module + (type $struct (struct (field (ref null (exact $struct))))) + (type $array (array (field (ref (exact $struct))))) + (type $func (func (param (ref null (exact $struct))) (result (ref (exact $array))))) + + (import "" "struct" (global $struct (ref $struct))) + (import "" "array" (global $array (ref $array))) + (import "" "func" (global $func (ref $func))) +) From 51727f65ca957dc79a5c0d1ad34a8d4e98c85c71 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 29 Apr 2025 21:23:13 -0700 Subject: [PATCH 475/622] Ignore public-exact.wast in fuzzing.py (#7562) --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 96121d4649e..571c7777939 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -127,6 +127,7 @@ 'type-merging-exact.wast', 'type-refining-exact.wast', 'type-refining-gufa-exact.wast', + 'public-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending From 762dd9cfa7a2084de0380e9089780bc21df1fbc8 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 29 Apr 2025 22:20:25 -0700 Subject: [PATCH 476/622] Handle exactness in MinimizeRecGroups (#7555) Treat rec groups differing only in the exactness of a reference as different, but only when custom descriptors is enabled. When custom descriptors is not enabled, exactness will be erased before the binary is written, so if two minimized rec groups differed only in exactness, they would in fact be written as the same rec group. This would change the behavior of casts meant to differentiate between types in that rec group, so it would be incorrect. --- scripts/test/fuzzing.py | 2 + src/passes/MinimizeRecGroups.cpp | 39 ++++++++------ src/tools/wasm-fuzz-types.cpp | 6 +-- src/wasm-type-shape.h | 12 ++++- src/wasm/wasm-type-shape.cpp | 13 +++++ .../lit/passes/minimize-rec-groups-exact.wast | 19 +++++++ .../minimize-rec-groups-ignore-exact.wast | 53 +++++++++++++++++++ 7 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 test/lit/passes/minimize-rec-groups-exact.wast create mode 100644 test/lit/passes/minimize-rec-groups-ignore-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 571c7777939..613e5a7779b 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -127,6 +127,8 @@ 'type-merging-exact.wast', 'type-refining-exact.wast', 'type-refining-gufa-exact.wast', + 'mimimize-rec-groups-exact.wast', + 'mimimize-rec-groups-ignore-exact.wast', 'public-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', diff --git a/src/passes/MinimizeRecGroups.cpp b/src/passes/MinimizeRecGroups.cpp index 0faf297f545..4d727bd907f 100644 --- a/src/passes/MinimizeRecGroups.cpp +++ b/src/passes/MinimizeRecGroups.cpp @@ -208,14 +208,14 @@ struct GroupClassInfo { static std::vector> initSubtypeGraph(RecGroupInfo& info); GroupClassInfo(RecGroupInfo& info); - void advance() { + void advance(FeatureSet features) { ++orders; if (orders == orders.end()) { - advanceBrand(); + advanceBrand(features); } } - void advanceBrand() { + void advanceBrand(FeatureSet features) { if (brand) { ++*brand; } else { @@ -231,8 +231,8 @@ struct GroupClassInfo { } } // Make sure the brand is not the same as the real type. - if (singletonType && - RecGroupShape({**brand}) == RecGroupShape({*singletonType})) { + if (singletonType && RecGroupShape({**brand}, features) == + RecGroupShape({*singletonType}, features)) { ++*brand; } // Start back at the initial permutation with the new brand. @@ -370,9 +370,13 @@ struct MinimizeRecGroups : Pass { // whose shapes we need to check for uniqueness to avoid deep recursions. std::vector shapesToUpdate; + // The comparison of rec group shapes depends on the features. + FeatureSet features; + void run(Module* module) override { + features = module->features; // There are no recursion groups to minimize if GC is not enabled. - if (!module->features.hasGC()) { + if (!features.hasGC()) { return; } @@ -402,7 +406,7 @@ struct MinimizeRecGroups : Pass { for (auto group : publicGroups) { publicGroupTypes.emplace_back(group.begin(), group.end()); [[maybe_unused]] auto [_, inserted] = groupShapeIndices.insert( - {RecGroupShape(publicGroupTypes.back()), PublicGroupIndex}); + {RecGroupShape(publicGroupTypes.back(), features), PublicGroupIndex}); assert(inserted); } @@ -452,8 +456,8 @@ struct MinimizeRecGroups : Pass { } void updateShape(Index group) { - auto [it, inserted] = - groupShapeIndices.insert({RecGroupShape(groups[group].group), group}); + auto [it, inserted] = groupShapeIndices.insert( + {RecGroupShape(groups[group].group, features), group}); if (inserted) { // This shape was unique. We're done. return; @@ -509,7 +513,7 @@ struct MinimizeRecGroups : Pass { // We have everything we need to generate the next permutation of this // group. auto& classInfo = *groups[groupRep].classInfo; - classInfo.advance(); + classInfo.advance(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); return; @@ -538,7 +542,7 @@ struct MinimizeRecGroups : Pass { // Move to the next permutation after advancing the type brand to skip // further repeated shapes. - classInfo.advanceBrand(); + classInfo.advanceBrand(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); @@ -556,7 +560,7 @@ struct MinimizeRecGroups : Pass { // conflict. if (groups[groupRep].classInfo && groups[otherRep].classInfo) { auto& classInfo = *groups[groupRep].classInfo; - classInfo.advance(); + classInfo.advance(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); return; @@ -578,7 +582,7 @@ struct MinimizeRecGroups : Pass { // same shape. Advance `group` to the next permutation. otherInfo.classInfo = std::nullopt; otherInfo.permutation = groupInfo.permutation; - classInfo.advance(); + classInfo.advance(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); @@ -600,7 +604,7 @@ struct MinimizeRecGroups : Pass { // permutation. groupInfo.classInfo = std::nullopt; groupInfo.permutation = otherInfo.permutation; - classInfo.advance(); + classInfo.advance(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); @@ -754,9 +758,10 @@ struct MinimizeRecGroups : Pass { // shapes to lists of automorphically equivalent root types. std::map> typeClasses; for (const auto& order : dfsOrders) { - ComparableRecGroupShape shape(order, [this](HeapType a, HeapType b) { - return this->typeIndices.at(a) < this->typeIndices.at(b); - }); + ComparableRecGroupShape shape( + order, features, [this](HeapType a, HeapType b) { + return this->typeIndices.at(a) < this->typeIndices.at(b); + }); typeClasses[shape].push_back(order[0]); } diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index 7ba341e09df..0f5e8bff2a6 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -542,7 +542,7 @@ void Fuzzer::checkRecGroupShapes() { }; for (size_t i = 0; i < groups.size(); ++i) { - ComparableRecGroupShape shape(groups[i], less); + ComparableRecGroupShape shape(groups[i], FeatureSet::All, less); // A rec group should compare equal to itself. if (shape != shape) { Fatal() << "Rec group shape " << i << " not equal to itself"; @@ -556,7 +556,7 @@ void Fuzzer::checkRecGroupShapes() { // Check how it compares to other groups. for (size_t j = i + 1; j < groups.size(); ++j) { - ComparableRecGroupShape other(groups[j], less); + ComparableRecGroupShape other(groups[j], FeatureSet::All, less); bool isLess = shape < other; bool isEq = shape == other; bool isGreater = shape > other; @@ -598,7 +598,7 @@ void Fuzzer::checkRecGroupShapes() { if (j + 1 < groups.size()) { // Check transitivity. - RecGroupShape third(groups[j + 1]); + RecGroupShape third(groups[j + 1], FeatureSet::All); if ((isLess && other <= third && shape >= third) || (isEq && other == third && shape != third) || (isGreater && other >= third && shape <= third)) { diff --git a/src/wasm-type-shape.h b/src/wasm-type-shape.h index 5eb4250f0df..e72f28dd530 100644 --- a/src/wasm-type-shape.h +++ b/src/wasm-type-shape.h @@ -20,6 +20,7 @@ #include #include +#include "wasm-features.h" #include "wasm-type.h" namespace wasm { @@ -35,7 +36,13 @@ namespace wasm { struct RecGroupShape { const std::vector& types; - RecGroupShape(const std::vector& types) : types(types) {} + // Depending on the feature set, some types may be generalized when they are + // written out. Take the features into account to ensure our comparisons + // account for the rec groups that will ultimately be written. + const FeatureSet features; + + RecGroupShape(const std::vector& types, const FeatureSet features) + : types(types), features(features) {} bool operator==(const RecGroupShape& other) const; bool operator!=(const RecGroupShape& other) const { @@ -51,8 +58,9 @@ struct ComparableRecGroupShape : RecGroupShape { std::function less; ComparableRecGroupShape(const std::vector& types, + FeatureSet features, std::function less) - : RecGroupShape(types), less(less) {} + : RecGroupShape(types, features), less(less) {} bool operator<(const RecGroupShape& other) const; bool operator>(const RecGroupShape& other) const; diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index 734c5e4b903..522ee45bb3e 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -25,6 +25,7 @@ namespace { enum Comparison { EQ, LT, GT }; template struct RecGroupComparator { + FeatureSet features; std::unordered_map indicesA; std::unordered_map indicesB; CompareTypes compareTypes; @@ -32,6 +33,8 @@ template struct RecGroupComparator { RecGroupComparator(CompareTypes compareTypes) : compareTypes(compareTypes) {} Comparison compare(const RecGroupShape& a, const RecGroupShape& b) { + assert(a.features == b.features); + features = a.features; if (a.types.size() != b.types.size()) { return a.types.size() < b.types.size() ? LT : GT; } @@ -147,6 +150,11 @@ template struct RecGroupComparator { return compare(a.getTuple(), b.getTuple()); } assert(a.isRef() && b.isRef()); + // Only consider exactness if custom descriptors are enabled. Otherwise, it + // will be erased when the types are written, so we ignore it here, too. + if (features.hasCustomDescriptors() && a.isExact() != b.isExact()) { + return a.isExact() < b.isExact() ? LT : GT; + } if (a.isNullable() != b.isNullable()) { return a.isNullable() < b.isNullable() ? LT : GT; } @@ -201,9 +209,11 @@ template RecGroupComparator(CompareTypes) -> RecGroupComparator; struct RecGroupHasher { + FeatureSet features; std::unordered_map typeIndices; size_t hash(const RecGroupShape& shape) { + features = shape.features; for (auto type : shape.types) { typeIndices.insert({type, typeIndices.size()}); } @@ -285,6 +295,9 @@ struct RecGroupHasher { return digest; } assert(type.isRef()); + if (features.hasCustomDescriptors()) { + wasm::rehash(digest, type.isExact()); + } wasm::rehash(digest, type.isNullable()); hash_combine(digest, hash(type.getHeapType())); return digest; diff --git a/test/lit/passes/minimize-rec-groups-exact.wast b/test/lit/passes/minimize-rec-groups-exact.wast new file mode 100644 index 00000000000..e1a2a8f85cc --- /dev/null +++ b/test/lit/passes/minimize-rec-groups-exact.wast @@ -0,0 +1,19 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s -all --minimize-rec-groups -S -o - | filecheck %s + +(module + ;; CHECK: (type $foo (struct)) + (type $foo (struct)) + ;; CHECK: (type $exact (struct (field (ref (exact $foo))))) + (type $exact (struct (field (ref (exact $foo))))) + ;; CHECK: (type $inexact (struct (field (ref $foo)))) + (type $inexact (struct (field (ref $foo)))) + + ;; If we didn't differentiate between exact and inexact types, there would be + ;; an assertion failure on adding these public types to the set of public + ;; shapes. + ;; CHECK: (import "" "exact" (global $exact (ref null $exact))) + (import "" "exact" (global $exact (ref null $exact))) + ;; CHECK: (import "" "inexact" (global $inexact (ref null $inexact))) + (import "" "inexact" (global $inexact (ref null $inexact))) +) diff --git a/test/lit/passes/minimize-rec-groups-ignore-exact.wast b/test/lit/passes/minimize-rec-groups-ignore-exact.wast new file mode 100644 index 00000000000..1a1fa8a7c82 --- /dev/null +++ b/test/lit/passes/minimize-rec-groups-ignore-exact.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Check that we take exactness into account correctly depending on the +;; features. It differentiates shapes only when custom descriptors is enabled. + +;; RUN: wasm-opt %s -all --minimize-rec-groups -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --disable-custom-descriptors --minimize-rec-groups -S -o - | filecheck %s --check-prefix=NO_CD + +(module + (rec + (type $foo (struct)) + + ;; This SCC has only one distinct permutation. + ;; CHECK: (rec + ;; CHECK-NEXT: (type $b-inexact (struct (field (ref $a-inexact)))) + + ;; CHECK: (type $a-inexact (struct (field (ref $b-inexact)))) + ;; NO_CD: (rec + ;; NO_CD-NEXT: (type $b-inexact (struct (field (ref $a-inexact)))) + + ;; NO_CD: (type $a-inexact (struct (field (ref $b-inexact)))) + (type $a-inexact (struct (field (ref $b-inexact)))) + (type $b-inexact (struct (field (ref $a-inexact)))) + + ;; This SCC is only different because of exactness. It needs a brand only if + ;; custom descriptors is disabled. + ;; CHECK: (rec + ;; CHECK-NEXT: (type $b-exact (struct (field (ref (exact $a-exact))))) + + ;; CHECK: (type $a-exact (struct (field (ref (exact $b-exact))))) + ;; NO_CD: (rec + ;; NO_CD-NEXT: (type $2 (struct)) + + ;; NO_CD: (type $b-exact (struct (field (ref (exact $a-exact))))) + + ;; NO_CD: (type $a-exact (struct (field (ref (exact $b-exact))))) + (type $a-exact (struct (field (ref (exact $b-exact))))) + (type $b-exact (struct (field (ref (exact $a-exact))))) + ) + + ;; CHECK: (global $a-inexact (ref null $a-inexact) (ref.null none)) + ;; NO_CD: (global $a-inexact (ref null $a-inexact) (ref.null none)) + (global $a-inexact (ref null $a-inexact) (ref.null none)) + ;; CHECK: (global $b-inexact (ref null $b-inexact) (ref.null none)) + ;; NO_CD: (global $b-inexact (ref null $b-inexact) (ref.null none)) + (global $b-inexact (ref null $b-inexact) (ref.null none)) + ;; CHECK: (global $a-exact (ref null $a-exact) (ref.null none)) + ;; NO_CD: (global $a-exact (ref null $a-exact) (ref.null none)) + (global $a-exact (ref null $a-exact) (ref.null none)) + ;; CHECK: (global $b-exact (ref null $b-exact) (ref.null none)) + ;; NO_CD: (global $b-exact (ref null $b-exact) (ref.null none)) + (global $b-exact (ref null $b-exact) (ref.null none)) +) From efb987b9ba6a8fdad8dac2aea2c46dce398ce55d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 30 Apr 2025 11:17:02 -0700 Subject: [PATCH 477/622] Fix typos in fuzzing.py (#7563) Fix ignored test names so the tests are properly ignored. --- scripts/test/fuzzing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 613e5a7779b..dc48bb7033e 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -127,8 +127,8 @@ 'type-merging-exact.wast', 'type-refining-exact.wast', 'type-refining-gufa-exact.wast', - 'mimimize-rec-groups-exact.wast', - 'mimimize-rec-groups-ignore-exact.wast', + 'minimize-rec-groups-exact.wast', + 'minimize-rec-groups-ignore-exact.wast', 'public-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', From 45f3c268a2115e4ff98c08ebdcb3f1f99631d39e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Apr 2025 15:33:43 -0700 Subject: [PATCH 478/622] [Fuzzing] Fuzz V8 with the revectorize flag on ClusterFuzz (#7564) (some of the time) --- scripts/clusterfuzz/run.py | 13 +++++++++++-- test/unit/test_cluster_fuzz.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index 4e7bf6659f0..70bc5a2bde9 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -33,7 +33,12 @@ # The V8 flags we put in the "fuzzer flags" files, which tell ClusterFuzz how to # run V8. By default we apply all staging flags. -FUZZER_FLAGS_FILE_CONTENTS = '--wasm-staging' +FUZZER_FLAGS = '--wasm-staging' + +# Optional V8 flags to add to FUZZER_FLAGS, some of the time. +OPTIONAL_FUZZER_FLAGS = [ + '--experimental-wasm-revectorize', +] # Maximum size of the random data that we feed into wasm-opt -ttf. This is # smaller than fuzz_opt.py's INPUT_SIZE_MAX because that script is tuned for @@ -292,7 +297,11 @@ def main(argv): flags_file_path = os.path.join(output_dir, get_file_name(FLAGS_FILENAME_PREFIX, i)) with open(flags_file_path, 'w') as file: - file.write(FUZZER_FLAGS_FILE_CONTENTS) + flags = FUZZER_FLAGS + # Some of the time add an additional flag for V8. + if OPTIONAL_FUZZER_FLAGS and system_random.random() < 0.5: + flags += ' ' + system_random.choice(OPTIONAL_FUZZER_FLAGS) + file.write(flags) print(f'Created testcase: {testcase_file_path}') diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 72197939d76..50e2f99ac87 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -186,7 +186,7 @@ def test_file_contents(self): # The flags file must contain --wasm-staging with open(flags_file) as f: - self.assertEqual(f.read(), '--wasm-staging') + self.assertIn('--wasm-staging', f.read()) # Extract the wasm file(s) from the JS. Make sure to not notice # stale files. From 527140ac4f02945d43e1b51fae066694fed2396e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 1 May 2025 14:48:09 -0700 Subject: [PATCH 479/622] [NFC] Use a SmallVector for HeapType children (#7565) This was one of danleh's top findings in the mimalloc investigation, where this method accounted for 25% (!) of all allocations. I measured various sizes of SmallVectors and indeed it is possible to get noticeably faster here: this PR is a 5% speedup for -O3 as a whole, tested on a large Java testcase and a large Kotlin testcase. The change to MinimizeRecGroups is NFC (the order is reversed, but it doesn't matters). This is needed to compile, as our current SmallVector doesn't have reverse iteration support (I looked into that for a few minutes but it was not trivial to add; anyhow, the new code is idiomatic in the codebase, I think). --- src/passes/MinimizeRecGroups.cpp | 4 +++- src/wasm-type.h | 16 +++++++++++----- src/wasm/wasm-type.cpp | 8 ++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/passes/MinimizeRecGroups.cpp b/src/passes/MinimizeRecGroups.cpp index 4d727bd907f..e00051de1ba 100644 --- a/src/passes/MinimizeRecGroups.cpp +++ b/src/passes/MinimizeRecGroups.cpp @@ -748,7 +748,9 @@ struct MinimizeRecGroups : Pass { if (seen.insert(curr).second) { dfsOrders[i].push_back(curr); auto children = curr.getReferencedHeapTypes(); - workList.insert(workList.end(), children.rbegin(), children.rend()); + for (auto child : children) { + workList.push_back(child); + } } } assert(dfsOrders[i].size() == types.size()); diff --git a/src/wasm-type.h b/src/wasm-type.h index 579d65e727a..b5e9a88a3c6 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -28,6 +28,7 @@ #include "support/index.h" #include "support/name.h" #include "support/parent_index_iterator.h" +#include "support/small_vector.h" #include "wasm-features.h" // TODO: At various code locations we were assuming that single types are basic @@ -79,6 +80,10 @@ using HeapTypeNameGenerator = std::function; // HeapType. using TypeID = uint64_t; +// The number of HeapType children is typically small (1 for an array, and for +// a struct, in practice <=4 is common). +using HeapTypeChildren = SmallVector; + enum Shareability { Shared, Unshared }; enum class HeapTypeKind { @@ -242,11 +247,12 @@ class HeapType { std::vector getTypeChildren() const; // Return the ordered HeapType children, looking through child Types. - std::vector getHeapTypeChildren() const; + HeapTypeChildren getHeapTypeChildren() const; - // Similar to `getHeapTypeChildren`, but also includes the supertype if it - // exists. - std::vector getReferencedHeapTypes() const; + // Similar to `getHeapTypeChildren`, but also includes references types that + // are not children (i.e. that are not in fields of a struct, etc.; such + // referenced types include the super, and descriptor/described types). + HeapTypeChildren getReferencedHeapTypes() const; // Return the LUB of two HeapTypes, which may or may not exist. static std::optional getLeastUpperBound(HeapType a, HeapType b); @@ -489,7 +495,7 @@ class Type { static bool isSubType(Type left, Type right); // Return the ordered HeapType children, looking through child Types. - std::vector getHeapTypeChildren(); + HeapTypeChildren getHeapTypeChildren(); // Computes the least upper bound from the type lattice. // If one of the type is unreachable, the other type becomes the result. If diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 2605a0aaf37..c4787f07312 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -361,7 +361,7 @@ template struct HeapTypeChildWalker : TypeGraphWalkerBase { }; struct HeapTypeChildCollector : HeapTypeChildWalker { - std::vector children; + HeapTypeChildren children; void noteChild(HeapType type) { children.push_back(type); } }; @@ -753,7 +753,7 @@ bool Type::isSubType(Type left, Type right) { return SubTyper().isSubType(left, right); } -std::vector Type::getHeapTypeChildren() { +HeapTypeChildren Type::getHeapTypeChildren() { HeapTypeChildCollector collector; collector.walkRoot(this); return collector.children; @@ -1175,13 +1175,13 @@ std::vector HeapType::getTypeChildren() const { WASM_UNREACHABLE("unexpected kind"); } -std::vector HeapType::getHeapTypeChildren() const { +HeapTypeChildren HeapType::getHeapTypeChildren() const { HeapTypeChildCollector collector; collector.walkRoot(const_cast(this)); return collector.children; } -std::vector HeapType::getReferencedHeapTypes() const { +HeapTypeChildren HeapType::getReferencedHeapTypes() const { auto types = getHeapTypeChildren(); if (auto super = getDeclaredSuperType()) { types.push_back(*super); From 1df8e5cb4f31cf2a57fcfa616eb1950c83d1f095 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 2 May 2025 14:29:02 -0700 Subject: [PATCH 480/622] [Code Annotations] Start Branch Hinting support, in the text format, for Breaks (#7567) This adds the first minimal amount of work for Branch Hinting (and code annotations, more generally): Just the text format, and just a single instruction (Break). This design follows debug info: Assuming most instructions have no branch hints (like most do not have debug info), we do not add a field to the Break class itself, but to metadata on the side. --------- Co-authored-by: Thomas Lively --- src/parser/contexts.h | 39 ++++++++++++++++++++++++++++++- src/passes/Print.cpp | 38 ++++++++++++++++++++++--------- src/wasm-annotations.h | 32 ++++++++++++++++++++++++++ src/wasm-ir-builder.h | 7 +++--- src/wasm.h | 10 ++++++++ src/wasm/wasm-ir-builder.cpp | 14 ++++++++++-- src/wasm/wasm.cpp | 7 ++++++ test/lit/wat-annotations.wast | 43 +++++++++++++++++++++++++++++++++++ 8 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 src/wasm-annotations.h create mode 100644 test/lit/wat-annotations.wast diff --git a/src/parser/contexts.h b/src/parser/contexts.h index fd835134eb5..b4d27133dcb 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -23,6 +23,7 @@ #include "support/name.h" #include "support/result.h" #include "support/string.h" +#include "wasm-annotations.h" #include "wasm-builder.h" #include "wasm-ir-builder.h" #include "wasm.h" @@ -2339,11 +2340,47 @@ struct ParseDefsCtx : TypeParserCtx { return withLoc(pos, irBuilder.makeCallIndirect(*t, type, isReturn)); } + // Return the branch hint for a branching instruction, if there is one. + std::optional + getBranchHint(const std::vector& annotations) { + // Find and apply (the last) branch hint. + const Annotation* hint = nullptr; + for (auto& a : annotations) { + if (a.kind == Annotations::BranchHint) { + hint = &a; + } + } + if (!hint) { + return std::nullopt; + } + + Lexer lexer(hint->contents); + if (lexer.empty()) { + std::cerr << "warning: empty BranchHint\n"; + return std::nullopt; + } + + auto str = lexer.takeString(); + if (!str || str->size() != 1) { + std::cerr << "warning: invalid BranchHint string\n"; + return std::nullopt; + } + + auto value = (*str)[0]; + if (value != 0 && value != 1) { + std::cerr << "warning: invalid BranchHint value\n"; + return std::nullopt; + } + + return bool(value); + } + Result<> makeBreak(Index pos, const std::vector& annotations, Index label, bool isConditional) { - return withLoc(pos, irBuilder.makeBreak(label, isConditional)); + auto likely = getBranchHint(annotations); + return withLoc(pos, irBuilder.makeBreak(label, isConditional, likely)); } Result<> makeSwitch(Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 35fa57ea51a..435b5061dad 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -267,15 +268,17 @@ struct PrintSExpression : public UnifiedExpressionVisitor { void printDebugLocation(const std::optional& location); - void printDebugLocation(Expression* curr); // Prints debug info for a delimiter in an expression. void printDebugDelimiterLocation(Expression* curr, Index i); + // Prints debug info and code annotations. + void printMetadata(Expression* curr); + void printExpressionContents(Expression* curr); void visit(Expression* curr) { - printDebugLocation(curr); + printMetadata(curr); UnifiedExpressionVisitor::visit(curr); } @@ -2677,20 +2680,20 @@ void PrintSExpression::printDebugLocation( doIndent(o, indent); } -void PrintSExpression::printDebugLocation(Expression* curr) { +void PrintSExpression::printMetadata(Expression* curr) { if (currFunction) { - // show an annotation, if there is one - auto& debugLocations = currFunction->debugLocations; - auto iter = debugLocations.find(curr); - if (iter != debugLocations.end()) { + // Show a debug location, if there is one. + if (auto iter = currFunction->debugLocations.find(curr); + iter != currFunction->debugLocations.end()) { printDebugLocation(iter->second); } else { printDebugLocation(std::nullopt); } - // show a binary position, if there is one + + // Show a binary position, if there is one. if (debugInfo) { - auto iter = currFunction->expressionLocations.find(curr); - if (iter != currFunction->expressionLocations.end()) { + if (auto iter = currFunction->expressionLocations.find(curr); + iter != currFunction->expressionLocations.end()) { Colors::grey(o); o << ";; code offset: 0x" << std::hex << iter->second.start << std::dec << '\n'; @@ -2698,6 +2701,19 @@ void PrintSExpression::printDebugLocation(Expression* curr) { doIndent(o, indent); } } + + // Show a code annotation, if there is one. + if (auto iter = currFunction->codeAnnotations.find(curr); + iter != currFunction->codeAnnotations.end()) { + auto& annotation = iter->second; + if (annotation.branchLikely) { + Colors::grey(o); + o << "(@" << Annotations::BranchHint << " \"\\0" + << (*annotation.branchLikely ? "1" : "0") << "\")\n"; + restoreNormalColor(o); + doIndent(o, indent); + } + } } } @@ -2781,7 +2797,7 @@ void PrintSExpression::visitBlock(Block* curr) { while (1) { if (stack.size() > 0) { doIndent(o, indent); - printDebugLocation(curr); + printMetadata(curr); } stack.push_back(curr); o << '('; diff --git a/src/wasm-annotations.h b/src/wasm-annotations.h new file mode 100644 index 00000000000..42f0e56d732 --- /dev/null +++ b/src/wasm-annotations.h @@ -0,0 +1,32 @@ +/* + * Copyright 2025 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. + */ + +// +// Support for code annotations. +// + +#ifndef wasm_annotations_h +#define wasm_annotations_h + +#include "support/name.h" + +namespace wasm::Annotations { + +extern const Name BranchHint; + +} // namespace wasm::Annotations + +#endif // wasm_annotations_h diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 7fbe089c127..d979fb52597 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -72,8 +72,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { this->codeSectionOffset = codeSectionOffset; } - // Set the function used to add scratch locals when constructing an isolated - // sequence of IR. + // The current function, used to create scratch locals, add annotations, etc. void setFunction(Function* func) { this->func = func; } // Handle the boundaries of control flow structures. Users may choose to use @@ -119,7 +118,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeBlock(Name label, Signature sig); Result<> makeIf(Name label, Signature sig); Result<> makeLoop(Name label, Signature sig); - Result<> makeBreak(Index label, bool isConditional); + Result<> makeBreak(Index label, + bool isConditional, + std::optional likely = std::nullopt); Result<> makeSwitch(const std::vector& labels, Index defaultLabel); // Unlike Builder::makeCall, this assumes the function already exists. Result<> makeCall(Name func, bool isReturn); diff --git a/src/wasm.h b/src/wasm.h index b6541fc6cf5..60efbfd65b8 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2191,6 +2191,16 @@ class Function : public Importable { delimiterLocations; BinaryLocations::FunctionLocations funcLocation; + // Code annotations. As with debug info, we do not store these on Expressions + // themselves, as we assume most instances are unannotated, and do not want to + // add constant memory overhead. + struct CodeAnnotation { + // Branch hinting proposal: Whether the branch is likely, or unlikely. + std::optional branchLikely; + }; + + std::unordered_map codeAnnotations; + // The effects for this function, if they have been computed. We use a shared // ptr here to avoid compilation errors with the forward-declared // EffectAnalyzer. diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 0928c99c927..269682c5fa0 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1393,7 +1393,9 @@ Result<> IRBuilder::makeLoop(Name label, Signature sig) { return visitLoopStart(loop, sig.params); } -Result<> IRBuilder::makeBreak(Index label, bool isConditional) { +Result<> IRBuilder::makeBreak(Index label, + bool isConditional, + std::optional likely) { auto name = getLabelName(label); CHECK_ERR(name); auto labelType = getLabelType(label); @@ -1404,7 +1406,15 @@ Result<> IRBuilder::makeBreak(Index label, bool isConditional) { // Use a dummy condition value if we need to pop a condition. curr.condition = isConditional ? &curr : nullptr; CHECK_ERR(ChildPopper{*this}.visitBreak(&curr, *labelType)); - push(builder.makeBreak(curr.name, curr.value, curr.condition)); + auto* br = builder.makeBreak(curr.name, curr.value, curr.condition); + push(br); + + if (likely) { + // Branches are only possible inside functions. + assert(func); + func->codeAnnotations[br].branchLikely = likely; + } + return Ok{}; } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 3f885cbccdf..b66a2a7c6d3 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -16,6 +16,7 @@ #include "wasm.h" #include "ir/branch-utils.h" +#include "wasm-annotations.h" #include "wasm-traversal.h" #include "wasm-type.h" @@ -63,6 +64,12 @@ const char* CustomDescriptorsFeature = "custom-descriptors"; } // namespace BinaryConsts::CustomSections +namespace Annotations { + +const Name BranchHint = "metadata.code.branch_hint"; + +} // namespace Annotations + Name STACK_POINTER("__stack_pointer"); Name MODULE("module"); Name START("start"); diff --git a/test/lit/wat-annotations.wast b/test/lit/wat-annotations.wast new file mode 100644 index 00000000000..6d5be1f0a9d --- /dev/null +++ b/test/lit/wat-annotations.wast @@ -0,0 +1,43 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (func $branch-hints-br_if (type $0) (param $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $branch-hints-br_if (param $x i32) + (block $out + ;; A branch annotated as unlikely, and one as likely. + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $x) + ) + ;; The last one wins. + (@metadata.code.branch_hint "\01") + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + ) + ) +) From 0934d6cd3abb5bc8c9ddb214eac1f7b4b1bcf899 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Sat, 3 May 2025 06:16:28 +0800 Subject: [PATCH 481/622] RemoveUnusedBrs: Optimize block tails where a dropped br_if's value is redundant (#7506) If a block ends with a br_if followed by a value that is the same as the br_if's value (and has no side effects), the value of br_if and br_if itself are redundant and can be removed, if the br_if goes to that block anyhow. For example: (block $block (result i32) .. (drop (br_if $block (value) (condition) ) ) (value) ) => (block $block (result i32) .. (drop (condition) ) (value) ) Fixes: #7489 --- src/passes/RemoveUnusedBrs.cpp | 39 ++++++ test/lit/passes/remove-unused-brs.wast | 166 ++++++++++++++++++++++--- 2 files changed, 185 insertions(+), 20 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index add46585269..142810eb2f2 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -1263,6 +1263,45 @@ struct RemoveUnusedBrs : public WalkerPass> { tablify(curr); // Pattern-patch ifs, recreating them when it makes sense. restructureIf(curr); + + // Optimize block tails where a dropped `br_if`'s value is redundant + // when the br_if targets the block itself: + // + // (block $block (result i32) + // .. + // (drop + // (br_if $block ;; <- MUST target parent $block + // (value) + // (condition) + // ) + // ) + // (value) ;; <- MUST be same as br_if's value + // ) + // => + // (block $block (result i32) + // .. + // (drop + // (condition) + // ) + // (value) + // ) + size_t size = curr->list.size(); + auto* secondLast = curr->list[size - 2]; + auto* last = curr->list[size - 1]; + if (auto* drop = secondLast->dynCast()) { + if (auto* br = drop->value->dynCast(); + br && br->value && br->condition) { + if (br->name == curr->name) { + if (!EffectAnalyzer(passOptions, *getModule(), br->value) + .hasUnremovableSideEffects()) { + if (ExpressionAnalyzer::equal(br->value, last)) { + // All conditions met, perform the update. + drop->value = br->condition; + } + } + } + } + } } } diff --git a/test/lit/passes/remove-unused-brs.wast b/test/lit/passes/remove-unused-brs.wast index c61320602a8..84c9ef6be05 100644 --- a/test/lit/passes/remove-unused-brs.wast +++ b/test/lit/passes/remove-unused-brs.wast @@ -5,7 +5,7 @@ (module ;; Regression test in which we need to calculate a proper LUB. - ;; CHECK: (func $selectify-fresh-lub (type $3) (param $x i32) (result anyref) + ;; CHECK: (func $selectify-fresh-lub (type $4) (param $x i32) (result anyref) ;; CHECK-NEXT: (select (result i31ref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.i31 @@ -30,7 +30,7 @@ ) ) - ;; CHECK: (func $selectify-simple (type $1) (param $0 i32) (result i32) + ;; CHECK: (func $selectify-simple (type $0) (param $0 i32) (result i32) ;; CHECK-NEXT: (select ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.lt_u @@ -73,7 +73,7 @@ ) ) - ;; CHECK: (func $restructure-br_if (type $1) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then @@ -104,12 +104,12 @@ ) ) - ;; CHECK: (func $nothing (type $0) + ;; CHECK: (func $nothing (type $1) ;; CHECK-NEXT: ) (func $nothing) - ;; CHECK: (func $restructure-br_if-condition-reorderable (type $1) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-condition-reorderable (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $nothing) @@ -146,7 +146,7 @@ ) ) - ;; CHECK: (func $restructure-br_if-value-effectful (type $1) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (select ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $nothing) @@ -188,7 +188,7 @@ ) ) - ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-1 (type $1) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-1 (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (block $x (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $x @@ -226,14 +226,14 @@ ) ) - ;; CHECK: (func $get-i32 (type $4) (result i32) + ;; CHECK: (func $get-i32 (type $2) (result i32) ;; CHECK-NEXT: (i32.const 400) ;; CHECK-NEXT: ) (func $get-i32 (result i32) (i32.const 400) ) - ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-2 (type $1) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-2 (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (block $x (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $x @@ -272,7 +272,7 @@ (call $get-i32) ) ) - ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-3 (type $1) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-3 (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (block $x (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $x @@ -305,7 +305,7 @@ ) ) - ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-4 (type $1) (param $x i32) (result i32) + ;; CHECK: (func $restructure-br_if-value-effectful-corner-case-4 (type $0) (param $x i32) (result i32) ;; CHECK-NEXT: (block $x (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $x @@ -340,9 +340,135 @@ ) ) - ;; CHECK: (func $restructure-select-no-multivalue (type $0) + ;; CHECK: (func $restructure-br_if-value-redundant-in-block-tail-1 (type $2) (result i32) + ;; CHECK-NEXT: (block $parent (result i32) + ;; CHECK-NEXT: (call $nothing) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-br_if-value-redundant-in-block-tail-1 (result i32) + ;; The br_if's value is equal to the value right after it, so we can remove it. + (block $parent (result i32) + (call $nothing) + (drop + (br_if $parent + (i32.const 1) + (call $get-i32) + ) + ) + (i32.const 1) + ) + ) + + ;; CHECK: (func $restructure-br_if-value-redundant-in-block-tail-2 (type $2) (result i32) + ;; CHECK-NEXT: (block $parent (result i32) + ;; CHECK-NEXT: (call $nothing) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $parent + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-br_if-value-redundant-in-block-tail-2 (result i32) + ;; As above, but now the value is different, so we do not optimize + (block $parent (result i32) + (call $nothing) + (drop + (br_if $parent + (i32.const 2) + (call $get-i32) + ) + ) + (i32.const 1) + ) + ) + + ;; CHECK: (func $restructure-br_if-value-redundant-in-block-tail-3 (type $0) (param $x i32) (result i32) + ;; CHECK-NEXT: (block $parent (result i32) + ;; CHECK-NEXT: (call $nothing) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $parent + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-br_if-value-redundant-in-block-tail-3 (param $x i32) (result i32) + ;; As above, but now the value has effects, so we do not optimize + (block $parent (result i32) + (call $nothing) + (drop + (br_if $parent + (call $get-i32) + (call $get-i32) + ) + ) + (call $get-i32) + ) + ) + + ;; CHECK: (func $restructure-br_if-value-redundant-in-block-tail-4 (type $2) (result i32) + ;; CHECK-NEXT: (block $outer (result i32) + ;; CHECK-NEXT: (block $inner (result i32) + ;; CHECK-NEXT: (call $nothing) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $outer + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-br_if-value-redundant-in-block-tail-4 (result i32) + ;; As above, but the br_if targets another block, so we do not optimize. + (block $outer (result i32) + (block $inner (result i32) + (call $nothing) + (drop + (br_if $outer + (i32.const 1) + (call $get-i32) + ) + ) + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $restructure-br_if-value-redundant-in-block-tail-5 (type $2) (result i32) + ;; CHECK-NEXT: (block $parent (result i32) + ;; CHECK-NEXT: (call $nothing) + ;; CHECK-NEXT: (br $parent + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-br_if-value-redundant-in-block-tail-5 (result i32) + ;; As above, but the br lacks a condition. We do not bother to optimize + ;; the dead code after it, but also should not error here. + (block $parent (result i32) + (call $nothing) + (br $parent + (i32.const 1) + ) + (i32.const 1) + ) + ) + + ;; CHECK: (func $restructure-select-no-multivalue (type $1) ;; CHECK-NEXT: (tuple.drop 2 - ;; CHECK-NEXT: (block $block (type $2) (result i32 i32) + ;; CHECK-NEXT: (block $block (type $3) (result i32 i32) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (tuple.make 2 @@ -387,7 +513,7 @@ ) ) - ;; CHECK: (func $if-of-if (type $0) + ;; CHECK: (func $if-of-if (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (select @@ -421,7 +547,7 @@ ) ) - ;; CHECK: (func $if-of-if-but-side-effects (type $0) + ;; CHECK: (func $if-of-if-but-side-effects (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.tee $x @@ -460,7 +586,7 @@ ) ) - ;; CHECK: (func $if-of-if-but-too-costly (type $0) + ;; CHECK: (func $if-of-if-but-too-costly (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.tee $x @@ -515,7 +641,7 @@ ) ) - ;; CHECK: (func $if-of-if-but-inner-else (type $0) + ;; CHECK: (func $if-of-if-but-inner-else (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.tee $x @@ -555,7 +681,7 @@ ) ) - ;; CHECK: (func $if-of-if-but-outer-else (type $0) + ;; CHECK: (func $if-of-if-but-outer-else (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.tee $x @@ -595,7 +721,7 @@ ) ) - ;; CHECK: (func $unreachable-if (type $0) + ;; CHECK: (func $unreachable-if (type $1) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (unreachable) @@ -625,7 +751,7 @@ ) ) - ;; CHECK: (func $loop-with-unreachable-if (type $0) + ;; CHECK: (func $loop-with-unreachable-if (type $1) ;; CHECK-NEXT: (loop $label ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (unreachable) From 3be188b79fa7b439b623de2dc45a7c87e2539d34 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Tue, 6 May 2025 07:16:53 +0800 Subject: [PATCH 482/622] OptimizeInstructions: Optimize eqz of fallthrough const (#7558) Precompute doesn't do math computations like (i32.eqz (local.tee $x (i32.const 1) ) ) It could emit 0 on the outside, but it would repeat the operation each time the pass is run, which we don't want Instead, handle this in OptimizeInstructions. Fixes: #7492 --- src/passes/OptimizeInstructions.cpp | 6 + .../optimize-instructions-ignore-traps.wast | 6 +- .../lit/passes/optimize-instructions-mvp.wast | 343 +++++++++++++++++- 3 files changed, 339 insertions(+), 16 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index a8a2c6bf5bc..e04988779f8 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2891,6 +2891,12 @@ struct OptimizeInstructions } } } + if (unary->op == EqZInt32 || unary->op == EqZInt64) { + if (auto* c = getFallthrough(unary->value)->dynCast()) { + return getDroppedChildrenAndAppend( + unary, Literal::makeFromInt32(c->value.isZero(), Type::i32)); + } + } } } else if (auto* binary = boolean->dynCast()) { if (binary->op == SubInt32) { diff --git a/test/lit/passes/optimize-instructions-ignore-traps.wast b/test/lit/passes/optimize-instructions-ignore-traps.wast index 8902cbc28f5..2417e58d48a 100644 --- a/test/lit/passes/optimize-instructions-ignore-traps.wast +++ b/test/lit/passes/optimize-instructions-ignore-traps.wast @@ -688,6 +688,7 @@ ;; CHECK: (func $conditionalize-if-type-change (type $3) (result f64) ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (loop $label$1 (result f32) ;; CHECK-NEXT: (block $label$2 (result f32) @@ -709,7 +710,7 @@ ;; CHECK-NEXT: (i64.const 58) ;; CHECK-NEXT: (i64.const -982757) ;; CHECK-NEXT: (i64.eqz - ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -725,6 +726,7 @@ ;; CHECK-NEXT: ) (func $conditionalize-if-type-change (result f64) (local $0 i32) + (local $1 i64) (drop (loop $label$1 (result f32) (block $label$2 (result f32) @@ -746,7 +748,7 @@ (i64.const 58) (i64.const -982757) (i64.eqz - (i64.const 0) + (local.get $1) ) ) ) diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 7b5551a67a9..340cce32dcf 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -113,6 +113,99 @@ ) ) ) + ;; CHECK: (func $if-eqz-one-arm-effect-condition-1 (param $i1 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-one-arm-effect-condition-1 (param $i1 i32) + (if + (i32.eqz + (local.tee $i1 + (i32.const 0) + ) + ) + (then + (drop + (i32.const 10) + ) + ) + ) + ) + ;; CHECK: (func $if-eqz-one-arm-effect-condition-2 (param $i1 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-one-arm-effect-condition-2 (param $i1 i32) + (if + (i32.eqz + (local.tee $i1 + (i32.const 1) + ) + ) + (then + (drop + (i32.const 10) + ) + ) + ) + ) + ;; CHECK: (func $if-eqz-one-arm-effect-condition-3 (param $i1 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i1 + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-one-arm-effect-condition-3 (param $i1 i32) + (if + (i32.eqz + (local.tee $i1 + (i32.const 100) + ) + ) + (then + (drop + (i32.const 10) + ) + ) + ) + ) ;; CHECK: (func $if-eqz-two-arms (param $i1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (if (result i32) @@ -143,6 +236,123 @@ ) ) ) + ;; CHECK: (func $if-eqz-two-arms-effect-condition-1 (param $i1 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-two-arms-effect-condition-1 (param $i1 i32) + (if + (i32.eqz + (local.tee $i1 + (i32.const 0) + ) + ) + (then + (drop + (i32.const 11) + ) + ) + (else + (drop + (i32.const 12) + ) + ) + ) + ) + ;; CHECK: (func $if-eqz-two-arms-effect-condition-2 (param $i1 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-two-arms-effect-condition-2 (param $i1 i32) + (if + (i32.eqz + (local.tee $i1 + (i32.const 1) + ) + ) + (then + (drop + (i32.const 11) + ) + ) + (else + (drop + (i32.const 12) + ) + ) + ) + ) + ;; CHECK: (func $if-eqz-two-arms-effect-condition-3 (param $i1 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i1 + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-two-arms-effect-condition-3 (param $i1 i32) + (if + (i32.eqz + (local.tee $i1 + (i32.const 100) + ) + ) + (then + (drop + (i32.const 11) + ) + ) + (else + (drop + (i32.const 12) + ) + ) + ) + ) ;; CHECK: (func $if-eqz-two-arms-i64 (param $i2 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (if (result i32) @@ -175,6 +385,123 @@ ) ) ) + ;; CHECK: (func $if-eqz-two-arms-i64-effect-condition-1 (param $i2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i2 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-two-arms-i64-effect-condition-1 (param $i2 i64) + (if + (i64.eqz + (local.tee $i2 + (i64.const 0) + ) + ) + (then + (drop + (i32.const 11) + ) + ) + (else + (drop + (i32.const 12) + ) + ) + ) + ) + ;; CHECK: (func $if-eqz-two-arms-i64-effect-condition-2 (param $i2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i2 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-two-arms-i64-effect-condition-2 (param $i2 i64) + (if + (i64.eqz + (local.tee $i2 + (i64.const 1) + ) + ) + (then + (drop + (i32.const 11) + ) + ) + (else + (drop + (i32.const 12) + ) + ) + ) + ) + ;; CHECK: (func $if-eqz-two-arms-i64-effect-condition-3 (param $i2 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $i2 + ;; CHECK-NEXT: (i64.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-eqz-two-arms-i64-effect-condition-3 (param $i2 i64) + (if + (i64.eqz + (local.tee $i2 + (i64.const 100) + ) + ) + (then + (drop + (i32.const 11) + ) + ) + (else + (drop + (i32.const 12) + ) + ) + ) + ) ;; CHECK: (func $eqz-gt_s (result i32) ;; CHECK-NEXT: (i32.eqz @@ -9681,22 +10008,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: (i64.eqz - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: (i64.const 2) - ;; CHECK-NEXT: (i64.eqz - ;; CHECK-NEXT: (i64.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $select-on-const (param $x i32) (param $y i64) From f03044981fad2c1c4d366485b25995c6b4bb4052 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 6 May 2025 12:35:27 -0700 Subject: [PATCH 483/622] [NFC] Cleanly separate expression tracking from source maps (#7571) We track debug info in two ways: for source maps, and for DWARF. DWARF needs more information, not just the start of each expression's location but also the end and delimiters (else for an if) as well. The existing code slightly mixed the two together, which is annoying because code annotations require the same tracking DWARF does. To improve that, this PR refactors the code to cleanly separate the two forms of tracking: * writeSourceMapLocation is now the only method that source maps use. * trackExpressionStart|End|Delimiter is now used by DWARF (and soon code annotations). As a result, * BinaryInstWriter no longer needs a sourceMap param. It was using !sourceMap in the sense of "maybe DWARF", but given custom annotations we'll need more anyhow. Simplify the code by letting the parent decide what to do. Replace DWARF scanning ahead of sections with a "preScan" method. This will be extended for code annotations later. --- src/wasm-binary.h | 24 ++++++++---- src/wasm-stack.h | 18 ++++----- src/wasm/wasm-binary.cpp | 83 ++++++++++++++++++++++++---------------- src/wasm/wasm-stack.cpp | 21 +++++----- 4 files changed, 83 insertions(+), 63 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 95725eff47a..f6e0372ca6c 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1351,9 +1351,12 @@ class WasmBinaryWriter { void writeSourceMapEpilog(); void writeDebugLocation(const Function::DebugLocation& loc); void writeNoDebugLocation(); - void writeDebugLocation(Expression* curr, Function* func); - void writeDebugLocationEnd(Expression* curr, Function* func); - void writeExtraDebugLocation(Expression* curr, Function* func, size_t id); + void writeSourceMapLocation(Expression* curr, Function* func); + + // Track where expressions go in the binary format. + void trackExpressionStart(Expression* curr, Function* func); + void trackExpressionEnd(Expression* curr, Function* func); + void trackExpressionDelimiter(Expression* curr, Function* func, size_t id); // helpers void writeInlineString(std::string_view name); @@ -1614,9 +1617,9 @@ class WasmBinaryReader { static Name escape(Name name); void findAndReadNames(); - void readFeatures(size_t); - void readDylink(size_t); - void readDylink0(size_t); + void readFeatures(size_t payloadLen); + void readDylink(size_t payloadLen); + void readDylink0(size_t payloadLen); Index readMemoryAccess(Address& alignment, Address& offset); std::tuple getMemarg(); @@ -1627,7 +1630,14 @@ class WasmBinaryReader { } private: - bool hasDWARFSections(); + // In certain modes we need to note the locations of expressions, to match + // them against sections like DWARF or custom annotations. As this incurs + // overhead, we only note locations when we actually need to. + bool needCodeLocations = false; + + // Scans ahead in the binary to check certain conditions like + // needCodeLocations. + void preScan(); }; } // namespace wasm diff --git a/src/wasm-stack.h b/src/wasm-stack.h index f48233333da..f97c9c7acc7 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -93,17 +93,16 @@ class BinaryInstWriter : public OverriddenVisitor { BinaryInstWriter(WasmBinaryWriter& parent, BufferWithRandomAccess& o, Function* func, - bool sourceMap, bool DWARF) - : parent(parent), o(o), func(func), sourceMap(sourceMap), DWARF(DWARF) {} + : parent(parent), o(o), func(func), DWARF(DWARF) {} void visit(Expression* curr) { - if (func && !sourceMap) { - parent.writeDebugLocation(curr, func); + if (func) { + parent.trackExpressionStart(curr, func); } OverriddenVisitor::visit(curr); - if (func && !sourceMap) { - parent.writeDebugLocationEnd(curr, func); + if (func) { + parent.trackExpressionEnd(curr, func); } } @@ -136,7 +135,6 @@ class BinaryInstWriter : public OverriddenVisitor { WasmBinaryWriter& parent; BufferWithRandomAccess& o; Function* func = nullptr; - bool sourceMap; bool DWARF; std::vector breakStack; @@ -452,7 +450,7 @@ class BinaryenIRToBinaryWriter bool sourceMap = false, bool DWARF = false) : BinaryenIRWriter(func), parent(parent), - writer(parent, o, func, sourceMap, DWARF), sourceMap(sourceMap) {} + writer(parent, o, func, DWARF), sourceMap(sourceMap) {} void emit(Expression* curr) { writer.visit(curr); } void emitHeader() { @@ -480,7 +478,7 @@ class BinaryenIRToBinaryWriter void emitUnreachable() { writer.emitUnreachable(); } void emitDebugLocation(Expression* curr) { if (sourceMap) { - parent.writeDebugLocation(curr, func); + parent.writeSourceMapLocation(curr, func); } } @@ -521,7 +519,7 @@ class StackIRToBinaryWriter { StackIR& stackIR, bool sourceMap = false, bool DWARF = false) - : parent(parent), writer(parent, o, func, sourceMap, DWARF), func(func), + : parent(parent), writer(parent, o, func, DWARF), func(func), stackIR(stackIR), sourceMap(sourceMap) {} void write(); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 88fb43b345a..282cfe1811c 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1489,20 +1489,26 @@ void WasmBinaryWriter::writeNoDebugLocation() { } } -void WasmBinaryWriter::writeDebugLocation(Expression* curr, Function* func) { - if (sourceMap) { - auto& debugLocations = func->debugLocations; - auto iter = debugLocations.find(curr); - if (iter != debugLocations.end() && iter->second) { - // There is debug information here, write it out. - writeDebugLocation(*(iter->second)); - } else { - // This expression has no debug location. - writeNoDebugLocation(); - } +void WasmBinaryWriter::writeSourceMapLocation(Expression* curr, + Function* func) { + assert(sourceMap); + + auto& debugLocations = func->debugLocations; + auto iter = debugLocations.find(curr); + if (iter != debugLocations.end() && iter->second) { + // There is debug information here, write it out. + writeDebugLocation(*(iter->second)); + } else { + // This expression has no debug location. + writeNoDebugLocation(); } +} + +void WasmBinaryWriter::trackExpressionStart(Expression* curr, Function* func) { // If this is an instruction in a function, and if the original wasm had - // binary locations tracked, then track it in the output as well. + // binary locations tracked, then track it in the output as well. We also + // track locations of instructions that have code annotations, as their binary + // location goes in the custom section. if (func && !func->expressionLocations.empty()) { binaryLocations.expressions[curr] = BinaryLocations::Span{BinaryLocation(o.size()), 0}; @@ -1510,16 +1516,16 @@ void WasmBinaryWriter::writeDebugLocation(Expression* curr, Function* func) { } } -void WasmBinaryWriter::writeDebugLocationEnd(Expression* curr, Function* func) { +void WasmBinaryWriter::trackExpressionEnd(Expression* curr, Function* func) { if (func && !func->expressionLocations.empty()) { auto& span = binaryLocations.expressions.at(curr); span.end = o.size(); } } -void WasmBinaryWriter::writeExtraDebugLocation(Expression* curr, - Function* func, - size_t id) { +void WasmBinaryWriter::trackExpressionDelimiter(Expression* curr, + Function* func, + size_t id) { if (func && !func->expressionLocations.empty()) { binaryLocations.delimiters[curr][id] = o.size(); } @@ -1799,11 +1805,19 @@ WasmBinaryReader::WasmBinaryReader(Module& wasm, wasm.features = features; } -bool WasmBinaryReader::hasDWARFSections() { +void WasmBinaryReader::preScan() { + // TODO: Once we support code annotations here, we will need to always scan, + // but for now, DWARF is the only reason. + if (!DWARF) { + return; + } + assert(pos == 0); getInt32(); // magic getInt32(); // version - bool has = false; + + bool foundDWARF = false; + while (more()) { uint8_t sectionCode = getInt8(); uint32_t payloadLen = getU32LEB(); @@ -1813,31 +1827,32 @@ bool WasmBinaryReader::hasDWARFSections() { auto oldPos = pos; if (sectionCode == BinaryConsts::Section::Custom) { auto sectionName = getInlineString(); - if (Debug::isDWARFSection(sectionName)) { - has = true; + // DWARF sections contain code offsets. + if (DWARF && Debug::isDWARFSection(sectionName)) { + needCodeLocations = true; + foundDWARF = true; break; } } pos = oldPos + payloadLen; } + + if (DWARF && !foundDWARF) { + // The user asked for DWARF, but no DWARF sections exist in practice, so + // disable the support. + DWARF = false; + } + + // Reset. pos = 0; - return has; } void WasmBinaryReader::read() { - if (DWARF) { - // In order to update dwarf, we must store info about each IR node's - // binary position. This has noticeable memory overhead, so we don't do it - // by default: the user must request it by setting "DWARF", and even if so - // we scan ahead to see that there actually *are* DWARF sections, so that - // we don't do unnecessary work. - if (!hasDWARFSections()) { - DWARF = false; - } - } + preScan(); // Skip ahead and read the name section so we know what names to use when we // construct module elements. + // TODO: Combine this pre-scan with the one in preScan(). if (debugInfo) { findAndReadNames(); } @@ -1879,7 +1894,7 @@ void WasmBinaryReader::read() { readFunctionSignatures(); break; case BinaryConsts::Section::Code: - if (DWARF) { + if (needCodeLocations) { codeSectionLocation = pos; } readFunctions(); @@ -2871,7 +2886,7 @@ void WasmBinaryReader::readFunctions() { if (numFuncBodies + numFuncImports != wasm.functions.size()) { throwError("invalid function section size, must equal types"); } - if (DWARF) { + if (needCodeLocations) { builder.setBinaryLocation(&pos, codeSectionLocation); } for (size_t i = 0; i < numFuncBodies; i++) { @@ -2885,7 +2900,7 @@ void WasmBinaryReader::readFunctions() { auto& func = wasm.functions[numFuncImports + i]; currFunction = func.get(); - if (DWARF) { + if (needCodeLocations) { func->funcLocation = BinaryLocations::FunctionLocations{ BinaryLocation(sizePos - codeSectionLocation), BinaryLocation(pos - codeSectionLocation), diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 6f40a1ba3ab..889ab5e119f 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -50,8 +50,8 @@ void BinaryInstWriter::visitIf(If* curr) { } void BinaryInstWriter::emitIfElse(If* curr) { - if (func && !sourceMap) { - parent.writeExtraDebugLocation(curr, func, BinaryLocations::Else); + if (func) { + parent.trackExpressionDelimiter(curr, func, BinaryLocations::Else); } o << int8_t(BinaryConsts::Else); } @@ -2156,16 +2156,16 @@ void BinaryInstWriter::visitTryTable(TryTable* curr) { } void BinaryInstWriter::emitCatch(Try* curr, Index i) { - if (func && !sourceMap) { - parent.writeExtraDebugLocation(curr, func, i); + if (func) { + parent.trackExpressionDelimiter(curr, func, i); } o << int8_t(BinaryConsts::Catch_Legacy) << U32LEB(parent.getTagIndex(curr->catchTags[i])); } void BinaryInstWriter::emitCatchAll(Try* curr) { - if (func && !sourceMap) { - parent.writeExtraDebugLocation(curr, func, curr->catchBodies.size()); + if (func) { + parent.trackExpressionDelimiter(curr, func, curr->catchBodies.size()); } o << int8_t(BinaryConsts::CatchAll_Legacy); } @@ -2736,8 +2736,8 @@ void BinaryInstWriter::emitScopeEnd(Expression* curr) { assert(!breakStack.empty()); breakStack.pop_back(); o << int8_t(BinaryConsts::End); - if (func && !sourceMap) { - parent.writeDebugLocationEnd(curr, func); + if (func) { + parent.trackExpressionEnd(curr, func); } } @@ -3213,12 +3213,9 @@ void StackIRToBinaryWriter::write() { case StackInst::LoopBegin: case StackInst::TryTableBegin: { if (sourceMap) { - parent.writeDebugLocation(inst->origin, func); + parent.writeSourceMapLocation(inst->origin, func); } writer.visit(inst->origin); - if (sourceMap) { - parent.writeDebugLocationEnd(inst->origin, func); - } break; } case StackInst::TryEnd: From 9e6bae044929de108af46afddda91bd334376898 Mon Sep 17 00:00:00 2001 From: Congcong Cai Date: Thu, 8 May 2025 03:14:32 +0800 Subject: [PATCH 484/622] add `evaluateFunctionExit` for backward analyzer in `monotone-analyzer` (#7547) For backward analyzer, it is helpful to have a function to init state in the "entry point" (exit block). --- src/analysis/monotone-analyzer-impl.h | 9 +++++++++ src/analysis/monotone-analyzer.h | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/analysis/monotone-analyzer-impl.h b/src/analysis/monotone-analyzer-impl.h index fc2fbf7060d..74e33b62c0d 100644 --- a/src/analysis/monotone-analyzer-impl.h +++ b/src/analysis/monotone-analyzer-impl.h @@ -21,6 +21,15 @@ inline void MonotoneCFGAnalyzer::evaluateFunctionEntry(Function* func) { txfn.evaluateFunctionEntry(func, states[0]); } +template +inline void MonotoneCFGAnalyzer::evaluateFunctionExit(Function* func) { + for (size_t i = 0; i < cfg.size(); i++) { + if (cfg[i].isExit()) { + txfn.evaluateFunctionExit(func, states[i]); + break; + } + } +} template inline void MonotoneCFGAnalyzer::evaluate() { diff --git a/src/analysis/monotone-analyzer.h b/src/analysis/monotone-analyzer.h index c2ec7c2b4a2..91251103248 100644 --- a/src/analysis/monotone-analyzer.h +++ b/src/analysis/monotone-analyzer.h @@ -34,6 +34,11 @@ template class MonotoneCFGAnalyzer { // entry block depends on no other blocks, and hence cannot be changed by // them. void evaluateFunctionEntry(Function* func); + // This modifies the state of the CFG's exit block, with function + // information. This cannot be done otherwise in a backward analysis, as the + // exit block depends on no other blocks, and hence cannot be changed by + // them. + void evaluateFunctionExit(Function* func); // Iterates over all of the BlockStates after evaluate() is completed for the // transfer function to collect the finalized intermediate states from each From d84d376343f77e2ec6ac6c38ce0757bd5aa7d915 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 7 May 2025 15:16:54 -0700 Subject: [PATCH 485/622] [NFC] Move optimal-LEB emitting logic to a shared place (#7575) This allows generating sections in side buffers, rather than just on the main output buffer ("o"). This will help code annotations, where it is useful to emit them to a side buffer, then copy them into the right place (which is before the code section, but we must emit the code first, to know the offsets for the annotations, so reordering is unavoidable). --- src/wasm-binary.h | 46 ++++++++++++++++++++++++++++++++++++++++ src/wasm/wasm-binary.cpp | 38 +++++++++++---------------------- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index f6e0372ca6c..b59275013cb 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -258,6 +258,52 @@ class BufferWithRandomAccess : public std::vector { std::copy(begin(), end(), ret.begin()); return ret; } + + // Writes bytes in the maximum amount for a U32 LEB placeholder. Return the + // offset we wrote it at. The LEB can then be patched with the proper value + // later, when the size is known. + BinaryLocation writeU32LEBPlaceholder() { + BinaryLocation ret = size(); + *this << int32_t(0); + *this << int8_t(0); + return ret; + } + + // Given the location of a maximum-size LEB placeholder, as returned from + // writeU32LEBPlaceholder, use the current buffer size to figure out the size + // that should be written there, and emit an optimal-size LEB. Move contents + // backwards if we used fewer bytes, and return the number of bytes we moved. + // (Thus, if we return >0, we moved code backwards, and the caller may need to + // adjust things.) + BinaryLocation emitRetroactiveSectionSizeLEB(BinaryLocation start) { + // Do not include the LEB itself in the section size. + auto sectionSize = size() - start - MaxLEB32Bytes; + auto sizeFieldSize = writeAt(start, U32LEB(sectionSize)); + + // We can move things back if the actual LEB for the size doesn't use the + // maximum 5 bytes. In that case we need to adjust offsets after we move + // things backwards. + auto adjustmentForLEBShrinking = MaxLEB32Bytes - sizeFieldSize; + if (adjustmentForLEBShrinking) { + // We can save some room. + assert(sizeFieldSize < MaxLEB32Bytes); + std::move(&(*this)[start] + MaxLEB32Bytes, + &(*this)[start] + MaxLEB32Bytes + sectionSize, + &(*this)[start] + sizeFieldSize); + resize(size() - adjustmentForLEBShrinking); + } + + return adjustmentForLEBShrinking; + } + + void writeInlineString(std::string_view name) { + auto size = name.size(); + auto data = name.data(); + *this << U32LEB(size); + for (size_t i = 0; i < size; i++) { + *this << int8_t(data[i]); + } + } }; namespace BinaryConsts { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 282cfe1811c..376fbfc1069 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -107,10 +107,7 @@ void WasmBinaryWriter::writeHeader() { } int32_t WasmBinaryWriter::writeU32LEBPlaceholder() { - int32_t ret = o.size(); - o << int32_t(0); - o << int8_t(0); - return ret; + return o.writeU32LEBPlaceholder(); } void WasmBinaryWriter::writeResizableLimits( @@ -142,26 +139,12 @@ template int32_t WasmBinaryWriter::startSection(T code) { } void WasmBinaryWriter::finishSection(int32_t start) { - // section size does not include the reserved bytes of the size field itself - int32_t size = o.size() - start - MaxLEB32Bytes; - auto sizeFieldSize = o.writeAt(start, U32LEB(size)); - // We can move things back if the actual LEB for the size doesn't use the - // maximum 5 bytes. In that case we need to adjust offsets after we move - // things backwards. - auto adjustmentForLEBShrinking = MaxLEB32Bytes - sizeFieldSize; - if (adjustmentForLEBShrinking) { - // we can save some room, nice - assert(sizeFieldSize < MaxLEB32Bytes); - std::move(&o[start] + MaxLEB32Bytes, - &o[start] + MaxLEB32Bytes + size, - &o[start] + sizeFieldSize); - o.resize(o.size() - adjustmentForLEBShrinking); - if (sourceMap) { - for (auto i = sourceMapLocationsSizeAtSectionStart; - i < sourceMapLocations.size(); - ++i) { - sourceMapLocations[i].first -= adjustmentForLEBShrinking; - } + auto adjustmentForLEBShrinking = o.emitRetroactiveSectionSizeLEB(start); + if (adjustmentForLEBShrinking && sourceMap) { + for (auto i = sourceMapLocationsSizeAtSectionStart; + i < sourceMapLocations.size(); + ++i) { + sourceMapLocations[i].first -= adjustmentForLEBShrinking; } } @@ -172,6 +155,10 @@ void WasmBinaryWriter::finishSection(int32_t start) { // The section type byte is right before the LEB for the size; we want // offsets that are relative to the body, which is after that section type // byte and the the size LEB. + // + // We can compute the size of the size field LEB by considering the original + // size of the maximal LEB, and the adjustment due to shrinking. + auto sizeFieldSize = MaxLEB32Bytes - adjustmentForLEBShrinking; auto body = start + sizeFieldSize; // Offsets are relative to the body of the code section: after the // section type byte and the size. @@ -1538,8 +1525,7 @@ void WasmBinaryWriter::writeData(const char* data, size_t size) { } void WasmBinaryWriter::writeInlineString(std::string_view name) { - o << U32LEB(name.size()); - writeData(name.data(), name.size()); + o.writeInlineString(name); } static bool isHexDigit(char ch) { From f634373fb0d2ea84382de120c73a07622918f68e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 8 May 2025 10:26:32 -0700 Subject: [PATCH 486/622] [Branch Hinting] Add binary support (#7572) --- src/wasm-binary.h | 15 ++ src/wasm/wasm-binary.cpp | 218 +++++++++++++++++- .../code-annotations-optimized-away.wast | 31 +++ test/lit/wat-annotations.wast | 110 ++++++++- 4 files changed, 366 insertions(+), 8 deletions(-) create mode 100644 test/lit/passes/code-annotations-optimized-away.wast diff --git a/src/wasm-binary.h b/src/wasm-binary.h index b59275013cb..ccd07aa1c7e 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1404,6 +1404,12 @@ class WasmBinaryWriter { void trackExpressionEnd(Expression* curr, Function* func); void trackExpressionDelimiter(Expression* curr, Function* func, size_t id); + // Writes code annotations into a buffer and returns it. We cannot write them + // directly into the output since we write function code first (to get the + // offsets for the annotations), and only then can write annotations, which we + // must then insert before the code (as the spec requires that). + std::optional writeCodeAnnotations(); + // helpers void writeInlineString(std::string_view name); void writeEscapedName(std::string_view name); @@ -1667,6 +1673,15 @@ class WasmBinaryReader { void readDylink(size_t payloadLen); void readDylink0(size_t payloadLen); + // We read branch hints *after* the code section, even though they appear + // earlier. That is simpler for us as we note expression locations as we scan + // code, and then just need to match them up. To do this, we note the branch + // hint position and size in the first pass, and handle it later. + size_t branchHintsPos = 0; + size_t branchHintsLen = 0; + + void readBranchHints(size_t payloadLen); + Index readMemoryAccess(Address& alignment, Address& offset); std::tuple getMemarg(); MemoryOrder getMemoryOrder(bool isRMW = false); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 376fbfc1069..dd7c9e647a0 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -28,6 +28,7 @@ #include "support/debug.h" #include "support/stdckdint.h" #include "support/string.h" +#include "wasm-annotations.h" #include "wasm-binary.h" #include "wasm-debug.h" #include "wasm-limits.h" @@ -474,6 +475,21 @@ void WasmBinaryWriter::writeFunctions() { } }); finishSection(sectionStart); + + // Code annotations must come before the code section (see comment on + // writeCodeAnnotations). + if (auto annotations = writeCodeAnnotations()) { + // We need to move the code section and put the annotations before it. + auto& annotationsBuffer = *annotations; + auto oldSize = o.size(); + o.resize(oldSize + annotationsBuffer.size()); + + // |sectionStart| is the start of the contents of the section. Subtract 1 to + // include the section code as well, so we move all of it. + std::move_backward(&o[sectionStart - 1], &o[oldSize], o.end()); + std::copy( + annotationsBuffer.begin(), annotationsBuffer.end(), &o[sectionStart - 1]); + } } void WasmBinaryWriter::writeStrings() { @@ -1496,7 +1512,8 @@ void WasmBinaryWriter::trackExpressionStart(Expression* curr, Function* func) { // binary locations tracked, then track it in the output as well. We also // track locations of instructions that have code annotations, as their binary // location goes in the custom section. - if (func && !func->expressionLocations.empty()) { + if (func && (!func->expressionLocations.empty() || + func->codeAnnotations.count(curr))) { binaryLocations.expressions[curr] = BinaryLocations::Span{BinaryLocation(o.size()), 0}; binaryLocationTrackedExpressionsForFunc.push_back(curr); @@ -1504,6 +1521,8 @@ void WasmBinaryWriter::trackExpressionStart(Expression* curr, Function* func) { } void WasmBinaryWriter::trackExpressionEnd(Expression* curr, Function* func) { + // TODO: If we need to track the end of annotated code locations, we need to + // enable that here. if (func && !func->expressionLocations.empty()) { auto& span = binaryLocations.expressions.at(curr); span.end = o.size(); @@ -1513,11 +1532,123 @@ void WasmBinaryWriter::trackExpressionEnd(Expression* curr, Function* func) { void WasmBinaryWriter::trackExpressionDelimiter(Expression* curr, Function* func, size_t id) { + // TODO: If we need to track the delimiters of annotated code locations, we + // need to enable that here. if (func && !func->expressionLocations.empty()) { binaryLocations.delimiters[curr][id] = o.size(); } } +std::optional WasmBinaryWriter::writeCodeAnnotations() { + // Assemble the info for Branch Hinting: for each function, a vector of the + // hints. + struct ExprHint { + Expression* expr; + // The offset we will write in the custom section. + BinaryLocation offset; + Function::CodeAnnotation* hint; + }; + + struct FuncHints { + Name func; + std::vector exprHints; + }; + + std::vector funcHintsVec; + + for (auto& func : wasm->functions) { + // Collect the Branch Hints for this function. + FuncHints funcHints; + + // We compute the location of the function declaration area (where the + // locals are declared) the first time we need it. + BinaryLocation funcDeclarationsOffset = 0; + + for (auto& [expr, annotation] : func->codeAnnotations) { + if (annotation.branchLikely) { + auto exprIter = binaryLocations.expressions.find(expr); + if (exprIter == binaryLocations.expressions.end()) { + // No expression exists for this annotation - perhaps optimizations + // removed it. + continue; + } + auto exprOffset = exprIter->second.start; + + if (!funcDeclarationsOffset) { + auto funcIter = binaryLocations.functions.find(func.get()); + assert(funcIter != binaryLocations.functions.end()); + funcDeclarationsOffset = funcIter->second.declarations; + } + + // Compute the offset: it should be relative to the start of the + // function locals (i.e. the function declarations). + auto offset = exprOffset - funcDeclarationsOffset; + + funcHints.exprHints.push_back(ExprHint{expr, offset, &annotation}); + } + } + + if (funcHints.exprHints.empty()) { + continue; + } + + // We found something. Finalize the data. + funcHints.func = func->name; + + // Hints must be sorted by increasing binary offset. + std::sort( + funcHints.exprHints.begin(), + funcHints.exprHints.end(), + [](const ExprHint& a, const ExprHint& b) { return a.offset < b.offset; }); + + funcHintsVec.emplace_back(std::move(funcHints)); + } + + if (funcHintsVec.empty()) { + return {}; + } + + if (sourceMap) { + // TODO: This mode may not matter (when debugging, code annotations are an + // optimization that can be skipped), but atm source maps cause + // annotations to break. + Fatal() << "Annotations are not supported with source maps"; + } + + BufferWithRandomAccess buffer; + + // We found data: emit the section. + buffer << uint8_t(BinaryConsts::Custom); + auto lebPos = buffer.writeU32LEBPlaceholder(); + buffer.writeInlineString(Annotations::BranchHint.str); + + buffer << U32LEB(funcHintsVec.size()); + for (auto& funcHints : funcHintsVec) { + buffer << U32LEB(getFunctionIndex(funcHints.func)); + + buffer << U32LEB(funcHints.exprHints.size()); + for (auto& exprHint : funcHints.exprHints) { + buffer << U32LEB(exprHint.offset); + + // Hint size, always 1 for now. + buffer << U32LEB(1); + + // We must only emit hints that are present. + assert(exprHint.hint->branchLikely); + + // Hint contents: likely or not. + buffer << U32LEB(int(*exprHint.hint->branchLikely)); + } + } + + // Write the final size. We can ignore the return value, which is the number + // of bytes we shrank (if the LEB was smaller than the maximum size), as no + // value in this section cares. + buffer.emitRetroactiveSectionSizeLEB(lebPos); + + return buffer; +} + void WasmBinaryWriter::writeData(const char* data, size_t size) { for (size_t i = 0; i < size; i++) { o << int8_t(data[i]); @@ -1792,12 +1923,6 @@ WasmBinaryReader::WasmBinaryReader(Module& wasm, } void WasmBinaryReader::preScan() { - // TODO: Once we support code annotations here, we will need to always scan, - // but for now, DWARF is the only reason. - if (!DWARF) { - return; - } - assert(pos == 0); getInt32(); // magic getInt32(); // version @@ -1813,12 +1938,25 @@ void WasmBinaryReader::preScan() { auto oldPos = pos; if (sectionCode == BinaryConsts::Section::Custom) { auto sectionName = getInlineString(); + + // Code annotations require code locations. + // TODO: For Branch Hinting, we could note which functions require + // code locations, as an optimization. + if (sectionName == Annotations::BranchHint) { + needCodeLocations = true; + // Do not break, so we keep looking for DWARF. + } + // DWARF sections contain code offsets. if (DWARF && Debug::isDWARFSection(sectionName)) { needCodeLocations = true; foundDWARF = true; break; } + + // TODO: We could stop early if we see the Code section and DWARF is + // disabled, as BranchHint must appear first, but this seems to + // make practically no difference in practice. } pos = oldPos + payloadLen; } @@ -1933,6 +2071,12 @@ void WasmBinaryReader::read() { } } + // Go back and parse things we deferred. + if (branchHintsPos) { + pos = branchHintsPos; + readBranchHints(branchHintsLen); + } + validateBinary(); } @@ -1953,6 +2097,10 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { readDylink(payloadLen); } else if (sectionName.equals(BinaryConsts::CustomSections::Dylink0)) { readDylink0(payloadLen); + } else if (sectionName == Annotations::BranchHint) { + // Only note the position and length, we read this later. + branchHintsPos = pos; + branchHintsLen = payloadLen; } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5100,6 +5248,62 @@ void WasmBinaryReader::readDylink0(size_t payloadLen) { } } +void WasmBinaryReader::readBranchHints(size_t payloadLen) { + auto sectionPos = pos; + + auto numFuncs = getU32LEB(); + for (Index i = 0; i < numFuncs; i++) { + auto funcIndex = getU32LEB(); + if (funcIndex >= wasm.functions.size()) { + throwError("bad BranchHint function"); + } + + auto& func = wasm.functions[funcIndex]; + + // The encoded offsets we read below are relative to the start of the + // function's locals (the declarations). + auto funcLocalsOffset = func->funcLocation.declarations; + + // We have a map of expressions to their locations. Invert that to get the + // map we will use below, from offsets to expressions. + std::unordered_map locationsMap; + + for (auto& [expr, span] : func->expressionLocations) { + locationsMap[span.start] = expr; + } + + auto numHints = getU32LEB(); + for (Index hint = 0; hint < numHints; hint++) { + // To get the absolute offset, add the function's offset. + auto relativeOffset = getU32LEB(); + auto absoluteOffset = funcLocalsOffset + relativeOffset; + + auto iter = locationsMap.find(absoluteOffset); + if (iter == locationsMap.end()) { + throwError("bad BranchHint offset"); + } + auto* expr = iter->second; + + auto size = getU32LEB(); + if (size != 1) { + throwError("bad BranchHint size"); + } + + auto likely = getU32LEB(); + if (likely != 0 && likely != 1) { + throwError("bad BranchHint value"); + } + + // Apply the valid hint. + func->codeAnnotations[expr].branchLikely = likely; + } + } + + if (pos != sectionPos + payloadLen) { + throwError("bad BranchHint section size"); + } +} + Index WasmBinaryReader::readMemoryAccess(Address& alignment, Address& offset) { auto rawAlignment = getU32LEB(); bool hasMemIdx = false; diff --git a/test/lit/passes/code-annotations-optimized-away.wast b/test/lit/passes/code-annotations-optimized-away.wast new file mode 100644 index 00000000000..26805354add --- /dev/null +++ b/test/lit/passes/code-annotations-optimized-away.wast @@ -0,0 +1,31 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s --monomorphize --roundtrip -all -S -o - | filecheck %s + +;; The callee, which we will monomorphize, has a branch hint. The hinted code +;; will end up vanishing entirely, but not the function it is in, so we end up +;; with an annotation without an instruction in the binary for it. We should +;; ignore it and not error. +(module + ;; CHECK: (func $callee (type $1) (param $0 i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $callee (param $0 i32) + (block $block + (@metadata.code.branch_hint "\00") + (br_if $block + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $caller (type $0) + ;; CHECK-NEXT: (call $callee_2) + ;; CHECK-NEXT: ) + (func $caller + (call $callee + (i32.const 0) + ) + ) +) + diff --git a/test/lit/wat-annotations.wast b/test/lit/wat-annotations.wast index 6d5be1f0a9d..658c00224be 100644 --- a/test/lit/wat-annotations.wast +++ b/test/lit/wat-annotations.wast @@ -1,10 +1,38 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: wasm-opt -all %s -S -o - | filecheck %s +;; RUN: wasm-opt -all %s -S -o - | filecheck %s +;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP (module + ;; CHECK: (type $0 (func (param i32))) + ;; CHECK: (func $no-annotations (type $0) (param $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (type $0 (func (param i32))) + + ;; RTRIP: (func $no-annotations (type $0) (param $x i32) + ;; RTRIP-NEXT: (block $block + ;; RTRIP-NEXT: (br_if $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $no-annotations (param $x i32) + ;; A function with no annotations. This tests that we use function indexes + ;; properly in the section. + (block $out + (br_if $out + (local.get $x) + ) + ) + ) + ;; CHECK: (func $branch-hints-br_if (type $0) (param $x i32) ;; CHECK-NEXT: (block $out ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") @@ -21,6 +49,22 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; RTRIP: (func $branch-hints-br_if (type $0) (param $x i32) + ;; RTRIP-NEXT: (block $block + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\00") + ;; RTRIP-NEXT: (br_if $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\01") + ;; RTRIP-NEXT: (br_if $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\00") + ;; RTRIP-NEXT: (br_if $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) (func $branch-hints-br_if (param $x i32) (block $out ;; A branch annotated as unlikely, and one as likely. @@ -40,4 +84,68 @@ ) ) ) + + ;; CHECK: (func $branch_hints-br_if-2 (type $0) (param $x i32) + ;; CHECK-NEXT: (local $unused f64) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $branch_hints-br_if-2 (type $0) (param $x i32) + ;; RTRIP-NEXT: (local $unused f64) + ;; RTRIP-NEXT: (block $block + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\01") + ;; RTRIP-NEXT: (br_if $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $branch_hints-br_if-2 (param $x i32) + (local $unused f64) + ;; A second function with hints. This one also has local definitions, which + ;; should not confuse us (branch hint offsets are relative to the start of + ;; the local definitions, not the end). + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $mixing (type $0) (param $x i32) + ;; CHECK-NEXT: ;;@ mixing.src:1337:42 + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $mixing (type $0) (param $x i32) + ;; RTRIP-NEXT: (block $block + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\01") + ;; RTRIP-NEXT: (br_if $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $mixing (param $x i32) + ;; Mix branch hints with source locations. Both hints should remain. + ;; TODO: Fix this in the binary format. Atm we test with --roundtrip here, + ;; which does not use source maps, so it is expected for the source + ;; annotation to vanish. But using source maps does not fix it, see + ;; the TODO in the code. + + ;;@ mixing.src:1337:42 + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $x) + ) + ) + ) ) From 4227e606ec43bb907af2a880a84e664b69aeb5a3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 8 May 2025 12:06:08 -0700 Subject: [PATCH 487/622] [NFC] Combine the two binary reading scans of sections into one (#7577) Rather than scan for DWARF and code annotations, and then scan for names, it is simpler to do them in a single loop. I don't see a speedup (I guess being in cache makes it not matter), but the code is simpler at least, avoiding the two loops. --- src/wasm-binary.h | 2 +- src/wasm/wasm-binary.cpp | 61 ++++++++-------------------------------- 2 files changed, 12 insertions(+), 51 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index ccd07aa1c7e..cd49442e3fc 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1668,7 +1668,7 @@ class WasmBinaryReader { void readTags(); static Name escape(Name name); - void findAndReadNames(); + void readNames(size_t sectionPos, size_t payloadLen); void readFeatures(size_t payloadLen); void readDylink(size_t payloadLen); void readDylink0(size_t payloadLen); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index dd7c9e647a0..f772dff7bd1 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1939,24 +1939,21 @@ void WasmBinaryReader::preScan() { if (sectionCode == BinaryConsts::Section::Custom) { auto sectionName = getInlineString(); - // Code annotations require code locations. - // TODO: For Branch Hinting, we could note which functions require - // code locations, as an optimization. if (sectionName == Annotations::BranchHint) { + // Code annotations require code locations. + // TODO: For Branch Hinting, we could note which functions require + // code locations, as an optimization. needCodeLocations = true; - // Do not break, so we keep looking for DWARF. - } - - // DWARF sections contain code offsets. - if (DWARF && Debug::isDWARFSection(sectionName)) { + } else if (DWARF && Debug::isDWARFSection(sectionName)) { + // DWARF sections contain code offsets. needCodeLocations = true; foundDWARF = true; - break; + } else if (debugInfo && + sectionName == BinaryConsts::CustomSections::Name) { + readNames(oldPos, payloadLen); } - - // TODO: We could stop early if we see the Code section and DWARF is - // disabled, as BranchHint must appear first, but this seems to - // make practically no difference in practice. + // TODO: We could stop early in some cases, if we've seen enough (e.g. + // seeing Code implies no BranchHint will appear, due to ordering). } pos = oldPos + payloadLen; } @@ -1973,14 +1970,6 @@ void WasmBinaryReader::preScan() { void WasmBinaryReader::read() { preScan(); - - // Skip ahead and read the name section so we know what names to use when we - // construct module elements. - // TODO: Combine this pre-scan with the one in preScan(). - if (debugInfo) { - findAndReadNames(); - } - readHeader(); sourceMapReader.parse(wasm); @@ -4942,32 +4931,7 @@ class NameProcessor { } // anonymous namespace -void WasmBinaryReader::findAndReadNames() { - // Find the names section. Skip the magic and version. - assert(pos == 0); - getInt32(); - getInt32(); - Index payloadLen, sectionPos; - bool found = false; - while (more()) { - uint8_t sectionCode = getInt8(); - payloadLen = getU32LEB(); - sectionPos = pos; - if (sectionCode == BinaryConsts::Section::Custom) { - auto sectionName = getInlineString(); - if (sectionName.equals(BinaryConsts::CustomSections::Name)) { - found = true; - break; - } - } - pos = sectionPos + payloadLen; - } - if (!found) { - // No names section to read. - pos = 0; - return; - } - +void WasmBinaryReader::readNames(size_t sectionPos, size_t payloadLen) { // Read the names. uint32_t lastType = 0; while (pos < sectionPos + payloadLen) { @@ -5092,9 +5056,6 @@ void WasmBinaryReader::findAndReadNames() { if (pos != sectionPos + payloadLen) { throwError("bad names section position change"); } - - // Reset the position; we were just reading ahead. - pos = 0; } void WasmBinaryReader::readFeatures(size_t payloadLen) { From 1bd42673af3a86b12e4d25ac287a189c3f224129 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 8 May 2025 13:09:19 -0700 Subject: [PATCH 488/622] [NFC] Properties::hasUnwritableTypeImmediate (#7576) Many WasmGC instructions have type index immediates that determine the static types of certain operands. When we emit the IR, we need to look at these operands to determine the type index immediate to emit. In cases where the operand is unreachable or has bottom type, there is no possible type index to emit, so we have to emit a replacement. Previously Print.cpp had a visitor for each instruction that has such a type immediate to individually check whether the replacement was necessary. Refactor this to use a new `Properties::hasUnwritableTypeImmediate` function implemented on top of a new DELEGATE_FIELD_REF_CHILD macro in wasm-delegations-fields.def. This function will be used elsewhere in a following PR. --- src/ir/properties.h | 35 +++++++++++ src/passes/Print.cpp | 102 +++++++------------------------- src/wasm-delegations-fields.def | 42 ++++++++----- 3 files changed, 82 insertions(+), 97 deletions(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index 8d3266b76f8..4fb0c2b2145 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -514,6 +514,41 @@ inline MemoryOrder getMemoryOrder(Expression* curr) { return MemoryOrder::Unordered; } +// Whether this instruction will be unwritable in the text and binary formats +// because it requires a type index immediate giving the type of a child that +// has unreachable or null type, and therefore does not have a type index. +inline bool hasUnwritableTypeImmediate(Expression* curr) { +#define DELEGATE_ID curr->_id + +#define DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) \ + { \ + auto type = curr->cast()->field->type; \ + if (type == Type::unreachable || type.isNull()) { \ + return true; \ + } \ + } + +#define DELEGATE_FIELD_CHILD(id, field) +#define DELEGATE_FIELD_CHILD_VECTOR(id, field) +#define DELEGATE_FIELD_INT(id, field) +#define DELEGATE_FIELD_INT_ARRAY(id, field) +#define DELEGATE_FIELD_INT_VECTOR(id, field) +#define DELEGATE_FIELD_LITERAL(id, field) +#define DELEGATE_FIELD_NAME(id, field) +#define DELEGATE_FIELD_NAME_VECTOR(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_DEF(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(id, field) +#define DELEGATE_FIELD_TYPE(id, field) +#define DELEGATE_FIELD_TYPE_VECTOR(id, field) +#define DELEGATE_FIELD_HEAPTYPE(id, field) +#define DELEGATE_FIELD_ADDRESS(id, field) + +#include "wasm-delegations-fields.def" + + return false; +} + // A "generative" expression is one that can generate different results for the // same inputs, and that difference is *not* explained by other expressions that // interact with this one. This is an intrinsic/internal property of the diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 435b5061dad..a9ea58f07db 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -313,13 +313,8 @@ struct PrintSExpression : public UnifiedExpressionVisitor { void visitTry(Try* curr); void visitTryTable(TryTable* curr); + void printUnreachableReplacement(Expression* curr); bool maybePrintUnreachableReplacement(Expression* curr, Type type); - bool maybePrintUnreachableOrNullReplacement(Expression* curr, Type type); - void visitCallRef(CallRef* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->target->type)) { - visitExpression(curr); - } - } void visitRefCast(RefCast* curr) { if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); @@ -330,26 +325,6 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } - void visitStructGet(StructGet* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } - void visitStructSet(StructSet* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } - void visitStructRMW(StructRMW* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } - void visitStructCmpxchg(StructCmpxchg* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } void visitArrayNew(ArrayNew* curr) { if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); @@ -370,63 +345,28 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } - void visitArraySet(ArraySet* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } - void visitArrayGet(ArrayGet* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } - void visitArrayCopy(ArrayCopy* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->srcRef->type) && - !maybePrintUnreachableOrNullReplacement(curr, curr->destRef->type)) { - visitExpression(curr); - } - } - void visitArrayFill(ArrayFill* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } - void visitArrayInitData(ArrayInitData* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } - void visitArrayInitElem(ArrayInitElem* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->ref->type)) { - visitExpression(curr); - } - } void visitContNew(ContNew* curr) { if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); } } void visitContBind(ContBind* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && - !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); } } void visitResume(Resume* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && - !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); } } void visitResumeThrow(ResumeThrow* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && - !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); } } void visitStackSwitch(StackSwitch* curr) { - if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && - !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); } } @@ -2776,6 +2716,11 @@ void PrintSExpression::maybePrintImplicitBlock(Expression* curr) { } void PrintSExpression::visitExpression(Expression* curr) { + if (Properties::hasUnwritableTypeImmediate(curr)) { + printUnreachableReplacement(curr); + return; + } + o << '('; printExpressionContents(curr); auto it = ChildIterator(curr); @@ -2987,16 +2932,7 @@ void PrintSExpression::visitTryTable(TryTable* curr) { controlFlowDepth--; } -bool PrintSExpression::maybePrintUnreachableReplacement(Expression* curr, - Type type) { - // When we cannot print an instruction because the child from which it's - // supposed to get a type immediate is unreachable, then we print a - // semantically-equivalent block that drops each of the children and ends in - // an unreachable. - if (type != Type::unreachable) { - return false; - } - +void PrintSExpression::printUnreachableReplacement(Expression* curr) { // Emit a block with drops of the children. o << "(block"; if (!minify) { @@ -3012,15 +2948,19 @@ bool PrintSExpression::maybePrintUnreachableReplacement(Expression* curr, Unreachable unreachable; printFullLine(&unreachable); decIndent(); - return true; } -bool PrintSExpression::maybePrintUnreachableOrNullReplacement(Expression* curr, - Type type) { - if (type.isNull()) { - type = Type::unreachable; +bool PrintSExpression::maybePrintUnreachableReplacement(Expression* curr, + Type type) { + // When we cannot print an instruction because the child from which it's + // supposed to get a type immediate is unreachable, then we print a + // semantically-equivalent block that drops each of the children and ends in + // an unreachable. + if (type == Type::unreachable) { + printUnreachableReplacement(curr); + return true; } - return maybePrintUnreachableReplacement(curr, type); + return false; } static bool requiresExplicitFuncType(HeapType type) { diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index e40f7a06f02..7566573beff 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -38,6 +38,10 @@ // are visited in reverse order, which is convenient for walking by pushing // them to a stack first). // +// DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) - called for each child field +// whose type is given by an immediate in the binary format. If you do not +// define this, then DELEGATE_FIELD_CHILD is called. +// // DELEGATE_FIELD_OPTIONAL_CHILD(id, field) - called for a child that may not be // present (like a Return's value). If you do not define this then // DELEGATE_FIELD_CHILD is called. @@ -109,6 +113,10 @@ #error please define DELEGATE_FIELD_CHILD(id, field) #endif +#ifndef DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD +#define DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_CHILD(id, field) +#endif + #ifndef DELEGATE_FIELD_OPTIONAL_CHILD #define DELEGATE_FIELD_OPTIONAL_CHILD(id, field) DELEGATE_FIELD_CHILD(id, field) #endif @@ -230,6 +238,7 @@ // By default we emit a switch and cases, but that can be customized using the // following: + #ifndef DELEGATE_FIELD_MAIN_START #define DELEGATE_FIELD_MAIN_START \ switch (DELEGATE_ID) { \ @@ -610,7 +619,7 @@ DELEGATE_FIELD_INT(I31Get, signed_) DELEGATE_FIELD_CASE_END(I31Get) DELEGATE_FIELD_CASE_START(CallRef) -DELEGATE_FIELD_CHILD(CallRef, target) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(CallRef, target) DELEGATE_FIELD_CHILD_VECTOR(CallRef, operands) DELEGATE_FIELD_INT(CallRef, isReturn) DELEGATE_FIELD_CASE_END(CallRef) @@ -637,7 +646,7 @@ DELEGATE_FIELD_CASE_END(StructNew) DELEGATE_FIELD_CASE_START(StructGet) DELEGATE_FIELD_INT(StructGet, index) -DELEGATE_FIELD_CHILD(StructGet, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructGet, ref) DELEGATE_FIELD_INT(StructGet, signed_) DELEGATE_FIELD_INT(StructGet, order) DELEGATE_FIELD_CASE_END(StructGet) @@ -645,7 +654,7 @@ DELEGATE_FIELD_CASE_END(StructGet) DELEGATE_FIELD_CASE_START(StructSet) DELEGATE_FIELD_INT(StructSet, index) DELEGATE_FIELD_CHILD(StructSet, value) -DELEGATE_FIELD_CHILD(StructSet, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructSet, ref) DELEGATE_FIELD_INT(StructSet, order) DELEGATE_FIELD_CASE_END(StructSet) @@ -653,7 +662,7 @@ DELEGATE_FIELD_CASE_START(StructRMW) DELEGATE_FIELD_INT(StructRMW, op) DELEGATE_FIELD_INT(StructRMW, index) DELEGATE_FIELD_CHILD(StructRMW, value) -DELEGATE_FIELD_CHILD(StructRMW, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructRMW, ref) DELEGATE_FIELD_INT(StructRMW, order) DELEGATE_FIELD_CASE_END(StructRMW) @@ -661,7 +670,7 @@ DELEGATE_FIELD_CASE_START(StructCmpxchg) DELEGATE_FIELD_INT(StructCmpxchg, index) DELEGATE_FIELD_CHILD(StructCmpxchg, replacement) DELEGATE_FIELD_CHILD(StructCmpxchg, expected) -DELEGATE_FIELD_CHILD(StructCmpxchg, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructCmpxchg, ref) DELEGATE_FIELD_INT(StructCmpxchg, order) DELEGATE_FIELD_CASE_END(StructCmpxchg) @@ -688,14 +697,14 @@ DELEGATE_FIELD_CASE_END(ArrayNewFixed) DELEGATE_FIELD_CASE_START(ArrayGet) DELEGATE_FIELD_CHILD(ArrayGet, index) -DELEGATE_FIELD_CHILD(ArrayGet, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayGet, ref) DELEGATE_FIELD_INT(ArrayGet, signed_) DELEGATE_FIELD_CASE_END(ArrayGet) DELEGATE_FIELD_CASE_START(ArraySet) DELEGATE_FIELD_CHILD(ArraySet, value) DELEGATE_FIELD_CHILD(ArraySet, index) -DELEGATE_FIELD_CHILD(ArraySet, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArraySet, ref) DELEGATE_FIELD_CASE_END(ArraySet) DELEGATE_FIELD_CASE_START(ArrayLen) @@ -705,16 +714,16 @@ DELEGATE_FIELD_CASE_END(ArrayLen) DELEGATE_FIELD_CASE_START(ArrayCopy) DELEGATE_FIELD_CHILD(ArrayCopy, length) DELEGATE_FIELD_CHILD(ArrayCopy, srcIndex) -DELEGATE_FIELD_CHILD(ArrayCopy, srcRef) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayCopy, srcRef) DELEGATE_FIELD_CHILD(ArrayCopy, destIndex) -DELEGATE_FIELD_CHILD(ArrayCopy, destRef) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayCopy, destRef) DELEGATE_FIELD_CASE_END(ArrayCopy) DELEGATE_FIELD_CASE_START(ArrayFill) DELEGATE_FIELD_CHILD(ArrayFill, size) DELEGATE_FIELD_CHILD(ArrayFill, value) DELEGATE_FIELD_CHILD(ArrayFill, index) -DELEGATE_FIELD_CHILD(ArrayFill, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayFill, ref) DELEGATE_FIELD_CASE_END(ArrayFill) DELEGATE_FIELD_CASE_START(ArrayInitData) @@ -722,7 +731,7 @@ DELEGATE_FIELD_NAME_KIND(ArrayInitData, segment, ModuleItemKind::DataSegment) DELEGATE_FIELD_CHILD(ArrayInitData, size) DELEGATE_FIELD_CHILD(ArrayInitData, offset) DELEGATE_FIELD_CHILD(ArrayInitData, index) -DELEGATE_FIELD_CHILD(ArrayInitData, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayInitData, ref) DELEGATE_FIELD_CASE_END(ArrayInitData) DELEGATE_FIELD_CASE_START(ArrayInitElem) @@ -730,7 +739,7 @@ DELEGATE_FIELD_NAME_KIND(ArrayInitElem, segment, ModuleItemKind::ElementSegment) DELEGATE_FIELD_CHILD(ArrayInitElem, size) DELEGATE_FIELD_CHILD(ArrayInitElem, offset) DELEGATE_FIELD_CHILD(ArrayInitElem, index) -DELEGATE_FIELD_CHILD(ArrayInitElem, ref) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayInitElem, ref) DELEGATE_FIELD_CASE_END(ArrayInitElem) DELEGATE_FIELD_CASE_START(RefAs) @@ -788,7 +797,7 @@ DELEGATE_FIELD_CHILD(ContNew, func) DELEGATE_FIELD_CASE_END(ContNew) DELEGATE_FIELD_CASE_START(ContBind) -DELEGATE_FIELD_CHILD(ContBind, cont) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ContBind, cont) DELEGATE_FIELD_CHILD_VECTOR(ContBind, operands) DELEGATE_FIELD_CASE_END(ContBind) @@ -799,7 +808,7 @@ DELEGATE_FIELD_CASE_END(Suspend) DELEGATE_FIELD_CASE_START(Resume) DELEGATE_FIELD_TYPE_VECTOR(Resume, sentTypes) -DELEGATE_FIELD_CHILD(Resume, cont) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(Resume, cont) DELEGATE_FIELD_CHILD_VECTOR(Resume, operands) DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(Resume, handlerBlocks) DELEGATE_FIELD_NAME_KIND_VECTOR(Resume, handlerTags, ModuleItemKind::Tag) @@ -807,7 +816,7 @@ DELEGATE_FIELD_CASE_END(Resume) DELEGATE_FIELD_CASE_START(ResumeThrow) DELEGATE_FIELD_TYPE_VECTOR(ResumeThrow, sentTypes) -DELEGATE_FIELD_CHILD(ResumeThrow, cont) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ResumeThrow, cont) DELEGATE_FIELD_CHILD_VECTOR(ResumeThrow, operands) DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(ResumeThrow, handlerBlocks) DELEGATE_FIELD_NAME_KIND_VECTOR(ResumeThrow, handlerTags, ModuleItemKind::Tag) @@ -815,7 +824,7 @@ DELEGATE_FIELD_NAME_KIND(ResumeThrow, tag, ModuleItemKind::Tag) DELEGATE_FIELD_CASE_END(ResumeThrow) DELEGATE_FIELD_CASE_START(StackSwitch) -DELEGATE_FIELD_CHILD(StackSwitch, cont) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StackSwitch, cont) DELEGATE_FIELD_CHILD_VECTOR(StackSwitch, operands) DELEGATE_FIELD_NAME_KIND(StackSwitch, tag, ModuleItemKind::Tag) DELEGATE_FIELD_CASE_END(StackSwitch) @@ -827,6 +836,7 @@ DELEGATE_FIELD_MAIN_END #undef DELEGATE_START #undef DELEGATE_END #undef DELEGATE_FIELD_CHILD +#undef DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD #undef DELEGATE_FIELD_OPTIONAL_CHILD #undef DELEGATE_FIELD_CHILD_VECTOR #undef DELEGATE_FIELD_INT From 841b59fc59d24c07c1299a79051e65deb49af6d0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 8 May 2025 16:16:44 -0700 Subject: [PATCH 489/622] [Branch Hinting] Support branch hints on If and BrOn (#7578) With this, support for the feature is complete. --- src/parser/contexts.h | 7 ++- src/wasm-ir-builder.h | 13 ++++-- src/wasm/wasm-ir-builder.cpp | 30 ++++++++----- test/lit/wat-annotations.wast | 81 +++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 15 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index b4d27133dcb..db3ff363798 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1914,8 +1914,10 @@ struct ParseDefsCtx : TypeParserCtx { if (!type.isSignature()) { return in.err(pos, "expected function type"); } + auto likely = getBranchHint(annotations); return withLoc( - pos, irBuilder.makeIf(label ? *label : Name{}, type.getSignature())); + pos, + irBuilder.makeIf(label ? *label : Name{}, type.getSignature(), likely)); } Result<> visitElse() { return withLoc(irBuilder.visitElse()); } @@ -2546,7 +2548,8 @@ struct ParseDefsCtx : TypeParserCtx { BrOnOp op, Type in = Type::none, Type out = Type::none) { - return withLoc(pos, irBuilder.makeBrOn(label, op, in, out)); + auto likely = getBranchHint(annotations); + return withLoc(pos, irBuilder.makeBrOn(label, op, in, out, likely)); } Result<> makeStructNew(Index pos, diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index d979fb52597..eeb985df1a2 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -116,7 +116,8 @@ class IRBuilder : public UnifiedExpressionVisitor> { // signatures ensure that there are no missing fields. Result<> makeNop(); Result<> makeBlock(Name label, Signature sig); - Result<> makeIf(Name label, Signature sig); + Result<> + makeIf(Name label, Signature sig, std::optional likely = std::nullopt); Result<> makeLoop(Name label, Signature sig); Result<> makeBreak(Index label, bool isConditional, @@ -201,8 +202,11 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeCallRef(HeapType type, bool isReturn); Result<> makeRefTest(Type type); Result<> makeRefCast(Type type); - Result<> - makeBrOn(Index label, BrOnOp op, Type in = Type::none, Type out = Type::none); + Result<> makeBrOn(Index label, + BrOnOp op, + Type in = Type::none, + Type out = Type::none, + std::optional likely = std::nullopt); Result<> makeStructNew(HeapType type); Result<> makeStructNewDefault(HeapType type); Result<> @@ -694,6 +698,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { Expression* fixExtraOutput(ScopeCtx& scope, Name label, Expression* expr); void fixLoopWithInput(Loop* loop, Type inputType, Index scratch); + // Add a branch hint, if |likely| is present. + void addBranchHint(Expression* expr, std::optional likely); + void dump(); }; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 269682c5fa0..4f8da899251 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1380,9 +1380,11 @@ Result<> IRBuilder::makeBlock(Name label, Signature sig) { return visitBlockStart(block, sig.params); } -Result<> IRBuilder::makeIf(Name label, Signature sig) { +Result<> +IRBuilder::makeIf(Name label, Signature sig, std::optional likely) { auto* iff = wasm.allocator.alloc(); iff->type = sig.results; + addBranchHint(iff, likely); return visitIfStart(iff, label, sig.params); } @@ -1407,14 +1409,9 @@ Result<> IRBuilder::makeBreak(Index label, curr.condition = isConditional ? &curr : nullptr; CHECK_ERR(ChildPopper{*this}.visitBreak(&curr, *labelType)); auto* br = builder.makeBreak(curr.name, curr.value, curr.condition); + addBranchHint(br, likely); push(br); - if (likely) { - // Branches are only possible inside functions. - assert(func); - func->codeAnnotations[br].branchLikely = likely; - } - return Ok{}; } @@ -1979,7 +1976,8 @@ Result<> IRBuilder::makeRefCast(Type type) { return Ok{}; } -Result<> IRBuilder::makeBrOn(Index label, BrOnOp op, Type in, Type out) { +Result<> IRBuilder::makeBrOn( + Index label, BrOnOp op, Type in, Type out, std::optional likely) { BrOn curr; curr.op = op; curr.castType = out; @@ -2036,7 +2034,9 @@ Result<> IRBuilder::makeBrOn(Index label, BrOnOp op, Type in, Type out) { auto name = getLabelName(label); CHECK_ERR(name); - push(builder.makeBrOn(op, *name, curr.ref, out)); + auto* br = builder.makeBrOn(op, *name, curr.ref, out); + addBranchHint(br, likely); + push(br); return Ok{}; } @@ -2058,7 +2058,9 @@ Result<> IRBuilder::makeBrOn(Index label, BrOnOp op, Type in, Type out) { // Perform the branch. CHECK_ERR(visitBrOn(&curr)); - push(builder.makeBrOn(op, extraLabel, curr.ref, out)); + auto* br = builder.makeBrOn(op, extraLabel, curr.ref, out); + addBranchHint(br, likely); + push(br); // If the branch wasn't taken, we need to leave the extra values on the // stack. For all instructions except br_on_non_null the extra values need @@ -2531,4 +2533,12 @@ Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { return Ok{}; } +void IRBuilder::addBranchHint(Expression* expr, std::optional likely) { + if (likely) { + // Branches are only possible inside functions. + assert(func); + func->codeAnnotations[expr].branchLikely = likely; + } +} + } // namespace wasm diff --git a/test/lit/wat-annotations.wast b/test/lit/wat-annotations.wast index 658c00224be..154b79e0d2c 100644 --- a/test/lit/wat-annotations.wast +++ b/test/lit/wat-annotations.wast @@ -7,6 +7,8 @@ ;; CHECK: (type $0 (func (param i32))) + ;; CHECK: (type $1 (func (param anyref))) + ;; CHECK: (func $no-annotations (type $0) (param $x i32) ;; CHECK-NEXT: (block $out ;; CHECK-NEXT: (br_if $out @@ -16,6 +18,8 @@ ;; CHECK-NEXT: ) ;; RTRIP: (type $0 (func (param i32))) + ;; RTRIP: (type $1 (func (param anyref))) + ;; RTRIP: (func $no-annotations (type $0) (param $x i32) ;; RTRIP-NEXT: (block $block ;; RTRIP-NEXT: (br_if $block @@ -148,4 +152,81 @@ ) ) ) + + ;; CHECK: (func $branch-hints-if (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $branch-hints-if (type $0) (param $x i32) + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\00") + ;; RTRIP-NEXT: (if + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: (then + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\01") + ;; RTRIP-NEXT: (if + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: (then + ;; RTRIP-NEXT: (nop) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $branch-hints-if (param $x i32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (nop) + ) + ) + ) + ) + ) + + ;; CHECK: (func $branch-hints-br_on (type $1) (param $x anyref) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_on_null $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $branch-hints-br_on (type $1) (param $x anyref) + ;; RTRIP-NEXT: (block $block + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\00") + ;; RTRIP-NEXT: (br_on_null $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $branch-hints-br_on (param $x anyref) + (block $out + (drop + (@metadata.code.branch_hint "\00") + (br_on_null $out + (local.get $x) + ) + ) + ) + ) ) From 6e7a4a0507986d067550911a3bac59ce76f8453c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 8 May 2025 17:08:05 -0700 Subject: [PATCH 490/622] Update TypeSSA for exact types (#7579) TypeSSA replaces allocations with allocations of new subtypes of the original allocated type. This is not valid when the exactness of the original allocation can be observed, e.g. because the allocation can flow into some other instruction that would not be valid unless the allocation is exactly the original type. Fix TypeSSA for the presence of exact types by using ChildTyper to find instructions that require a child to be an exact reference. When such a child is found, inhibit all optimization of allocations for that child's type. This is the best we can do without performing a data flow analysis to prove that individual allocations do not flow into locations where their exactness can be observed. --- scripts/test/fuzzing.py | 2 + src/ir/child-typer.h | 6 +- src/passes/TypeSSA.cpp | 189 ++- test/lit/passes/type-ssa-exact-rmw.wast | 184 +++ test/lit/passes/type-ssa-exact.wast | 1694 +++++++++++++++++++++++ 5 files changed, 2055 insertions(+), 20 deletions(-) create mode 100644 test/lit/passes/type-ssa-exact-rmw.wast create mode 100644 test/lit/passes/type-ssa-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index dc48bb7033e..580bbb012b4 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -84,6 +84,7 @@ 'optimize-instructions-struct-rmw.wast', 'gto-removals-rmw.wast', 'type-refining-rmw.wast', + 'type-ssa-exact-rmw.wast', 'cfp-rmw.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', @@ -127,6 +128,7 @@ 'type-merging-exact.wast', 'type-refining-exact.wast', 'type-refining-gufa-exact.wast', + 'type-ssa-exact.wast', 'minimize-rec-groups-exact.wast', 'minimize-rec-groups-ignore-exact.wast', 'public-exact.wast', diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 68398fa906b..346a10bad40 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -33,9 +33,9 @@ namespace wasm { // noteAnyType(Expression** childp) - The child may have any non-tuple type. // Used for the children of polymorphic instructions like `drop` and `select`. // -// noteAnyReference(Expression** childp) - The child may have any reference -// type. Used for the children of polymorphic reference instructions like -// `ref.is_null`. +// noteAnyReferenceType(Expression** childp) - The child may have any +// reference type. Used for the children of polymorphic reference instructions +// like `ref.is_null`. // // noteAnyTupleType(Expression** childp, size_t arity) - The child may have // any tuple type with the given arity. Used for the children of polymorphic diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 3d68c991396..e55cadc5acf 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -47,6 +47,7 @@ // This pass works well with TypeMerging. See notes there for more. // +#include "ir/child-typer.h" #include "ir/find_all.h" #include "ir/module-utils.h" #include "ir/names.h" @@ -147,14 +148,138 @@ std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, // A vector of struct.new or one of the variations on array.new. using News = std::vector; -struct NewFinder : public PostWalker { +// A set of types for which the exactness of allocations may be observed. +using TypeSet = std::unordered_set; + +struct Analyzer + : public PostWalker> { + // Find allocations we can potentially optimize. News news; - void visitStructNew(StructNew* curr) { news.push_back(curr); } - void visitArrayNew(ArrayNew* curr) { news.push_back(curr); } - void visitArrayNewData(ArrayNewData* curr) { news.push_back(curr); } - void visitArrayNewElem(ArrayNewElem* curr) { news.push_back(curr); } - void visitArrayNewFixed(ArrayNewFixed* curr) { news.push_back(curr); } + // Also find heap types for which the exactness of allocations is observed. We + // will not be able to optimize allocations of these types without an analysis + // proving that an allocation does not flow into any location where its + // exactness is observed. + TypeSet disallowedTypes; + + // Find allocations we can potentially optimize. + void visitStructNew(StructNew* curr) { + news.push_back(curr); + visitExpression(curr); + } + void visitArrayNew(ArrayNew* curr) { + news.push_back(curr); + visitExpression(curr); + } + void visitArrayNewData(ArrayNewData* curr) { + news.push_back(curr); + visitExpression(curr); + } + void visitArrayNewElem(ArrayNewElem* curr) { + news.push_back(curr); + visitExpression(curr); + } + void visitArrayNewFixed(ArrayNewFixed* curr) { + news.push_back(curr); + visitExpression(curr); + } + + // Find casts to exact types. Allocations of these types will not be able to + // be optimized. + template void visitCast(Cast* cast) { + if (auto type = cast->getCastType(); type.isExact()) { + disallowedTypes.insert(type.getHeapType()); + } + } + void visitRefTest(RefTest* curr) { visitCast(curr); } + void visitRefCast(RefCast* curr) { visitCast(curr); } + void visitBrOn(BrOn* curr) { + if (curr->op == BrOnCast || curr->op == BrOnCastFail) { + visitCast(curr); + } + } + + void visitExpression(Expression* curr) { + // Look at the constraints on this expression's operands to see if it + // requires an exact operand. If it does, we cannot optimize allocations of + // that type. As an optimization, do not let control flow structures or + // branches inhibit optimization since they can safely be refinalized to use + // new types as long as no other instruction expected the original exact + // type. Also allow optimizing if the instruction that would inhibit + // optimizing will not be written in the final output. Skipping further + // analysis for these instructions also ensures that the ChildTyper below + // sees the type information it expects in the instructions it analyzes. + if (Properties::isControlFlowStructure(curr) || + Properties::isBranch(curr) || + Properties::hasUnwritableTypeImmediate(curr)) { + return; + } + + // Also do not let unreachable instructions inhibit optimization, as long as + // they are unreachable because of an unreachable child. (Some other + // unreachable instructions, such as a return_call, can still require an + // exact operand and may inhibit optimization.) + if (curr->type == Type::unreachable) { + for (auto* child : ChildIterator(curr)) { + if (child->type == Type::unreachable) { + return; + } + } + } + + struct ExactChildTyper : ChildTyper { + Analyzer& parent; + ExactChildTyper(Analyzer& parent) + : ChildTyper(*parent.getModule(), parent.getFunction()), + parent(parent) {} + + void noteSubtype(Expression**, Type type) { + for (Type t : type) { + if (t.isExact()) { + parent.disallowedTypes.insert(t.getHeapType()); + } + } + } + + // Other constraints do not matter to us. + void noteAnyType(Expression**) {} + void noteAnyReferenceType(Expression**) {} + void noteAnyTupleType(Expression**, size_t) {} + void noteAnyI8ArrayReferenceType(Expression**) {} + void noteAnyI16ArrayReferenceType(Expression**) {} + + Type getLabelType(Name label) { WASM_UNREACHABLE("unexpected branch"); } + } typer(*this); + typer.visit(curr); + } + + void visitFunction(Function* func) { + // Returned exact references must remain exact references to the original + // heap types. + for (auto type : func->getSig().results) { + if (type.isExact()) { + disallowedTypes.insert(type.getHeapType()); + } + } + } + + void visitGlobal(Global* global) { + // This could be more precise by checking that the init expression is not + // null before inhibiting optimization, or by just inhibiting optmization of + // the allocations used in the initialization, but this is simpler. + for (auto type : global->type) { + if (type.isExact()) { + disallowedTypes.insert(type.getHeapType()); + } + } + } + + void visitElementSegment(ElementSegment* segment) { + assert(!segment->type.isTuple()); + if (segment->type.isExact()) { + disallowedTypes.insert(segment->type.getHeapType()); + } + } }; struct TypeSSA : public Pass { @@ -170,28 +295,48 @@ struct TypeSSA : public Pass { return; } - // First, find all the struct/array.news. + struct Info { + News news; + TypeSet disallowedTypes; + }; - ModuleUtils::ParallelFunctionAnalysis analysis( - *module, [&](Function* func, News& news) { + // First, analyze the function to find struct/array.news and disallowed + // types. + ModuleUtils::ParallelFunctionAnalysis analysis( + *module, [&](Function* func, Info& info) { if (func->imported()) { return; } - NewFinder finder; - finder.walk(func->body); - news = std::move(finder.news); + Analyzer analyzer; + analyzer.walkFunctionInModule(func, module); + info.news = std::move(analyzer.news); + info.disallowedTypes = std::move(analyzer.disallowedTypes); }); // Also find news in the module scope. - NewFinder moduleFinder; - moduleFinder.walkModuleCode(module); + Analyzer moduleAnalyzer; + moduleAnalyzer.walkModuleCode(module); + for (auto& global : module->globals) { + moduleAnalyzer.visitGlobal(global.get()); + } + for (auto& segment : module->elementSegments) { + moduleAnalyzer.visitElementSegment(segment.get()); + } + // TODO: Visit tables with initializers once we support those. + + // Find all the types that are unoptimizable because the exactness of their + // allocations may be observed. + ModuleUtils::iterDefinedFunctions(*module, [&](Function* func) { + processDisallowedTypes(analysis.map[func].disallowedTypes); + }); + processDisallowedTypes(moduleAnalyzer.disallowedTypes); // Process all the news to find the ones we want to modify, adding them to // newsToModify. Note that we must do so in a deterministic order. ModuleUtils::iterDefinedFunctions( - *module, [&](Function* func) { processNews(analysis.map[func]); }); - processNews(moduleFinder.news); + *module, [&](Function* func) { processNews(analysis.map[func].news); }); + processNews(moduleAnalyzer.news); // Modify the ones we found are relevant. We must modify them all at once as // in the isorecursive type system we want to create a single new rec group @@ -203,6 +348,12 @@ struct TypeSSA : public Pass { ReFinalize().runOnModuleCode(getPassRunner(), module); } + TypeSet disallowedTypes; + + void processDisallowedTypes(const TypeSet& types) { + disallowedTypes.insert(types.begin(), types.end()); + } + News newsToModify; // As we generate new names, use a consistent index. @@ -210,7 +361,11 @@ struct TypeSSA : public Pass { void processNews(const News& news) { for (auto* curr : news) { - if (isInteresting(curr)) { + bool disallowed = false; + if (curr->type.isRef()) { + disallowed = disallowedTypes.count(curr->type.getHeapType()); + } + if (!disallowed && isInteresting(curr)) { newsToModify.push_back(curr); } } diff --git a/test/lit/passes/type-ssa-exact-rmw.wast b/test/lit/passes/type-ssa-exact-rmw.wast new file mode 100644 index 00000000000..d658a34dc6c --- /dev/null +++ b/test/lit/passes/type-ssa-exact-rmw.wast @@ -0,0 +1,184 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s -all --type-ssa --preserve-type-order -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $used-in-struct-rmw (sub (struct (field anyref)))) + (type $used-in-struct-rmw (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-struct-rmw-ok (sub (struct (field anyref)))) + (type $used-in-struct-rmw-ok (sub (struct (field anyref)))) + ;; CHECK: (type $expected-in-struct-cmpxchg (sub (struct (field anyref)))) + (type $expected-in-struct-cmpxchg (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-struct-cmpxchg (sub $expected-in-struct-cmpxchg (struct (field anyref)))) + (type $used-in-struct-cmpxchg (sub $expected-in-struct-cmpxchg (struct (field anyref)))) + ;; CHECK: (type $used-in-struct-cmpxchg-ok (sub $expected-in-struct-cmpxchg (struct (field anyref)))) + (type $used-in-struct-cmpxchg-ok (sub $expected-in-struct-cmpxchg (struct (field anyref)))) + ) + + ;; CHECK: (type $struct-rmw-exact (struct (field (mut (ref (exact $used-in-struct-rmw)))))) + (type $struct-rmw-exact (struct (field (mut (ref (exact $used-in-struct-rmw)))))) + ;; CHECK: (type $struct-rmw-inexact (struct (field (mut (ref $used-in-struct-rmw-ok))))) + (type $struct-rmw-inexact (struct (field (mut (ref $used-in-struct-rmw-ok))))) + + ;; CHECK: (type $struct-cmpxchg-exact (struct (field (mut (ref (exact $used-in-struct-cmpxchg)))))) + (type $struct-cmpxchg-exact (struct (field (mut (ref (exact $used-in-struct-cmpxchg)))))) + ;; CHECK: (type $struct-cmpxchg-inexact (struct (field (mut (ref $used-in-struct-cmpxchg-ok))))) + (type $struct-cmpxchg-inexact (struct (field (mut (ref $used-in-struct-cmpxchg-ok))))) + + ;; CHECK: (type $9 (func (param (ref (exact $used-in-struct-rmw)) (ref $struct-rmw-exact)))) + + ;; CHECK: (type $10 (func (param (ref (exact $used-in-struct-rmw-ok)) (ref $struct-rmw-inexact)))) + + ;; CHECK: (type $11 (func (param (ref (exact $used-in-struct-cmpxchg)) (ref (exact $expected-in-struct-cmpxchg)) (ref $struct-cmpxchg-exact)))) + + ;; CHECK: (type $12 (func (param (ref (exact $used-in-struct-cmpxchg-ok)) (ref (exact $expected-in-struct-cmpxchg)) (ref $struct-cmpxchg-inexact)))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $used-in-struct-rmw-ok_1 (sub $used-in-struct-rmw-ok (struct (field anyref)))) + + ;; CHECK: (type $expected-in-struct-cmpxchg_2 (sub $expected-in-struct-cmpxchg (struct (field anyref)))) + + ;; CHECK: (type $expected-in-struct-cmpxchg_3 (sub $expected-in-struct-cmpxchg (struct (field anyref)))) + + ;; CHECK: (type $used-in-struct-cmpxchg-ok_4 (sub $used-in-struct-cmpxchg-ok (struct (field anyref)))) + + ;; CHECK: (func $struct-rmw (type $9) (param $used (ref (exact $used-in-struct-rmw))) (param $ref (ref $struct-rmw-exact)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct-rmw-exact 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-struct-rmw + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-rmw (param $used (ref (exact $used-in-struct-rmw))) (param $ref (ref $struct-rmw-exact)) + (drop + ;; This requires and observes an exact $used-in-struct-rmw + (struct.atomic.rmw.xchg $struct-rmw-exact 0 + (local.get $ref) + (local.get $used) + ) + ) + (drop + ;; So this cannot be optimized since we don't do a flow analysis and don't + ;; know that this particular allocation is never observed to be exact. + (struct.new $used-in-struct-rmw + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $struct-rmw-ok (type $10) (param $used (ref (exact $used-in-struct-rmw-ok))) (param $ref (ref $struct-rmw-inexact)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct-rmw-inexact 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-struct-rmw-ok_1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-rmw-ok (param $used (ref (exact $used-in-struct-rmw-ok))) (param $ref (ref $struct-rmw-inexact)) + (drop + ;; But this time the operand is not required to be exact. + (struct.atomic.rmw.xchg $struct-rmw-inexact 0 + (local.get $ref) + (local.get $used) + ) + ) + (drop + ;; So this can be optimized. + (struct.new $used-in-struct-rmw-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $struct-cmpxchg (type $11) (param $used (ref (exact $used-in-struct-cmpxchg))) (param $expected (ref (exact $expected-in-struct-cmpxchg))) (param $ref (ref $struct-cmpxchg-exact)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct-cmpxchg-exact 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $expected) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $expected-in-struct-cmpxchg_2 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-struct-cmpxchg + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-cmpxchg (param $used (ref (exact $used-in-struct-cmpxchg))) (param $expected (ref (exact $expected-in-struct-cmpxchg))) (param $ref (ref $struct-cmpxchg-exact)) + (drop + (struct.atomic.rmw.cmpxchg $struct-cmpxchg-exact 0 + (local.get $ref) + (local.get $expected) + (local.get $used) + ) + ) + (drop + ;; This can still be optimized. Only the written value is affected. + (struct.new $expected-in-struct-cmpxchg + (ref.null none) + ) + ) + (drop + (struct.new $used-in-struct-cmpxchg + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $struct-cmpxchg-ok (type $12) (param $used (ref (exact $used-in-struct-cmpxchg-ok))) (param $expected (ref (exact $expected-in-struct-cmpxchg))) (param $ref (ref $struct-cmpxchg-inexact)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct-cmpxchg-inexact 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $expected) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $expected-in-struct-cmpxchg_3 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-struct-cmpxchg-ok_4 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-cmpxchg-ok (param $used (ref (exact $used-in-struct-cmpxchg-ok))) (param $expected (ref (exact $expected-in-struct-cmpxchg))) (param $ref (ref $struct-cmpxchg-inexact)) + (drop + (struct.atomic.rmw.cmpxchg $struct-cmpxchg-inexact 0 + (local.get $ref) + (local.get $expected) + (local.get $used) + ) + ) + ;; Now both can be optimized. + (drop + (struct.new $expected-in-struct-cmpxchg + (ref.null none) + ) + ) + (drop + (struct.new $used-in-struct-cmpxchg-ok + (ref.null none) + ) + ) + ) +) diff --git a/test/lit/passes/type-ssa-exact.wast b/test/lit/passes/type-ssa-exact.wast new file mode 100644 index 00000000000..a53b65493b7 --- /dev/null +++ b/test/lit/passes/type-ssa-exact.wast @@ -0,0 +1,1694 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s -all --type-ssa --preserve-type-order -S -o - | filecheck %s + +(module + (rec + ;; Casts + ;; CHECK: (rec + ;; CHECK-NEXT: (type $used-in-ref-test (sub (struct (field anyref)))) + (type $used-in-ref-test (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-ref-cast (sub (struct (field anyref)))) + (type $used-in-ref-cast (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-br-on-cast (sub (struct (field anyref)))) + (type $used-in-br-on-cast (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-br-on-cast-fail (sub (struct (field anyref)))) + (type $used-in-br-on-cast-fail (sub (struct (field anyref)))) + + ;; Control flow structures + ;; CHECK: (type $used-in-block (sub (struct (field anyref)))) + (type $used-in-block (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-loop (sub (struct (field anyref)))) + (type $used-in-loop (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-if (sub (struct (field anyref)))) + (type $used-in-if (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-try (sub (struct (field anyref)))) + (type $used-in-try (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-try-table (sub (struct (field anyref)))) + (type $used-in-try-table (sub (struct (field anyref)))) + + ;; Other expressions + ;; CHECK: (type $used-in-branch (sub (struct (field anyref)))) + (type $used-in-branch (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-br-table (sub (struct (field anyref)))) + (type $used-in-br-table (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-return (sub (struct (field anyref)))) + (type $used-in-return (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-return-ok (sub (struct (field anyref)))) + (type $used-in-return-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-call (sub (struct (field anyref)))) + (type $used-in-call (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-call-ok (sub (struct (field anyref)))) + (type $used-in-call-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-call-indirect (sub (struct (field anyref)))) + (type $used-in-call-indirect (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-call-indirect-ok (sub (struct (field anyref)))) + (type $used-in-call-indirect-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-call-ref (sub (struct (field anyref)))) + (type $used-in-call-ref (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-call-ref-ok (sub (struct (field anyref)))) + (type $used-in-call-ref-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-call-ref-unreachable (sub (struct (field anyref)))) + (type $used-in-call-ref-unreachable (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-call-ref-bot (sub (struct (field anyref)))) + (type $used-in-call-ref-bot (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-ret-call (sub (struct (field anyref)))) + (type $used-in-ret-call (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-ret-call-ok (sub (struct (field anyref)))) + (type $used-in-ret-call-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-ret-call-unreachable (sub (struct (field anyref)))) + (type $used-in-ret-call-unreachable (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-throw (sub (struct (field anyref)))) + (type $used-in-throw (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-throw-ok (sub (struct (field anyref)))) + (type $used-in-throw-ok (sub (struct (field anyref)))) + ;; TODO: resume, suspend, and switch + ;; CHECK: (type $used-in-local-set (sub (struct (field anyref)))) + (type $used-in-local-set (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-local-set-ok (sub (struct (field anyref)))) + (type $used-in-local-set-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-local-set-tuple (sub (struct (field anyref)))) + (type $used-in-local-set-tuple (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-local-set-tuple-ok (sub (struct (field anyref)))) + (type $used-in-local-set-tuple-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-global-set (sub (struct (field anyref)))) + (type $used-in-global-set (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-global-set-ok (sub (struct (field anyref)))) + (type $used-in-global-set-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-table-set (sub (struct (field anyref)))) + (type $used-in-table-set (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-table-set-ok (sub (struct (field anyref)))) + (type $used-in-table-set-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-struct-new (sub (struct (field anyref)))) + (type $used-in-struct-new (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-struct-new-ok (sub (struct (field anyref)))) + (type $used-in-struct-new-ok (sub (struct (field anyref)))) + ;; TODO: Struct.new with descriptor + ;; CHECK: (type $used-in-struct-set (sub (struct (field anyref)))) + (type $used-in-struct-set (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-struct-set-ok (sub (struct (field anyref)))) + (type $used-in-struct-set-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-new (sub (struct (field anyref)))) + (type $used-in-array-new (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-new-ok (sub (struct (field anyref)))) + (type $used-in-array-new-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-new-fixed (sub (struct (field anyref)))) + (type $used-in-array-new-fixed (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-new-fixed-ok (sub (struct (field anyref)))) + (type $used-in-array-new-fixed-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-set (sub (struct (field anyref)))) + (type $used-in-array-set (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-set-ok (sub (struct (field anyref)))) + (type $used-in-array-set-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-fill (sub (struct (field anyref)))) + (type $used-in-array-fill (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-fill-ok (sub (struct (field anyref)))) + (type $used-in-array-fill-ok (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-array-copy-ok (sub (struct (field anyref)))) + (type $used-in-array-copy-ok (sub (struct (field anyref)))) + ;; TODO: Array atomic accessors + ;; TODO: cont.new and cont.bind + + ;; Module fields + ;; CHECK: (type $used-in-func (sub (struct (field anyref)))) + (type $used-in-func (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-global (sub (struct (field anyref)))) + (type $used-in-global (sub (struct (field anyref)))) + ;; CHECK: (type $used-in-segment (sub (struct (field anyref)))) + (type $used-in-segment (sub (struct (field anyref)))) + ) + + ;; CHECK: (type $call-ref-exact (func (param (ref (exact $used-in-call-ref))))) + (type $call-ref-exact (func (param (ref (exact $used-in-call-ref))))) + ;; CHECK: (type $call-ref-inexact (func (param (ref $used-in-call-ref-ok)))) + (type $call-ref-inexact (func (param (ref $used-in-call-ref-ok)))) + ;; CHECK: (type $call-ref-unreachable (func (param (ref (exact $used-in-call-ref-unreachable))))) + (type $call-ref-unreachable (func (param (ref (exact $used-in-call-ref-unreachable))))) + ;; CHECK: (type $call-ref-bot (func (param (ref (exact $used-in-call-ref-bot))))) + (type $call-ref-bot (func (param (ref (exact $used-in-call-ref-bot))))) + + ;; CHECK: (type $struct-new-exact (struct (field (ref (exact $used-in-struct-new))) (field anyref))) + (type $struct-new-exact (struct (field (ref (exact $used-in-struct-new)) anyref))) + ;; CHECK: (type $struct-new-inexact (struct (field (ref $used-in-struct-new-ok)) (field anyref))) + (type $struct-new-inexact (struct (field (ref $used-in-struct-new-ok) anyref))) + + ;; CHECK: (type $struct-set-exact (struct (field (mut (ref (exact $used-in-struct-set)))))) + (type $struct-set-exact (struct (field (mut (ref (exact $used-in-struct-set)))))) + ;; CHECK: (type $struct-set-inexact (struct (field (mut (ref $used-in-struct-set-ok))))) + (type $struct-set-inexact (struct (field (mut (ref $used-in-struct-set-ok))))) + + ;; CHECK: (type $array-new-exact (sub (array (ref null (exact $used-in-array-new))))) + (type $array-new-exact (sub (array (field (ref null (exact $used-in-array-new)))))) + ;; CHECK: (type $array-new-inexact (sub (array (ref null $used-in-array-new-ok)))) + (type $array-new-inexact (sub (array (field (ref null $used-in-array-new-ok))))) + + ;; CHECK: (type $array-new-fixed-exact (sub (array (ref null (exact $used-in-array-new-fixed))))) + (type $array-new-fixed-exact (sub (array (field (ref null (exact $used-in-array-new-fixed)))))) + ;; CHECK: (type $array-new-fixed-inexact (sub (array (ref null $used-in-array-new-fixed-ok)))) + (type $array-new-fixed-inexact (sub (array (field (ref null $used-in-array-new-fixed-ok))))) + + ;; CHECK: (type $array-set-exact (sub (array (mut (ref (exact $used-in-array-set)))))) + (type $array-set-exact (sub (array (field (mut (ref (exact $used-in-array-set))))))) + ;; CHECK: (type $array-set-inexact (sub (array (mut (ref $used-in-array-set-ok))))) + (type $array-set-inexact (sub (array (field (mut (ref $used-in-array-set-ok)))))) + + ;; CHECK: (type $array-fill-exact (sub (array (mut (ref (exact $used-in-array-fill)))))) + (type $array-fill-exact (sub (array (field (mut (ref (exact $used-in-array-fill))))))) + ;; CHECK: (type $array-fill-inexact (sub (array (mut (ref $used-in-array-fill-ok))))) + (type $array-fill-inexact (sub (array (field (mut (ref $used-in-array-fill-ok)))))) + + ;; CHECK: (type $array-copy-exact (sub (array (mut (ref (exact $used-in-array-copy-ok)))))) + (type $array-copy-exact (sub (array (field (mut (ref (exact $used-in-array-copy-ok))))))) + + ;; CHECK: (type $67 (func (param (ref (exact $used-in-throw))))) + + ;; CHECK: (type $68 (func (param (ref $used-in-throw-ok)))) + + ;; CHECK: (type $69 (func (param (ref (exact $used-in-ref-test))))) + + ;; CHECK: (type $70 (func (param (ref (exact $used-in-ref-cast))))) + + ;; CHECK: (type $71 (func (param (ref (exact $used-in-br-on-cast))))) + + ;; CHECK: (type $72 (func (param (ref (exact $used-in-br-on-cast-fail))))) + + ;; CHECK: (type $73 (func (param (ref (exact $used-in-block))))) + + ;; CHECK: (type $74 (func (param (ref (exact $used-in-loop))))) + + ;; CHECK: (type $75 (func (param (ref (exact $used-in-if))))) + + ;; CHECK: (type $76 (func (param (ref (exact $used-in-try))))) + + ;; CHECK: (type $77 (func (param (ref (exact $used-in-try-table))))) + + ;; CHECK: (type $78 (func (param (ref (exact $used-in-branch))))) + + ;; CHECK: (type $79 (func (param (ref (exact $used-in-br-table))))) + + ;; CHECK: (type $80 (func (param (ref (exact $used-in-return))) (result (ref (exact $used-in-return))))) + + ;; CHECK: (type $81 (func (param (ref (exact $used-in-return-ok))) (result (ref $used-in-return-ok)))) + + ;; CHECK: (type $82 (func (param (ref (exact $used-in-call))))) + + ;; CHECK: (type $83 (func (param (ref $used-in-call-ok)))) + + ;; CHECK: (type $84 (func (param (ref (exact $used-in-call-ok))))) + + ;; CHECK: (type $85 (func (param (ref (exact $used-in-call-indirect))))) + + ;; CHECK: (type $86 (func (param (ref (exact $used-in-call-indirect-ok))))) + + ;; CHECK: (type $87 (func (param (ref $used-in-call-indirect-ok)))) + + ;; CHECK: (type $88 (func (param (ref (exact $used-in-call-ref)) (ref $call-ref-exact)))) + + ;; CHECK: (type $89 (func (param (ref (exact $used-in-call-ref-ok)) (ref $call-ref-inexact)))) + + ;; CHECK: (type $90 (func (param (ref (exact $used-in-ret-call))))) + + ;; CHECK: (type $91 (func (param (ref $used-in-ret-call-ok)))) + + ;; CHECK: (type $92 (func (param (ref (exact $used-in-ret-call-ok))))) + + ;; CHECK: (type $93 (func (param (ref (exact $used-in-ret-call-unreachable)) i32))) + + ;; CHECK: (type $94 (func (param (ref (exact $used-in-throw-ok))))) + + ;; CHECK: (type $95 (func (param (ref (exact $used-in-local-set))))) + + ;; CHECK: (type $96 (func (param (ref (exact $used-in-local-set-ok))))) + + ;; CHECK: (type $97 (func (param (ref (exact $used-in-local-set-tuple))))) + + ;; CHECK: (type $98 (func (param (ref (exact $used-in-local-set-tuple-ok))))) + + ;; CHECK: (type $99 (func (param (ref (exact $used-in-global-set))))) + + ;; CHECK: (type $100 (func (param (ref (exact $used-in-global-set-ok))))) + + ;; CHECK: (type $101 (func (param (ref (exact $used-in-table-set))))) + + ;; CHECK: (type $102 (func (param (ref (exact $used-in-table-set-ok))))) + + ;; CHECK: (type $103 (func (param (ref (exact $used-in-struct-new))))) + + ;; CHECK: (type $104 (func (param (ref (exact $used-in-struct-new-ok))))) + + ;; CHECK: (type $105 (func (param (ref (exact $used-in-struct-set)) (ref $struct-set-exact)))) + + ;; CHECK: (type $106 (func (param (ref (exact $used-in-struct-set-ok)) (ref $struct-set-inexact)))) + + ;; CHECK: (type $107 (func (param (ref null (exact $used-in-array-new))))) + + ;; CHECK: (type $108 (func (param (ref null (exact $used-in-array-new-ok))))) + + ;; CHECK: (type $109 (func (param (ref null (exact $used-in-array-new-fixed))))) + + ;; CHECK: (type $110 (func (param (ref null (exact $used-in-array-new-fixed-ok))))) + + ;; CHECK: (type $111 (func (param (ref (exact $used-in-array-set)) (ref $array-set-exact)))) + + ;; CHECK: (type $112 (func (param (ref (exact $used-in-array-set-ok)) (ref $array-set-inexact)))) + + ;; CHECK: (type $113 (func (param (ref (exact $used-in-array-fill)) (ref $array-fill-exact)))) + + ;; CHECK: (type $114 (func (param (ref (exact $used-in-array-fill-ok)) (ref $array-fill-inexact)))) + + ;; CHECK: (type $115 (func (param (ref (exact $used-in-array-copy-ok)) (ref $array-copy-exact)))) + + ;; CHECK: (type $116 (func (param (ref (exact $used-in-func))) (result (ref (exact $used-in-func))))) + + ;; CHECK: (type $117 (func)) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $used-in-block_1 (sub $used-in-block (struct (field anyref)))) + + ;; CHECK: (type $used-in-loop_2 (sub $used-in-loop (struct (field anyref)))) + + ;; CHECK: (type $used-in-if_3 (sub $used-in-if (struct (field anyref)))) + + ;; CHECK: (type $used-in-try_4 (sub $used-in-try (struct (field anyref)))) + + ;; CHECK: (type $used-in-try-table_5 (sub $used-in-try-table (struct (field anyref)))) + + ;; CHECK: (type $used-in-branch_6 (sub $used-in-branch (struct (field anyref)))) + + ;; CHECK: (type $used-in-br-table_7 (sub $used-in-br-table (struct (field anyref)))) + + ;; CHECK: (type $used-in-return-ok_8 (sub $used-in-return-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-call-ok_9 (sub $used-in-call-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-call-indirect-ok_10 (sub $used-in-call-indirect-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-call-ref-ok_11 (sub $used-in-call-ref-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-call-ref-unreachable_12 (sub $used-in-call-ref-unreachable (struct (field anyref)))) + + ;; CHECK: (type $used-in-call-ref-bot_13 (sub $used-in-call-ref-bot (struct (field anyref)))) + + ;; CHECK: (type $used-in-ret-call-ok_14 (sub $used-in-ret-call-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-ret-call-unreachable_15 (sub $used-in-ret-call-unreachable (struct (field anyref)))) + + ;; CHECK: (type $used-in-throw-ok_16 (sub $used-in-throw-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-local-set-ok_17 (sub $used-in-local-set-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-local-set-tuple-ok_18 (sub $used-in-local-set-tuple-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-global-set-ok_19 (sub $used-in-global-set-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-table-set-ok_20 (sub $used-in-table-set-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-struct-new-ok_21 (sub $used-in-struct-new-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-struct-set-ok_22 (sub $used-in-struct-set-ok (struct (field anyref)))) + + ;; CHECK: (type $array-new-exact_23 (sub $array-new-exact (array (ref null (exact $used-in-array-new))))) + + ;; CHECK: (type $array-new-inexact_24 (sub $array-new-inexact (array (ref null $used-in-array-new-ok)))) + + ;; CHECK: (type $array-new-inexact_25 (sub $array-new-inexact (array (ref null $used-in-array-new-ok)))) + + ;; CHECK: (type $used-in-array-new-ok_26 (sub $used-in-array-new-ok (struct (field anyref)))) + + ;; CHECK: (type $array-new-fixed-exact_27 (sub $array-new-fixed-exact (array (ref null (exact $used-in-array-new-fixed))))) + + ;; CHECK: (type $array-new-fixed-inexact_28 (sub $array-new-fixed-inexact (array (ref null $used-in-array-new-fixed-ok)))) + + ;; CHECK: (type $array-new-fixed-inexact_29 (sub $array-new-fixed-inexact (array (ref null $used-in-array-new-fixed-ok)))) + + ;; CHECK: (type $used-in-array-new-fixed-ok_30 (sub $used-in-array-new-fixed-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-array-set-ok_31 (sub $used-in-array-set-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-array-fill-ok_32 (sub $used-in-array-fill-ok (struct (field anyref)))) + + ;; CHECK: (type $used-in-array-copy-ok_33 (sub $used-in-array-copy-ok (struct (field anyref)))) + + ;; CHECK: (global $global-exact (mut (ref null (exact $used-in-global-set))) (ref.null none)) + (global $global-exact (mut (ref null (exact $used-in-global-set))) (ref.null none)) + ;; CHECK: (global $global-inexact (mut (ref null $used-in-global-set-ok)) (ref.null none)) + (global $global-inexact (mut (ref null $used-in-global-set-ok)) (ref.null none)) + + ;; CHECK: (global $global-exact-unused (ref null (exact $used-in-global)) (ref.null none)) + (global $global-exact-unused (ref null (exact $used-in-global)) (ref.null none)) + + ;; CHECK: (table $indirect-call-table 1 1 funcref) + (table $indirect-call-table 1 1 funcref) + + ;; CHECK: (table $table-exact 1 1 (ref null (exact $used-in-table-set))) + (table $table-exact 1 1 (ref null (exact $used-in-table-set))) + ;; CHECK: (table $table-inexact 1 1 (ref null $used-in-table-set-ok)) + (table $table-inexact 1 1 (ref null $used-in-table-set-ok)) + + ;; CHECK: (elem $segment-exact-unused (ref null (exact $used-in-segment)) (item (ref.null none))) + (elem $segment-exact-unused (ref null (exact $used-in-segment)) (item (ref.null none))) + + ;; CHECK: (tag $throw-exact (type $67) (param (ref (exact $used-in-throw)))) + (tag $throw-exact (param (ref (exact $used-in-throw)))) + ;; CHECK: (tag $throw-inexact (type $68) (param (ref $used-in-throw-ok))) + (tag $throw-inexact (param (ref $used-in-throw-ok))) + + ;; CHECK: (func $ref-test (type $69) (param $used (ref (exact $used-in-ref-test))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test (ref (exact $used-in-ref-test)) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-ref-test + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test (param $used (ref (exact $used-in-ref-test))) + (drop + ;; This cast will observe the exactness of the $used-in-ref-test. + (ref.test (ref (exact $used-in-ref-test)) + (local.get $used) + ) + ) + (drop + ;; So this cannot be optimized since we don't do a flow analysis and don't + ;; know that this particular allocation is never observed to be exact. + (struct.new $used-in-ref-test + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $ref-cast (type $70) (param $used (ref (exact $used-in-ref-cast))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref (exact $used-in-ref-cast)) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-ref-cast + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast (param $used (ref (exact $used-in-ref-cast))) + (drop + (ref.cast (ref (exact $used-in-ref-cast)) + (local.get $used) + ) + ) + (drop + (struct.new $used-in-ref-cast + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $br_on_cast (type $71) (param $used (ref (exact $used-in-br-on-cast))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref (exact $used-in-br-on-cast))) + ;; CHECK-NEXT: (br_on_cast $l (ref (exact $used-in-br-on-cast)) (ref (exact $used-in-br-on-cast)) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-br-on-cast + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast (param $used (ref (exact $used-in-br-on-cast))) + (drop + (block $l (result anyref) + (br_on_cast $l anyref (ref (exact $used-in-br-on-cast)) + (local.get $used) + ) + ) + (unreachable) + ) + (drop + (struct.new $used-in-br-on-cast + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $br_on_cast_fail (type $72) (param $used (ref (exact $used-in-br-on-cast-fail))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref (exact $used-in-br-on-cast-fail))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_fail $l (ref (exact $used-in-br-on-cast-fail)) (ref (exact $used-in-br-on-cast-fail)) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-br-on-cast-fail + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail (param $used (ref (exact $used-in-br-on-cast-fail))) + (drop + (block $l (result anyref) + (br_on_cast_fail 0 (ref $used-in-br-on-cast-fail) (ref (exact $used-in-br-on-cast-fail)) + (local.get $used) + ) + (unreachable) + ) + ) + (drop + (struct.new $used-in-br-on-cast-fail + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $block (type $73) (param $used (ref (exact $used-in-block))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref (exact $used-in-block))) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-block_1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block (param $used (ref (exact $used-in-block))) + (drop + ;; Blocks do not inhibit optimization, even though they observe exactness. + (block $l (result (ref (exact $used-in-block))) + (local.get $used) + ) + ) + ;; TODO: Move this allocation into the block once it is typed as exact to + ;; show that refinalization works correctly. + (drop + (struct.new $used-in-block + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $loop (type $74) (param $used (ref (exact $used-in-loop))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (loop (result (ref (exact $used-in-loop))) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-loop_2 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop (param $used (ref (exact $used-in-loop))) + (drop + ;; Same with loops and other control flow. + (loop (result (ref (exact $used-in-loop))) + (local.get $used) + ) + ) + (drop + (struct.new $used-in-loop + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $if (type $75) (param $used (ref (exact $used-in-if))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result (ref (exact $used-in-if))) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-if_3 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if (param $used (ref (exact $used-in-if))) + (drop + (if (result (ref (exact $used-in-if))) + (i32.const 0) + (then + (local.get $used) + ) + (else + (local.get $used) + ) + ) + ) + (drop + (struct.new $used-in-if + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $try (type $76) (param $used (ref (exact $used-in-try))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try (result (ref (exact $used-in-try))) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-try_4 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try (param $used (ref (exact $used-in-try))) + (drop + (try (result (ref (exact $used-in-try))) + (do + (local.get $used) + ) + ) + ) + (drop + (struct.new $used-in-try + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $try-table (type $77) (param $used (ref (exact $used-in-try-table))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try_table (result (ref (exact $used-in-try-table))) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-try-table_5 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-table (param $used (ref (exact $used-in-try-table))) + (drop + (try_table (result (ref (exact $used-in-try-table))) + (local.get $used) + ) + ) + (drop + (struct.new $used-in-try-table + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $branch (type $78) (param $used (ref (exact $used-in-branch))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref (exact $used-in-branch))) + ;; CHECK-NEXT: (br $l + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-branch_6 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $branch (param $used (ref (exact $used-in-branch))) + (drop + (block $l (result (ref (exact $used-in-branch))) + ;; Branches don't inhibit optimizations, either. + (br $l + (local.get $used) + ) + (unreachable) + ) + ) + (drop + ;; TODO: Use this allocation in the branch once it is typed as exact. + (struct.new $used-in-branch + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $br-table (type $79) (param $used (ref (exact $used-in-br-table))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref (exact $used-in-br-table))) + ;; CHECK-NEXT: (br_table $l + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-br-table_7 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-table (param $used (ref (exact $used-in-br-table))) + (drop + (block $l (result (ref (exact $used-in-br-table))) + (br_table $l + (local.get $used) + (i32.const 0) + ) + (unreachable) + ) + ) + (drop + ;; TODO: Use this allocation in the branch once it is typed as exact. + (struct.new $used-in-br-table + (ref.null none) + ) + ) + ) + + + ;; CHECK: (func $return (type $80) (param $used (ref (exact $used-in-return))) (result (ref (exact $used-in-return))) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-return + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $return (param $used (ref (exact $used-in-return))) (result (ref (exact $used-in-return))) + ;; This return will inhibit optimization because it requires its operand to be exact. + (return + (local.get $used) + ) + (drop + (struct.new $used-in-return + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $return-ok (type $81) (param $used (ref (exact $used-in-return-ok))) (result (ref $used-in-return-ok)) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-return-ok_8 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $return-ok (param $used (ref (exact $used-in-return-ok))) (result (ref $used-in-return-ok)) + ;; The result is not exact, so the return doesn't need to be, either. + (return + (local.get $used) + ) + (drop + (struct.new $used-in-return-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $call (type $82) (param $used (ref (exact $used-in-call))) + ;; CHECK-NEXT: (call $call + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-call + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call (param $used (ref (exact $used-in-call))) + (call $call + (local.get $used) + ) + (drop + (struct.new $used-in-call + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $inexact-callee (type $83) (param $0 (ref $used-in-call-ok)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $inexact-callee (param (ref $used-in-call-ok)) + ;; Helper for call-ok. + (unreachable) + ) + + ;; CHECK: (func $call-ok (type $84) (param $used (ref (exact $used-in-call-ok))) + ;; CHECK-NEXT: (call $inexact-callee + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-call-ok_9 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ok (param $used (ref (exact $used-in-call-ok))) + ;; The callee parameter is not exact, so this argument does not need to be, either. + (call $inexact-callee + (local.get $used) + ) + (drop + (struct.new $used-in-call-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $call-indirect (type $85) (param $used (ref (exact $used-in-call-indirect))) + ;; CHECK-NEXT: (call_indirect $indirect-call-table (type $85) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-call-indirect + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-indirect (param $used (ref (exact $used-in-call-indirect))) + (call_indirect $indirect-call-table (param (ref (exact $used-in-call-indirect))) + (local.get $used) + (i32.const 0) + ) + (drop + (struct.new $used-in-call-indirect + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $call-indirect-ok (type $86) (param $used (ref (exact $used-in-call-indirect-ok))) + ;; CHECK-NEXT: (call_indirect $indirect-call-table (type $87) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-call-indirect-ok_10 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-indirect-ok (param $used (ref (exact $used-in-call-indirect-ok))) + ;; The callee parameter is not exact, so this argument does not need to be, either. + (call_indirect $indirect-call-table (param (ref $used-in-call-indirect-ok)) + (local.get $used) + (i32.const 0) + ) + (drop + (struct.new $used-in-call-indirect-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $call-ref (type $88) (param $used (ref (exact $used-in-call-ref))) (param $ref (ref $call-ref-exact)) + ;; CHECK-NEXT: (call_ref $call-ref-exact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-call-ref + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref (param $used (ref (exact $used-in-call-ref))) (param $ref (ref $call-ref-exact)) + (call_ref $call-ref-exact + (local.get $used) + (local.get $ref) + ) + (drop + (struct.new $used-in-call-ref + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $call-ref-ok (type $89) (param $used (ref (exact $used-in-call-ref-ok))) (param $ref (ref $call-ref-inexact)) + ;; CHECK-NEXT: (call_ref $call-ref-inexact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-call-ref-ok_11 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref-ok (param $used (ref (exact $used-in-call-ref-ok))) (param $ref (ref $call-ref-inexact)) + ;; The callee parameter is not exact, so this argument does not need to be, either. + (call_ref $call-ref-inexact + (local.get $used) + (local.get $ref) + ) + (drop + (struct.new $used-in-call-ref-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $call-ref-unreachable (type $call-ref-unreachable) (param $used (ref (exact $used-in-call-ref-unreachable))) + ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-call-ref-unreachable_12 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref-unreachable (param $used (ref (exact $used-in-call-ref-unreachable))) + ;; The callee parameter is not exact, so this argument does not need to be, either. + (call_ref $call-ref-unreachable + (local.get $used) + (unreachable) + ) + (drop + ;; This is still optimizable. + (struct.new $used-in-call-ref-unreachable + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $call-ref-bot (type $call-ref-bot) (param $used (ref (exact $used-in-call-ref-bot))) + ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-call-ref-bot_13 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref-bot (param $used (ref (exact $used-in-call-ref-bot))) + ;; This call_ref will not be emitted, so it does not inhibit optimization. + (call_ref $call-ref-bot + (local.get $used) + (ref.null nofunc) + ) + (drop + ;; This is still optimizable. + (struct.new $used-in-call-ref-bot + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $ret-call (type $90) (param $used (ref (exact $used-in-ret-call))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-ret-call + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return_call $ret-call + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ret-call (param $used (ref (exact $used-in-ret-call))) + (drop + (struct.new $used-in-ret-call + (ref.null none) + ) + ) + (return_call $ret-call + (local.get $used) + ) + ) + + ;; CHECK: (func $inexact-ret-callee (type $91) (param $0 (ref $used-in-ret-call-ok)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $inexact-ret-callee (param (ref $used-in-ret-call-ok)) + ;; Helper for ret-call-ok. + (unreachable) + ) + + ;; CHECK: (func $ret-call-ok (type $92) (param $used (ref (exact $used-in-ret-call-ok))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-ret-call-ok_14 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return_call $inexact-ret-callee + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ret-call-ok (param $used (ref (exact $used-in-ret-call-ok))) + (drop + (struct.new $used-in-ret-call-ok + (ref.null none) + ) + ) + ;; The callee parameter is not exact, so this argument does not need to be, either. + (return_call $inexact-ret-callee + (local.get $used) + ) + ) + + + ;; CHECK: (func $ret-call-unreachable (type $93) (param $used (ref (exact $used-in-ret-call-unreachable))) (param $1 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-ret-call-unreachable_15 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return_call $ret-call-unreachable + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ret-call-unreachable (param $used (ref (exact $used-in-ret-call-unreachable))) (param i32) + (drop + (struct.new $used-in-ret-call-unreachable + (ref.null none) + ) + ) + ;; The callee parameter is exact, but since the call is unreachable (with an + ;; unreachable child), it does not inhibit optimization. + (return_call $ret-call-unreachable + (local.get $used) + (unreachable) + ) + ) + + ;; CHECK: (func $throw (type $67) (param $used (ref (exact $used-in-throw))) + ;; CHECK-NEXT: (throw $throw-exact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-throw + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw (param $used (ref (exact $used-in-throw))) + (throw $throw-exact + (local.get $used) + ) + (drop + (struct.new $used-in-throw + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $throw-ok (type $94) (param $used (ref (exact $used-in-throw-ok))) + ;; CHECK-NEXT: (throw $throw-inexact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-throw-ok_16 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-ok (param $used (ref (exact $used-in-throw-ok))) + (throw $throw-inexact + (local.get $used) + ) + (drop + (struct.new $used-in-throw-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $local-set (type $95) (param $used (ref (exact $used-in-local-set))) + ;; CHECK-NEXT: (local $exact (ref (exact $used-in-local-set))) + ;; CHECK-NEXT: (local.set $exact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-local-set + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $local-set (param $used (ref (exact $used-in-local-set))) + (local $exact (ref (exact $used-in-local-set))) + (local.set $exact + (local.get $used) + ) + (drop + (struct.new $used-in-local-set + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $local-set-ok (type $96) (param $used (ref (exact $used-in-local-set-ok))) + ;; CHECK-NEXT: (local $inexact (ref $used-in-local-set-ok)) + ;; CHECK-NEXT: (local.set $inexact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-local-set-ok_17 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $local-set-ok (param $used (ref (exact $used-in-local-set-ok))) + (local $inexact (ref $used-in-local-set-ok)) + (local.set $inexact + (local.get $used) + ) + (drop + (struct.new $used-in-local-set-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $local-set-tuple (type $97) (param $used (ref (exact $used-in-local-set-tuple))) + ;; CHECK-NEXT: (local $tuple-exact (tuple i32 (ref (exact $used-in-local-set-tuple)))) + ;; CHECK-NEXT: (local.set $tuple-exact + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-local-set-tuple + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $local-set-tuple (param $used (ref (exact $used-in-local-set-tuple))) + (local $tuple-exact (tuple i32 (ref (exact $used-in-local-set-tuple)))) + (local.set $tuple-exact + (tuple.make 2 + (i32.const 0) + (local.get $used) + ) + ) + (drop + (struct.new $used-in-local-set-tuple + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $local-set-tuple-ok (type $98) (param $used (ref (exact $used-in-local-set-tuple-ok))) + ;; CHECK-NEXT: (local $tuple-inexact (tuple i32 (ref $used-in-local-set-tuple-ok))) + ;; CHECK-NEXT: (local.set $tuple-inexact + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-local-set-tuple-ok_18 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $local-set-tuple-ok (param $used (ref (exact $used-in-local-set-tuple-ok))) + (local $tuple-inexact (tuple i32 (ref $used-in-local-set-tuple-ok))) + (local.set $tuple-inexact + (tuple.make 2 + (i32.const 0) + (local.get $used) + ) + ) + (drop + (struct.new $used-in-local-set-tuple-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $global-set (type $99) (param $used (ref (exact $used-in-global-set))) + ;; CHECK-NEXT: (global.set $global-exact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-global-set + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $global-set (param $used (ref (exact $used-in-global-set))) + (global.set $global-exact + (local.get $used) + ) + (drop + (struct.new $used-in-global-set + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $global-set-ok (type $100) (param $used (ref (exact $used-in-global-set-ok))) + ;; CHECK-NEXT: (global.set $global-inexact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-global-set-ok_19 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $global-set-ok (param $used (ref (exact $used-in-global-set-ok))) + (global.set $global-inexact + (local.get $used) + ) + (drop + (struct.new $used-in-global-set-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $table-set (type $101) (param $used (ref (exact $used-in-table-set))) + ;; CHECK-NEXT: (table.set $table-exact + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-table-set + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-set (param $used (ref (exact $used-in-table-set))) + (table.set $table-exact + (i32.const 0) + (local.get $used) + ) + (drop + (struct.new $used-in-table-set + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $table-set-ok (type $102) (param $used (ref (exact $used-in-table-set-ok))) + ;; CHECK-NEXT: (table.set $table-inexact + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-table-set-ok_20 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-set-ok (param $used (ref (exact $used-in-table-set-ok))) + (table.set $table-inexact + (i32.const 0) + (local.get $used) + ) + (drop + (struct.new $used-in-table-set-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $struct-new (type $103) (param $used (ref (exact $used-in-struct-new))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct-new-exact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-struct-new + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-new (param $used (ref (exact $used-in-struct-new))) + (drop + ;; The outer new can still be optimized. + (struct.new $struct-new-exact + (local.get $used) + (ref.null none) + ) + ) + (drop + (struct.new $used-in-struct-new + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $struct-new-ok (type $104) (param $used (ref (exact $used-in-struct-new-ok))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct-new-inexact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-struct-new-ok_21 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-new-ok (param $used (ref (exact $used-in-struct-new-ok))) + (drop + (struct.new $struct-new-inexact + (local.get $used) + (ref.null none) + ) + ) + (drop + (struct.new $used-in-struct-new-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $struct-set (type $105) (param $used (ref (exact $used-in-struct-set))) (param $ref (ref $struct-set-exact)) + ;; CHECK-NEXT: (struct.set $struct-set-exact 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-struct-set + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-set (param $used (ref (exact $used-in-struct-set))) (param $ref (ref $struct-set-exact)) + (struct.set $struct-set-exact 0 + (local.get $ref) + (local.get $used) + ) + (drop + (struct.new $used-in-struct-set + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $struct-set-ok (type $106) (param $used (ref (exact $used-in-struct-set-ok))) (param $ref (ref $struct-set-inexact)) + ;; CHECK-NEXT: (struct.set $struct-set-inexact 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-struct-set-ok_22 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-set-ok (param $used (ref (exact $used-in-struct-set-ok))) (param $ref (ref $struct-set-inexact)) + (struct.set $struct-set-inexact 0 + (local.get $ref) + (local.get $used) + ) + (drop + (struct.new $used-in-struct-set-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-new (type $107) (param $used (ref null (exact $used-in-array-new))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new $array-new-exact + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new $array-new-exact_23 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-new + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-new (param $used (ref null (exact $used-in-array-new))) + (drop + (array.new $array-new-exact + (local.get $used) + (i32.const 0) + ) + ) + ;; The outer type can still be optimized + (drop + (array.new $array-new-exact + (ref.null none) + (i32.const 0) + ) + ) + (drop + (struct.new $used-in-array-new + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-new-ok (type $108) (param $used (ref null (exact $used-in-array-new-ok))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new $array-new-inexact_24 + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new $array-new-inexact_25 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-new-ok_26 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-new-ok (param $used (ref null (exact $used-in-array-new-ok))) + (drop + (array.new $array-new-inexact + (local.get $used) + (i32.const 0) + ) + ) + ;; The outer type can also be optimized + (drop + (array.new $array-new-inexact + (ref.null none) + (i32.const 0) + ) + ) + (drop + (struct.new $used-in-array-new-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-new-fixed (type $109) (param $used (ref null (exact $used-in-array-new-fixed))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new_fixed $array-new-fixed-exact 1 + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new $array-new-fixed-exact_27 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-new-fixed + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-new-fixed (param $used (ref null (exact $used-in-array-new-fixed))) + (drop + (array.new_fixed $array-new-fixed-exact 1 + (local.get $used) + ) + ) + ;; The outer type can still be optimized + (drop + (array.new $array-new-fixed-exact + (ref.null none) + (i32.const 0) + ) + ) + (drop + (struct.new $used-in-array-new-fixed + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-new-fixed-ok (type $110) (param $used (ref null (exact $used-in-array-new-fixed-ok))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new_fixed $array-new-fixed-inexact_28 1 + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new $array-new-fixed-inexact_29 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-new-fixed-ok_30 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-new-fixed-ok (param $used (ref null (exact $used-in-array-new-fixed-ok))) + (drop + (array.new_fixed $array-new-fixed-inexact 1 + (local.get $used) + ) + ) + ;; The outer type can also be optimized + (drop + (array.new $array-new-fixed-inexact + (ref.null none) + (i32.const 0) + ) + ) + (drop + (struct.new $used-in-array-new-fixed-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-set (type $111) (param $used (ref (exact $used-in-array-set))) (param $ref (ref $array-set-exact)) + ;; CHECK-NEXT: (array.set $array-set-exact + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-set + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-set (param $used (ref (exact $used-in-array-set))) (param $ref (ref $array-set-exact)) + (array.set $array-set-exact + (local.get $ref) + (i32.const 0) + (local.get $used) + ) + (drop + (struct.new $used-in-array-set + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-set-ok (type $112) (param $used (ref (exact $used-in-array-set-ok))) (param $ref (ref $array-set-inexact)) + ;; CHECK-NEXT: (array.set $array-set-inexact + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-set-ok_31 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-set-ok (param $used (ref (exact $used-in-array-set-ok))) (param $ref (ref $array-set-inexact)) + (array.set $array-set-inexact + (local.get $ref) + (i32.const 0) + (local.get $used) + ) + (drop + (struct.new $used-in-array-set-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-fill (type $113) (param $used (ref (exact $used-in-array-fill))) (param $ref (ref $array-fill-exact)) + ;; CHECK-NEXT: (array.fill $array-fill-exact + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-fill + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-fill (param $used (ref (exact $used-in-array-fill))) (param $ref (ref $array-fill-exact)) + (array.fill $array-fill-exact + (local.get $ref) + (i32.const 0) + (local.get $used) + (i32.const 0) + ) + (drop + (struct.new $used-in-array-fill + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-fill-ok (type $114) (param $used (ref (exact $used-in-array-fill-ok))) (param $ref (ref $array-fill-inexact)) + ;; CHECK-NEXT: (array.fill $array-fill-inexact + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-fill-ok_32 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-fill-ok (param $used (ref (exact $used-in-array-fill-ok))) (param $ref (ref $array-fill-inexact)) + (array.fill $array-fill-inexact + (local.get $ref) + (i32.const 0) + (local.get $used) + (i32.const 0) + ) + (drop + (struct.new $used-in-array-fill-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $array-copy-ok (type $115) (param $used (ref (exact $used-in-array-copy-ok))) (param $ref (ref $array-copy-exact)) + ;; CHECK-NEXT: (array.copy $array-copy-exact $array-copy-exact + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-array-copy-ok_33 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-copy-ok (param $used (ref (exact $used-in-array-copy-ok))) (param $ref (ref $array-copy-exact)) + ;; Even though the destination array contains exact references, the copy + ;; instruction itself does not require any exact operands. We can still + ;; optimize, but in practice there would be other instructions creating the + ;; array in the first place that would inhibit optimization. + (array.copy $array-copy-exact $array-copy-exact + (local.get $ref) + (i32.const 0) + (local.get $ref) + (i32.const 0) + (i32.const 0) + ) + (drop + (struct.new $used-in-array-copy-ok + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $func (type $116) (param $used (ref (exact $used-in-func))) (result (ref (exact $used-in-func))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-func + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $used) + ;; CHECK-NEXT: ) + (func $func (param $used (ref (exact $used-in-func))) (result (ref (exact $used-in-func))) + (drop + ;; TODO: Return this directly once it is typed as exact. + (struct.new $used-in-func + (ref.null none) + ) + ) + (local.get $used) + ) + + ;; CHECK: (func $global (type $117) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-global + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $global + (drop + ;; This optimization is inhibited even though no instruction accesses the + ;; global. TODO: we could be more precise here. + (struct.new $used-in-global + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $segment (type $117) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $used-in-segment + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $segment + (drop + ;; This optimization is inhibited even though no instruction accesses the + ;; segment. TODO: we could be more precise here. + (struct.new $used-in-segment + (ref.null none) + ) + ) + ) +) From 15df7fe134fcc20d1698899865aaccf8b94be9de Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 12 May 2025 15:07:36 -0700 Subject: [PATCH 491/622] Fix BranchHinting + source maps (#7585) We move the Code section around when writing BranchHints (since we emit the Code first to find the offsets, then put BranchHints before it as the spec requires). Source map offsets are not relative to the Code section but absolute to the start of the binary, so we must adjust. --- src/wasm/wasm-binary.cpp | 18 ++++---- test/lit/wat-annotations.wast | 82 ++++++++++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f772dff7bd1..192768e5235 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -482,13 +482,22 @@ void WasmBinaryWriter::writeFunctions() { // We need to move the code section and put the annotations before it. auto& annotationsBuffer = *annotations; auto oldSize = o.size(); - o.resize(oldSize + annotationsBuffer.size()); + auto annotationsSectionSize = annotationsBuffer.size(); + o.resize(oldSize + annotationsSectionSize); // |sectionStart| is the start of the contents of the section. Subtract 1 to // include the section code as well, so we move all of it. std::move_backward(&o[sectionStart - 1], &o[oldSize], o.end()); std::copy( annotationsBuffer.begin(), annotationsBuffer.end(), &o[sectionStart - 1]); + + // Source map offsets are absolute (from the start of the binary) so we must + // adjust them after moving the code section. + if (sourceMap) { + for (auto& location : sourceMapLocations) { + location.first += annotationsSectionSize; + } + } } } @@ -1608,13 +1617,6 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { return {}; } - if (sourceMap) { - // TODO: This mode may not matter (when debugging, code annotations are an - // optimization that can be skipped), but atm source maps cause - // annotations to break. - Fatal() << "Annotations are not supported with source maps"; - } - BufferWithRandomAccess buffer; // We found data: emit the section. diff --git a/test/lit/wat-annotations.wast b/test/lit/wat-annotations.wast index 154b79e0d2c..07277f0bc67 100644 --- a/test/lit/wat-annotations.wast +++ b/test/lit/wat-annotations.wast @@ -1,8 +1,12 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: wasm-opt -all %s -S -o - | filecheck %s + ;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP +;; RUN: wasm-opt -all %s -g -o %t.wasm -osm %t.map +;; RUN: wasm-opt -all %t.wasm -ism %t.map -S -o - | filecheck %s --check-prefix=SRCMP + (module ;; CHECK: (type $0 (func (param i32))) @@ -27,6 +31,17 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) + ;; SRCMP: (type $0 (func (param i32))) + + ;; SRCMP: (type $1 (func (param anyref))) + + ;; SRCMP: (func $no-annotations (type $0) (param $x i32) + ;; SRCMP-NEXT: (block $block + ;; SRCMP-NEXT: (br_if $block + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) (func $no-annotations (param $x i32) ;; A function with no annotations. This tests that we use function indexes ;; properly in the section. @@ -69,6 +84,22 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) + ;; SRCMP: (func $branch-hints-br_if (type $0) (param $x i32) + ;; SRCMP-NEXT: (block $block + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\00") + ;; SRCMP-NEXT: (br_if $block + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\01") + ;; SRCMP-NEXT: (br_if $block + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\00") + ;; SRCMP-NEXT: (br_if $block + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) (func $branch-hints-br_if (param $x i32) (block $out ;; A branch annotated as unlikely, and one as likely. @@ -107,6 +138,15 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) + ;; SRCMP: (func $branch_hints-br_if-2 (type $0) (param $x i32) + ;; SRCMP-NEXT: (local $unused f64) + ;; SRCMP-NEXT: (block $block + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\01") + ;; SRCMP-NEXT: (br_if $block + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) (func $branch_hints-br_if-2 (param $x i32) (local $unused f64) ;; A second function with hints. This one also has local definitions, which @@ -137,13 +177,18 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) + ;; SRCMP: (func $mixing (type $0) (param $x i32) + ;; SRCMP-NEXT: ;;@ mixing.src:1337:42 + ;; SRCMP-NEXT: (block $block + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\01") + ;; SRCMP-NEXT: (br_if $block + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) (func $mixing (param $x i32) - ;; Mix branch hints with source locations. Both hints should remain. - ;; TODO: Fix this in the binary format. Atm we test with --roundtrip here, - ;; which does not use source maps, so it is expected for the source - ;; annotation to vanish. But using source maps does not fix it, see - ;; the TODO in the code. - + ;; Mix branch hints with source locations. Both hints should remain (though + ;; not in --roundtrip, which does not use source maps). ;;@ mixing.src:1337:42 (block $out (@metadata.code.branch_hint "\01") @@ -183,6 +228,21 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) + ;; SRCMP: (func $branch-hints-if (type $0) (param $x i32) + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\00") + ;; SRCMP-NEXT: (if + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: (then + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\01") + ;; SRCMP-NEXT: (if + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: (then + ;; SRCMP-NEXT: (nop) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) (func $branch-hints-if (param $x i32) (@metadata.code.branch_hint "\00") (if @@ -219,6 +279,16 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) + ;; SRCMP: (func $branch-hints-br_on (type $1) (param $x anyref) + ;; SRCMP-NEXT: (block $block + ;; SRCMP-NEXT: (drop + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\00") + ;; SRCMP-NEXT: (br_on_null $block + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) (func $branch-hints-br_on (param $x anyref) (block $out (drop From 677e0c609b83a48b2ee0f2afcf63991ea1f43ba1 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 13 May 2025 09:56:56 -0700 Subject: [PATCH 492/622] Update TypeMerging for exact types (#7580) TypeMerging was already careful not to merge a subtype and its supertype where there is a cast to the subtype that could differentiate it from its supertype. Now with exact types, it's possible to have an exact cast to the supertype that can differentiate it from the subtype as well. Update the pass to collect types used as the target of exact casts and ensure they are not merged with their subtypes. It would be natural to use a single map from HeapType to bool to track the cast target types and whether each one is used in an exact cast, but that would regress performance because this pass previously used a SmallSet to track cast types. I spent a few hours trying to create a SmallMap that shared most of its code with SmallSet, but it would require more time to make that work. For now, just use a second SmallSet to track exact exact casts. --- src/passes/TypeMerging.cpp | 68 +++++++++----- test/lit/passes/type-merging-exact.wast | 118 +++++++++++++++++++++++- 2 files changed, 159 insertions(+), 27 deletions(-) diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 566e25f0135..e072ea38b45 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -71,6 +71,11 @@ using CastTypes = SmallUnorderedSet; struct CastFinder : public PostWalker { CastTypes castTypes; + // For each cast target, record whether there is an exact cast. Exact casts + // will additionally prevent subtypes from being merged into the cast target. + // TODO: Use a SmallMap to combine this with `castTypes`. + CastTypes exactCastTypes; + // If traps never happen, then ref.cast and call_indirect can never // differentiate between types since they always succeed. Take advantage of // that by not having those instructions inhibit merges in TNH mode. @@ -83,6 +88,9 @@ struct CastFinder : public PostWalker { template void visitCast(T* curr) { if (auto type = curr->getCastType(); type != Type::unreachable) { castTypes.insert(type.getHeapType()); + if (type.isExact()) { + exactCastTypes.insert(type.getHeapType()); + } } } @@ -126,8 +134,9 @@ struct TypeMerging : public Pass { // All private original types. std::unordered_set privateTypes; - // Types that are distinguished by cast instructions. + // Types that are distinguished by casts and exact casts. CastTypes castTypes; + CastTypes exactCastTypes; // The list of remaining types that have not been merged into other types. // Candidates for further merging. @@ -169,7 +178,8 @@ struct TypeMerging : public Pass { std::vector> splitSupertypePartition(const std::vector&); - CastTypes findCastTypes(); + // Return the cast types and the exact cast types. + std::pair findCastTypes(); std::vector getPublicChildren(HeapType type); DFA::State makeDFAState(HeapType type); void applyMerges(); @@ -220,7 +230,9 @@ void TypeMerging::run(Module* module_) { mergeable = ModuleUtils::getPrivateHeapTypes(*module); privateTypes = std::unordered_set(mergeable.begin(), mergeable.end()); - castTypes = findCastTypes(); + auto casts = findCastTypes(); + castTypes = std::move(casts.first); + exactCastTypes = std::move(casts.second); // Merging supertypes or siblings can unlock more sibling merging // opportunities, but merging siblings can never unlock more supertype merging @@ -329,17 +341,18 @@ bool TypeMerging::merge(MergeKind kind) { switch (kind) { case Supertypes: { auto super = type.getDeclaredSuperType(); - if (super && shapeEq(type, *super)) { - // The current type and its supertype have the same top-level - // structure and are not distinguished, so add the current type to its - // supertype's partition. - auto it = ensurePartition(*super); - it->push_back(makeDFAState(type)); - typePartitions[type] = it; - } else { - // Otherwise, create a new partition for this type. + bool superHasExactCast = super && exactCastTypes.count(*super); + if (!super || !shapeEq(type, *super) || superHasExactCast) { + // Create a new partition for this type and bail. ensurePartition(type); + break; } + // The current type and its supertype have the same top-level + // structure and are not distinguished, so add the current type to its + // supertype's partition. + auto it = ensurePartition(*super); + it->push_back(makeDFAState(type)); + typePartitions[type] = it; break; } case Siblings: { @@ -476,17 +489,19 @@ TypeMerging::splitSupertypePartition(const std::vector& types) { return partitions; } -CastTypes TypeMerging::findCastTypes() { - ModuleUtils::ParallelFunctionAnalysis analysis( - *module, [&](Function* func, CastTypes& castTypes) { - if (func->imported()) { - return; - } +std::pair TypeMerging::findCastTypes() { + ModuleUtils::ParallelFunctionAnalysis> + analysis(*module, + [&](Function* func, std::pair& castTypes) { + if (func->imported()) { + return; + } - CastFinder finder(getPassOptions()); - finder.walk(func->body); - castTypes = std::move(finder.castTypes); - }); + CastFinder finder(getPassOptions()); + finder.walk(func->body); + castTypes = {std::move(finder.castTypes), + std::move(finder.exactCastTypes)}; + }); // Also find cast types in the module scope (not possible in the current // spec, but do it to be future-proof). @@ -495,12 +510,17 @@ CastTypes TypeMerging::findCastTypes() { // Accumulate all the castTypes. auto& allCastTypes = moduleFinder.castTypes; - for (auto& [k, castTypes] : analysis.map) { + auto& allExactCastTypes = moduleFinder.exactCastTypes; + for (auto& [k, types] : analysis.map) { + auto& [castTypes, exactCastTypes] = types; for (auto type : castTypes) { allCastTypes.insert(type); } + for (auto type : exactCastTypes) { + allExactCastTypes.insert(type); + } } - return allCastTypes; + return {std::move(allCastTypes), std::move(allExactCastTypes)}; } std::vector TypeMerging::getPublicChildren(HeapType type) { diff --git a/test/lit/passes/type-merging-exact.wast b/test/lit/passes/type-merging-exact.wast index d56da52338b..1ef70067e3c 100644 --- a/test/lit/passes/type-merging-exact.wast +++ b/test/lit/passes/type-merging-exact.wast @@ -1,10 +1,9 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; Check that types that differ only in exactness are not merged. - -;; RUN: wasm-opt %s -all --closed-world --preserve-type-order \ +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ ;; RUN: --type-merging --remove-unused-types -S -o - | filecheck %s +;; Check that types that differ only in exactness are not merged. (module ;; CHECK: (rec ;; CHECK-NEXT: (type $foo (struct)) @@ -19,3 +18,116 @@ ;; CHECK: (global $b (ref null $B) (ref.null none)) (global $b (ref null $B) (ref.null none)) ) + +;; Check that exact casts to a supertype prevent subtypes from being merged into +;; it. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (func $ref-cast (type $2) (param $any anyref) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref (exact $super)) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast (param $any anyref) + (local $sub (ref null $sub)) + (drop + (ref.cast (ref (exact $super)) + (local.get $any) + ) + ) + ) +) + +;; Same as above but with ref.test. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (func $ref-test (type $2) (param $any anyref) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test (ref (exact $super)) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test (param $any anyref) + (local $sub (ref null $sub)) + (drop + (ref.test (ref (exact $super)) + (local.get $any) + ) + ) + ) +) + +;; Same as above but with br_on_cast. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (func $br-on-cast (type $2) (param $any anyref) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (br_on_cast $l anyref (ref (exact $super)) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast (param $any anyref) + (local $sub (ref null $sub)) + (drop + (block $l (result anyref) + (br_on_cast $l anyref (ref (exact $super)) + (local.get $any) + ) + ) + ) + ) +) + +;; Same as above but with br_on_cast_fail +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (func $br-on-cast-fail (type $2) (param $any anyref) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (br_on_cast_fail $l anyref (ref (exact $super)) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast-fail (param $any anyref) + (local $sub (ref null $sub)) + (drop + (block $l (result anyref) + (br_on_cast_fail $l anyref (ref (exact $super)) + (local.get $any) + ) + ) + ) + ) +) From 1f27e2fc9747354b3feea9ca448945e145a7cffe Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 13 May 2025 10:17:49 -0700 Subject: [PATCH 493/622] Use exactness to optimize GUFA cone depth (#7582) Exact references are known to point to exactly their heap type and not one of its subtypes. GUFA already analyzed exactness via the more general "cone" types that include an allowed subtyping depth. Exact types are equivalent to cone types of depth zero. Let GUFA take advantage of exactness information by normalizing cone depths to 0 whenever the cone type is exact. --- src/ir/possible-contents.cpp | 72 ++++---- src/ir/possible-contents.h | 24 +-- src/passes/GUFA.cpp | 2 +- test/gtest/possible-contents.cpp | 213 +++++++++++------------ test/lit/passes/gufa-cast-all-exact.wast | 71 ++++++++ 5 files changed, 222 insertions(+), 160 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 58298383f89..cf7b7d11506 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -144,7 +144,9 @@ PossibleContents PossibleContents::combine(const PossibleContents& a, void PossibleContents::intersect(const PossibleContents& other) { // This does not yet handle all possible content. - assert(other.isFullConeType() || other.isLiteral() || other.isNone()); + assert((other.isConeType() && + (other.getType().isExact() || other.hasFullCone())) || + other.isLiteral() || other.isNone()); if (*this == other) { // Nothing changes. @@ -201,6 +203,7 @@ void PossibleContents::intersect(const PossibleContents& other) { // The heap types are compatible, so intersect the cones. auto depthFromRoot = heapType.getDepth(); auto otherDepthFromRoot = otherHeapType.getDepth(); + auto newDepthFromRoot = newType.getHeapType().getDepth(); // Note the global's information, if we started as a global. In that case, the // code below will refine our type but we can remain a global, which we will @@ -210,38 +213,21 @@ void PossibleContents::intersect(const PossibleContents& other) { globalName = getGlobal(); } - // By assumption |other| has full depth. Consider the other cone in |this|. - if (hasFullCone()) { + if (hasFullCone() && other.hasFullCone()) { // Both are full cones, so the result is as well. - value = FullConeType(newType); + value = DefaultConeType(newType); } else { - // The result is a partial cone. If the cone starts in |otherHeapType| then - // we need to adjust the depth down, since it will be smaller than the - // original cone: - /* - // .. - // / - // otherHeapType - // / \ - // heapType .. - // \ - */ - // E.g. if |this| is a cone of depth 10, and |otherHeapType| is an immediate - // subtype of |this|, then the new cone must be of depth 9. - auto newDepth = getCone().depth; - if (newType.getHeapType() == otherHeapType) { - assert(depthFromRoot <= otherDepthFromRoot); - auto reduction = otherDepthFromRoot - depthFromRoot; - if (reduction > newDepth) { - // The cone on heapType does not even reach the cone on otherHeapType, - // so the result is not a cone. - setNoneOrNull(); - return; - } - newDepth -= reduction; + // The result is a partial cone. Check whether the cones overlap, and if + // they do, find the new depth. + if (newDepthFromRoot - depthFromRoot > getCone().depth || + newDepthFromRoot - otherDepthFromRoot > other.getCone().depth) { + setNoneOrNull(); + return; } - - value = ConeType{newType, newDepth}; + Index newDepth = getCone().depth - (newDepthFromRoot - depthFromRoot); + Index otherNewDepth = + other.getCone().depth - (newDepthFromRoot - otherDepthFromRoot); + value = ConeType{newType, std::min(newDepth, otherNewDepth)}; } if (globalName) { @@ -371,7 +357,19 @@ bool PossibleContents::isSubContents(const PossibleContents& a, return false; } - WASM_UNREACHABLE("unhandled case of isSubContents"); + if (b.isGlobal()) { + // We've already ruled out anything but another global or non-full cone type + // for a. + return false; + } + + assert(b.isConeType() && (a.isConeType() || a.isGlobal())); + if (!Type::isSubType(a.getType(), b.getType())) { + return false; + } + // Check that a's cone type is enclosed in b's cone type. + return a.getType().getHeapType().getDepth() + a.getCone().depth <= + b.getType().getHeapType().getDepth() + b.getCone().depth; } namespace { @@ -1463,7 +1461,7 @@ class TNHOracle : public ModuleUtils::ParallelFunctionAnalysis { // Get the type we inferred was possible at a location. PossibleContents getContents(Expression* curr) { - auto naiveContents = PossibleContents::fullConeType(curr->type); + auto naiveContents = PossibleContents::coneType(curr->type); // If we inferred nothing, use the naive type. auto iter = inferences.find(curr); @@ -1871,8 +1869,8 @@ void TNHOracle::optimizeCallCasts(Expression* call, // There are two constraints on this location: any value there must // be of the declared type (curr->type) and also the cast type, so // we know only their intersection can appear here. - auto declared = PossibleContents::fullConeType(curr->type); - auto intersection = PossibleContents::fullConeType(castType); + auto declared = PossibleContents::coneType(curr->type); + auto intersection = PossibleContents::coneType(castType); intersection.intersect(declared); if (intersection.isConeType()) { auto intersectionType = intersection.getType(); @@ -1956,7 +1954,7 @@ struct Flower { PossibleContents getTNHContents(Expression* curr) { if (!tnhOracle) { // No oracle; just use the type in the IR. - return PossibleContents::fullConeType(curr->type); + return PossibleContents::coneType(curr->type); } return tnhOracle->getContents(curr); } @@ -2858,7 +2856,7 @@ void Flower::readFromData(Type declaredType, #ifndef NDEBUG // We must not have anything in the reference that is invalid for the wasm // type there. - auto maximalContents = PossibleContents::fullConeType(declaredType); + auto maximalContents = PossibleContents::coneType(declaredType); assert(PossibleContents::isSubContents(refContents, maximalContents)); #endif @@ -2953,7 +2951,7 @@ void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { #ifndef NDEBUG // We must not have anything in the reference that is invalid for the wasm // type there. - auto maximalContents = PossibleContents::fullConeType(ref->type); + auto maximalContents = PossibleContents::coneType(ref->type); assert(PossibleContents::isSubContents(refContents, maximalContents)); #endif diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 538b23d4d3c..520abe58850 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -126,9 +126,12 @@ class PossibleContents { static constexpr Index FullDepth = -1; - // Internal convenience for creating a cone type of unbounded depth, i.e., the - // full cone of all subtypes for that type. - static ConeType FullConeType(Type type) { return ConeType{type, FullDepth}; } + // Internal convenience for creating a cone type carrying no information + // besides the type. For exact references, the depth is 0 and for all other + // types the depth is unbounded and includes all possible subtypes. + static ConeType DefaultConeType(Type type) { + return type.isExact() ? ExactType(type) : ConeType{type, FullDepth}; + } template PossibleContents(T value) : value(value) {} @@ -148,10 +151,11 @@ class PossibleContents { static PossibleContents exactType(Type type) { return PossibleContents{ExactType(type)}; } - // Helper for a cone with unbounded depth, i.e., the full cone of all subtypes - // for that type. - static PossibleContents fullConeType(Type type) { - return PossibleContents{FullConeType(type)}; + // Helper for a cone with default depth for the given type. For exact + // references this is depth 0 and for all other this tyis is unbounded depth + // and includes all possible subtypes. + static PossibleContents coneType(Type type) { + return PossibleContents{DefaultConeType(type)}; } static PossibleContents coneType(Type type, Index depth) { return PossibleContents{ConeType{type, depth}}; @@ -165,7 +169,7 @@ class PossibleContents { if (type.isRef()) { // For a reference, subtyping matters. - return fullConeType(type); + return coneType(type); } if (type == Type::unreachable) { @@ -251,7 +255,7 @@ class PossibleContents { if (auto* literal = std::get_if(&value)) { return ExactType(literal->type); } else if (auto* global = std::get_if(&value)) { - return FullConeType(global->type); + return DefaultConeType(global->type); } else if (auto* coneType = std::get_if(&value)) { return *coneType; } else if (std::get_if(&value)) { @@ -333,7 +337,7 @@ class PossibleContents { // the separate items in the tuple (tuples themselves have no subtyping, // so the tuple's depth must be 0, i.e., an exact type). assert(cone->depth == 0); - return fullConeType(type[i]); + return coneType(type[i]); } else { WASM_UNREACHABLE("not a tuple"); } diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 480e4cfd857..21f9aa8521d 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -246,7 +246,7 @@ struct GUFAOptimizer // We have some knowledge of the type here. Use that to optimize: RefTest // returns 1 if the input is of a subtype of the intended type, that is, // we are looking for a type in that cone of types. - auto intendedContents = PossibleContents::fullConeType(curr->castType); + auto intendedContents = PossibleContents::coneType(curr->castType); auto optimize = [&](int32_t result) { auto* last = Builder(*getModule()).makeConst(Literal(int32_t(result))); diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index f267742a71b..7c1503ffe7b 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -111,8 +111,8 @@ class PossibleContentsTest : public testing::Test { PossibleContents many = PossibleContents::many(); - PossibleContents coneAnyref = PossibleContents::fullConeType(anyref); - PossibleContents coneFuncref = PossibleContents::fullConeType(funcref); + PossibleContents coneAnyref = PossibleContents::coneType(anyref); + PossibleContents coneFuncref = PossibleContents::coneType(funcref); PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1); }; @@ -663,29 +663,29 @@ TEST_F(PossibleContentsTest, TestStructCones) { PossibleContents::coneType(structref, 5)); // Full cones. - assertCombination(PossibleContents::fullConeType(nullA), + assertCombination(PossibleContents::coneType(nullA), exactA, - PossibleContents::fullConeType(nullA)); - assertCombination(PossibleContents::fullConeType(nullA), + PossibleContents::coneType(nullA)); + assertCombination(PossibleContents::coneType(nullA), PossibleContents::coneType(nullA, 2), - PossibleContents::fullConeType(nullA)); + PossibleContents::coneType(nullA)); // All full cones with A remain full cones, except for E. - assertCombination(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullA)); - assertCombination(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullB), - PossibleContents::fullConeType(nullA)); - assertCombination(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullC), - PossibleContents::fullConeType(nullA)); - assertCombination(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullD), - PossibleContents::fullConeType(nullA)); - assertCombination(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullE), - PossibleContents::fullConeType(structref)); + assertCombination(PossibleContents::coneType(nullA), + PossibleContents::coneType(nullA), + PossibleContents::coneType(nullA)); + assertCombination(PossibleContents::coneType(nullA), + PossibleContents::coneType(nullB), + PossibleContents::coneType(nullA)); + assertCombination(PossibleContents::coneType(nullA), + PossibleContents::coneType(nullC), + PossibleContents::coneType(nullA)); + assertCombination(PossibleContents::coneType(nullA), + PossibleContents::coneType(nullD), + PossibleContents::coneType(nullA)); + assertCombination(PossibleContents::coneType(nullA), + PossibleContents::coneType(nullE), + PossibleContents::coneType(structref)); // Intersections. Test with non-nullable types to avoid the null being a // possible intersection. @@ -710,180 +710,169 @@ TEST_F(PossibleContentsTest, TestStructCones) { // Neither is a subtype of the other, but nulls are possible, so a null can be // the intersection. - assertHaveIntersection(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullE)); + assertHaveIntersection(PossibleContents::coneType(nullA), + PossibleContents::coneType(nullE)); // Without null on one side, we cannot intersect. - assertLackIntersection(PossibleContents::fullConeType(nnA), - PossibleContents::fullConeType(nullE)); + assertLackIntersection(PossibleContents::coneType(nnA), + PossibleContents::coneType(nullE)); // Computing intersections is supported with a full cone type. - assertIntersection(none, PossibleContents::fullConeType(nnA), none); - assertIntersection(many, - PossibleContents::fullConeType(nnA), - PossibleContents::fullConeType(nnA)); - assertIntersection(many, - PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullA)); - - assertIntersection(exactA, PossibleContents::fullConeType(nullA), exactA); - assertIntersection(nnExactA, PossibleContents::fullConeType(nullA), nnExactA); - assertIntersection(exactA, PossibleContents::fullConeType(nnA), nnExactA); - - assertIntersection(exactB, PossibleContents::fullConeType(nullA), exactB); - assertIntersection(nnExactB, PossibleContents::fullConeType(nullA), nnExactB); - assertIntersection(exactB, PossibleContents::fullConeType(nnA), nnExactB); + assertIntersection(none, PossibleContents::coneType(nnA), none); + assertIntersection( + many, PossibleContents::coneType(nnA), PossibleContents::coneType(nnA)); + assertIntersection( + many, PossibleContents::coneType(nullA), PossibleContents::coneType(nullA)); + + assertIntersection(exactA, PossibleContents::coneType(nullA), exactA); + assertIntersection(nnExactA, PossibleContents::coneType(nullA), nnExactA); + assertIntersection(exactA, PossibleContents::coneType(nnA), nnExactA); + + assertIntersection(exactB, PossibleContents::coneType(nullA), exactB); + assertIntersection(nnExactB, PossibleContents::coneType(nullA), nnExactB); + assertIntersection(exactB, PossibleContents::coneType(nnA), nnExactB); auto literalNullA = PossibleContents::literal(Literal::makeNull(A)); assertIntersection( - literalNullA, PossibleContents::fullConeType(nullA), literalNullA); - assertIntersection(literalNullA, PossibleContents::fullConeType(nnA), none); + literalNullA, PossibleContents::coneType(nullA), literalNullA); + assertIntersection(literalNullA, PossibleContents::coneType(nnA), none); assertIntersection( - literalNullA, PossibleContents::fullConeType(nullB), literalNullA); - assertIntersection(literalNullA, PossibleContents::fullConeType(nnB), none); + literalNullA, PossibleContents::coneType(nullB), literalNullA); + assertIntersection(literalNullA, PossibleContents::coneType(nnB), none); assertIntersection( - literalNullA, PossibleContents::fullConeType(nullE), literalNullA); - assertIntersection(literalNullA, PossibleContents::fullConeType(nnE), none); + literalNullA, PossibleContents::coneType(nullE), literalNullA); + assertIntersection(literalNullA, PossibleContents::coneType(nnE), none); assertIntersection(exactA, - PossibleContents::fullConeType(nullB), + PossibleContents::coneType(nullB), PossibleContents::literal(Literal::makeNull(B))); - assertIntersection(nnExactA, PossibleContents::fullConeType(nullB), none); - assertIntersection(exactA, PossibleContents::fullConeType(nnB), none); + assertIntersection(nnExactA, PossibleContents::coneType(nullB), none); + assertIntersection(exactA, PossibleContents::coneType(nnB), none); // A and E have no intersection, so the only possibility is a null, and that // null must be the bottom type. assertIntersection( exactA, - PossibleContents::fullConeType(nullE), + PossibleContents::coneType(nullE), PossibleContents::literal(Literal::makeNull(HeapType::none))); assertIntersection(PossibleContents::coneType(nnA, 1), - PossibleContents::fullConeType(nnB), + PossibleContents::coneType(nnB), nnExactB); assertIntersection(PossibleContents::coneType(nnB, 1), - PossibleContents::fullConeType(nnA), + PossibleContents::coneType(nnA), PossibleContents::coneType(nnB, 1)); assertIntersection(PossibleContents::coneType(nnD, 2), - PossibleContents::fullConeType(nnA), + PossibleContents::coneType(nnA), PossibleContents::coneType(nnD, 2)); assertIntersection(PossibleContents::coneType(nnA, 5), - PossibleContents::fullConeType(nnD), + PossibleContents::coneType(nnD), PossibleContents::coneType(nnD, 3)); - assertIntersection(PossibleContents::coneType(nnA, 1), - PossibleContents::fullConeType(nnD), - none); + assertIntersection( + PossibleContents::coneType(nnA, 1), PossibleContents::coneType(nnD), none); // Globals stay as globals, but their type might get refined. assertIntersection( - funcGlobal, PossibleContents::fullConeType(funcref), funcGlobal); + funcGlobal, PossibleContents::coneType(funcref), funcGlobal); // No global filtering. auto signature = Type(Signature(Type::none, Type::none), Nullable); assertIntersection( - nonNullFunc, PossibleContents::fullConeType(signature), nonNullFunc); + nonNullFunc, PossibleContents::coneType(signature), nonNullFunc); // Filter a global to a more specific type. assertIntersection(funcGlobal, - PossibleContents::fullConeType(signature), + PossibleContents::coneType(signature), PossibleContents::global("funcGlobal", signature)); // Filter a global's nullability only. auto nonNullFuncRef = Type(HeapType::func, NonNullable); - assertIntersection(funcGlobal, - PossibleContents::fullConeType(nonNullFuncRef), - nonNullFuncGlobal); + assertIntersection( + funcGlobal, PossibleContents::coneType(nonNullFuncRef), nonNullFuncGlobal); // Incompatible global and cone types have no intersection. - assertIntersection(funcGlobal, PossibleContents::fullConeType(nullE), none); + assertIntersection(funcGlobal, PossibleContents::coneType(nullE), none); // Incompatible hierarchies have no intersection. - assertIntersection( - literalNullA, PossibleContents::fullConeType(funcref), none); + assertIntersection(literalNullA, PossibleContents::coneType(funcref), none); // Computing intersections is also supported with a Literal. assertIntersection(i32Zero, i32Zero, i32Zero); assertIntersection(i32One, i32Zero, none); assertIntersection(i32Global1, i32Zero, i32Zero); assertIntersection(funcGlobal, i32Zero, none); - assertIntersection( - PossibleContents::fullConeType(Type::i32), i32Zero, i32Zero); - assertIntersection(PossibleContents::fullConeType(Type::f64), i32Zero, none); + assertIntersection(PossibleContents::coneType(Type::i32), i32Zero, i32Zero); + assertIntersection(PossibleContents::coneType(Type::f64), i32Zero, none); // Computing intersections is also supported with empty contents. assertIntersection(none, none, none); assertIntersection(literalNullA, none, none); assertIntersection(funcGlobal, none, none); - assertIntersection(PossibleContents::fullConeType(signature), none, none); + assertIntersection(PossibleContents::coneType(signature), none, none); // Subcontents. This API only supports the case where one of the inputs is a // full cone type. // First, compare exact types to such a cone. + EXPECT_TRUE( + PossibleContents::isSubContents(exactA, PossibleContents::coneType(nullA))); + EXPECT_TRUE( + PossibleContents::isSubContents(nnExactA, PossibleContents::coneType(nnA))); EXPECT_TRUE(PossibleContents::isSubContents( - exactA, PossibleContents::fullConeType(nullA))); - EXPECT_TRUE(PossibleContents::isSubContents( - nnExactA, PossibleContents::fullConeType(nnA))); - EXPECT_TRUE(PossibleContents::isSubContents( - nnExactA, PossibleContents::fullConeType(nullA))); + nnExactA, PossibleContents::coneType(nullA))); EXPECT_TRUE(PossibleContents::isSubContents( - nnExactD, PossibleContents::fullConeType(nullA))); + nnExactD, PossibleContents::coneType(nullA))); - EXPECT_FALSE(PossibleContents::isSubContents( - exactA, PossibleContents::fullConeType(nnA))); - EXPECT_FALSE(PossibleContents::isSubContents( - exactA, PossibleContents::fullConeType(nullB))); + EXPECT_FALSE( + PossibleContents::isSubContents(exactA, PossibleContents::coneType(nnA))); + EXPECT_FALSE( + PossibleContents::isSubContents(exactA, PossibleContents::coneType(nullB))); // Next, compare cones. - EXPECT_TRUE( - PossibleContents::isSubContents(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullA))); - EXPECT_TRUE( - PossibleContents::isSubContents(PossibleContents::fullConeType(nnA), - PossibleContents::fullConeType(nullA))); EXPECT_TRUE(PossibleContents::isSubContents( - PossibleContents::fullConeType(nnA), PossibleContents::fullConeType(nnA))); - EXPECT_TRUE( - PossibleContents::isSubContents(PossibleContents::fullConeType(nullD), - PossibleContents::fullConeType(nullA))); + PossibleContents::coneType(nullA), PossibleContents::coneType(nullA))); + EXPECT_TRUE(PossibleContents::isSubContents( + PossibleContents::coneType(nnA), PossibleContents::coneType(nullA))); + EXPECT_TRUE(PossibleContents::isSubContents(PossibleContents::coneType(nnA), + PossibleContents::coneType(nnA))); + EXPECT_TRUE(PossibleContents::isSubContents( + PossibleContents::coneType(nullD), PossibleContents::coneType(nullA))); - EXPECT_FALSE( - PossibleContents::isSubContents(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nnA))); - EXPECT_FALSE( - PossibleContents::isSubContents(PossibleContents::fullConeType(nullA), - PossibleContents::fullConeType(nullD))); + EXPECT_FALSE(PossibleContents::isSubContents( + PossibleContents::coneType(nullA), PossibleContents::coneType(nnA))); + EXPECT_FALSE(PossibleContents::isSubContents( + PossibleContents::coneType(nullA), PossibleContents::coneType(nullD))); // Trivial values. EXPECT_TRUE(PossibleContents::isSubContents( - PossibleContents::none(), PossibleContents::fullConeType(nullA))); + PossibleContents::none(), PossibleContents::coneType(nullA))); EXPECT_FALSE(PossibleContents::isSubContents( - PossibleContents::many(), PossibleContents::fullConeType(nullA))); + PossibleContents::many(), PossibleContents::coneType(nullA))); EXPECT_TRUE(PossibleContents::isSubContents( - anyNull, PossibleContents::fullConeType(nullA))); - EXPECT_FALSE(PossibleContents::isSubContents( - anyNull, PossibleContents::fullConeType(nnA))); + anyNull, PossibleContents::coneType(nullA))); + EXPECT_FALSE( + PossibleContents::isSubContents(anyNull, PossibleContents::coneType(nnA))); // Tests cases with a full cone only on the left. Such a cone is only a sub- // contents of Many. + EXPECT_FALSE( + PossibleContents::isSubContents(PossibleContents::coneType(nullA), exactA)); EXPECT_FALSE(PossibleContents::isSubContents( - PossibleContents::fullConeType(nullA), exactA)); - EXPECT_FALSE(PossibleContents::isSubContents( - PossibleContents::fullConeType(nullA), nnExactA)); + PossibleContents::coneType(nullA), nnExactA)); EXPECT_FALSE(PossibleContents::isSubContents( - PossibleContents::fullConeType(nullA), PossibleContents::none())); - EXPECT_TRUE(PossibleContents::isSubContents( - PossibleContents::fullConeType(nullA), PossibleContents::many())); + PossibleContents::coneType(nullA), PossibleContents::none())); + EXPECT_TRUE(PossibleContents::isSubContents(PossibleContents::coneType(nullA), + PossibleContents::many())); EXPECT_FALSE(PossibleContents::isSubContents( - PossibleContents::fullConeType(nullA), anyNull)); - EXPECT_FALSE(PossibleContents::isSubContents( - PossibleContents::fullConeType(nnA), anyNull)); + PossibleContents::coneType(nullA), anyNull)); + EXPECT_FALSE( + PossibleContents::isSubContents(PossibleContents::coneType(nnA), anyNull)); } TEST_F(PossibleContentsTest, TestOracleManyTypes) { @@ -957,6 +946,6 @@ TEST_F(PossibleContentsTest, TestTupleItems) { // We can get the tuple items. The funcref is a full cone, as we did not have // depth info for it. - EXPECT_EQ(tuple.getTupleItem(0), PossibleContents::fullConeType(Type::i32)); - EXPECT_EQ(tuple.getTupleItem(1), PossibleContents::fullConeType(funcref)); + EXPECT_EQ(tuple.getTupleItem(0), PossibleContents::coneType(Type::i32)); + EXPECT_EQ(tuple.getTupleItem(1), PossibleContents::coneType(funcref)); } diff --git a/test/lit/passes/gufa-cast-all-exact.wast b/test/lit/passes/gufa-cast-all-exact.wast index 1b40f9ee266..0a03f8930bd 100644 --- a/test/lit/passes/gufa-cast-all-exact.wast +++ b/test/lit/passes/gufa-cast-all-exact.wast @@ -139,6 +139,77 @@ ) ) +(module + ;; CHECK: (type $super (sub (struct))) + ;; NO_CD: (type $super (sub (struct))) + (type $super (sub (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (import "" "a" (global $exact-a (ref (exact $super)))) + ;; NO_CD: (import "" "a" (global $exact-a (ref (exact $super)))) + (import "" "a" (global $exact-a (ref (exact $super)))) + ;; CHECK: (import "" "b" (global $exact-b (ref (exact $super)))) + ;; NO_CD: (import "" "b" (global $exact-b (ref (exact $super)))) + (import "" "b" (global $exact-b (ref (exact $super)))) + ;; CHECK: (import "" "x" (global $x i32)) + ;; NO_CD: (import "" "x" (global $x i32)) + (import "" "x" (global $x i32)) + + ;; CHECK: (func $get-exact (type $1) (result (ref $super)) + ;; CHECK-NEXT: (select (result (ref (exact $super))) + ;; CHECK-NEXT: (global.get $exact-a) + ;; CHECK-NEXT: (global.get $exact-b) + ;; CHECK-NEXT: (global.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $get-exact (type $1) (result (ref $super)) + ;; NO_CD-NEXT: (select (result (ref (exact $super))) + ;; NO_CD-NEXT: (global.get $exact-a) + ;; NO_CD-NEXT: (global.get $exact-b) + ;; NO_CD-NEXT: (global.get $x) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $get-exact (result (ref $super)) + (select (result (ref (exact $super))) + (global.get $exact-a) + (global.get $exact-b) + (global.get $x) + ) + ) + + ;; CHECK: (func $cast-sub (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref (exact $super)) + ;; CHECK-NEXT: (call $get-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $cast-sub (type $2) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (block + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (call $get-exact) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + (func $cast-sub + (drop + ;; We should be able to infer that this cast will not succeed and insert an + ;; unreachable after it. + (ref.cast (ref $sub) + (call $get-exact) + ) + ) + ) +) + (module ;; CHECK: (type $foo (sub (struct (field i32)))) ;; NO_CD: (type $foo (sub (struct (field i32)))) From d7aaeab12a3ca5708fea77abdafc5455d589123b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 May 2025 11:29:35 -0700 Subject: [PATCH 494/622] Fix RemoveUnusedBrs bug on removed br_if value where condition interferes (#7586) Fixes a regression from #7506 . That PR checked for effects, but we forgot to also check for interactions between effects. --- src/passes/RemoveUnusedBrs.cpp | 34 ++++++++++++++++++++------ test/lit/passes/remove-unused-brs.wast | 31 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 142810eb2f2..a6a94aa26e0 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -1290,14 +1290,32 @@ struct RemoveUnusedBrs : public WalkerPass> { auto* last = curr->list[size - 1]; if (auto* drop = secondLast->dynCast()) { if (auto* br = drop->value->dynCast(); - br && br->value && br->condition) { - if (br->name == curr->name) { - if (!EffectAnalyzer(passOptions, *getModule(), br->value) - .hasUnremovableSideEffects()) { - if (ExpressionAnalyzer::equal(br->value, last)) { - // All conditions met, perform the update. - drop->value = br->condition; - } + br && br->value && br->condition && br->name == curr->name && + ExpressionAnalyzer::equal(br->value, last)) { + // The value must have no effects, as we are removing one copy + // of it. Also, the condition must not interfere with that + // value, or it might change, e.g. + // + // (drop + // (br_if $block + // (read a value) ;; this original value is returned, + // (write that value) ;; if we branch + // ) + // ) + // (read a value) + // => + // (drop + // (write that value) + // ) + // (read a value) ;; now the written value is used + auto valueEffects = + EffectAnalyzer(passOptions, *getModule(), br->value); + if (!valueEffects.hasUnremovableSideEffects()) { + auto conditionEffects = + EffectAnalyzer(passOptions, *getModule(), br->condition); + if (!conditionEffects.invalidates(valueEffects)) { + // All conditions met, perform the update. + drop->value = br->condition; } } } diff --git a/test/lit/passes/remove-unused-brs.wast b/test/lit/passes/remove-unused-brs.wast index 84c9ef6be05..ed7fd168148 100644 --- a/test/lit/passes/remove-unused-brs.wast +++ b/test/lit/passes/remove-unused-brs.wast @@ -466,6 +466,37 @@ ) ) + ;; CHECK: (func $restructure-br_if-condition-invalidates-6 (type $2) (result i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: (local.tee $temp + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-br_if-condition-invalidates-6 (result i32) + (local $temp i32) + ;; The br value is syntactically identical to the value at the end of the + ;; block, however, the local.tee changes that value so we cannot optimize. + (block $block (result i32) + (drop + (br_if $block + (local.get $temp) + (local.tee $temp + (i32.const 1) + ) + ) + ) + (local.get $temp) + ) + ) + ;; CHECK: (func $restructure-select-no-multivalue (type $1) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (block $block (type $3) (result i32 i32) From e97f13e86d77fb659089811f1bc71e1993f75cb4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 May 2025 14:47:32 -0700 Subject: [PATCH 495/622] [Compilation Hints] Start Compilation Hints by adding Call support in the text format (#7587) Left as TODOs are other instructions, the binary format, and function-level annotations. --- src/parser/contexts.h | 52 +++++++++++++++++-- src/passes/Print.cpp | 47 ++++++++++++----- src/wasm-annotations.h | 1 + src/wasm-ir-builder.h | 7 ++- src/wasm.h | 17 ++++-- src/wasm/wasm-ir-builder.cpp | 18 ++++++- src/wasm/wasm.cpp | 1 + ...t-annotations.wast => branch-hinting.wast} | 0 test/lit/compilation-hints.wast | 36 +++++++++++++ 9 files changed, 156 insertions(+), 23 deletions(-) rename test/lit/{wat-annotations.wast => branch-hinting.wast} (100%) create mode 100644 test/lit/compilation-hints.wast diff --git a/src/parser/contexts.h b/src/parser/contexts.h index db3ff363798..47ba1305909 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1257,9 +1257,47 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { } }; +struct AnnotationParserCtx { + // Return the inline hint for a call instruction, if there is one. + std::optional + getInlineHint(const std::vector& annotations) { + // Find and apply (the last) inline hint. + const Annotation* hint = nullptr; + for (auto& a : annotations) { + if (a.kind == Annotations::InlineHint) { + hint = &a; + } + } + if (!hint) { + return std::nullopt; + } + + Lexer lexer(hint->contents); + if (lexer.empty()) { + std::cerr << "warning: empty InlineHint\n"; + return std::nullopt; + } + + auto str = lexer.takeString(); + if (!str || str->size() != 1) { + std::cerr << "warning: invalid InlineHint string\n"; + return std::nullopt; + } + + uint8_t value = (*str)[0]; + if (value > 127) { + std::cerr << "warning: invalid InlineHint value\n"; + return std::nullopt; + } + + return value; + } +}; + // Phase 4: Parse and set the types of module elements. struct ParseModuleTypesCtx : TypeParserCtx, - NullInstrParserCtx { + NullInstrParserCtx, + AnnotationParserCtx { // In this phase we have constructed all the types, so we can materialize and // validate them when they are used. @@ -1367,6 +1405,13 @@ struct ParseModuleTypesCtx : TypeParserCtx, Builder::addVar(f.get(), l.name, l.type); } } + // TODO: Add function-level annotations (stored using the nullptr key, as + // they are tied to no instruction in particular), but this should wait on + // figuring out + // https://github.com/WebAssembly/tool-conventions/issues/251 + // if (auto inline_ = getInlineHint(annotations)) { + // f->codeAnnotations[nullptr].inline_ = inline_; + // } return Ok{}; } @@ -1427,7 +1472,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, }; // Phase 5: Parse module element definitions, including instructions. -struct ParseDefsCtx : TypeParserCtx { +struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { using GlobalTypeT = Ok; using TableTypeT = Ok; using TypeUseT = HeapType; @@ -2329,7 +2374,8 @@ struct ParseDefsCtx : TypeParserCtx { const std::vector& annotations, Name func, bool isReturn) { - return withLoc(pos, irBuilder.makeCall(func, isReturn)); + auto inline_ = getInlineHint(annotations); + return withLoc(pos, irBuilder.makeCall(func, isReturn, inline_)); } Result<> makeCallIndirect(Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index a9ea58f07db..971700c0368 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -19,6 +19,7 @@ // #include +#include #include #include @@ -275,6 +276,10 @@ struct PrintSExpression : public UnifiedExpressionVisitor { // Prints debug info and code annotations. void printMetadata(Expression* curr); + // Print code annotations for an expression. If the expression is nullptr, + // prints for the current function. + void printCodeAnnotations(Expression* curr); + void printExpressionContents(Expression* curr); void visit(Expression* curr) { @@ -2642,18 +2647,8 @@ void PrintSExpression::printMetadata(Expression* curr) { } } - // Show a code annotation, if there is one. - if (auto iter = currFunction->codeAnnotations.find(curr); - iter != currFunction->codeAnnotations.end()) { - auto& annotation = iter->second; - if (annotation.branchLikely) { - Colors::grey(o); - o << "(@" << Annotations::BranchHint << " \"\\0" - << (*annotation.branchLikely ? "1" : "0") << "\")\n"; - restoreNormalColor(o); - doIndent(o, indent); - } - } + // Show code annotations. + printCodeAnnotations(curr); } } @@ -2670,6 +2665,31 @@ void PrintSExpression::printDebugDelimiterLocation(Expression* curr, Index i) { } } +void PrintSExpression::printCodeAnnotations(Expression* curr) { + if (auto iter = currFunction->codeAnnotations.find(curr); + iter != currFunction->codeAnnotations.end()) { + auto& annotation = iter->second; + if (annotation.branchLikely) { + Colors::grey(o); + o << "(@" << Annotations::BranchHint << " \"\\0" + << (*annotation.branchLikely ? "1" : "0") << "\")\n"; + restoreNormalColor(o); + doIndent(o, indent); + } + if (annotation.inline_) { + Colors::grey(o); + std::ofstream saved; + saved.copyfmt(std::cout); + o << "(@" << Annotations::InlineHint << " \"\\" << std::hex + << std::setfill('0') << std::setw(2) << int(*annotation.inline_) + << "\")\n"; + std::cout.copyfmt(saved); + restoreNormalColor(o); + doIndent(o, indent); + } + } +} + void PrintSExpression::printExpressionContents(Expression* curr) { PrintExpressionContents(*this).visit(curr); } @@ -3125,6 +3145,9 @@ void PrintSExpression::visitDefinedFunction(Function* curr) { if (currFunction->prologLocation) { printDebugLocation(*currFunction->prologLocation); } + // TODO: print code annotations in the right place, depending on + // https://github.com/WebAssembly/tool-conventions/issues/251 + // printCodeAnnotations(nullptr); handleSignature(curr, true); incIndent(); for (size_t i = curr->getVarIndexBase(); i < curr->getNumLocals(); i++) { diff --git a/src/wasm-annotations.h b/src/wasm-annotations.h index 42f0e56d732..888c356dcb9 100644 --- a/src/wasm-annotations.h +++ b/src/wasm-annotations.h @@ -26,6 +26,7 @@ namespace wasm::Annotations { extern const Name BranchHint; +extern const Name InlineHint; } // namespace wasm::Annotations diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index eeb985df1a2..430e6d1ec78 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -124,7 +124,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { std::optional likely = std::nullopt); Result<> makeSwitch(const std::vector& labels, Index defaultLabel); // Unlike Builder::makeCall, this assumes the function already exists. - Result<> makeCall(Name func, bool isReturn); + Result<> makeCall(Name func, + bool isReturn, + std::optional inline_ = std::nullopt); Result<> makeCallIndirect(Name table, HeapType type, bool isReturn); Result<> makeLocalGet(Index local); Result<> makeLocalSet(Index local); @@ -701,6 +703,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { // Add a branch hint, if |likely| is present. void addBranchHint(Expression* expr, std::optional likely); + // Add an inlining hint, if |inline_| is present. + void addInlineHint(Expression* expr, std::optional inline_); + void dump(); }; diff --git a/src/wasm.h b/src/wasm.h index 60efbfd65b8..2af21d933d2 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2191,14 +2191,21 @@ class Function : public Importable { delimiterLocations; BinaryLocations::FunctionLocations funcLocation; - // Code annotations. As with debug info, we do not store these on Expressions - // themselves, as we assume most instances are unannotated, and do not want to + // Code annotations for VMs. As with debug info, we do not store these on + // Expressions as we assume most instances are unannotated, and do not want to // add constant memory overhead. struct CodeAnnotation { - // Branch hinting proposal: Whether the branch is likely, or unlikely. + // Branch Hinting proposal: Whether the branch is likely, or unlikely. std::optional branchLikely; + + // Compilation Hints proposal. + static const uint8_t NeverInline = 0; + static const uint8_t AlwaysInline = 127; + std::optional inline_; }; + // Function-level annotations are implemented with a key of nullptr, matching + // the 0 byte offset in the spec. std::unordered_map codeAnnotations; // The effects for this function, if they have been computed. We use a shared @@ -2208,8 +2215,8 @@ class Function : public Importable { // See addsEffects() in pass.h for more details. std::shared_ptr effects; - // Inlining metadata: whether to disallow full and/or partial inlining (for - // details on what those mean, see Inlining.cpp). + // Inlining metadata: whether to disallow full and/or partial inlining. This + // is a toolchain-level hint. For more details, see Inlining.cpp. bool noFullInline = false; bool noPartialInline = false; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 4f8da899251..6e7ea0763b8 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1441,13 +1441,18 @@ Result<> IRBuilder::makeSwitch(const std::vector& labels, return Ok{}; } -Result<> IRBuilder::makeCall(Name func, bool isReturn) { +Result<> IRBuilder::makeCall(Name func, + bool isReturn, + std::optional inline_) { auto sig = wasm.getFunction(func)->getSig(); Call curr(wasm.allocator); curr.target = func; curr.operands.resize(sig.params.size()); CHECK_ERR(visitCall(&curr)); - push(builder.makeCall(curr.target, curr.operands, sig.results, isReturn)); + auto* call = + builder.makeCall(curr.target, curr.operands, sig.results, isReturn); + push(call); + addInlineHint(call, inline_); return Ok{}; } @@ -2541,4 +2546,13 @@ void IRBuilder::addBranchHint(Expression* expr, std::optional likely) { } } +void IRBuilder::addInlineHint(Expression* expr, + std::optional inline_) { + if (inline_) { + // Branches are only possible inside functions. + assert(func); + func->codeAnnotations[expr].inline_ = inline_; + } +} + } // namespace wasm diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index b66a2a7c6d3..a05ead7a5eb 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -67,6 +67,7 @@ const char* CustomDescriptorsFeature = "custom-descriptors"; namespace Annotations { const Name BranchHint = "metadata.code.branch_hint"; +const Name InlineHint = "metadata.code.inline"; } // namespace Annotations diff --git a/test/lit/wat-annotations.wast b/test/lit/branch-hinting.wast similarity index 100% rename from test/lit/wat-annotations.wast rename to test/lit/branch-hinting.wast diff --git a/test/lit/compilation-hints.wast b/test/lit/compilation-hints.wast new file mode 100644 index 00000000000..e72cd1ebee7 --- /dev/null +++ b/test/lit/compilation-hints.wast @@ -0,0 +1,36 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s + +;; TODO: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (func $func (type $0) + ;; CHECK-NEXT: (@metadata.code.inline "\00") + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (@metadata.code.inline "\01") + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (@metadata.code.inline "\7e") + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (@metadata.code.inline "\7f") + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: ) + (func $func + (@metadata.code.inline "\00") + (call $func) + (@metadata.code.inline "\01") + (call $func) + (@metadata.code.inline "\7e") + (call $func) + (@metadata.code.inline "\7f") + (call $func) + ;; Unannotated + (call $func) + ) + + ;; TODO: test function annotations, after + ;; https://github.com/WebAssembly/tool-conventions/issues/251 +) From 23a9e69f12afd749f35308dc504c8f6a89fc75f1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 May 2025 08:21:13 -0700 Subject: [PATCH 496/622] [Compilation Hints] Add all call instructions (#7588) --- src/parser/contexts.h | 7 +++++-- src/wasm-ir-builder.h | 9 +++++++-- src/wasm/wasm-ir-builder.cpp | 20 +++++++++++++++----- test/lit/compilation-hints.wast | 31 +++++++++++++++++++++++++++++-- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 47ba1305909..ca46498dde7 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -2385,7 +2385,9 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { bool isReturn) { auto t = getTable(pos, table); CHECK_ERR(t); - return withLoc(pos, irBuilder.makeCallIndirect(*t, type, isReturn)); + auto inline_ = getInlineHint(annotations); + return withLoc(pos, + irBuilder.makeCallIndirect(*t, type, isReturn, inline_)); } // Return the branch hint for a branching instruction, if there is one. @@ -2561,7 +2563,8 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { const std::vector& annotations, HeapType type, bool isReturn) { - return withLoc(pos, irBuilder.makeCallRef(type, isReturn)); + auto inline_ = getInlineHint(annotations); + return withLoc(pos, irBuilder.makeCallRef(type, isReturn, inline_)); } Result<> makeRefI31(Index pos, diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 430e6d1ec78..16cf7a6483d 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -127,7 +127,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeCall(Name func, bool isReturn, std::optional inline_ = std::nullopt); - Result<> makeCallIndirect(Name table, HeapType type, bool isReturn); + Result<> makeCallIndirect(Name table, + HeapType type, + bool isReturn, + std::optional inline_ = std::nullopt); Result<> makeLocalGet(Index local); Result<> makeLocalSet(Index local); Result<> makeLocalTee(Index local); @@ -201,7 +204,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeTupleDrop(uint32_t arity); Result<> makeRefI31(Shareability share); Result<> makeI31Get(bool signed_); - Result<> makeCallRef(HeapType type, bool isReturn); + Result<> makeCallRef(HeapType type, + bool isReturn, + std::optional inline_ = std::nullopt); Result<> makeRefTest(Type type); Result<> makeRefCast(Type type); Result<> makeBrOn(Index label, diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 6e7ea0763b8..e58a207cfdc 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1456,13 +1456,18 @@ Result<> IRBuilder::makeCall(Name func, return Ok{}; } -Result<> IRBuilder::makeCallIndirect(Name table, HeapType type, bool isReturn) { +Result<> IRBuilder::makeCallIndirect(Name table, + HeapType type, + bool isReturn, + std::optional inline_) { CallIndirect curr(wasm.allocator); curr.heapType = type; curr.operands.resize(type.getSignature().params.size()); CHECK_ERR(visitCallIndirect(&curr)); - push(builder.makeCallIndirect( - table, curr.target, curr.operands, type, isReturn)); + auto* call = + builder.makeCallIndirect(table, curr.target, curr.operands, type, isReturn); + push(call); + addInlineHint(call, inline_); return Ok{}; } @@ -1952,7 +1957,9 @@ Result<> IRBuilder::makeI31Get(bool signed_) { return Ok{}; } -Result<> IRBuilder::makeCallRef(HeapType type, bool isReturn) { +Result<> IRBuilder::makeCallRef(HeapType type, + bool isReturn, + std::optional inline_) { CallRef curr(wasm.allocator); if (!type.isSignature()) { return Err{"expected function type"}; @@ -1961,7 +1968,10 @@ Result<> IRBuilder::makeCallRef(HeapType type, bool isReturn) { curr.operands.resize(type.getSignature().params.size()); CHECK_ERR(ChildPopper{*this}.visitCallRef(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.target)); - push(builder.makeCallRef(curr.target, curr.operands, sig.results, isReturn)); + auto* call = + builder.makeCallRef(curr.target, curr.operands, sig.results, isReturn); + push(call); + addInlineHint(call, inline_); return Ok{}; } diff --git a/test/lit/compilation-hints.wast b/test/lit/compilation-hints.wast index e72cd1ebee7..0f3f6593796 100644 --- a/test/lit/compilation-hints.wast +++ b/test/lit/compilation-hints.wast @@ -5,9 +5,15 @@ ;; TODO: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP (module - ;; CHECK: (type $0 (func)) + ;; CHECK: (type $func (func)) + (type $func (func)) - ;; CHECK: (func $func (type $0) + ;; CHECK: (table $table 10 20 funcref) + (table $table 10 20 funcref) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $func) ;; CHECK-NEXT: (@metadata.code.inline "\00") ;; CHECK-NEXT: (call $func) ;; CHECK-NEXT: (@metadata.code.inline "\01") @@ -31,6 +37,27 @@ (call $func) ) + ;; CHECK: (func $other-calls (type $func) + ;; CHECK-NEXT: (@metadata.code.inline "\12") + ;; CHECK-NEXT: (call_indirect $table (type $func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.inline "\34") + ;; CHECK-NEXT: (call_ref $func + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other-calls + (@metadata.code.inline "\12") + (call_indirect (type $func) + (i32.const 0) + ) + (@metadata.code.inline "\34") + (call_ref $func + (ref.func $func) + ) + ) + ;; TODO: test function annotations, after ;; https://github.com/WebAssembly/tool-conventions/issues/251 ) From fe6f97bb24d2d7a94a0a2b52313dd727b1268497 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 May 2025 14:21:52 -0700 Subject: [PATCH 497/622] [NFC] Refactor a generic function for binary reading of a code annotations section (#7593) This will save code with future annotations like inline hints. --- src/wasm-binary.h | 7 +++++++ src/wasm/wasm-binary.cpp | 41 +++++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index cd49442e3fc..ad92f8912fd 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1699,6 +1699,13 @@ class WasmBinaryReader { // Scans ahead in the binary to check certain conditions like // needCodeLocations. void preScan(); + + // Internal helper for reading a code annotation section for a hint that is + // expression offset based. Receives the section name, payload length of the + // section and a function to read a single hint (receiving the annotation to + // update). + template + void readExpressionHints(Name sectionName, size_t payloadLen, ReadFunc read); }; } // namespace wasm diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 192768e5235..f07cc573dbc 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -5211,14 +5211,17 @@ void WasmBinaryReader::readDylink0(size_t payloadLen) { } } -void WasmBinaryReader::readBranchHints(size_t payloadLen) { +template +void WasmBinaryReader::readExpressionHints(Name sectionName, + size_t payloadLen, + ReadFunc read) { auto sectionPos = pos; auto numFuncs = getU32LEB(); for (Index i = 0; i < numFuncs; i++) { auto funcIndex = getU32LEB(); if (funcIndex >= wasm.functions.size()) { - throwError("bad BranchHint function"); + throwError("bad function in " + sectionName.toString()); } auto& func = wasm.functions[funcIndex]; @@ -5243,22 +5246,11 @@ void WasmBinaryReader::readBranchHints(size_t payloadLen) { auto iter = locationsMap.find(absoluteOffset); if (iter == locationsMap.end()) { - throwError("bad BranchHint offset"); + throwError("bad offset in " + sectionName.toString()); } auto* expr = iter->second; - auto size = getU32LEB(); - if (size != 1) { - throwError("bad BranchHint size"); - } - - auto likely = getU32LEB(); - if (likely != 0 && likely != 1) { - throwError("bad BranchHint value"); - } - - // Apply the valid hint. - func->codeAnnotations[expr].branchLikely = likely; + read(func->codeAnnotations[expr]); } } @@ -5267,6 +5259,25 @@ void WasmBinaryReader::readBranchHints(size_t payloadLen) { } } +void WasmBinaryReader::readBranchHints(size_t payloadLen) { + readExpressionHints(Annotations::BranchHint, + payloadLen, + [&](Function::CodeAnnotation& annotation) { + auto size = getU32LEB(); + if (size != 1) { + throwError("bad BranchHint size"); + } + + auto likely = getU32LEB(); + if (likely != 0 && likely != 1) { + throwError("bad BranchHint value"); + } + + // Apply the valid hint. + annotation.branchLikely = likely; + }); +} + Index WasmBinaryReader::readMemoryAccess(Address& alignment, Address& offset) { auto rawAlignment = getU32LEB(); bool hasMemIdx = false; From 42011493179d21c7c7162390fab2b6e900494654 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 May 2025 15:35:22 -0700 Subject: [PATCH 498/622] [NFC] Refactor a generic function for binary writing of a code annotations section (#7592) This will allow 95% of the code to be shared for binary writing, between branch hints and inline hints for example. They have almost identical formats in structure, differing only in the hint itself and section name. Also prepare for multiple code annotations sections. --- src/wasm-binary.h | 12 ++++++++- src/wasm/wasm-binary.cpp | 58 +++++++++++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index ad92f8912fd..c0ef68c03e5 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1410,6 +1410,8 @@ class WasmBinaryWriter { // must then insert before the code (as the spec requires that). std::optional writeCodeAnnotations(); + std::optional getBranchHintsBuffer(); + // helpers void writeInlineString(std::string_view name); void writeEscapedName(std::string_view name); @@ -1492,6 +1494,15 @@ class WasmBinaryWriter { std::unordered_map stringIndexes; void prepare(); + + // Internal helper for emitting a code annotation section for a hint that is + // expression offset based. Receives the name of the section and two + // functions, one to check if the annotation we care about exists (receiving + // the annotation), and another to emit it (receiving the annotation and the + // buffer to write in). + template + std::optional + writeExpressionHints(Name sectionName, HasFunc has, EmitFunc emit); }; extern std::vector defaultEmptySourceMap; @@ -1679,7 +1690,6 @@ class WasmBinaryReader { // hint position and size in the first pass, and handle it later. size_t branchHintsPos = 0; size_t branchHintsLen = 0; - void readBranchHints(size_t payloadLen); Index readMemoryAccess(Address& alignment, Address& offset); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f07cc573dbc..d9ad17bf814 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1549,8 +1549,28 @@ void WasmBinaryWriter::trackExpressionDelimiter(Expression* curr, } std::optional WasmBinaryWriter::writeCodeAnnotations() { - // Assemble the info for Branch Hinting: for each function, a vector of the - // hints. + std::optional ret; + + auto append = [&](std::optional&& temp) { + if (temp) { + if (!ret) { + // This is the first section. + ret = std::move(temp); + } else { + // This is a later section, append. + ret->insert(ret->end(), temp->begin(), temp->end()); + } + } + }; + + append(getBranchHintsBuffer()); + return ret; +} + +template +std::optional WasmBinaryWriter::writeExpressionHints( + Name sectionName, HasFunc has, EmitFunc emit) { + // Assemble the info: for each function, a vector of the hints. struct ExprHint { Expression* expr; // The offset we will write in the custom section. @@ -1566,7 +1586,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { std::vector funcHintsVec; for (auto& func : wasm->functions) { - // Collect the Branch Hints for this function. + // Collect the hints for this function. FuncHints funcHints; // We compute the location of the function declaration area (where the @@ -1574,7 +1594,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { BinaryLocation funcDeclarationsOffset = 0; for (auto& [expr, annotation] : func->codeAnnotations) { - if (annotation.branchLikely) { + if (has(annotation)) { auto exprIter = binaryLocations.expressions.find(expr); if (exprIter == binaryLocations.expressions.end()) { // No expression exists for this annotation - perhaps optimizations @@ -1622,7 +1642,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { // We found data: emit the section. buffer << uint8_t(BinaryConsts::Custom); auto lebPos = buffer.writeU32LEBPlaceholder(); - buffer.writeInlineString(Annotations::BranchHint.str); + buffer.writeInlineString(sectionName.str); buffer << U32LEB(funcHintsVec.size()); for (auto& funcHints : funcHintsVec) { @@ -1632,14 +1652,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { for (auto& exprHint : funcHints.exprHints) { buffer << U32LEB(exprHint.offset); - // Hint size, always 1 for now. - buffer << U32LEB(1); - - // We must only emit hints that are present. - assert(exprHint.hint->branchLikely); - - // Hint contents: likely or not. - buffer << U32LEB(int(*exprHint.hint->branchLikely)); + emit(*exprHint.hint, buffer); } } @@ -1651,6 +1664,25 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { return buffer; } +std::optional WasmBinaryWriter::getBranchHintsBuffer() { + return writeExpressionHints( + Annotations::BranchHint, + [](const Function::CodeAnnotation& annotation) { + return annotation.branchLikely; + }, + [](const Function::CodeAnnotation& annotation, + BufferWithRandomAccess& buffer) { + // Hint size, always 1 for now. + buffer << U32LEB(1); + + // We must only emit hints that are present. + assert(annotation.branchLikely); + + // Hint contents: likely or not. + buffer << U32LEB(int(*annotation.branchLikely)); + }); +} + void WasmBinaryWriter::writeData(const char* data, size_t size) { for (size_t i = 0; i < size; i++) { o << int8_t(data[i]); From 51ba8373c6f742cfb883703fe26e62957d35be6e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 14 May 2025 16:05:28 -0700 Subject: [PATCH 499/622] Allow GSI to work with supertype globals (#7594) GSI previously only optimized globals whose types were the same as their initializer's types to ensure the glboal types were equality comparable. However, this is an overly conservative restriction because the global can have any type up to eqref without causing problems. Update the restriction to be more precise. This will help the GSI tests continue working once `struct.new` is typed as exact. --- src/passes/GlobalStructInference.cpp | 9 +-- test/lit/passes/gsi.wast | 85 ++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 89441a53fed..387e2c94277 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -137,12 +137,9 @@ struct GlobalStructInference : public Pass { auto type = global->init->type.getHeapType(); - // The global's declared type must match the init's type. If not, say if - // we had a global declared as type |any| but that contains (ref $A), then - // that is not something we can optimize, as ref.eq on a global.get of - // that global will not validate. (This should not be a problem after - // GlobalSubtyping runs, which will specialize the type of the global.) - if (global->type != global->init->type) { + // The global's declared type must be equality comparable. + if (auto eq = wasm::HeapTypes::eq.getBasic(type.getShared()); + !Type::isSubType(global->type, Type(eq, Nullable))) { unoptimizable.insert(type); continue; } diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast index 6e15091bdc4..72149873eab 100644 --- a/test/lit/passes/gsi.wast +++ b/test/lit/passes/gsi.wast @@ -2156,3 +2156,88 @@ ) ) ) + +;; The basic case, but now the globals have type eqref. This should still work. +(module + ;; CHECK: (type $struct (struct (field i32))) + (type $struct (struct i32)) + + ;; CHECK: (type $1 (func (param (ref null $struct)))) + + ;; CHECK: (global $global1 eqref (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + (global $global1 eqref (struct.new $struct + (i32.const 42) + )) + + ;; CHECK: (global $global2 eqref (struct.new $struct + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + (global $global2 eqref (struct.new $struct + (i32.const 1337) + )) + + ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $global1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $struct (ref null $struct)) + ;; We can infer that this get can reference either $global1 or $global2, + ;; and nothing else (aside from a null), and can emit a select between + ;; those values. + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) +) + +;; Same, but now the globals have type anyref, so they are not comparable and we +;; cannot optimize. +(module + ;; CHECK: (type $struct (struct (field i32))) + (type $struct (struct i32)) + + ;; CHECK: (type $1 (func (param (ref null $struct)))) + + ;; CHECK: (global $global1 anyref (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + (global $global1 anyref (struct.new $struct + (i32.const 42) + )) + + ;; CHECK: (global $global2 anyref (struct.new $struct + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + (global $global2 anyref (struct.new $struct + (i32.const 1337) + )) + + ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $struct (ref null $struct)) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) +) From 2bef62a882ef30d2ed70d7813642913774ee78f5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 14 May 2025 16:31:55 -0700 Subject: [PATCH 500/622] Fix tests of refinalization after optimization of selects (#7595) According to their comments, these tests purported to show that refinalize was run after optimizing a select to a single arm with a more refined type. However,the tests were flawed because the select arms had the same type, meaning the parsed select node did not have a less refined type than the arms. Fix this by giving the arms different types. Use a block to directly show that refinalization occurred instead of the cast that was there previously. --- test/lit/passes/optimize-instructions-gc.wast | 166 ++++++++++-------- 1 file changed, 95 insertions(+), 71 deletions(-) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 4b88507ffc7..38715fb747a 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -22,16 +22,19 @@ ;; CHECK: (type $B (sub $A (struct (field i32) (field i32) (field f32)))) (type $B (sub $A (struct (field i32) (field i32) (field f32)))) + ;; CHECK: (type $void (sub (func))) + ;; CHECK: (type $B-child (sub $B (struct (field i32) (field i32) (field f32) (field i64)))) (type $B-child (sub $B (struct (field i32) (field i32) (field f32) (field i64)))) (type $empty (struct)) - ;; CHECK: (type $void (sub (func))) + ;; CHECK: (type $C (sub $A (struct (field i32) (field i32) (field f64)))) - ;; CHECK: (type $void2 (sub $void (func))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $void1 (sub $void (func))) - ;; CHECK: (type $C (sub $A (struct (field i32) (field i32) (field f64)))) + ;; CHECK: (type $void2 (sub $void (func))) ;; CHECK: (type $struct.ref (struct (field funcref))) (type $struct.ref (struct (field funcref))) @@ -40,17 +43,20 @@ (type $void (sub (func))) - (type $void2 (sub $void (func))) + (rec + (type $void1 (sub $void (func))) + (type $void2 (sub $void (func))) + ) ;; CHECK: (type $struct_i64 (func (param structref) (result i64))) (type $struct_i64 (func (param (ref null struct)) (result i64))) - ;; CHECK: (import "env" "get-i32" (func $get-i32 (type $8) (result i32))) + ;; CHECK: (import "env" "get-i32" (func $get-i32 (type $9) (result i32))) (import "env" "get-i32" (func $get-i32 (result i32))) ;; These functions test if an `if` with subtyped arms is correctly folded ;; 1. if its `ifTrue` and `ifFalse` arms are identical (can fold) - ;; CHECK: (func $if-arms-subtype-fold (type $28) (result anyref) + ;; CHECK: (func $if-arms-subtype-fold (type $29) (result anyref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) (func $if-arms-subtype-fold (result anyref) @@ -65,7 +71,7 @@ ) ) ;; 2. if its `ifTrue` and `ifFalse` arms are not identical (cannot fold) - ;; CHECK: (func $if-arms-subtype-nofold (type $29) (param $i31ref i31ref) (result anyref) + ;; CHECK: (func $if-arms-subtype-nofold (type $30) (param $i31ref i31ref) (result anyref) ;; CHECK-NEXT: (if (result anyref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -122,7 +128,7 @@ ) ;; Similar, but for arrays. - ;; CHECK: (func $store-trunc2 (type $16) (param $x (ref null $array)) + ;; CHECK: (func $store-trunc2 (type $15) (param $x (ref null $array)) ;; CHECK-NEXT: (array.set $array ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 0) @@ -139,7 +145,7 @@ ;; ref.is_null is not needed on a non-nullable value, and if something is ;; cast to its own type, we don't need that either, etc. - ;; CHECK: (func $unneeded_test (type $17) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) + ;; CHECK: (func $unneeded_test (type $16) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop @@ -182,7 +188,7 @@ ;; similar to $unneeded_is, but the values are nullable. we can at least ;; leave just the null check. - ;; CHECK: (func $unneeded_test_null (type $18) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref) + ;; CHECK: (func $unneeded_test_null (type $17) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (local.get $struct) @@ -223,7 +229,7 @@ ;; ref.as_non_null is not needed on a non-nullable value, and if something is ;; a func we don't need that either etc., and can just return the value. - ;; CHECK: (func $unneeded_cast (type $17) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) + ;; CHECK: (func $unneeded_cast (type $16) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -251,7 +257,7 @@ ;; similar to $unneeded_cast, but the values are nullable. we can turn the ;; more specific things into ref.as_non_null. - ;; CHECK: (func $unneeded_cast_null (type $18) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref) + ;; CHECK: (func $unneeded_cast_null (type $17) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct) @@ -308,7 +314,7 @@ ) ) - ;; CHECK: (func $redundant-non-null-casts (type $30) (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void)) + ;; CHECK: (func $redundant-non-null-casts (type $31) (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $x) @@ -395,7 +401,7 @@ ) ) - ;; CHECK: (func $get-eqref (type $31) (result eqref) + ;; CHECK: (func $get-eqref (type $32) (result eqref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $get-eqref (result eqref) @@ -585,7 +591,7 @@ ) ) - ;; CHECK: (func $flip-cast-of-as-non-null (type $19) (param $x anyref) + ;; CHECK: (func $flip-cast-of-as-non-null (type $18) (param $x anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $struct) ;; CHECK-NEXT: (local.get $x) @@ -638,7 +644,7 @@ ) ) ) - ;; CHECK: (func $flip-tee-of-as-non-null (type $19) (param $x anyref) + ;; CHECK: (func $flip-tee-of-as-non-null (type $18) (param $x anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.tee $x @@ -658,7 +664,7 @@ ) ) - ;; CHECK: (func $flip-tee-of-as-non-null-non-nullable (type $32) (param $x (ref any)) (param $y anyref) + ;; CHECK: (func $flip-tee-of-as-non-null-non-nullable (type $33) (param $x (ref any)) (param $y anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (ref.as_non_null @@ -679,7 +685,7 @@ ) ) ) - ;; CHECK: (func $ternary-identical-arms (type $33) (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) + ;; CHECK: (func $ternary-identical-arms (type $34) (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (if (result (ref null $struct)) @@ -707,7 +713,7 @@ ) ) ) - ;; CHECK: (func $select-identical-arms-but-side-effect (type $20) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) + ;; CHECK: (func $select-identical-arms-but-side-effect (type $19) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (select ;; CHECK-NEXT: (struct.get_u $struct $i8 @@ -734,7 +740,7 @@ ) ) ) - ;; CHECK: (func $ternary-identical-arms-no-side-effect (type $34) (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) + ;; CHECK: (func $ternary-identical-arms-no-side-effect (type $35) (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get_u $struct $i8 ;; CHECK-NEXT: (select (result (ref $struct)) @@ -759,7 +765,7 @@ ) ) ) - ;; CHECK: (func $if-identical-arms-with-side-effect (type $20) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) + ;; CHECK: (func $if-identical-arms-with-side-effect (type $19) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get_u $struct $i8 ;; CHECK-NEXT: (if (result (ref null $struct)) @@ -1086,7 +1092,7 @@ ) ) - ;; CHECK: (func $hoist-LUB-danger (type $35) (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32) + ;; CHECK: (func $hoist-LUB-danger (type $36) (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then @@ -1125,7 +1131,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-of-non-null (type $36) (param $struct (ref $struct)) + ;; CHECK: (func $incompatible-cast-of-non-null (type $37) (param $struct (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref none)) ;; CHECK-NEXT: (drop @@ -1243,7 +1249,7 @@ ) ) - ;; CHECK: (func $subtype-compatible (type $21) (param $A (ref null $A)) (param $B (ref null $B)) + ;; CHECK: (func $subtype-compatible (type $20) (param $A (ref null $A)) (param $B (ref null $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $B) ;; CHECK-NEXT: (local.get $A) @@ -1412,7 +1418,7 @@ ) ) - ;; CHECK: (func $incompatible-test-heap-types-nonnullable (type $7) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-test-heap-types-nonnullable (type $8) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) @@ -1454,7 +1460,7 @@ ) ) - ;; CHECK: (func $incompatible-test-heap-types-nullable (type $7) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-test-heap-types-nullable (type $8) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) @@ -1495,7 +1501,7 @@ ) ) - ;; CHECK: (func $incompatible-test-heap-types-unreachable (type $7) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-test-heap-types-unreachable (type $8) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) @@ -1537,7 +1543,7 @@ ) ) - ;; CHECK: (func $ref.test-unreachable (type $37) (param $A (ref null $A)) + ;; CHECK: (func $ref.test-unreachable (type $38) (param $A (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $A) ;; CHECK-NEXT: (unreachable) @@ -1660,7 +1666,7 @@ ) ) - ;; CHECK: (func $ref-cast-static-general (type $21) (param $a (ref null $A)) (param $b (ref null $B)) + ;; CHECK: (func $ref-cast-static-general (type $20) (param $a (ref null $A)) (param $b (ref null $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) @@ -1946,7 +1952,7 @@ ) ) - ;; CHECK: (func $ref-cast-static-fallthrough-remaining-impossible (type $22) (param $x (ref eq)) + ;; CHECK: (func $ref-cast-static-fallthrough-remaining-impossible (type $21) (param $x (ref eq)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -1981,7 +1987,7 @@ ) ) - ;; CHECK: (func $ref-cast-static-fallthrough-remaining-nonnull (type $22) (param $x (ref eq)) + ;; CHECK: (func $ref-cast-static-fallthrough-remaining-nonnull (type $21) (param $x (ref eq)) ;; CHECK-NEXT: (local $1 (ref $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $B)) @@ -2090,7 +2096,7 @@ ) ) - ;; CHECK: (func $ref-test-static-same-type (type $23) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) + ;; CHECK: (func $ref-test-static-same-type (type $22) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (ref.is_null @@ -2154,7 +2160,7 @@ ) ) - ;; CHECK: (func $ref-test-static-supertype (type $23) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) + ;; CHECK: (func $ref-test-static-supertype (type $22) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $B) ;; CHECK-NEXT: (local.get $nullable) @@ -2181,7 +2187,7 @@ ) ) - ;; CHECK: (func $ref-test-static-impossible (type $38) (param $nullable (ref null $array)) (param $non-nullable (ref $array)) + ;; CHECK: (func $ref-test-static-impossible (type $39) (param $nullable (ref null $array)) (param $non-nullable (ref $array)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop @@ -2261,14 +2267,14 @@ ) ) - ;; CHECK: (func $impossible (type $39) (result (ref none)) + ;; CHECK: (func $impossible (type $40) (result (ref none)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $impossible (result (ref none)) (unreachable) ) - ;; CHECK: (func $bottom-type-accessors (type $40) (param $bot (ref none)) (param $null nullref) + ;; CHECK: (func $bottom-type-accessors (type $41) (param $bot (ref none)) (param $null nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -2416,7 +2422,7 @@ ) ) - ;; CHECK: (func $compatible-cast-separate-fallthrough (type $24) (param $eqref eqref) (result (ref i31)) + ;; CHECK: (func $compatible-cast-separate-fallthrough (type $23) (param $eqref eqref) (result (ref i31)) ;; CHECK-NEXT: (local $1 i31ref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $eqref @@ -2456,7 +2462,7 @@ ) ) - ;; CHECK: (func $compatible-cast-fallthrough-null-check (type $24) (param $eqref eqref) (result (ref i31)) + ;; CHECK: (func $compatible-cast-fallthrough-null-check (type $23) (param $eqref eqref) (result (ref i31)) ;; CHECK-NEXT: (local $1 i31ref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $eqref @@ -2488,7 +2494,7 @@ ) ) - ;; CHECK: (func $compatible-cast-separate-fallthrough-multiple-options-1 (type $25) (param $eqref eqref) (result (ref eq)) + ;; CHECK: (func $compatible-cast-separate-fallthrough-multiple-options-1 (type $24) (param $eqref eqref) (result (ref eq)) ;; CHECK-NEXT: (local $1 i31ref) ;; CHECK-NEXT: (block $outer (result (ref eq)) ;; CHECK-NEXT: (block (result (ref i31)) @@ -2546,7 +2552,7 @@ ) ) - ;; CHECK: (func $compatible-cast-separate-fallthrough-multiple-options-2 (type $25) (param $eqref eqref) (result (ref eq)) + ;; CHECK: (func $compatible-cast-separate-fallthrough-multiple-options-2 (type $24) (param $eqref eqref) (result (ref eq)) ;; CHECK-NEXT: (local $1 (ref i31)) ;; CHECK-NEXT: (block $outer (result (ref eq)) ;; CHECK-NEXT: (block (result (ref i31)) @@ -2601,7 +2607,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-separate-fallthrough (type $41) (param $eqref eqref) (result structref) + ;; CHECK: (func $incompatible-cast-separate-fallthrough (type $42) (param $eqref eqref) (result structref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $eqref ;; CHECK-NEXT: (block (result (ref i31)) @@ -2635,7 +2641,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-heap-types-nonnullable (type $7) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-cast-heap-types-nonnullable (type $8) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result (ref any)) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -2671,7 +2677,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-heap-types-nullable (type $7) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-cast-heap-types-nullable (type $8) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (block (result nullref) @@ -2703,7 +2709,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-heap-types-unreachable (type $7) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-cast-heap-types-unreachable (type $8) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -2738,7 +2744,7 @@ ) ) - ;; CHECK: (func $as_of_unreachable (type $42) (result (ref $A)) + ;; CHECK: (func $as_of_unreachable (type $43) (result (ref $A)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $as_of_unreachable (result (ref $A)) @@ -2752,7 +2758,7 @@ ) ) - ;; CHECK: (func $cast-internalized-extern (type $43) (param $externref externref) + ;; CHECK: (func $cast-internalized-extern (type $44) (param $externref externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (any.convert_extern @@ -2839,22 +2845,36 @@ ) ) + ;; CHECK: (func $func.arm.1 (type $void1) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func.arm.1 (type $void1) + (nop) + ) + + ;; CHECK: (func $func.arm.2 (type $void2) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func.arm.2 (type $void2) + (nop) + ) + ;; CHECK: (func $refinalize.select.arm (type $void) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $void2) - ;; CHECK-NEXT: (ref.func $refinalize.select.arm) + ;; CHECK-NEXT: (block (result (ref $void1)) + ;; CHECK-NEXT: (ref.func $func.arm.1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $refinalize.select.arm (type $void) ;; Pick one of the two select sides using the condition. This changes the ;; type (the arms are more refined than the declared type), so we must - ;; refinalize or we'll error. + ;; refinalize. (drop - (ref.cast (ref null $void2) + (block (result (ref null $void)) (select (result (ref null $void)) - (ref.func $refinalize.select.arm) - (ref.func $refinalize.select.arm) + (ref.func $func.arm.1) + (ref.func $func.arm.2) (i32.const 1) ) ) @@ -2863,45 +2883,49 @@ ;; CHECK: (func $refinalize.select.arm.flip (type $5) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $void2) - ;; CHECK-NEXT: (ref.func $refinalize.select.arm) + ;; CHECK-NEXT: (block (result (ref $void2)) + ;; CHECK-NEXT: (ref.func $func.arm.2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $refinalize.select.arm.flip ;; Flipped of the above. (drop - (ref.cast (ref null $void2) + (block (result (ref null $void)) (select (result (ref null $void)) - (ref.func $refinalize.select.arm) - (ref.func $refinalize.select.arm) + (ref.func $func.arm.1) + (ref.func $func.arm.2) (i32.const 0) ) ) ) ) - ;; CHECK: (func $refinalize.select.arm.unknown (type $26) (param $x i32) + ;; CHECK: (func $refinalize.select.arm.unknown (type $27) (param $x i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $void2) - ;; CHECK-NEXT: (ref.func $refinalize.select.arm) + ;; CHECK-NEXT: (block (result (ref null $void)) + ;; CHECK-NEXT: (select (result (ref $void)) + ;; CHECK-NEXT: (ref.func $func.arm.1) + ;; CHECK-NEXT: (ref.func $func.arm.2) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $refinalize.select.arm.unknown (param $x i32) ;; As above but use an unknown value at compile time for the condition. (drop - (ref.cast (ref null $void2) + (block (result (ref null $void)) (select (result (ref null $void)) - (ref.func $refinalize.select.arm) - (ref.func $refinalize.select.arm) + (ref.func $func.arm.1) + (ref.func $func.arm.2) (local.get $x) ) ) ) ) - ;; CHECK: (func $non-null-bottom-ref (type $44) (result (ref func)) + ;; CHECK: (func $non-null-bottom-ref (type $45) (result (ref func)) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $0 @@ -2929,7 +2953,7 @@ ) ) - ;; CHECK: (func $non-null-bottom-cast (type $45) (result (ref nofunc)) + ;; CHECK: (func $non-null-bottom-cast (type $46) (result (ref nofunc)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $non-null-bottom-cast) ;; CHECK-NEXT: ) @@ -2942,7 +2966,7 @@ ) ) - ;; CHECK: (func $non-null-bottom-ref-test (type $8) (result i32) + ;; CHECK: (func $non-null-bottom-ref-test (type $9) (result i32) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $0 @@ -2965,7 +2989,7 @@ ) ) - ;; CHECK: (func $non-null-bottom-ref-test-notee (type $8) (result i32) + ;; CHECK: (func $non-null-bottom-ref-test-notee (type $9) (result i32) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (loop (result (ref nofunc)) @@ -2985,7 +3009,7 @@ ) ) - ;; CHECK: (func $non-null-bottom-test (type $8) (result i32) + ;; CHECK: (func $non-null-bottom-test (type $9) (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $non-null-bottom-cast) ;; CHECK-NEXT: ) @@ -3054,7 +3078,7 @@ ) ) - ;; CHECK: (func $ref.test-then-optimizeAddedConstants (type $8) (result i32) + ;; CHECK: (func $ref.test-then-optimizeAddedConstants (type $9) (result i32) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -3128,7 +3152,7 @@ (unreachable) ) - ;; CHECK: (func $array-copy-non-null (type $16) (param $x (ref null $array)) + ;; CHECK: (func $array-copy-non-null (type $15) (param $x (ref null $array)) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (array.copy $array $array ;; CHECK-NEXT: (ref.as_non_null @@ -3555,7 +3579,7 @@ ) ) - ;; CHECK: (func $array.new_fixed_fallthrough_local (type $26) (param $x i32) + ;; CHECK: (func $array.new_fixed_fallthrough_local (type $27) (param $x i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) From a15017750b2d9c1b7c7b1ed57d7f23ea1beb25f1 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 15 May 2025 08:31:29 -0700 Subject: [PATCH 501/622] Type allocations as exact (#7589) Update RefFunc, StructNew, ArrayNew*, ContNew, and ContBind expression to have exact reference types, as they will under the custom descriptors proposal. Even without that proposal enabled, using exact types in the IR can help better optimize casts and generally allows us to propagate more precise type information. Update the fuzzer to avoid assertion failures and invalid output now that it can observe exact types via allocations. Update test output to contain newly emitted exact types. --- src/literal.h | 2 +- src/passes/FuncCastEmulation.cpp | 3 +- src/passes/LegalizeJSInterface.cpp | 3 +- src/passes/TypeSSA.cpp | 2 +- src/tools/fuzzing/fuzzing.cpp | 16 +++- src/wasm-builder.h | 18 ++--- src/wasm/literal.cpp | 5 +- src/wasm/wasm-ir-builder.cpp | 4 +- src/wasm/wasm-validator.cpp | 23 ++++-- src/wasm/wasm.cpp | 2 +- test/binaryen.js/expressions.js | 21 +++-- test/binaryen.js/kitchen-sink.js.txt | 2 +- test/ctor-eval/gc-2.wast.out | 4 +- test/ctor-eval/gc-array.wast.out | 6 +- test/ctor-eval/gc.wast.out | 17 ++-- test/example/c-api-kitchen-sink.txt | 2 +- test/gtest/possible-contents.cpp | 4 +- test/lit/basic/reference-types.wast | 4 +- .../ctor-eval/ctor_after_serialization.wat | 19 ++--- test/lit/ctor-eval/extern.wast | 8 +- test/lit/ctor-eval/gc-cycle-multi.wast | 6 +- test/lit/ctor-eval/gc-cycle.wast | 70 ++++++++--------- test/lit/passes/cfp.wast | 2 +- test/lit/passes/dae-gc-refine-params.wast | 32 +++++--- test/lit/passes/dae-gc-refine-return.wast | 2 +- test/lit/passes/dae-gc.wast | 4 +- test/lit/passes/directize_all-features.wast | 4 +- test/lit/passes/global-refining.wast | 20 ++--- test/lit/passes/gto-removals.wast | 10 +-- test/lit/passes/gto_and_cfp_in_O.wast | 2 +- test/lit/passes/gufa-cast-all.wast | 12 +-- test/lit/passes/gufa-refs.wast | 46 +++++------ test/lit/passes/gufa-tnh-closed.wast | 4 +- test/lit/passes/gufa-tnh.wast | 4 +- test/lit/passes/gufa-vs-cfp.wast | 2 +- test/lit/passes/heap2local.wast | 2 +- test/lit/passes/local-subtyping-nn.wast | 2 +- test/lit/passes/local-subtyping.wast | 32 ++++---- test/lit/passes/monomorphize-benefit.wast | 4 +- test/lit/passes/opt_flatten.wast | 4 +- .../passes/optimize-instructions-gc-tnh.wast | 14 ++-- test/lit/passes/optimize-instructions-gc.wast | 39 +++++----- test/lit/passes/precompute-gc.wast | 4 +- test/lit/passes/precompute-partial.wast | 2 +- test/lit/passes/remove-unused-brs-gc.wast | 66 ++++++++-------- test/lit/passes/roundtrip-gc.wast | 4 +- .../signature-refining-isorecursive.wast | 36 ++++----- test/lit/passes/signature-refining.wast | 72 ++++++++--------- test/lit/passes/simplify-globals-gc.wast | 2 +- test/lit/passes/type-merging.wast | 14 ++-- test/lit/passes/type-refining-gufa.wast | 10 +-- test/lit/passes/type-refining.wast | 31 ++++---- test/lit/passes/type-ssa.wast | 17 ++-- test/lit/passes/unsubtyping-casts.wast | 77 +++++++++++-------- test/lit/passes/unsubtyping.wast | 27 ++++--- test/passes/Oz_fuzz-exec_all-features.txt | 27 ++----- test/passes/precompute_all-features.txt | 2 +- test/unit/test_cluster_fuzz.py | 3 +- test/wasm2js/refs.2asm.js | 4 +- 59 files changed, 463 insertions(+), 417 deletions(-) diff --git a/src/literal.h b/src/literal.h index aba8d974c07..357becab596 100644 --- a/src/literal.h +++ b/src/literal.h @@ -88,7 +88,7 @@ class Literal { explicit Literal(const std::array&); explicit Literal(const std::array&); explicit Literal(Name func, HeapType type) - : func(func), type(type, NonNullable) { + : func(func), type(type, NonNullable, Exact) { assert(type.isSignature()); } explicit Literal(std::shared_ptr gcData, HeapType type); diff --git a/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp index 1a1324c99e8..c534e6e647e 100644 --- a/src/passes/FuncCastEmulation.cpp +++ b/src/passes/FuncCastEmulation.cpp @@ -178,8 +178,7 @@ struct FuncCastEmulation : public Pass { } auto* thunk = iter->second; ref->func = thunk->name; - // TODO: Make this exact. - ref->type = Type(thunk->type, NonNullable); + ref->finalize(thunk->type); } } diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index f7187b3d533..327d28d76a4 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -148,8 +148,7 @@ struct LegalizeJSInterface : public Pass { } curr->func = iter->second->name; - // TODO: Make this exact. - curr->type = Type(iter->second->type, NonNullable); + curr->finalize(iter->second->type); } }; diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index e55cadc5acf..a29bf879590 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -437,7 +437,7 @@ struct TypeSSA : public Pass { auto* curr = newsToModify[i]; auto oldType = curr->type.getHeapType(); auto newType = newTypes[i]; - curr->type = Type(newType, NonNullable); + curr->type = Type(newType, NonNullable, Exact); // If the old type has a nice name, make a nice name for the new one. if (typeNames.count(oldType)) { diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 26cc41e6d73..af7313b1aef 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -4801,12 +4801,17 @@ Expression* TranslateToFuzzReader::makeRefTest(Type type) { // This unreachable avoids a warning on refType being possibly undefined. WASM_UNREACHABLE("bad case"); } + if (!wasm.features.hasCustomDescriptors()) { + // Exact cast targets disallowed without custom descriptors. + castType = castType.with(Inexact); + } return builder.makeRefTest(make(refType), castType); } Expression* TranslateToFuzzReader::makeRefCast(Type type) { assert(type.isRef()); assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC()); + assert(type.isInexact() || wasm.features.hasCustomDescriptors()); // As with RefTest, use possibly related types. Unlike there, we are given the // output type, which is the cast type, so just generate the ref's type. Type refType; @@ -4907,6 +4912,10 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { // nullability, so the combination of the two must be a subtype of // targetType. castType = getSubType(targetType); + if (!wasm.features.hasCustomDescriptors()) { + // Exact cast targets disallowed without custom descriptors. + castType = castType.with(Inexact); + } // The ref's type must be castable to castType, or we'd not validate. But // it can also be a subtype, which will trivially also succeed (so do that // more rarely). Pick subtypes rarely, as they make the cast trivial. @@ -4933,6 +4942,10 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { refType = getSubType(targetType); // See above on BrOnCast, but flipped. castType = oneIn(5) ? getSuperType(refType) : getSubType(refType); + if (!wasm.features.hasCustomDescriptors()) { + // Exact cast targets disallowed without custom descriptors. + castType = castType.with(Inexact); + } // There is no nullability to adjust: if targetType is non-nullable then // both refType and castType are as well, as subtypes of it. But we can // also allow castType to be nullable (it is not sent to the target). @@ -5546,7 +5559,8 @@ Type TranslateToFuzzReader::getSubType(Type type) { heapType = getSubType(heapType); } auto nullability = getSubType(type.getNullability()); - auto exactness = getSubType(type.getExactness()); + auto exactness = + heapType.isBasic() ? Inexact : getSubType(type.getExactness()); auto subType = Type(heapType, nullability, exactness); // We don't want to emit lots of uninhabitable types like (ref none), so // avoid them with high probability. Specifically, if the original type was diff --git a/src/wasm-builder.h b/src/wasm-builder.h index ee313a3341a..0393d1a0540 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -908,21 +908,21 @@ class Builder { std::initializer_list args) { auto* ret = wasm.allocator.alloc(); ret->operands.set(args); - ret->type = Type(type, NonNullable); + ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } StructNew* makeStructNew(HeapType type, ExpressionList&& args) { auto* ret = wasm.allocator.alloc(); ret->operands = std::move(args); - ret->type = Type(type, NonNullable); + ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } template StructNew* makeStructNew(HeapType type, const T& args) { auto* ret = wasm.allocator.alloc(); ret->operands.set(args); - ret->type = Type(type, NonNullable); + ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } @@ -985,7 +985,7 @@ class Builder { auto* ret = wasm.allocator.alloc(); ret->size = size; ret->init = init; - ret->type = Type(type, NonNullable); + ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } @@ -997,7 +997,7 @@ class Builder { ret->segment = seg; ret->offset = offset; ret->size = size; - ret->type = Type(type, NonNullable); + ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } @@ -1009,7 +1009,7 @@ class Builder { ret->segment = seg; ret->offset = offset; ret->size = size; - ret->type = Type(type, NonNullable); + ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } @@ -1017,7 +1017,7 @@ class Builder { ArrayNewFixed* makeArrayNewFixed(HeapType type, const T& values) { auto* ret = wasm.allocator.alloc(); ret->values.set(values); - ret->type = Type(type, NonNullable); + ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } @@ -1185,7 +1185,7 @@ class Builder { } ContNew* makeContNew(HeapType type, Expression* func) { auto* ret = wasm.allocator.alloc(); - ret->type = Type(type, NonNullable); + ret->type = Type(type, NonNullable, Exact); ret->func = func; ret->finalize(); return ret; @@ -1194,7 +1194,7 @@ class Builder { ExpressionList&& operands, Expression* cont) { auto* ret = wasm.allocator.alloc(); - ret->type = Type(targetType, NonNullable); + ret->type = Type(targetType, NonNullable, Exact); ret->operands = std::move(operands); ret->cont = cont; ret->finalize(); diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 6069757a062..c9a1f29be25 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -72,8 +72,9 @@ Literal::Literal(const uint8_t init[16]) : type(Type::v128) { } Literal::Literal(std::shared_ptr gcData, HeapType type) - : gcData(gcData), type(type, gcData ? NonNullable : Nullable) { - // TODO: Use exact types for gcData. + : gcData(gcData), type(type, + gcData ? NonNullable : Nullable, + gcData && !type.isBasic() ? Exact : Inexact) { // The type must be a proper type for GC data: either a struct, array, or // string; or an externalized version of the same; or a null; or an // internalized string (which appears as an anyref). diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index e58a207cfdc..4774ead855f 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2120,7 +2120,7 @@ Result<> IRBuilder::makeBrOn( Result<> IRBuilder::makeStructNew(HeapType type) { StructNew curr(wasm.allocator); - curr.type = Type(type, NonNullable); + curr.type = Type(type, NonNullable, Exact); // Differentiate from struct.new_default with a non-empty expression list. curr.operands.resize(type.getStruct().fields.size()); CHECK_ERR(visitStructNew(&curr)); @@ -2181,7 +2181,7 @@ IRBuilder::makeStructCmpxchg(HeapType type, Index field, MemoryOrder order) { Result<> IRBuilder::makeArrayNew(HeapType type) { ArrayNew curr; - curr.type = Type(type, NonNullable); + curr.type = Type(type, NonNullable, Exact); // Differentiate from array.new_default with dummy initializer. curr.init = (Expression*)0x01; CHECK_ERR(visitArrayNew(&curr)); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 03446d51efe..0285e1e0c3f 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -502,7 +502,7 @@ struct FunctionValidator : public WalkerPass> { void visitStructRMW(StructRMW* curr); void visitStructCmpxchg(StructCmpxchg* curr); void visitArrayNew(ArrayNew* curr); - template void visitArrayNew(ArrayNew* curr); + template void visitArrayNewSegment(ArrayNew* curr); void visitArrayNewData(ArrayNewData* curr); void visitArrayNewElem(ArrayNewElem* curr); void visitArrayNewFixed(ArrayNewFixed* curr); @@ -2375,6 +2375,8 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) { shouldBeTrue(func->type == curr->type.getHeapType(), curr, "function reference type must match referenced function type"); + shouldBeTrue( + curr->type.isExact(), curr, "function reference should be exact"); } void FunctionValidator::visitRefEq(RefEq* curr) { @@ -3022,6 +3024,7 @@ void FunctionValidator::visitStructNew(StructNew* curr) { "struct.new should have a non-nullable reference type")) { return; } + shouldBeTrue(curr->type.isExact(), curr, "struct.new should be exact"); auto heapType = curr->type.getHeapType(); if (!shouldBeTrue( heapType.isStruct(), curr, "struct.new heap type must be struct")) { @@ -3260,6 +3263,7 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) { "array.new should have a non-nullable reference type")) { return; } + shouldBeTrue(curr->type.isExact(), curr, "array.new* should be exact"); auto heapType = curr->type.getHeapType(); if (!shouldBeTrue( heapType.isArray(), curr, "array.new heap type must be array")) { @@ -3284,7 +3288,7 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) { } template -void FunctionValidator::visitArrayNew(ArrayNew* curr) { +void FunctionValidator::visitArrayNewSegment(ArrayNew* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, "array.new_{data, elem} requires gc [--enable-gc]"); @@ -3307,6 +3311,8 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) { "array.new_{data, elem} type should be an array reference")) { return; } + shouldBeTrue( + curr->type.isExact(), curr, "array.new_{data, elem} should be exact"); auto heapType = curr->type.getHeapType(); if (!shouldBeTrue( heapType.isArray(), @@ -3317,7 +3323,7 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) { } void FunctionValidator::visitArrayNewData(ArrayNewData* curr) { - visitArrayNew(curr); + visitArrayNewSegment(curr); shouldBeTrue( getModule()->features.hasBulkMemory(), @@ -3340,7 +3346,7 @@ void FunctionValidator::visitArrayNewData(ArrayNewData* curr) { } void FunctionValidator::visitArrayNewElem(ArrayNewElem* curr) { - visitArrayNew(curr); + visitArrayNewSegment(curr); if (!shouldBeTrue(getModule()->getElementSegment(curr->segment), curr, @@ -3363,13 +3369,14 @@ void FunctionValidator::visitArrayNewElem(ArrayNewElem* curr) { void FunctionValidator::visitArrayNewFixed(ArrayNewFixed* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, - "array.init requires gc [--enable-gc]"); + "array.new_fixed requires gc [--enable-gc]"); if (curr->type == Type::unreachable) { return; } + shouldBeTrue(curr->type.isExact(), curr, "array.new_fixed should be exact"); auto heapType = curr->type.getHeapType(); if (!shouldBeTrue( - heapType.isArray(), curr, "array.init heap type must be array")) { + heapType.isArray(), curr, "array.new_fixed heap type must be array")) { return; } const auto& element = heapType.getArray().element; @@ -3377,7 +3384,7 @@ void FunctionValidator::visitArrayNewFixed(ArrayNewFixed* curr) { shouldBeSubType(value->type, element.type, curr, - "array.init value must have proper type"); + "array.new_fixed value must have proper type"); } } @@ -3699,6 +3706,7 @@ void FunctionValidator::visitContNew(ContNew* curr) { "cont.new should have a non-nullable reference type")) { return; } + shouldBeTrue(curr->type.isExact(), curr, "cont.new should be exact"); shouldBeTrue(curr->type.isContinuation() && curr->type.getHeapType().getContinuation().type.isSignature(), @@ -3735,6 +3743,7 @@ void FunctionValidator::visitContBind(ContBind* curr) { "cont.bind should have a non-nullable reference type")) { return; } + shouldBeTrue(curr->type.isExact(), curr, "cont.bind should be exact"); } void FunctionValidator::visitSuspend(Suspend* curr) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index a05ead7a5eb..655a3156382 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -830,7 +830,7 @@ void RefFunc::finalize() { } void RefFunc::finalize(HeapType heapType) { - type = Type(heapType, NonNullable); + type = Type(heapType, NonNullable, Exact); } void RefEq::finalize() { diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index f249f95493c..3596e5e5f7a 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -1997,7 +1997,8 @@ console.log("# RefFunc"); assert(theRefFunc instanceof binaryen.RefFunc); assert(theRefFunc instanceof binaryen.Expression); assert(theRefFunc.func === func); - assert(theRefFunc.type === type); + // TODO: assert that the type is a non-nullable, exact reference to the + // function type once we can express that in the JS API. var info = binaryen.getExpressionInfo(theRefFunc); assert(info.id === theRefFunc.id); @@ -2007,7 +2008,8 @@ console.log("# RefFunc"); theRefFunc.func = func = "b"; assert(theRefFunc.func === func); theRefFunc.finalize(); - assert(theRefFunc.type === type); + // TODO: assert that the type is a non-nullable, exact reference to the + // function type once we can express that in the JS API. info = binaryen.getExpressionInfo(theRefFunc); assert(info.type === theRefFunc.type); @@ -2223,7 +2225,8 @@ console.log("# StructNew"); assert(theStructNew instanceof binaryen.Expression); assertDeepEqual(theStructNew.operands, operands); assertDeepEqual(theStructNew.getOperands(), operands); - assert(theStructNew.type === type); + // TODO: assert that the type is a non-nullable, exact reference to the + // function type once we can express that in the JS API. var info = binaryen.getExpressionInfo(theStructNew); assert(info.id === theStructNew.id); @@ -2405,7 +2408,8 @@ console.log("# ArrayNew"); assert(theArrayNew instanceof binaryen.Expression); assert(theArrayNew.size === size); assert(theArrayNew.init === init); - assert(theArrayNew.type === type); + // TODO: assert that the type is a non-nullable, exact reference to the + // function type once we can express that in the JS API. var info = binaryen.getExpressionInfo(theArrayNew); assert(info.id === theArrayNew.id); @@ -2458,7 +2462,8 @@ console.log("# ArrayNewFixed"); assert(theArrayNewFixed instanceof binaryen.Expression); assertDeepEqual(theArrayNewFixed.values, values); assertDeepEqual(theArrayNewFixed.getValues(), values); - assert(theArrayNewFixed.type === type); + // TODO: assert that the type is a non-nullable, exact reference to the + // function type once we can express that in the JS API. var info = binaryen.getExpressionInfo(theArrayNewFixed); assert(info.id === theArrayNewFixed.id); @@ -2519,7 +2524,8 @@ console.log("# ArrayNewData"); assert(theArrayNewData.segment === segment); assert(theArrayNewData.offset === offset); assert(theArrayNewData.size === size); - assert(theArrayNewData.type === type); + // TODO: assert that the type is a non-nullable, exact reference to the + // function type once we can express that in the JS API. var info = binaryen.getExpressionInfo(theArrayNewData); assert(info.id === theArrayNewData.id); @@ -2576,7 +2582,8 @@ console.log("# ArrayNewElem"); assert(theArrayNewElem.segment === segment); assert(theArrayNewElem.offset === offset); assert(theArrayNewElem.size === size); - assert(theArrayNewElem.type === type); + // TODO: assert that the type is a non-nullable, exact reference to the + // function type once we can express that in the JS API. var info = binaryen.getExpressionInfo(theArrayNewElem); assert(info.id === theArrayNewElem.id); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index be7a06f7ad8..45c7417dcda 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -2089,7 +2089,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} ) ) (drop - (select (result (ref null $0)) + (select (result (ref null (exact $0))) (ref.null nofunc) (ref.func $foobar) (i32.const 1) diff --git a/test/ctor-eval/gc-2.wast.out b/test/ctor-eval/gc-2.wast.out index ebac3765344..bd5e2e620ed 100644 --- a/test/ctor-eval/gc-2.wast.out +++ b/test/ctor-eval/gc-2.wast.out @@ -1,10 +1,10 @@ (module (type $struct (sub (struct (field i32)))) (type $1 (func (result i32))) - (global $ctor-eval$global (ref $struct) (struct.new $struct + (global $ctor-eval$global (ref (exact $struct)) (struct.new $struct (i32.const 1337) )) - (global $ctor-eval$global_4 (ref $struct) (struct.new $struct + (global $ctor-eval$global_4 (ref (exact $struct)) (struct.new $struct (i32.const 9999) )) (global $global1 (ref any) (global.get $ctor-eval$global)) diff --git a/test/ctor-eval/gc-array.wast.out b/test/ctor-eval/gc-array.wast.out index ed21823b8c7..2c64d03dd8f 100644 --- a/test/ctor-eval/gc-array.wast.out +++ b/test/ctor-eval/gc-array.wast.out @@ -1,16 +1,18 @@ (module (type $array (array (mut i32))) (type $1 (func (result i32))) - (global $global1 (ref $array) (array.new_fixed $array 4 + (global $ctor-eval$global (ref (exact $array)) (array.new_fixed $array 4 (i32.const 10) (i32.const 20) (i32.const 30) (i32.const 40) )) - (global $global2 (ref $array) (array.new_fixed $array 2 + (global $ctor-eval$global_3 (ref (exact $array)) (array.new_fixed $array 2 (i32.const 42) (i32.const 1337) )) + (global $global1 (ref $array) (global.get $ctor-eval$global)) + (global $global2 (ref $array) (global.get $ctor-eval$global_3)) (export "keepalive" (func $keepalive)) (func $keepalive (type $1) (result i32) (i32.add diff --git a/test/ctor-eval/gc.wast.out b/test/ctor-eval/gc.wast.out index ae3e21a180e..d4f0062ea9c 100644 --- a/test/ctor-eval/gc.wast.out +++ b/test/ctor-eval/gc.wast.out @@ -4,14 +4,15 @@ (type $2 (func (result i32))) (type $3 (func)) (import "import" "import" (func $import (type $1) (param anyref))) - (global $ctor-eval$global_5 (ref $struct) (struct.new $struct - (i32.const 42) - )) - (global $global2 (mut (ref null $struct)) (global.get $ctor-eval$global_5)) - (global $global1 (ref $struct) (struct.new $struct + (global $ctor-eval$global_7 (ref (exact $struct)) (struct.new $struct (i32.const 1337) )) - (global $ctor-eval$global_4 (ref $struct) (struct.new $struct + (global $ctor-eval$global_8 (ref (exact $struct)) (struct.new $struct + (i32.const 42) + )) + (global $global1 (ref $struct) (global.get $ctor-eval$global_7)) + (global $global2 (mut (ref null $struct)) (global.get $ctor-eval$global_8)) + (global $ctor-eval$global_6 (ref (exact $struct)) (struct.new $struct (i32.const 99) )) (export "test1" (func $test1_3)) @@ -27,9 +28,9 @@ ) ) (func $test1_3 (type $3) - (local $0 (ref $struct)) + (local $0 (ref (exact $struct))) (local.set $0 - (global.get $ctor-eval$global_4) + (global.get $ctor-eval$global_6) ) (call $import (ref.null none) diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 1961547c5a1..1b798654d1a 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -2122,7 +2122,7 @@ BinaryenFeatureAll: 4194303 ) ) (drop - (select (result (ref null $2)) + (select (result (ref null (exact $2))) (ref.null nofunc) (ref.func $"kitchen()sinker") (i32.const 1) diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 7c1503ffe7b..492f5830aaf 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -105,9 +105,9 @@ class PossibleContentsTest : public testing::Test { PossibleContents::exactType(Type(HeapType::i31, NonNullable)); PossibleContents exactFuncSignatureType = PossibleContents::exactType( - Type(Signature(Type::none, Type::none), Nullable)); + Type(Signature(Type::none, Type::none), Nullable, Exact)); PossibleContents exactNonNullFuncSignatureType = PossibleContents::exactType( - Type(Signature(Type::none, Type::none), NonNullable)); + Type(Signature(Type::none, Type::none), NonNullable, Exact)); PossibleContents many = PossibleContents::many(); diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 82b1f8da4e3..4058d316d6f 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -997,7 +997,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block6 (result funcref) - ;; CHECK-BIN-NEXT: (ref.cast (ref $3) + ;; CHECK-BIN-NEXT: (ref.cast (ref (exact $3)) ;; CHECK-BIN-NEXT: (br_if $block6 ;; CHECK-BIN-NEXT: (ref.func $foo) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -2334,7 +2334,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block6 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref $3) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref (exact $3)) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block6 ;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) diff --git a/test/lit/ctor-eval/ctor_after_serialization.wat b/test/lit/ctor-eval/ctor_after_serialization.wat index e3acfefe6c9..0ecd3fe2bc9 100644 --- a/test/lit/ctor-eval/ctor_after_serialization.wat +++ b/test/lit/ctor-eval/ctor_after_serialization.wat @@ -26,7 +26,7 @@ ;; CHECK: (type $2 (func)) -;; CHECK: (global $ctor-eval$global (ref $A) (struct.new_default $A)) +;; CHECK: (global $ctor-eval$global (ref (exact $A)) (struct.new_default $A)) ;; CHECK: (export "new" (func $new_2)) @@ -46,11 +46,6 @@ ;; CHECK: (type $A (struct)) (type $A (struct)) - ;; CHECK: (type $1 (func (result (ref any)))) - - ;; CHECK: (type $2 (func (result anyref))) - - ;; CHECK: (global $ctor-eval$global (ref $A) (struct.new_default $A)) (global $ctor-eval$global (ref $A) (struct.new_default $A) ) @@ -67,16 +62,22 @@ (global.get $ctor-eval$global) ) ) -;; CHECK: (global $ctor-eval$global_1 (ref $A) (struct.new_default $A)) +;; CHECK: (type $1 (func (result (ref any)))) + +;; CHECK: (type $2 (func (result anyref))) + +;; CHECK: (global $ctor-eval$global_3 (ref (exact $A)) (struct.new_default $A)) + +;; CHECK: (global $ctor-eval$global_2 (ref (exact $A)) (struct.new_default $A)) ;; CHECK: (export "new" (func $new_2)) ;; CHECK: (export "nop" (func $nop_3)) ;; CHECK: (func $new_2 (type $1) (result (ref any)) -;; CHECK-NEXT: (global.get $ctor-eval$global_1) +;; CHECK-NEXT: (global.get $ctor-eval$global_2) ;; CHECK-NEXT: ) ;; CHECK: (func $nop_3 (type $2) (result anyref) -;; CHECK-NEXT: (global.get $ctor-eval$global) +;; CHECK-NEXT: (global.get $ctor-eval$global_3) ;; CHECK-NEXT: ) diff --git a/test/lit/ctor-eval/extern.wast b/test/lit/ctor-eval/extern.wast index d8e08eaa2f6..17cb0097052 100644 --- a/test/lit/ctor-eval/extern.wast +++ b/test/lit/ctor-eval/extern.wast @@ -92,19 +92,19 @@ ;; CHECK: (type $7 (func (result (ref null (shared any))))) -;; CHECK: (global $ctor-eval$global (ref $array) (array.new_fixed $array 3 +;; CHECK: (global $ctor-eval$global (ref (exact $array)) (array.new_fixed $array 3 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: )) -;; CHECK: (global $ctor-eval$global_1 (ref $shared-array) (array.new_fixed $shared-array 3 +;; CHECK: (global $ctor-eval$global_1 (ref (exact $shared-array)) (array.new_fixed $shared-array 3 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: )) -;; CHECK: (global $ctor-eval$global_2 (ref $struct) (struct.new $struct +;; CHECK: (global $ctor-eval$global_2 (ref (exact $struct)) (struct.new $struct ;; CHECK-NEXT: (extern.convert_any ;; CHECK-NEXT: (ref.i31 ;; CHECK-NEXT: (i32.const 1) @@ -112,7 +112,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: )) -;; CHECK: (global $ctor-eval$global_3 (ref $shared-struct) (struct.new $shared-struct +;; CHECK: (global $ctor-eval$global_3 (ref (exact $shared-struct)) (struct.new $shared-struct ;; CHECK-NEXT: (extern.convert_any ;; CHECK-NEXT: (ref.i31_shared ;; CHECK-NEXT: (i32.const 1) diff --git a/test/lit/ctor-eval/gc-cycle-multi.wast b/test/lit/ctor-eval/gc-cycle-multi.wast index 07029143260..dc14403b32f 100644 --- a/test/lit/ctor-eval/gc-cycle-multi.wast +++ b/test/lit/ctor-eval/gc-cycle-multi.wast @@ -13,17 +13,17 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_6 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_6 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_8 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: )) diff --git a/test/lit/ctor-eval/gc-cycle.wast b/test/lit/ctor-eval/gc-cycle.wast index a342ba3c93f..949a3c3852c 100644 --- a/test/lit/ctor-eval/gc-cycle.wast +++ b/test/lit/ctor-eval/gc-cycle.wast @@ -9,7 +9,7 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_3 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_3 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) @@ -84,7 +84,7 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_3 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_3 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) @@ -151,12 +151,12 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_8 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) @@ -250,12 +250,12 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_8 (ref $B) (struct.new $B + ;; CHECK: (global $ctor-eval$global_8 (ref (exact $B)) (struct.new $B ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) @@ -348,12 +348,12 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_8 (ref $B) (struct.new $B + ;; CHECK: (global $ctor-eval$global_8 (ref (exact $B)) (struct.new $B ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) @@ -446,12 +446,12 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_8 (ref $B) (struct.new $B + ;; CHECK: (global $ctor-eval$global_8 (ref (exact $B)) (struct.new $B ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) @@ -536,12 +536,12 @@ ;; CHECK: (type $3 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_7 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_8 (ref $B) (struct.new $B + ;; CHECK: (global $ctor-eval$global_8 (ref (exact $B)) (struct.new $B ;; CHECK-NEXT: (global.get $ctor-eval$global_7) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) @@ -614,17 +614,17 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_12 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_12 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_14 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_13 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_13 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (i32.const 99999) ;; CHECK-NEXT: )) @@ -725,12 +725,12 @@ ;; CHECK: (type $4 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_12 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_12 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_14 (ref $B) (array.new_fixed $B 10 + ;; CHECK: (global $ctor-eval$global_14 (ref (exact $B)) (array.new_fixed $B 10 ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) @@ -780,7 +780,7 @@ ) ) - ;; CHECK: (global $ctor-eval$global_13 (ref $C) (array.new_fixed $C 2 + ;; CHECK: (global $ctor-eval$global_13 (ref (exact $C)) (array.new_fixed $C 2 ;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: )) @@ -833,12 +833,12 @@ ;; CHECK: (type $4 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_12 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_12 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_14 (ref $B) (array.new_fixed $B 10 + ;; CHECK: (global $ctor-eval$global_14 (ref (exact $B)) (array.new_fixed $B 10 ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) @@ -892,7 +892,7 @@ ) ) - ;; CHECK: (global $ctor-eval$global_13 (ref $C) (array.new_fixed $C 2 + ;; CHECK: (global $ctor-eval$global_13 (ref (exact $C)) (array.new_fixed $C 2 ;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (global.get $ctor-eval$global_12) ;; CHECK-NEXT: )) @@ -944,19 +944,19 @@ ;; CHECK: (type $3 (func (result anyref))) - ;; CHECK: (global $ctor-eval$global_17 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_17 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_14 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_18 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_18 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) @@ -994,15 +994,15 @@ ) ) - ;; CHECK: (global $ctor-eval$global_16 (ref $B) (array.new_fixed $B 3 + ;; CHECK: (global $ctor-eval$global_16 (ref (exact $B)) (array.new_fixed $B 3 ;; CHECK-NEXT: (global.get $ctor-eval$global_17) ;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (global.get $ctor-eval$global_18) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_15 (ref $B) (array.new_fixed $B 0)) + ;; CHECK: (global $ctor-eval$global_15 (ref (exact $B)) (array.new_fixed $B 0)) - ;; CHECK: (global $ctor-eval$global_19 (ref $B) (array.new_fixed $B 0)) + ;; CHECK: (global $ctor-eval$global_19 (ref (exact $B)) (array.new_fixed $B 0)) ;; CHECK: (export "test" (func $test_3)) @@ -1056,17 +1056,17 @@ ;; CHECK: (type $3 (func (result anyref))) - ;; CHECK: (global $ctor-eval$global_17 (ref $B) (array.new_fixed $B 0)) + ;; CHECK: (global $ctor-eval$global_17 (ref (exact $B)) (array.new_fixed $B 0)) - ;; CHECK: (global $ctor-eval$global_14 (ref $B) (array.new_fixed $B 3 + ;; CHECK: (global $ctor-eval$global_14 (ref (exact $B)) (array.new_fixed $B 3 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_18 (ref $B) (array.new_fixed $B 0)) + ;; CHECK: (global $ctor-eval$global_18 (ref (exact $B)) (array.new_fixed $B 0)) - ;; CHECK: (global $ctor-eval$global_16 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_16 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (global.get $ctor-eval$global_17) ;; CHECK-NEXT: (global.get $ctor-eval$global_14) ;; CHECK-NEXT: (global.get $ctor-eval$global_18) @@ -1105,13 +1105,13 @@ ) ) - ;; CHECK: (global $ctor-eval$global_15 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_15 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) - ;; CHECK: (global $ctor-eval$global_19 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_19 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) @@ -1167,7 +1167,7 @@ ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (global $ctor-eval$global_4 (ref $A) (struct.new $A + ;; CHECK: (global $ctor-eval$global_4 (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) @@ -1271,7 +1271,7 @@ ) ) ) -;; CHECK: (global $ctor-eval$global (ref $A) (struct.new $A +;; CHECK: (global $ctor-eval$global (ref (exact $A)) (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 0bc4372ec35..56d1a832d42 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -477,7 +477,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $1)) + ;; CHECK-NEXT: (block (result (ref (exact $1))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct) diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index 8cefbe88184..27f9a214d11 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -338,7 +338,7 @@ ) ) - ;; CHECK: (func $non-nullable-fixup (type $14) (param $0 (ref $"{}")) + ;; CHECK: (func $non-nullable-fixup (type $14) (param $0 (ref (exact $"{}"))) ;; CHECK-NEXT: (local $1 structref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $0) @@ -386,7 +386,7 @@ ) ) - ;; CHECK: (func $update-null (type $15) (param $x (ref null $"{}")) + ;; CHECK: (func $update-null (type $15) (param $x (ref null (exact $"{}"))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -401,22 +401,32 @@ ;; CHECK: (func $"get_null_{i32}" (type $5) (result (ref null $"{i32}")) ;; CHECK-NEXT: (select (result (ref null $"{i32}")) ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (struct.new_default $"{i32}") + ;; CHECK-NEXT: (select (result (ref $"{i32}")) + ;; CHECK-NEXT: (struct.new_default $"{i32}") + ;; CHECK-NEXT: (struct.new_default $"{i32_i64}") + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $"get_null_{i32}" (result (ref null $"{i32}")) - ;; Helper function that returns a null value of $"{i32}." We use this instead of - ;; a direct ref.null because those can be rewritten by LUBFinder. - (select + ;; Helper function that returns a null value of $"{i32}." We use this instead + ;; of a direct ref.null because those can be rewritten by LUBFinder. Use two + ;; selects to create a return type that cannot be improved to be non-null, a + ;; subtype, or exact. + (select (result (ref null $"{i32}")) (ref.null none) - (struct.new_default $"{i32}") + (select (result (ref $"{i32}")) + (struct.new_default $"{i32}") + (struct.new_default $"{i32_i64}") + (i32.const 0) + ) (i32.const 0) ) ) - ;; CHECK: (func $"get_null_{i32_i64}" (type $16) (result (ref null $"{i32_i64}")) - ;; CHECK-NEXT: (select (result (ref null $"{i32_i64}")) + ;; CHECK: (func $"get_null_{i32_i64}" (type $16) (result (ref null (exact $"{i32_i64}"))) + ;; CHECK-NEXT: (select (result (ref null (exact $"{i32_i64}"))) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (struct.new_default $"{i32_i64}") ;; CHECK-NEXT: (i32.const 0) @@ -430,8 +440,8 @@ ) ) - ;; CHECK: (func $"get_null_{i32_f32}" (type $17) (result (ref null $"{i32_f32}")) - ;; CHECK-NEXT: (select (result (ref null $"{i32_f32}")) + ;; CHECK: (func $"get_null_{i32_f32}" (type $17) (result (ref null (exact $"{i32_f32}"))) + ;; CHECK-NEXT: (select (result (ref null (exact $"{i32_f32}"))) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (struct.new_default $"{i32_f32}") ;; CHECK-NEXT: (i32.const 0) diff --git a/test/lit/passes/dae-gc-refine-return.wast b/test/lit/passes/dae-gc-refine-return.wast index 5e1f9d4e7f4..1ef7bc1b13c 100644 --- a/test/lit/passes/dae-gc-refine-return.wast +++ b/test/lit/passes/dae-gc-refine-return.wast @@ -306,7 +306,7 @@ (func $do-return-call (result funcref) (return_call $return-ref-func) ) - ;; CHECK: (func $return-ref-func (type $9) (result (ref $6)) + ;; CHECK: (func $return-ref-func (type $9) (result (ref (exact $6))) ;; CHECK-NEXT: (ref.func $do-return-call) ;; CHECK-NEXT: ) (func $return-ref-func (result funcref) diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 1f39567d146..6d014d0f8b4 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -68,8 +68,8 @@ ;; Test ref.func and ref.null optimization of constant parameter values. (module - ;; CHECK: (func $foo (type $1) (param $0 (ref $0)) - ;; CHECK-NEXT: (local $1 (ref $0)) + ;; CHECK: (func $foo (type $1) (param $0 (ref (exact $0))) + ;; CHECK-NEXT: (local $1 (ref (exact $0))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.func $a) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/directize_all-features.wast b/test/lit/passes/directize_all-features.wast index 074558d95ae..6957cf6d158 100644 --- a/test/lit/passes/directize_all-features.wast +++ b/test/lit/passes/directize_all-features.wast @@ -1167,7 +1167,7 @@ ) ;; CHECK: (func $select-non-nullable (type $1) (param $x i32) - ;; CHECK-NEXT: (local $1 (ref $1)) + ;; CHECK-NEXT: (local $1 (ref (exact $1))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.func $select-non-nullable) ;; CHECK-NEXT: ) @@ -1186,7 +1186,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; IMMUT: (func $select-non-nullable (type $1) (param $x i32) - ;; IMMUT-NEXT: (local $1 (ref $1)) + ;; IMMUT-NEXT: (local $1 (ref (exact $1))) ;; IMMUT-NEXT: (local.set $1 ;; IMMUT-NEXT: (ref.func $select-non-nullable) ;; IMMUT-NEXT: ) diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index 927dfd1f26c..a228aa67af6 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -14,8 +14,8 @@ ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) - ;; CHECK: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) - ;; CLOSD: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) + ;; CHECK: (global $func-func-init (mut (ref (exact $foo_t))) (ref.func $foo)) + ;; CLOSD: (global $func-func-init (mut (ref (exact $foo_t))) (ref.func $foo)) (global $func-func-init (mut funcref) (ref.func $foo)) ;; CHECK: (func $foo (type $foo_t) ;; CHECK-NEXT: ) @@ -35,8 +35,8 @@ ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) - ;; CHECK: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) - ;; CLOSD: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) + ;; CHECK: (global $func-func-init (mut (ref null (exact $foo_t))) (ref.func $foo)) + ;; CLOSD: (global $func-func-init (mut (ref null (exact $foo_t))) (ref.func $foo)) (global $func-func-init (mut funcref) (ref.func $foo)) ;; CHECK: (func $foo (type $foo_t) @@ -67,13 +67,13 @@ ;; CHECK: (type $0 (func)) - ;; CHECK: (global $func-null-init (mut (ref null $0)) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut (ref null (exact $0))) (ref.null nofunc)) ;; CLOSD: (type $0 (func)) - ;; CLOSD: (global $func-null-init (mut (ref null $0)) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut (ref null (exact $0))) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null func)) - ;; CHECK: (global $func-func-init (mut (ref $0)) (ref.func $foo)) - ;; CLOSD: (global $func-func-init (mut (ref $0)) (ref.func $foo)) + ;; CHECK: (global $func-func-init (mut (ref (exact $0))) (ref.func $foo)) + ;; CLOSD: (global $func-func-init (mut (ref (exact $0))) (ref.func $foo)) (global $func-func-init (mut funcref) (ref.func $foo)) ;; CHECK: (elem declare func $foo) @@ -178,8 +178,8 @@ ;; CLOSD: (type $sub (sub $super (func))) (type $sub (sub $super (func))) - ;; CHECK: (global $a (ref $sub) (ref.func $func)) - ;; CLOSD: (global $a (ref $sub) (ref.func $func)) + ;; CHECK: (global $a (ref (exact $sub)) (ref.func $func)) + ;; CLOSD: (global $a (ref (exact $sub)) (ref.func $func)) (global $a (ref $super) (ref.func $func)) ;; CHECK: (global $b (ref $super) (global.get $a)) ;; CLOSD: (global $b (ref $super) (global.get $a)) diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index edab804a5ed..9dbb712bd28 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -444,7 +444,7 @@ ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (local $2 (ref any)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $helper0 ;; CHECK-NEXT: (i32.const 0) @@ -484,7 +484,7 @@ ;; CHECK-NEXT: (local $0 f64) ;; CHECK-NEXT: (local $1 (ref any)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $helper1 ;; CHECK-NEXT: (i32.const 0) @@ -520,7 +520,7 @@ ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (local $2 (ref any)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (global.get $mut-i32) ;; CHECK-NEXT: ) @@ -585,7 +585,7 @@ ;; CHECK: (func $new-side-effect-in-kept (type $3) (param $any (ref any)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (call $helper0 ;; CHECK-NEXT: (i32.const 0) @@ -1651,7 +1651,7 @@ ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/gto_and_cfp_in_O.wast b/test/lit/passes/gto_and_cfp_in_O.wast index b1e9a47df50..783da3a1982 100644 --- a/test/lit/passes/gto_and_cfp_in_O.wast +++ b/test/lit/passes/gto_and_cfp_in_O.wast @@ -15,7 +15,7 @@ ;; OPEN_WORLD: (type $2 (func (result i32))) - ;; OPEN_WORLD: (global $glob (ref $struct) (struct.new $struct + ;; OPEN_WORLD: (global $glob (ref (exact $struct)) (struct.new $struct ;; OPEN_WORLD-NEXT: (ref.func $by-ref) ;; OPEN_WORLD-NEXT: (i32.const 100) ;; OPEN_WORLD-NEXT: )) diff --git a/test/lit/passes/gufa-cast-all.wast b/test/lit/passes/gufa-cast-all.wast index 92ba242fe34..d8f01441d2b 100644 --- a/test/lit/passes/gufa-cast-all.wast +++ b/test/lit/passes/gufa-cast-all.wast @@ -35,7 +35,7 @@ ;; CHECK-NEXT: (struct.new_default $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (ref.cast (ref (exact $B)) ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -97,14 +97,14 @@ ;; CHECK: (func $funcs (type $none_=>_none) ;; CHECK-NEXT: (local $a funcref) ;; CHECK-NEXT: (local.set $a - ;; CHECK-NEXT: (select (result (ref $none_=>_none)) + ;; CHECK-NEXT: (select (result (ref (exact $none_=>_none))) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: (ref.func $funcs) ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $none_=>_none) + ;; CHECK-NEXT: (ref.cast (ref (exact $none_=>_none)) ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -296,11 +296,11 @@ ;; CHECK: (func $test (type $A) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $A)) + ;; CHECK-NEXT: (block (result (ref (exact $A))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (ref $A)) + ;; CHECK-NEXT: (block $block (result (ref (exact $A))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $A)) + ;; CHECK-NEXT: (block (result (ref (exact $A))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (ref.func $test) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 2178f142383..0166bdae678 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -81,7 +81,7 @@ ;; CHECK: (func $breaks (type $1) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (ref $struct)) + ;; CHECK-NEXT: (block $block (result (ref (exact $struct))) ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) @@ -204,7 +204,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (select (result (ref (exact $struct))) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (call $import) @@ -991,7 +991,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $parent (result (ref none)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $parent (ref $unrelated) (ref none) + ;; CHECK-NEXT: (br_on_cast $parent (ref (exact $unrelated)) (ref none) ;; CHECK-NEXT: (struct.new_default $unrelated) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2405,8 +2405,8 @@ ;; CHECK: (func $test (type $1) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref - ;; CHECK-NEXT: (block (result (ref $struct)) - ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) @@ -2488,7 +2488,7 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $substruct) + ;; CHECK-NEXT: (ref.cast (ref (exact $substruct)) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) @@ -2496,7 +2496,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $subsubstruct) + ;; CHECK-NEXT: (ref.cast (ref (exact $subsubstruct)) ;; CHECK-NEXT: (struct.new $subsubstruct ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: (i32.const 4) @@ -2570,8 +2570,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref null $struct) - ;; CHECK-NEXT: (select (result (ref null $struct)) + ;; CHECK-NEXT: (ref.cast (ref null (exact $struct)) + ;; CHECK-NEXT: (select (result (ref null (exact $struct))) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 6) @@ -2623,8 +2623,8 @@ ;; CHECK: (func $test-cones (type $4) (param $x i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref null $struct) - ;; CHECK-NEXT: (select (result (ref null $struct)) + ;; CHECK-NEXT: (ref.cast (ref null (exact $struct)) + ;; CHECK-NEXT: (select (result (ref null (exact $struct))) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2773,8 +2773,8 @@ ;; CHECK: (func $ref.test-inexact (type $4) (param $x i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.test (ref $struct) - ;; CHECK-NEXT: (select (result (ref null $struct)) + ;; CHECK-NEXT: (ref.test (ref (exact $struct)) + ;; CHECK-NEXT: (select (result (ref null (exact $struct))) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3121,7 +3121,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (select (result (ref $substruct)) + ;; CHECK-NEXT: (select (result (ref (exact $substruct))) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: (i32.const 4) @@ -3584,7 +3584,7 @@ ;; CHECK: (func $foo (type $3) (result (ref $B)) ;; CHECK-NEXT: (local $A (ref null $A)) - ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (ref.cast (ref (exact $B)) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.tee $A ;; CHECK-NEXT: (struct.new $B @@ -5348,9 +5348,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $B (result (ref $B)) + ;; CHECK-NEXT: (block $B (result (ref none)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $B (ref $A) (ref $B) + ;; CHECK-NEXT: (br_on_cast $B (ref (exact $A)) (ref none) ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 100) ;; CHECK-NEXT: ) @@ -5365,9 +5365,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $A (result (ref $A)) + ;; CHECK-NEXT: (block $A (result (ref (exact $A))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $A (ref $A) (ref $A) + ;; CHECK-NEXT: (br_on_cast $A (ref (exact $A)) (ref (exact $A)) ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 200) ;; CHECK-NEXT: ) @@ -5933,7 +5933,7 @@ ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $2) (result (ref $A)) - ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (ref.cast (ref (exact $B)) ;; CHECK-NEXT: (call $get-B-def-any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -6017,10 +6017,10 @@ ;; CHECK-NEXT: (struct.new_default $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $label (result (ref null $A)) + ;; CHECK-NEXT: (block $label (result (ref null (exact $A))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label (ref $A) (ref $A) - ;; CHECK-NEXT: (ref.cast (ref $A) + ;; CHECK-NEXT: (br_on_cast $label (ref (exact $A)) (ref (exact $A)) + ;; CHECK-NEXT: (ref.cast (ref (exact $A)) ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/gufa-tnh-closed.wast b/test/lit/passes/gufa-tnh-closed.wast index 4f593aea34b..edcca54d62b 100644 --- a/test/lit/passes/gufa-tnh-closed.wast +++ b/test/lit/passes/gufa-tnh-closed.wast @@ -1060,7 +1060,7 @@ ;; CHECK: (func $called (type $A) (param $ref anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref null $Y1) + ;; CHECK-NEXT: (ref.cast (ref null (exact $Y1)) ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1081,7 +1081,7 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called - ;; CHECK-NEXT: (ref.cast (ref $Y1) + ;; CHECK-NEXT: (ref.cast (ref (exact $Y1)) ;; CHECK-NEXT: (select (result (ref $X)) ;; CHECK-NEXT: (struct.new_default $Y1) ;; CHECK-NEXT: (struct.new_default $Y2) diff --git a/test/lit/passes/gufa-tnh.wast b/test/lit/passes/gufa-tnh.wast index 1713dac6c56..a0dd9846191 100644 --- a/test/lit/passes/gufa-tnh.wast +++ b/test/lit/passes/gufa-tnh.wast @@ -1045,7 +1045,7 @@ ;; CHECK: (func $called (type $2) (param $x (ref null $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (ref.cast (ref (exact $B)) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2085,7 +2085,7 @@ ;; CHECK-NEXT: (struct.new_default $bot) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $bot) + ;; CHECK-NEXT: (ref.cast (ref (exact $bot)) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index bd731d89150..bfeeac5fa22 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -2052,7 +2052,7 @@ ) ;; CHECK: (func $set (type $0) ;; CHECK-NEXT: (struct.set $C 0 - ;; CHECK-NEXT: (ref.cast (ref $C) + ;; CHECK-NEXT: (ref.cast (ref (exact $C)) ;; CHECK-NEXT: (call $create-C) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 20) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 619a33a23c5..9af0e1b1bb1 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -642,7 +642,7 @@ ;; CHECK: (func $non-exclusive-set (type $2) (result f64) ;; CHECK-NEXT: (local $ref (ref null $struct.A)) ;; CHECK-NEXT: (local.set $ref - ;; CHECK-NEXT: (select (result (ref $struct.A)) + ;; CHECK-NEXT: (select (result (ref (exact $struct.A))) ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: (i32.const 1) diff --git a/test/lit/passes/local-subtyping-nn.wast b/test/lit/passes/local-subtyping-nn.wast index 3754230d82f..476e4d4b045 100644 --- a/test/lit/passes/local-subtyping-nn.wast +++ b/test/lit/passes/local-subtyping-nn.wast @@ -9,7 +9,7 @@ ;; CHECK: (func $non-nullable (type $1) ;; CHECK-NEXT: (local $x (ref none)) - ;; CHECK-NEXT: (local $y (ref $0)) + ;; CHECK-NEXT: (local $y (ref (exact $0))) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null none) diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 795a0a01cd1..4679ff9d660 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -84,7 +84,7 @@ ;; more specific type. A similar thing with a parameter, however, is not a ;; thing we can optimize. Also, ignore a local with zero assignments. ;; CHECK: (func $simple-local-but-not-param (type $8) (param $x funcref) - ;; CHECK-NEXT: (local $y (ref $1)) + ;; CHECK-NEXT: (local $y (ref (exact $1))) ;; CHECK-NEXT: (local $unused funcref) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $i32) @@ -179,9 +179,9 @@ ;; In some cases multiple iterations are necessary, as one inferred new type ;; applies to a get which then allows another inference. ;; CHECK: (func $multiple-iterations (type $0) - ;; CHECK-NEXT: (local $x (ref $1)) - ;; CHECK-NEXT: (local $y (ref $1)) - ;; CHECK-NEXT: (local $z (ref $1)) + ;; CHECK-NEXT: (local $x (ref (exact $1))) + ;; CHECK-NEXT: (local $y (ref (exact $1))) + ;; CHECK-NEXT: (local $z (ref (exact $1))) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $i32) ;; CHECK-NEXT: ) @@ -209,8 +209,8 @@ ;; Sometimes a refinalize is necessary in between the iterations. ;; CHECK: (func $multiple-iterations-refinalize (type $2) (param $i i32) - ;; CHECK-NEXT: (local $x (ref $1)) - ;; CHECK-NEXT: (local $y (ref $4)) + ;; CHECK-NEXT: (local $x (ref (exact $1))) + ;; CHECK-NEXT: (local $y (ref (exact $4))) ;; CHECK-NEXT: (local $z (ref func)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $i32) @@ -246,7 +246,7 @@ ) ;; CHECK: (func $multiple-iterations-refinalize-call-ref (type $0) - ;; CHECK-NEXT: (local $f (ref $ret-i31)) + ;; CHECK-NEXT: (local $f (ref (exact $ret-i31))) ;; CHECK-NEXT: (local $x i31ref) ;; CHECK-NEXT: (local.set $f ;; CHECK-NEXT: (ref.func $ret-i31) @@ -332,7 +332,7 @@ ) ;; CHECK: (func $uses-default (type $2) (param $i i32) - ;; CHECK-NEXT: (local $x (ref null $2)) + ;; CHECK-NEXT: (local $x (ref null (exact $2))) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $i) ;; CHECK-NEXT: (then @@ -362,13 +362,13 @@ ) ;; CHECK: (func $unreachables (type $3) (result funcref) - ;; CHECK-NEXT: (local $temp (ref $3)) + ;; CHECK-NEXT: (local $temp (ref (exact $3))) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (ref.func $unreachables) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $3)) + ;; CHECK-NEXT: (block (result (ref (exact $3))) ;; CHECK-NEXT: (local.tee $temp ;; CHECK-NEXT: (ref.func $unreachables) ;; CHECK-NEXT: ) @@ -397,7 +397,7 @@ ) ;; CHECK: (func $incompatible-sets (type $1) (result i32) - ;; CHECK-NEXT: (local $temp (ref null $1)) + ;; CHECK-NEXT: (local $temp (ref null (exact $1))) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (ref.func $incompatible-sets) ;; CHECK-NEXT: ) @@ -435,7 +435,7 @@ ) ;; CHECK: (func $become-non-nullable (type $0) - ;; CHECK-NEXT: (local $x (ref $0)) + ;; CHECK-NEXT: (local $x (ref (exact $0))) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $become-non-nullable) ;; CHECK-NEXT: ) @@ -454,7 +454,7 @@ ) ;; CHECK: (func $already-non-nullable (type $0) - ;; CHECK-NEXT: (local $x (ref $0)) + ;; CHECK-NEXT: (local $x (ref (exact $0))) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $already-non-nullable) ;; CHECK-NEXT: ) @@ -473,7 +473,7 @@ ) ;; CHECK: (func $cannot-become-non-nullable (type $0) - ;; CHECK-NEXT: (local $x (ref null $0)) + ;; CHECK-NEXT: (local $x (ref null (exact $0))) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then @@ -505,7 +505,7 @@ ) ;; CHECK: (func $cannot-become-non-nullable-block (type $0) - ;; CHECK-NEXT: (local $x (ref null $0)) + ;; CHECK-NEXT: (local $x (ref null (exact $0))) ;; CHECK-NEXT: (block $name ;; CHECK-NEXT: (br_if $name ;; CHECK-NEXT: (i32.const 1) @@ -536,7 +536,7 @@ ) ;; CHECK: (func $become-non-nullable-block-unnamed (type $0) - ;; CHECK-NEXT: (local $x (ref $0)) + ;; CHECK-NEXT: (local $x (ref (exact $0))) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $become-non-nullable) diff --git a/test/lit/passes/monomorphize-benefit.wast b/test/lit/passes/monomorphize-benefit.wast index 682fa686da4..a5c5e689f39 100644 --- a/test/lit/passes/monomorphize-benefit.wast +++ b/test/lit/passes/monomorphize-benefit.wast @@ -1523,7 +1523,7 @@ ;; ZERO___-NEXT: ) ;; ZERO___: (func $target-long_7 (type $5) (param $0 i32) (result (ref $A)) -;; ZERO___-NEXT: (local $1 (ref $A)) +;; ZERO___-NEXT: (local $1 (ref (exact $A))) ;; ZERO___-NEXT: (local.set $1 ;; ZERO___-NEXT: (struct.new $A ;; ZERO___-NEXT: (local.get $0) @@ -1548,7 +1548,7 @@ ;; ZERO___-NEXT: ) ;; ZERO___: (func $target-long_9 (type $7) (result (ref $A)) -;; ZERO___-NEXT: (local $0 (ref $A)) +;; ZERO___-NEXT: (local $0 (ref (exact $A))) ;; ZERO___-NEXT: (local.set $0 ;; ZERO___-NEXT: (struct.new $A ;; ZERO___-NEXT: (i32.const 42) diff --git a/test/lit/passes/opt_flatten.wast b/test/lit/passes/opt_flatten.wast index d37df0155b9..1ca2ce95b70 100644 --- a/test/lit/passes/opt_flatten.wast +++ b/test/lit/passes/opt_flatten.wast @@ -9,8 +9,8 @@ (export "foo" (func $foo)) ;; CHECK: (func $foo (type $0) (result funcref) ;; CHECK-NEXT: (local $0 funcref) - ;; CHECK-NEXT: (local $1 (ref $0)) - ;; CHECK-NEXT: (local $2 (ref $0)) + ;; CHECK-NEXT: (local $1 (ref (exact $0))) + ;; CHECK-NEXT: (local $2 (ref (exact $0))) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (local.set $0 diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index c930f2fc19a..56826da24a3 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -364,10 +364,10 @@ ;; TNH: (func $select.arm.null.effects (type $void) ;; TNH-NEXT: (local $temp i32) - ;; TNH-NEXT: (local $1 (ref $struct)) - ;; TNH-NEXT: (local $2 (ref $struct)) + ;; TNH-NEXT: (local $1 (ref (exact $struct))) + ;; TNH-NEXT: (local $2 (ref (exact $struct))) ;; TNH-NEXT: (struct.set $struct 0 - ;; TNH-NEXT: (block (result (ref $struct)) + ;; TNH-NEXT: (block (result (ref (exact $struct))) ;; TNH-NEXT: (local.set $1 ;; TNH-NEXT: (struct.new $struct ;; TNH-NEXT: (local.tee $temp @@ -393,7 +393,7 @@ ;; TNH-NEXT: (i32.const 1) ;; TNH-NEXT: ) ;; TNH-NEXT: (struct.set $struct 0 - ;; TNH-NEXT: (block (result (ref $struct)) + ;; TNH-NEXT: (block (result (ref (exact $struct))) ;; TNH-NEXT: (drop ;; TNH-NEXT: (block (result nullref) ;; TNH-NEXT: (local.set $temp @@ -402,7 +402,7 @@ ;; TNH-NEXT: (ref.null none) ;; TNH-NEXT: ) ;; TNH-NEXT: ) - ;; TNH-NEXT: (block (result (ref $struct)) + ;; TNH-NEXT: (block (result (ref (exact $struct))) ;; TNH-NEXT: (local.set $2 ;; TNH-NEXT: (struct.new $struct ;; TNH-NEXT: (local.tee $temp @@ -422,7 +422,7 @@ ;; NO_TNH: (func $select.arm.null.effects (type $void) ;; NO_TNH-NEXT: (local $temp i32) ;; NO_TNH-NEXT: (struct.set $struct 0 - ;; NO_TNH-NEXT: (select (result (ref null $struct)) + ;; NO_TNH-NEXT: (select (result (ref null (exact $struct))) ;; NO_TNH-NEXT: (struct.new $struct ;; NO_TNH-NEXT: (local.tee $temp ;; NO_TNH-NEXT: (i32.const 1) @@ -439,7 +439,7 @@ ;; NO_TNH-NEXT: (i32.const 1) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (struct.set $struct 0 - ;; NO_TNH-NEXT: (select (result (ref null $struct)) + ;; NO_TNH-NEXT: (select (result (ref null (exact $struct))) ;; NO_TNH-NEXT: (block (result nullref) ;; NO_TNH-NEXT: (local.set $temp ;; NO_TNH-NEXT: (i32.const 2) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 38715fb747a..a571dfa6138 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -2861,7 +2861,7 @@ ;; CHECK: (func $refinalize.select.arm (type $void) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $void1)) + ;; CHECK-NEXT: (block (result (ref (exact $void1))) ;; CHECK-NEXT: (ref.func $func.arm.1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2883,7 +2883,7 @@ ;; CHECK: (func $refinalize.select.arm.flip (type $5) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $void2)) + ;; CHECK-NEXT: (block (result (ref (exact $void2))) ;; CHECK-NEXT: (ref.func $func.arm.2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3026,12 +3026,15 @@ ;; CHECK: (func $ref.test-fallthrough (type $5) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.test (ref $B) - ;; CHECK-NEXT: (local.tee $A - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -3051,9 +3054,7 @@ ;; CHECK-NEXT: ) (func $ref.test-fallthrough (local $A (ref $A)) - ;; The test will fail, but this pass does not have exact type info, so it - ;; thinks it can succeed and nothing happens here (GUFA can optimize this, - ;; however). + ;; The test will fail, and because we have exact type info, we can optimize. (drop (ref.test (ref $B) (local.tee $A @@ -3203,7 +3204,7 @@ ;; CHECK: (func $struct.new (type $5) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $struct.new) @@ -3288,7 +3289,7 @@ ;; CHECK: (func $array.new (type $5) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $array.new) @@ -3359,7 +3360,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $array.new_fixed) @@ -3386,7 +3387,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $array.new_fixed) @@ -3469,7 +3470,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) @@ -3483,7 +3484,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) @@ -3497,7 +3498,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $array.new_fixed) @@ -3585,7 +3586,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) @@ -3599,7 +3600,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) @@ -3613,7 +3614,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (block (result (ref (exact $array))) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (local.set $x diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast index fc4cc7c2ee9..2e033162139 100644 --- a/test/lit/passes/precompute-gc.wast +++ b/test/lit/passes/precompute-gc.wast @@ -872,9 +872,9 @@ ) ;; CHECK: (func $br_on_cast-on-creation (type $16) (result (ref $empty)) - ;; CHECK-NEXT: (block $label (result (ref $empty)) + ;; CHECK-NEXT: (block $label (result (ref (exact $empty))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label (ref $empty) (ref $empty) + ;; CHECK-NEXT: (br_on_cast $label (ref (exact $empty)) (ref (exact $empty)) ;; CHECK-NEXT: (struct.new_default $empty) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/precompute-partial.wast b/test/lit/passes/precompute-partial.wast index 5d530e23e25..d3b4b2d3d41 100644 --- a/test/lit/passes/precompute-partial.wast +++ b/test/lit/passes/precompute-partial.wast @@ -729,7 +729,7 @@ )) ;; CHECK: (func $test-expanded (type $3) (param $x i32) (result funcref) - ;; CHECK-NEXT: (select (result (ref $2)) + ;; CHECK-NEXT: (select (result (ref (exact $2))) ;; CHECK-NEXT: (ref.func $A$func) ;; CHECK-NEXT: (ref.func $B$func) ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 268bbde2092..01f24852416 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -19,7 +19,7 @@ ;; CHECK: (global $struct (ref $struct) (struct.new_default $struct)) (global $struct (ref $struct) (struct.new $struct)) - ;; CHECK: (func $br_on-if (type $9) (param $0 (ref struct)) + ;; CHECK: (func $br_on-if (type $10) (param $0 (ref struct)) ;; CHECK-NEXT: (block $label ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (select (result (ref struct)) @@ -54,7 +54,7 @@ ) ) - ;; CHECK: (func $br_on_cast (type $5) (result (ref $struct)) + ;; CHECK: (func $br_on_cast (type $4) (param $0 (ref $struct)) (result (ref $struct)) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (block $block (result (ref $struct)) ;; CHECK-NEXT: (drop @@ -72,13 +72,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast $block (ref $struct) (ref $substruct) - ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $br_on_cast (result (ref $struct)) + (func $br_on_cast (param (ref $struct)) (result (ref $struct)) (local $struct (ref null $struct)) (block $block (result (ref $struct)) (drop @@ -98,20 +98,20 @@ (drop ;; This cast cannot be optimized at all. (br_on_cast $block anyref (ref $substruct) - (struct.new $struct) + (local.get 0) ) ) (unreachable) ) ) - ;; CHECK: (func $br_on_cast-fallthrough (type $5) (result (ref $struct)) + ;; CHECK: (func $br_on_cast-fallthrough (type $4) (param $0 (ref $struct)) (result (ref $struct)) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (local $any anyref) ;; CHECK-NEXT: (block $block (result (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br $block - ;; CHECK-NEXT: (ref.cast (ref $struct) + ;; CHECK-NEXT: (ref.cast (ref (exact $struct)) ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) @@ -133,14 +133,14 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast $block anyref (ref $substruct) ;; CHECK-NEXT: (local.tee $any - ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $br_on_cast-fallthrough (result (ref $struct)) + (func $br_on_cast-fallthrough (param (ref $struct)) (result (ref $struct)) ;; Same as above, but now the type information comes from fallthrough values. (local $struct (ref null $struct)) (local $any anyref) @@ -161,14 +161,14 @@ ;; This cannot be optimized, but at least it still doesn't need an ;; additional cast. (br_on_cast $block anyref (ref $substruct) - (local.tee $any (struct.new $struct)) + (local.tee $any (local.get 0)) ) ) (unreachable) ) ) - ;; CHECK: (func $nested_br_on_cast (type $10) (result i31ref) + ;; CHECK: (func $nested_br_on_cast (type $11) (result i31ref) ;; CHECK-NEXT: (block $label$1 (result (ref i31)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br $label$1 @@ -196,7 +196,7 @@ ) ) - ;; CHECK: (func $br_on_cast_unrelated (type $6) (result (ref null $struct)) + ;; CHECK: (func $br_on_cast_unrelated (type $5) (result (ref null $struct)) ;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2)) ;; CHECK-NEXT: (block $block (result nullref) ;; CHECK-NEXT: (drop @@ -250,7 +250,7 @@ ) ) - ;; CHECK: (func $br_on_cast_unrelated-fallthrough (type $6) (result (ref null $struct)) + ;; CHECK: (func $br_on_cast_unrelated-fallthrough (type $5) (result (ref null $struct)) ;; CHECK-NEXT: (local $any anyref) ;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2)) ;; CHECK-NEXT: (block $block (result nullref) @@ -316,7 +316,7 @@ ) ) - ;; CHECK: (func $br_on_cast_fail (type $3) (result anyref) + ;; CHECK: (func $br_on_cast_fail (type $6) (param $0 (ref $struct)) (result anyref) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (block $block (result (ref null $struct)) ;; CHECK-NEXT: (drop @@ -329,13 +329,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast_fail $block (ref $struct) (ref $substruct) - ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $br_on_cast_fail (result anyref) + (func $br_on_cast_fail (param (ref $struct)) (result anyref) (local $struct (ref null $struct)) (block $block (result anyref) (drop @@ -356,19 +356,19 @@ (drop ;; This cast cannot be optimized at all. (br_on_cast_fail $block anyref (ref $substruct) - (struct.new $struct) + (local.get 0) ) ) (unreachable) ) ) - ;; CHECK: (func $br_on_cast_fail-fallthrough (type $3) (result anyref) + ;; CHECK: (func $br_on_cast_fail-fallthrough (type $6) (param $0 (ref $struct)) (result anyref) ;; CHECK-NEXT: (local $any anyref) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (block $block (result anyref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $struct) + ;; CHECK-NEXT: (ref.cast (ref (exact $struct)) ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) @@ -384,14 +384,14 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast_fail $block anyref (ref $substruct) ;; CHECK-NEXT: (local.tee $any - ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $br_on_cast_fail-fallthrough (result anyref) + (func $br_on_cast_fail-fallthrough (param (ref $struct)) (result anyref) ;; Same as above, but now the type information comes from fallthrough values. (local $any anyref) (local $struct (ref null $struct)) @@ -412,14 +412,14 @@ (drop ;; This cast cannot be optimized at all. (br_on_cast_fail $block anyref (ref $substruct) - (local.tee $any (struct.new $struct)) + (local.tee $any (local.get 0)) ) ) (unreachable) ) ) - ;; CHECK: (func $br_on_cast_fail_unrelated (type $3) (result anyref) + ;; CHECK: (func $br_on_cast_fail_unrelated (type $7) (result anyref) ;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2)) ;; CHECK-NEXT: (block $block (result (ref null $struct2)) ;; CHECK-NEXT: (drop @@ -481,7 +481,7 @@ ) ) - ;; CHECK: (func $br_on_cast_fail_unrelated-fallthrough (type $3) (result anyref) + ;; CHECK: (func $br_on_cast_fail_unrelated-fallthrough (type $7) (result anyref) ;; CHECK-NEXT: (local $any anyref) ;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2)) ;; CHECK-NEXT: (block $block (result anyref) @@ -557,7 +557,7 @@ ) ) - ;; CHECK: (func $br_on_cast_fail_unrelated-fallthrough-non-null (type $11) (result (ref any)) + ;; CHECK: (func $br_on_cast_fail_unrelated-fallthrough-non-null (type $12) (result (ref any)) ;; CHECK-NEXT: (local $any anyref) ;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2)) ;; CHECK-NEXT: (block $block (result (ref any)) @@ -605,7 +605,7 @@ ) ) - ;; CHECK: (func $br_on_cast-unreachable (type $7) (param $i31ref i31ref) (result anyref) + ;; CHECK: (func $br_on_cast-unreachable (type $8) (param $i31ref i31ref) (result anyref) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -661,7 +661,7 @@ ) ) - ;; CHECK: (func $fallthrough-unreachable (type $7) (param $0 i31ref) (result anyref) + ;; CHECK: (func $fallthrough-unreachable (type $8) (param $0 i31ref) (result anyref) ;; CHECK-NEXT: (block $outer ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) @@ -700,7 +700,7 @@ ) ) - ;; CHECK: (func $casts-are-costly (type $8) (param $x i32) + ;; CHECK: (func $casts-are-costly (type $9) (param $x i32) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (if (result i32) @@ -845,9 +845,9 @@ ) ) - ;; CHECK: (func $allocations-are-costly (type $8) (param $x i32) + ;; CHECK: (func $allocations-are-costly (type $9) (param $x i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result (ref null $struct)) + ;; CHECK-NEXT: (if (result (ref null (exact $struct))) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (struct.new_default $struct) @@ -893,7 +893,7 @@ ) ) - ;; CHECK: (func $threading (type $12) (param $x anyref) + ;; CHECK: (func $threading (type $13) (param $x anyref) ;; CHECK-NEXT: (block $outer ;; CHECK-NEXT: (block $inner ;; CHECK-NEXT: (drop @@ -917,7 +917,7 @@ ) ) - ;; CHECK: (func $test (type $13) (param $x (ref any)) + ;; CHECK: (func $test (type $14) (param $x (ref any)) ;; CHECK-NEXT: (local $temp anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref $struct-nn)) @@ -969,7 +969,7 @@ ) ) - ;; CHECK: (func $select-refinalize (type $14) (param $param (ref $struct)) (result (ref struct)) + ;; CHECK: (func $select-refinalize (type $15) (param $param (ref $struct)) (result (ref struct)) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (global.get $struct) diff --git a/test/lit/passes/roundtrip-gc.wast b/test/lit/passes/roundtrip-gc.wast index d2c32542c58..805d477b8a2 100644 --- a/test/lit/passes/roundtrip-gc.wast +++ b/test/lit/passes/roundtrip-gc.wast @@ -6,9 +6,9 @@ ;; CHECK: (export "export" (func $test)) (export "export" (func $test)) ;; CHECK: (func $test (type $1) - ;; CHECK-NEXT: (local $scratch (ref $\7bi32\7d)) + ;; CHECK-NEXT: (local $scratch (ref (exact $\7bi32\7d))) ;; CHECK-NEXT: (call $help - ;; CHECK-NEXT: (block (result (ref $\7bi32\7d)) + ;; CHECK-NEXT: (block (result (ref (exact $\7bi32\7d))) ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (struct.new_default $\7bi32\7d) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/signature-refining-isorecursive.wast b/test/lit/passes/signature-refining-isorecursive.wast index 1eab7b6af52..2dcf90299d6 100644 --- a/test/lit/passes/signature-refining-isorecursive.wast +++ b/test/lit/passes/signature-refining-isorecursive.wast @@ -4,12 +4,12 @@ (module ;; The signature should be refined to a single self-referential type. - ;; CHECK: (type $refined (func (param (ref $refined)) (result (ref $refined)))) - (type $refined (func (param (ref $refined)) (result (ref $refined)))) + ;; CHECK: (type $refined (func (param (ref (exact $refined))) (result (ref (exact $refined))))) + (type $refined (func (param (ref (exact $refined))) (result (ref (exact $refined))))) ;; CHECK: (elem declare func $foo) - ;; CHECK: (func $foo (type $refined) (param $0 (ref $refined)) (result (ref $refined)) + ;; CHECK: (func $foo (type $refined) (param $0 (ref (exact $refined))) (result (ref (exact $refined))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $foo ;; CHECK-NEXT: (ref.func $foo) @@ -31,16 +31,16 @@ ;; The signatures should be refined to a pair of mutually self-referential types. ;; CHECK: (rec - ;; CHECK-NEXT: (type $1 (func (param f32 (ref $1)) (result (ref $0)))) + ;; CHECK-NEXT: (type $1 (func (param f32 (ref (exact $1))) (result (ref (exact $0))))) - ;; CHECK: (type $0 (func (param i32 (ref $0)) (result (ref $1)))) + ;; CHECK: (type $0 (func (param i32 (ref (exact $0))) (result (ref (exact $1))))) (type $0 (func (param i32 funcref) (result funcref))) (type $1 (func (param f32 funcref) (result funcref))) ;; CHECK: (elem declare func $bar $foo) - ;; CHECK: (func $foo (type $0) (param $0 i32) (param $1 (ref $0)) (result (ref $1)) + ;; CHECK: (func $foo (type $0) (param $0 i32) (param $1 (ref (exact $0))) (result (ref (exact $1))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $foo ;; CHECK-NEXT: (i32.const 0) @@ -59,7 +59,7 @@ (ref.func $bar) ) - ;; CHECK: (func $bar (type $1) (param $0 f32) (param $1 (ref $1)) (result (ref $0)) + ;; CHECK: (func $bar (type $1) (param $0 f32) (param $1 (ref (exact $1))) (result (ref (exact $0))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $bar ;; CHECK-NEXT: (f32.const 0) @@ -85,16 +85,16 @@ (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $1 (func (param (ref $0)))) + ;; CHECK-NEXT: (type $1 (func (param (ref (exact $0))))) - ;; CHECK: (type $0 (func (param (ref $0)))) + ;; CHECK: (type $0 (func (param (ref (exact $0))))) (type $0 (func (param funcref))) (type $1 (func (param funcref))) ) ;; CHECK: (elem declare func $foo) - ;; CHECK: (func $foo (type $0) (param $0 (ref $0)) + ;; CHECK: (func $foo (type $0) (param $0 (ref (exact $0))) ;; CHECK-NEXT: (call $foo ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: ) @@ -105,7 +105,7 @@ ) ) - ;; CHECK: (func $bar (type $1) (param $0 (ref $0)) + ;; CHECK: (func $bar (type $1) (param $0 (ref (exact $0))) ;; CHECK-NEXT: (call $bar ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: ) @@ -122,11 +122,11 @@ ;; another type that refers to them. ;; CHECK: (rec - ;; CHECK-NEXT: (type $2 (func (param (ref $0)) (result (ref $1)))) + ;; CHECK-NEXT: (type $2 (func (param (ref (exact $0))) (result (ref (exact $1))))) - ;; CHECK: (type $1 (func (param f32 (ref $1)) (result (ref $0)))) + ;; CHECK: (type $1 (func (param f32 (ref (exact $1))) (result (ref (exact $0))))) - ;; CHECK: (type $0 (func (param i32 (ref $0)) (result (ref $1)))) + ;; CHECK: (type $0 (func (param i32 (ref (exact $0))) (result (ref (exact $1))))) (type $0 (func (param i32 funcref) (result funcref))) (type $1 (func (param f32 funcref) (result funcref))) @@ -136,7 +136,7 @@ ;; CHECK: (elem declare func $bar $foo) - ;; CHECK: (func $foo (type $0) (param $0 i32) (param $1 (ref $0)) (result (ref $1)) + ;; CHECK: (func $foo (type $0) (param $0 i32) (param $1 (ref (exact $0))) (result (ref (exact $1))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $foo ;; CHECK-NEXT: (i32.const 0) @@ -155,7 +155,7 @@ (ref.func $bar) ) - ;; CHECK: (func $baz (type $2) (param $0 (ref $0)) (result (ref $1)) + ;; CHECK: (func $baz (type $2) (param $0 (ref (exact $0))) (result (ref (exact $1))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $quux ;; CHECK-NEXT: (ref.func $foo) @@ -172,7 +172,7 @@ (ref.func $bar) ) - ;; CHECK: (func $bar (type $1) (param $0 f32) (param $1 (ref $1)) (result (ref $0)) + ;; CHECK: (func $bar (type $1) (param $0 f32) (param $1 (ref (exact $1))) (result (ref (exact $0))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $bar ;; CHECK-NEXT: (f32.const 0) @@ -191,7 +191,7 @@ (ref.func $foo) ) - ;; CHECK: (func $quux (type $2) (param $0 (ref $0)) (result (ref $1)) + ;; CHECK: (func $quux (type $2) (param $0 (ref (exact $0))) (result (ref (exact $1))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $baz ;; CHECK-NEXT: (ref.func $foo) diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast index 471d95326f9..1f1b3f3d47d 100644 --- a/test/lit/passes/signature-refining.wast +++ b/test/lit/passes/signature-refining.wast @@ -13,10 +13,10 @@ ;; CHECK: (type $1 (func)) - ;; CHECK: (type $sig (sub (func (param (ref $struct))))) + ;; CHECK: (type $sig (sub (func (param (ref (exact $struct)))))) (type $sig (sub (func (param anyref)))) - ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK: (func $func (type $sig) (param $x (ref (exact $struct))) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -42,12 +42,12 @@ ;; CHECK: (type $1 (func)) - ;; CHECK: (type $sig (sub (func (param (ref $struct))))) + ;; CHECK: (type $sig (sub (func (param (ref (exact $struct)))))) (type $sig (sub (func (param anyref)))) ;; CHECK: (elem declare func $func) - ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK: (func $func (type $sig) (param $x (ref (exact $struct))) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -173,17 +173,17 @@ ;; CHECK: (type $1 (func)) - ;; CHECK: (type $sig (sub (func (param (ref $struct))))) + ;; CHECK: (type $sig (sub (func (param (ref (exact $struct)))))) (type $sig (sub (func (param anyref)))) (type $struct (struct)) - ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) + ;; CHECK: (func $func-1 (type $sig) (param $x (ref (exact $struct))) ;; CHECK-NEXT: ) (func $func-1 (type $sig) (param $x anyref) ) - ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) + ;; CHECK: (func $func-2 (type $sig) (param $x (ref (exact $struct))) ;; CHECK-NEXT: ) (func $func-2 (type $sig) (param $x anyref) ) @@ -209,14 +209,14 @@ ;; CHECK: (type $1 (func)) - ;; CHECK: (type $sig (sub (func (param (ref $struct) (ref $sig))))) + ;; CHECK: (type $sig (sub (func (param (ref (exact $struct)) (ref (exact $sig)))))) (type $sig (sub (func (param anyref funcref)))) (type $struct (sub (struct (field (ref $sig))))) ;; CHECK: (elem declare func $func) - ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) (param $f (ref $sig)) + ;; CHECK: (func $func (type $sig) (param $x (ref (exact $struct))) (param $f (ref (exact $sig))) ;; CHECK-NEXT: (local $temp (ref null $sig)) ;; CHECK-NEXT: (local $3 funcref) ;; CHECK-NEXT: (local.set $3 @@ -275,12 +275,12 @@ ;; CHECK: (type $1 (func)) - ;; CHECK: (type $sig (sub (func (param (ref $struct))))) + ;; CHECK: (type $sig (sub (func (param (ref (exact $struct)))))) (type $sig (sub (func (param anyref)))) ;; CHECK: (elem declare func $func) - ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK: (func $func (type $sig) (param $x (ref (exact $struct))) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -361,7 +361,7 @@ ;; CHECK: (type $1 (func)) - ;; CHECK: (type $sig-2 (sub (func (param eqref (ref $struct))))) + ;; CHECK: (type $sig-2 (sub (func (param eqref (ref (exact $struct)))))) ;; CHECK: (type $sig-1 (sub (func (param structref anyref)))) (type $sig-1 (sub (func (param anyref) (param anyref)))) @@ -375,7 +375,7 @@ (func $func-1 (type $sig-1) (param $x anyref) (param $y anyref) ) - ;; CHECK: (func $func-2 (type $sig-2) (param $x eqref) (param $y (ref $struct)) + ;; CHECK: (func $func-2 (type $sig-2) (param $x eqref) (param $y (ref (exact $struct))) ;; CHECK-NEXT: ) (func $func-2 (type $sig-2) (param $x anyref) (param $y anyref) ) @@ -468,12 +468,12 @@ ;; CHECK: (type $1 (func)) - ;; CHECK: (type $sig (sub (func (param (ref null $struct))))) + ;; CHECK: (type $sig (sub (func (param (ref null (exact $struct)))))) (type $sig (sub (func (param anyref)))) (type $struct (struct)) - ;; CHECK: (func $func (type $sig) (param $x (ref null $struct)) + ;; CHECK: (func $func (type $sig) (param $x (ref null (exact $struct))) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) @@ -510,7 +510,7 @@ ;; This signature has a single function using it, which returns a more ;; refined type, and we can refine to that. - ;; CHECK: (type $sig-can-refine (sub (func (result (ref $struct))))) + ;; CHECK: (type $sig-can-refine (sub (func (result (ref (exact $struct)))))) (type $sig-can-refine (sub (func (result anyref)))) ;; Also a single function, but no refinement is possible. @@ -522,7 +522,7 @@ ;; CHECK: (elem declare func $func-can-refine $func-cannot-refine) - ;; CHECK: (func $func-can-refine (type $sig-can-refine) (result (ref $struct)) + ;; CHECK: (func $func-can-refine (type $sig-can-refine) (result (ref (exact $struct))) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) (func $func-can-refine (type $sig-can-refine) (result anyref) @@ -553,7 +553,7 @@ ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result (ref $struct)) + ;; CHECK-NEXT: (if (result (ref (exact $struct))) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $func-can-refine) @@ -564,7 +564,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result (ref $struct)) + ;; CHECK-NEXT: (if (result (ref (exact $struct))) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call_ref $sig-can-refine @@ -616,31 +616,31 @@ ;; This signature has multiple functions using it, and some of them have nulls ;; which should be updated when we refine. - ;; CHECK: (type $sig (sub (func (result (ref null $struct))))) + ;; CHECK: (type $sig (sub (func (result (ref null (exact $struct)))))) (type $sig (sub (func (result anyref)))) - ;; CHECK: (func $func-1 (type $sig) (result (ref null $struct)) + ;; CHECK: (func $func-1 (type $sig) (result (ref null (exact $struct))) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) (func $func-1 (type $sig) (result anyref) (struct.new $struct) ) - ;; CHECK: (func $func-2 (type $sig) (result (ref null $struct)) + ;; CHECK: (func $func-2 (type $sig) (result (ref null (exact $struct))) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) (func $func-2 (type $sig) (result anyref) (ref.null any) ) - ;; CHECK: (func $func-3 (type $sig) (result (ref null $struct)) + ;; CHECK: (func $func-3 (type $sig) (result (ref null (exact $struct))) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) (func $func-3 (type $sig) (result anyref) (ref.null eq) ) - ;; CHECK: (func $func-4 (type $sig) (result (ref null $struct)) + ;; CHECK: (func $func-4 (type $sig) (result (ref null (exact $struct))) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then @@ -858,7 +858,7 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $0 (func (param (ref $"[i8]")))) + ;; CHECK-NEXT: (type $0 (func (param (ref (exact $"[i8]"))))) ;; CHECK: (type $"[i8]" (array i8)) (type $"[i8]" (array i8)) @@ -876,7 +876,7 @@ ) ) - ;; CHECK: (func $1 (type $0) (param $2 (ref $"[i8]")) + ;; CHECK: (func $1 (type $0) (param $2 (ref (exact $"[i8]"))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref none) ;; CHECK-NEXT: (local.get $2) @@ -907,9 +907,9 @@ ;; CHECK: (type $C (sub $B (struct))) (type $C (sub $B (struct))) - ;; CHECK: (type $return_A_2 (func (result (ref $C)))) + ;; CHECK: (type $return_A_2 (func (result (ref (exact $C))))) - ;; CHECK: (type $return_A (func (result (ref $B)))) + ;; CHECK: (type $return_A (func (result (ref (exact $B))))) (type $return_A (func (result (ref null $A)))) (type $return_A_2 (func (result (ref null $A)))) @@ -919,9 +919,9 @@ ;; CHECK: (type $6 (func (param funcref) (result (ref null $A)))) - ;; CHECK: (type $7 (func (param funcref) (result (ref $B)))) + ;; CHECK: (type $7 (func (param funcref) (result (ref (exact $B))))) - ;; CHECK: (type $8 (func (param funcref) (result (ref $C)))) + ;; CHECK: (type $8 (func (param funcref) (result (ref (exact $C))))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects (type $6) (param funcref) (result (ref null $A)))) (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects @@ -929,9 +929,9 @@ (result (ref null $A)) )) - ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects_4 (type $7) (param funcref) (result (ref $B)))) + ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects_4 (type $7) (param funcref) (result (ref (exact $B))))) - ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects_5 (type $8) (param funcref) (result (ref $C)))) + ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects_5 (type $8) (param funcref) (result (ref (exact $C))))) ;; CHECK: (elem declare func $other $other2) @@ -976,14 +976,14 @@ ) ) - ;; CHECK: (func $other (type $return_A) (result (ref $B)) + ;; CHECK: (func $other (type $return_A) (result (ref (exact $B))) ;; CHECK-NEXT: (struct.new_default $B) ;; CHECK-NEXT: ) (func $other (type $return_A) (result (ref null $A)) (struct.new $B) ;; this will allow this function's result to be refined to $B ) - ;; CHECK: (func $other2 (type $return_A_2) (result (ref $C)) + ;; CHECK: (func $other2 (type $return_A_2) (result (ref (exact $C))) ;; CHECK-NEXT: (struct.new_default $C) ;; CHECK-NEXT: ) (func $other2 (type $return_A_2) (result (ref null $A)) @@ -1055,7 +1055,7 @@ ;; CHECK-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) - ;; CHECK: (type $1 (func (param (ref $B)))) + ;; CHECK: (type $1 (func (param (ref (exact $B))))) ;; CHECK: (type $B (sub $A (struct))) (type $B (sub $A (struct))) @@ -1092,7 +1092,7 @@ ) ) - ;; CHECK: (func $target (type $1) (param $x (ref $B)) + ;; CHECK: (func $target (type $1) (param $x (ref (exact $B))) ;; CHECK-NEXT: ) (func $target (param $x (ref $A)) ;; The two calls above both send $B, so we can refine the parameter to $B. diff --git a/test/lit/passes/simplify-globals-gc.wast b/test/lit/passes/simplify-globals-gc.wast index a24f769d258..71a36ae79d8 100644 --- a/test/lit/passes/simplify-globals-gc.wast +++ b/test/lit/passes/simplify-globals-gc.wast @@ -14,7 +14,7 @@ ;; CHECK: (func $func (type $A) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast (ref $A) + ;; CHECK-NEXT: (ref.cast (ref (exact $A)) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast index b216cede00c..8157fce12fa 100644 --- a/test/lit/passes/type-merging.wast +++ b/test/lit/passes/type-merging.wast @@ -845,25 +845,25 @@ (type $b1 (sub $b (struct (ref null $y)))) ) - ;; CHECK: (type $5 (func (result (ref $b)))) + ;; CHECK: (type $5 (func (param (ref $x)) (result (ref $b)))) - ;; CHECK: (func $test (type $5) (result (ref $b)) - ;; CHECK-NEXT: (local $0 (ref null $a)) + ;; CHECK: (func $test (type $5) (param $x (ref $x)) (result (ref $b)) + ;; CHECK-NEXT: (local $1 (ref null $a)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $y) - ;; CHECK-NEXT: (struct.new_default $x) + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new_default $b1) ;; CHECK-NEXT: ) - (func $test (result (ref $b)) + (func $test (param $x (ref $x)) (result (ref $b)) ;; Use $a to prevent it from being dropped completely. (local (ref null $a)) ;; Cast to prevent $x and $y from being merged. (drop (ref.test (ref $y) - (struct.new_default $x) + (local.get $x) ) ) @@ -1002,7 +1002,7 @@ ;; CHECK: (func $test (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result (ref $subA)) + ;; CHECK-NEXT: (select (result (ref (exact $subA))) ;; CHECK-NEXT: (struct.new_default $subA) ;; CHECK-NEXT: (struct.new_default $subA) ;; CHECK-NEXT: (i32.const 0) diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index c866d80dca2..7e4a5f55479 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -33,7 +33,7 @@ ;; O3O3-NEXT: (type $A (sub (struct))) (type $A (sub (struct (field (mut anyref))))) ;; NRML: (type $B (sub (struct (field (mut anyref))))) - ;; GUFA: (type $B (sub (struct (field (mut (ref null $A)))))) + ;; GUFA: (type $B (sub (struct (field (mut (ref null (exact $A))))))) ;; O3O3: (type $B (sub (struct (field anyref)))) (type $B (sub (struct (field (mut anyref))))) ) @@ -83,7 +83,7 @@ ;; GUFA: (export "work" (func $work)) ;; GUFA: (func $get_from_global (type $2) (param $x i32) (result anyref) - ;; GUFA-NEXT: (if (result (ref null $A)) + ;; GUFA-NEXT: (if (result (ref null (exact $A))) ;; GUFA-NEXT: (local.get $x) ;; GUFA-NEXT: (then ;; GUFA-NEXT: (struct.get $A 0 @@ -188,14 +188,14 @@ ;; GUFA-NEXT: ) ;; GUFA-NEXT: (local.set $b ;; GUFA-NEXT: (struct.new $B - ;; GUFA-NEXT: (ref.cast (ref null $A) + ;; GUFA-NEXT: (ref.cast (ref null (exact $A)) ;; GUFA-NEXT: (local.get $a) ;; GUFA-NEXT: ) ;; GUFA-NEXT: ) ;; GUFA-NEXT: ) ;; GUFA-NEXT: (local.set $b ;; GUFA-NEXT: (struct.new $B - ;; GUFA-NEXT: (ref.cast (ref null $A) + ;; GUFA-NEXT: (ref.cast (ref null (exact $A)) ;; GUFA-NEXT: (call $get_from_global ;; GUFA-NEXT: (local.get $x) ;; GUFA-NEXT: ) @@ -217,7 +217,7 @@ ;; GUFA-NEXT: ) ;; GUFA-NEXT: ) ;; O3O3: (func $work (type $3) (param $0 i32) - ;; O3O3-NEXT: (local $1 (ref $B)) + ;; O3O3-NEXT: (local $1 (ref (exact $B))) ;; O3O3-NEXT: (local.set $1 ;; O3O3-NEXT: (struct.new $B ;; O3O3-NEXT: (if (result anyref) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 622e7422011..ec64c58bebc 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -460,13 +460,13 @@ ;; CHECK: (type $Y (sub $X (struct))) (type $Y (sub $X (struct))) - ;; CHECK: (type $A (sub (struct (field (ref $Y))))) + ;; CHECK: (type $A (sub (struct (field (ref (exact $Y)))))) (type $A (sub (struct (field (ref $X))))) - ;; CHECK: (type $C (sub $A (struct (field (ref $Y))))) + ;; CHECK: (type $C (sub $A (struct (field (ref (exact $Y)))))) (type $C (sub $A (struct (field (ref $X))))) - ;; CHECK: (type $B (sub $A (struct (field (ref $Y))))) + ;; CHECK: (type $B (sub $A (struct (field (ref (exact $Y)))))) (type $B (sub $A (struct (field (ref $X))))) ) @@ -539,19 +539,20 @@ ) (module - ;; CHECK: (type $X (sub (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $X (sub (struct))) (type $X (sub (struct))) - ;; CHECK: (type $Y (sub $X (struct))) + ;; CHECK: (type $Y (sub $X (struct))) (type $Y (sub $X (struct))) - ;; CHECK: (type $A (sub (struct (field (ref $X))))) + ;; CHECK: (type $A (sub (struct (field (ref (exact $X)))))) (type $A (sub (struct (field (ref $X))))) - ;; CHECK: (type $B (sub $A (struct (field (ref $Y))))) + ;; CHECK: (type $B (sub $A (struct (field (ref (exact $X)))))) (type $B (sub $A (struct (field (ref $Y))))) - ;; CHECK: (type $4 (func)) + ;; CHECK: (type $4 (func)) ;; CHECK: (func $foo (type $4) ;; CHECK-NEXT: (local $unused2 (ref null $B)) @@ -824,13 +825,13 @@ ;; CHECK: (type $Leaf2-Inner (sub $Root-Inner (struct))) (type $Leaf2-Inner (sub $Root-Inner (struct ))) - ;; CHECK: (type $Root-Outer (sub (struct (field (ref $Leaf2-Inner))))) + ;; CHECK: (type $Root-Outer (sub (struct (field (ref (exact $Leaf2-Inner)))))) (type $Root-Outer (sub (struct (field (ref $Root-Inner))))) - ;; CHECK: (type $Leaf1-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner))))) + ;; CHECK: (type $Leaf1-Outer (sub $Root-Outer (struct (field (ref (exact $Leaf2-Inner)))))) (type $Leaf1-Outer (sub $Root-Outer (struct (field (ref $Leaf1-Inner))))) - ;; CHECK: (type $Leaf2-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner))))) + ;; CHECK: (type $Leaf2-Outer (sub $Root-Outer (struct (field (ref (exact $Leaf2-Inner)))))) (type $Leaf2-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner))))) ;; CHECK: (type $6 (func (param (ref null $Leaf1-Outer)))) @@ -1138,7 +1139,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (ref $B)))) + ;; CHECK-NEXT: (type $A (struct (field (ref (exact $B))))) (type $A (struct (field (ref struct)))) ;; CHECK: (type $B (struct)) (type $B (struct)) @@ -1148,7 +1149,7 @@ ;; CHECK: (func $0 (type $2) (result (ref $A)) ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (ref.cast (ref (exact $B)) ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (struct.new_default $B) @@ -1532,7 +1533,7 @@ ;; CHECK: (type $3 (func (result (ref null $8)))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $5 (sub (struct (field (ref $3))))) + ;; CHECK-NEXT: (type $5 (sub (struct (field (ref (exact $3)))))) (type $5 (sub (struct (field (ref func))))) ;; CHECK: (type $6 (sub $1 (struct (field (mut (ref null $1)))))) (type $6 (sub $1 (struct (field (mut (ref null $1)))))) @@ -1648,7 +1649,7 @@ ) ;; CHECK: (type $2 (func (param (ref null $A) (ref null $B)))) - ;; CHECK: (type $optimizable (sub (struct (field (ref $2))))) + ;; CHECK: (type $optimizable (sub (struct (field (ref (exact $2)))))) (type $optimizable (sub (struct (field funcref)))) ;; CHECK: (elem declare func $test) diff --git a/test/lit/passes/type-ssa.wast b/test/lit/passes/type-ssa.wast index 599c0504eaa..617c004865c 100644 --- a/test/lit/passes/type-ssa.wast +++ b/test/lit/passes/type-ssa.wast @@ -460,23 +460,26 @@ ;; CHECK: (type $2 (func (result (ref $A)))) ;; CHECK: (func $0 (type $2) (result (ref $A)) - ;; CHECK-NEXT: (block $label (result (ref $A_1)) + ;; CHECK-NEXT: (block $label (result (ref (exact $A_1))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label (ref $A_1) (ref $A_1) - ;; CHECK-NEXT: (struct.new_default $A_1) + ;; CHECK-NEXT: (br_on_cast $label (ref (exact $A_1)) (ref (exact $A_1)) + ;; CHECK-NEXT: (block (result (ref (exact $A_1))) + ;; CHECK-NEXT: (struct.new_default $A_1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $0 (result (ref $A)) - ;; After creating a subtype of $A as the input to the br_on_cast, the cast - ;; must be refinalized so that it validates (otherwise, it would try to cast - ;; to a supertype). + ;; After creating a subtype of $A as the input to the br_on_cast, the block + ;; and cast should be refinalized. (block $label (result (ref $A)) (drop (br_on_cast $label (ref $A) (ref $A) - (struct.new_default $A) + (block (result (ref $A)) + (struct.new_default $A) + ) ) ) (unreachable) diff --git a/test/lit/passes/unsubtyping-casts.wast b/test/lit/passes/unsubtyping-casts.wast index 9852fe4705b..d27629c383d 100644 --- a/test/lit/passes/unsubtyping-casts.wast +++ b/test/lit/passes/unsubtyping-casts.wast @@ -39,20 +39,20 @@ ;; CHECK: (type $bot (sub $mid (struct))) (type $bot (sub $mid (struct))) - ;; CHECK: (type $3 (func)) + ;; CHECK: (type $3 (func (param (ref $top)))) - ;; CHECK: (func $cast (type $3) + ;; CHECK: (func $cast (type $3) (param $top (ref $top)) ;; CHECK-NEXT: (local $l (ref null $top)) ;; CHECK-NEXT: (local.set $l ;; CHECK-NEXT: (struct.new_default $bot) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $mid) - ;; CHECK-NEXT: (struct.new_default $top) + ;; CHECK-NEXT: (local.get $top) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast + (func $cast (param $top (ref $top)) (local $l (ref null $top)) (local.set $l ;; Require $bot <: $top. @@ -62,7 +62,7 @@ ;; Now the cast requires $mid <: $top so that a $bot value appearing in the ;; $top location would still pass the cast to $mid. (ref.cast (ref $mid) - (struct.new $top) + (local.get $top) ) ) ) @@ -77,20 +77,20 @@ ;; CHECK: (type $bot (sub $mid (struct))) (type $bot (sub $mid (struct))) - ;; CHECK: (type $3 (func)) + ;; CHECK: (type $3 (func (param (ref $top)))) - ;; CHECK: (func $cast (type $3) + ;; CHECK: (func $cast (type $3) (param $top (ref $top)) ;; CHECK-NEXT: (local $l (ref null $top)) ;; CHECK-NEXT: (local.set $l ;; CHECK-NEXT: (struct.new_default $bot) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $mid) - ;; CHECK-NEXT: (struct.new_default $top) + ;; CHECK-NEXT: (local.get $top) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast + (func $cast (param $top (ref $top)) (local $l (ref null $top)) (local.set $l ;; Require $bot <: $top. @@ -99,7 +99,7 @@ (drop ;; Same as above, but with a ref.test. (ref.test (ref $mid) - (struct.new $top) + (local.get $top) ) ) ) @@ -114,9 +114,9 @@ ;; CHECK: (type $bot (sub $mid (struct))) (type $bot (sub $mid (struct))) - ;; CHECK: (type $3 (func)) + ;; CHECK: (type $3 (func (param (ref $top)))) - ;; CHECK: (func $cast (type $3) + ;; CHECK: (func $cast (type $3) (param $top (ref $top)) ;; CHECK-NEXT: (local $l (ref null $top)) ;; CHECK-NEXT: (local.set $l ;; CHECK-NEXT: (struct.new_default $bot) @@ -125,14 +125,14 @@ ;; CHECK-NEXT: (block $l (result (ref $mid)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast $l (ref $top) (ref $mid) - ;; CHECK-NEXT: (struct.new_default $top) + ;; CHECK-NEXT: (local.get $top) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast + (func $cast (param $top (ref $top)) (local $l (ref null $top)) (local.set $l ;; Require $bot <: $top. @@ -143,7 +143,7 @@ ;; Same as above, but with a br_on_cast. (drop (br_on_cast $l anyref (ref $mid) - (struct.new $top) + (local.get $top) ) ) (unreachable) @@ -161,9 +161,9 @@ ;; CHECK: (type $bot (sub $mid (struct))) (type $bot (sub $mid (struct))) - ;; CHECK: (type $3 (func)) + ;; CHECK: (type $3 (func (param (ref $top)))) - ;; CHECK: (func $cast (type $3) + ;; CHECK: (func $cast (type $3) (param $top (ref $top)) ;; CHECK-NEXT: (local $l (ref null $top)) ;; CHECK-NEXT: (local.set $l ;; CHECK-NEXT: (struct.new_default $bot) @@ -172,14 +172,14 @@ ;; CHECK-NEXT: (block $l (result (ref $top)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast_fail $l (ref $top) (ref $mid) - ;; CHECK-NEXT: (struct.new_default $top) + ;; CHECK-NEXT: (local.get $top) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast + (func $cast (param $top (ref $top)) (local $l (ref null $top)) (local.set $l ;; Require $bot <: $top. @@ -190,7 +190,7 @@ ;; Same as above, but with a br_on_cast_fail. (drop (br_on_cast_fail $l anyref (ref $mid) - (struct.new $top) + (local.get $top) ) ) (unreachable) @@ -208,25 +208,34 @@ ;; CHECK: (type $bot (sub $mid (func))) (type $bot (sub $mid (func))) + ;; CHECK: (type $3 (func (param (ref $top)))) + ;; CHECK: (table $t 1 1 (ref null $top)) (table $t 1 1 (ref null $top)) - ;; CHECK: (elem declare func $cast) + ;; CHECK: (elem declare func $bot) + + ;; CHECK: (func $bot (type $bot) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $bot (type $bot) + (nop) + ) - ;; CHECK: (func $cast (type $bot) + ;; CHECK: (func $cast (type $3) (param $top (ref $top)) ;; CHECK-NEXT: (local $l (ref null $top)) ;; CHECK-NEXT: (local.set $l - ;; CHECK-NEXT: (ref.func $cast) + ;; CHECK-NEXT: (ref.func $bot) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_indirect $t (type $mid) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast (type $bot) + (func $cast (param $top (ref $top)) (local $l (ref null $top)) (local.set $l ;; Require $bot <: $top. - (ref.func $cast) + (ref.func $bot) ) ;; Same as above, but with a call_indirect (call_indirect $t (type $mid) @@ -501,30 +510,30 @@ (type $botA (sub $midA (struct (ref null $botB)))) ) - ;; CHECK: (type $9 (func)) + ;; CHECK: (type $9 (func (param (ref $topA) (ref $topB) (ref $topC)))) - ;; CHECK: (func $cast (type $9) + ;; CHECK: (func $cast (type $9) (param $topA (ref $topA)) (param $topB (ref $topB)) (param $topC (ref $topC)) ;; CHECK-NEXT: (local $l (ref null $topA)) ;; CHECK-NEXT: (local.set $l ;; CHECK-NEXT: (struct.new_default $botA) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $midA) - ;; CHECK-NEXT: (struct.new_default $topA) + ;; CHECK-NEXT: (local.get $topA) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $midB) - ;; CHECK-NEXT: (struct.new_default $topB) + ;; CHECK-NEXT: (local.get $topB) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $midC) - ;; CHECK-NEXT: (struct.new_default $topC) + ;; CHECK-NEXT: (local.get $topC) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast + (func $cast (param $topA (ref $topA)) (param $topB (ref $topB)) (param $topC (ref $topC)) (local $l (ref null $topA)) (local.set $l ;; Require $botA <: $topA. @@ -535,21 +544,21 @@ ;; the $topA location would still pass the cast to $midA. This will ;; transitively require $botB <: $topB. (ref.cast (ref $midA) - (struct.new_default $topA) + (local.get $topA) ) ) (drop ;; Same as before, but now for the B types. This requires $botC <: $topC, but ;; only after the previous cast has already been analyzed. (ref.cast (ref $midB) - (struct.new_default $topB) + (local.get $topB) ) ) (drop ;; Same as before, but now for the C types. Now no types will able to be ;; optimized. (ref.cast (ref $midC) - (struct.new $topC) + (local.get $topC) ) ) ) diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index 8696e565af5..971fe63da3c 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -229,7 +229,7 @@ ;; CHECK-NEXT: (struct.new_default $sub) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $other (result (ref $opt)) + ;; CHECK-NEXT: (block $other (result (ref (exact $opt))) ;; CHECK-NEXT: (br $other ;; CHECK-NEXT: (struct.new_default $opt) ;; CHECK-NEXT: ) @@ -271,7 +271,7 @@ ;; CHECK: (func $if (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result (ref $sub)) + ;; CHECK-NEXT: (if (result (ref (exact $sub))) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (struct.new_default $sub) @@ -309,7 +309,7 @@ ;; CHECK: (func $loop (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (loop (result (ref $sub)) + ;; CHECK-NEXT: (loop (result (ref (exact $sub))) ;; CHECK-NEXT: (struct.new_default $sub) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -335,9 +335,9 @@ ;; CHECK: (func $loop (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $super (result (ref $sub)) + ;; CHECK-NEXT: (block $super (result (ref (exact $sub))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $sub (result (ref $sub)) + ;; CHECK-NEXT: (block $sub (result (ref (exact $sub))) ;; CHECK-NEXT: (br_table $super $sub ;; CHECK-NEXT: (struct.new_default $sub) ;; CHECK-NEXT: (i32.const 0) @@ -377,9 +377,9 @@ ;; CHECK: (func $br-table (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $super (result (ref $sub)) + ;; CHECK-NEXT: (block $super (result (ref (exact $sub))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $sub (result (ref $sub)) + ;; CHECK-NEXT: (block $sub (result (ref (exact $sub))) ;; CHECK-NEXT: (br_table $sub $super ;; CHECK-NEXT: (struct.new_default $sub) ;; CHECK-NEXT: (i32.const 0) @@ -1067,15 +1067,14 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) - ;; CHECK: (type $sub (sub $super (struct))) (type $sub (sub $super (struct))) - ;; CHECK: (type $2 (func)) + ;; CHECK: (type $1 (func)) - ;; CHECK: (func $br-on-cast (type $2) + ;; CHECK: (func $br-on-cast (type $1) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $l (result (ref $super)) - ;; CHECK-NEXT: (br_on_cast $l (ref $super) (ref $sub) + ;; CHECK-NEXT: (block $l (result (ref (exact $super))) + ;; CHECK-NEXT: (br_on_cast $l (ref (exact $super)) (ref none) ;; CHECK-NEXT: (struct.new_default $super) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1104,8 +1103,8 @@ ;; CHECK: (func $br-on-cast-fail (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $l (result (ref $sub)) - ;; CHECK-NEXT: (br_on_cast_fail $l (ref $sub) (ref none) + ;; CHECK-NEXT: (block $l (result (ref (exact $sub))) + ;; CHECK-NEXT: (br_on_cast_fail $l (ref (exact $sub)) (ref none) ;; CHECK-NEXT: (struct.new_default $sub) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index d1a80c3182b..845a45d166c 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -60,7 +60,6 @@ (type $void_func (func)) (type $struct (sub (struct (field (mut i32))))) (type $3 (func (param i32))) - (type $extendedstruct (sub $struct (struct (field (mut i32)) (field f64)))) (type $int_func (func (result i32))) (import "fuzzing-support" "log-i32" (func $log (type $3) (param i32))) (export "structs" (func $structs)) @@ -99,7 +98,7 @@ ) ) (func $arrays (type $void_func) - (local $0 (ref $bytes)) + (local $0 (ref (exact $bytes))) (local.set $0 (array.new $bytes (i32.const 42) @@ -145,25 +144,15 @@ ) ) (func $br_on_failed_cast-1 (type $void_func) - (local $0 (ref $struct)) + (local $0 (ref (exact $struct))) (local.set $0 (struct.new_default $struct) ) - (drop - (block $any (result (ref null $struct)) - (call $log - (i32.const 1) - ) - (drop - (br_on_cast_fail $any (ref $struct) (ref $extendedstruct) - (local.get $0) - ) - ) - (call $log - (i32.const 999) - ) - (ref.null none) + (block $any + (call $log + (i32.const 1) ) + (br $any) ) ) (func $br_on_failed_cast-2 (type $void_func) @@ -210,8 +199,8 @@ ) ) (func $array-copy (type $void_func) - (local $0 (ref $bytes)) - (local $1 (ref $bytes)) + (local $0 (ref (exact $bytes))) + (local $1 (ref (exact $bytes))) (array.set $bytes (local.tee $1 (array.new_default $bytes diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index 189adadcec1..c11e8332248 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -301,7 +301,7 @@ ) ) (drop - (block $l4 (result (ref null $1)) + (block $l4 (result (ref null (exact $1))) (drop (block $l5 (global.set $global-mut diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 50e2f99ac87..4b942c6d799 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -421,7 +421,8 @@ def test_file_contents(self): for i in range(1, N + 1): fuzz_file = os.path.join(temp_dir.name, f'fuzz-binaryen-{i}.js') - cmd = [shared.V8, '--wasm-staging', fuzz_file] + # Add --fuzzing to allow legacy and standard EH to coexist + cmd = [shared.V8, '--wasm-staging', '--fuzzing', fuzz_file] proc = subprocess.run(cmd, stdout=subprocess.PIPE) # An execution is valid if we exited without error, and if we diff --git a/test/wasm2js/refs.2asm.js b/test/wasm2js/refs.2asm.js index c105b1c1cbf..aa6f92c510d 100644 --- a/test/wasm2js/refs.2asm.js +++ b/test/wasm2js/refs.2asm.js @@ -62,9 +62,9 @@ function asmFunc(imports) { } function named_type_temps() { - var $0 = null, wasm2js__ref_null_$func_0_$0_1 = null, wasm2js__ref_null_$func_0_$1_1 = null, wasm2js_i32$0 = 0; + var $0 = null, wasm2js__ref_null__exact_$func_0__$0_1 = null, wasm2js__ref_null__exact_$func_0__$1_1 = null, wasm2js_i32$0 = 0; $0 = named_type_temps; - return wasm2js__ref_null_$func_0_$0 = null, wasm2js__ref_null_$func_0_$1 = $0 || wasm2js_trap(), wasm2js_i32$0 = 0, wasm2js_i32$0 ? wasm2js__ref_null_$func_0_$0 : wasm2js__ref_null_$func_0_$1; + return wasm2js__ref_null__exact_$func_0__$0 = null, wasm2js__ref_null__exact_$func_0__$1 = $0 || wasm2js_trap(), wasm2js_i32$0 = 0, wasm2js_i32$0 ? wasm2js__ref_null__exact_$func_0__$0 : wasm2js__ref_null__exact_$func_0__$1; } return { From c25a2e40f5a69c13d7634c58d36a9d7707700d52 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 May 2025 10:02:53 -0700 Subject: [PATCH 502/622] [Compilation Hints] Add binary format support for inline hints (#7596) --- src/wasm-binary.h | 6 ++ src/wasm/wasm-binary.cpp | 56 +++++++++++- ...mpilation-hints.wast => inline-hints.wast} | 27 +++++- test/lit/multi-hints.wast | 90 +++++++++++++++++++ 4 files changed, 174 insertions(+), 5 deletions(-) rename test/lit/{compilation-hints.wast => inline-hints.wast} (63%) create mode 100644 test/lit/multi-hints.wast diff --git a/src/wasm-binary.h b/src/wasm-binary.h index c0ef68c03e5..5ee7452060a 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1411,6 +1411,7 @@ class WasmBinaryWriter { std::optional writeCodeAnnotations(); std::optional getBranchHintsBuffer(); + std::optional getInlineHintsBuffer(); // helpers void writeInlineString(std::string_view name); @@ -1692,6 +1693,11 @@ class WasmBinaryReader { size_t branchHintsLen = 0; void readBranchHints(size_t payloadLen); + // Like branch hints, we note where the section is to read it later. + size_t inlineHintsPos = 0; + size_t inlineHintsLen = 0; + void readInlineHints(size_t payloadLen); + Index readMemoryAccess(Address& alignment, Address& offset); std::tuple getMemarg(); MemoryOrder getMemoryOrder(bool isRMW = false); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index d9ad17bf814..4421a4f471c 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1564,6 +1564,7 @@ std::optional WasmBinaryWriter::writeCodeAnnotations() { }; append(getBranchHintsBuffer()); + append(getInlineHintsBuffer()); return ret; } @@ -1683,6 +1684,28 @@ std::optional WasmBinaryWriter::getBranchHintsBuffer() { }); } +std::optional WasmBinaryWriter::getInlineHintsBuffer() { + return writeExpressionHints( + Annotations::InlineHint, + [](const Function::CodeAnnotation& annotation) { + return annotation.inline_; + }, + [](const Function::CodeAnnotation& annotation, + BufferWithRandomAccess& buffer) { + // Hint size, always 1 for now. + buffer << U32LEB(1); + + // We must only emit hints that are present. + assert(annotation.inline_); + + // Hint must fit in one byte. + assert(*annotation.inline_ <= 127); + + // Hint contents: inline frequency count + buffer << U32LEB(*annotation.inline_); + }); +} + void WasmBinaryWriter::writeData(const char* data, size_t size) { for (size_t i = 0; i < size; i++) { o << int8_t(data[i]); @@ -1973,10 +1996,11 @@ void WasmBinaryReader::preScan() { if (sectionCode == BinaryConsts::Section::Custom) { auto sectionName = getInlineString(); - if (sectionName == Annotations::BranchHint) { + if (sectionName == Annotations::BranchHint || + sectionName == Annotations::InlineHint) { // Code annotations require code locations. - // TODO: For Branch Hinting, we could note which functions require - // code locations, as an optimization. + // TODO: We could note which functions require code locations, as an + // optimization. needCodeLocations = true; } else if (DWARF && Debug::isDWARFSection(sectionName)) { // DWARF sections contain code offsets. @@ -2099,6 +2123,10 @@ void WasmBinaryReader::read() { pos = branchHintsPos; readBranchHints(branchHintsLen); } + if (inlineHintsPos) { + pos = inlineHintsPos; + readInlineHints(inlineHintsLen); + } validateBinary(); } @@ -2124,6 +2152,9 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) { // Only note the position and length, we read this later. branchHintsPos = pos; branchHintsLen = payloadLen; + } else if (sectionName == Annotations::InlineHint) { + inlineHintsPos = pos; + inlineHintsLen = payloadLen; } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { @@ -5305,11 +5336,28 @@ void WasmBinaryReader::readBranchHints(size_t payloadLen) { throwError("bad BranchHint value"); } - // Apply the valid hint. annotation.branchLikely = likely; }); } +void WasmBinaryReader::readInlineHints(size_t payloadLen) { + readExpressionHints(Annotations::InlineHint, + payloadLen, + [&](Function::CodeAnnotation& annotation) { + auto size = getU32LEB(); + if (size != 1) { + throwError("bad InlineHint size"); + } + + uint8_t inline_ = getInt8(); + if (inline_ > 127) { + throwError("bad InlineHint value"); + } + + annotation.inline_ = inline_; + }); +} + Index WasmBinaryReader::readMemoryAccess(Address& alignment, Address& offset) { auto rawAlignment = getU32LEB(); bool hasMemIdx = false; diff --git a/test/lit/compilation-hints.wast b/test/lit/inline-hints.wast similarity index 63% rename from test/lit/compilation-hints.wast rename to test/lit/inline-hints.wast index 0f3f6593796..69a67aa6bd9 100644 --- a/test/lit/compilation-hints.wast +++ b/test/lit/inline-hints.wast @@ -2,13 +2,15 @@ ;; RUN: wasm-opt -all %s -S -o - | filecheck %s -;; TODO: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP +;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP (module ;; CHECK: (type $func (func)) + ;; RTRIP: (type $func (func)) (type $func (func)) ;; CHECK: (table $table 10 20 funcref) + ;; RTRIP: (table $table 10 20 funcref) (table $table 10 20 funcref) ;; CHECK: (elem declare func $func) @@ -24,6 +26,19 @@ ;; CHECK-NEXT: (call $func) ;; CHECK-NEXT: (call $func) ;; CHECK-NEXT: ) + ;; RTRIP: (elem declare func $func) + + ;; RTRIP: (func $func (type $func) + ;; RTRIP-NEXT: (@metadata.code.inline "\00") + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (@metadata.code.inline "\01") + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (@metadata.code.inline "\7e") + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (@metadata.code.inline "\7f") + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: (call $func) + ;; RTRIP-NEXT: ) (func $func (@metadata.code.inline "\00") (call $func) @@ -47,6 +62,16 @@ ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; RTRIP: (func $other-calls (type $func) + ;; RTRIP-NEXT: (@metadata.code.inline "\12") + ;; RTRIP-NEXT: (call_indirect $table (type $func) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (@metadata.code.inline "\34") + ;; RTRIP-NEXT: (call_ref $func + ;; RTRIP-NEXT: (ref.func $func) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) (func $other-calls (@metadata.code.inline "\12") (call_indirect (type $func) diff --git a/test/lit/multi-hints.wast b/test/lit/multi-hints.wast new file mode 100644 index 00000000000..5849f5c0935 --- /dev/null +++ b/test/lit/multi-hints.wast @@ -0,0 +1,90 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s + +;; RUN: wasm-opt -all --roundtrip %s -S -o - | filecheck %s --check-prefix=RTRIP + +;; Check that separate code annotations do not interfere with each other, even +;; in the same function. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (type $func (func)) + ;; RTRIP: (type $0 (func (param i32))) + + ;; RTRIP: (type $func (func)) + (type $func (func)) + + ;; CHECK: (table $table 10 20 funcref) + ;; RTRIP: (table $table 10 20 funcref) + (table $table 10 20 funcref) + + ;; CHECK: (func $inline (type $func) + ;; CHECK-NEXT: (@metadata.code.inline "\00") + ;; CHECK-NEXT: (call $inline) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $inline (type $func) + ;; RTRIP-NEXT: (@metadata.code.inline "\00") + ;; RTRIP-NEXT: (call $inline) + ;; RTRIP-NEXT: ) + (func $inline + (@metadata.code.inline "\00") + (call $inline) + ) + + ;; CHECK: (func $branch (type $0) (param $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $branch (type $0) (param $x i32) + ;; RTRIP-NEXT: (block $block + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\01") + ;; RTRIP-NEXT: (br_if $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $branch (param $x i32) + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $both (type $0) (param $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.inline "\02") + ;; CHECK-NEXT: (call $inline) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $both (type $0) (param $x i32) + ;; RTRIP-NEXT: (block $block + ;; RTRIP-NEXT: (@metadata.code.inline "\02") + ;; RTRIP-NEXT: (call $inline) + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\00") + ;; RTRIP-NEXT: (br_if $block + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $both (param $x i32) + (block $out + (@metadata.code.inline "\02") + (call $inline) + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + ) + ) +) From 2bba2eb10031310a09f7dfdd0ef807ca8373f9dd Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 15 May 2025 13:50:21 -0700 Subject: [PATCH 503/622] Add PublicTypeValidator for use in fuzzer (#7597) When custom descriptors is not enabled, we consider it a validation error for a public type to include an exact reference because the identity of that type will change when the binary is written and that reference becomes inexact. Once the fuzzer observes functions signatures with exact references, it will have to be careful not to export those functions and make the exact reference public when custom descriptors is disabled. Add a utility to efficiently determine whether a type is valid to be made public, memoizing results to avoid traversing any type definition more than once. Use this utility in the fuzzer. --- src/ir/CMakeLists.txt | 1 + src/ir/public-type-validator.cpp | 95 +++++++++++++++++++ src/ir/public-type-validator.h | 74 +++++++++++++++ src/tools/fuzzing.h | 8 ++ src/tools/fuzzing/fuzzing.cpp | 13 ++- test/gtest/CMakeLists.txt | 1 + test/gtest/public-type-validator.cpp | 132 +++++++++++++++++++++++++++ 7 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 src/ir/public-type-validator.cpp create mode 100644 src/ir/public-type-validator.h create mode 100644 test/gtest/public-type-validator.cpp diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index fa2ee1127d7..264828e0e8d 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -16,6 +16,7 @@ set(ir_SOURCES properties.cpp LocalGraph.cpp LocalStructuralDominance.cpp + public-type-validator.cpp ReFinalize.cpp return-utils.cpp stack-utils.cpp diff --git a/src/ir/public-type-validator.cpp b/src/ir/public-type-validator.cpp new file mode 100644 index 00000000000..5da16e51f28 --- /dev/null +++ b/src/ir/public-type-validator.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ir/public-type-validator.h" +#include "wasm-type.h" + +namespace wasm { + +bool PublicTypeValidator::isValidPublicTypeImpl(HeapType type) { + assert(!features.hasCustomDescriptors()); + assert(!type.isBasic()); + // If custom descriptors is not enabled and exposing this type would make an + // exact reference public, then this type is not valid to make public. + // Traverse the heap types reachable from this one, looking for exact + // references. Cache the findings for each group along the way to minimize + // future work. + struct Task { + RecGroup group; + bool finished; + }; + std::vector workList{Task{type.getRecGroup(), false}}; + std::unordered_set visiting; + + auto markVisitingInvalid = [&]() { + for (auto group : visiting) { + auto [_, inserted] = allowedPublicGroupCache.insert({group, false}); + assert(inserted); + } + }; + + while (!workList.empty()) { + auto task = workList.back(); + workList.pop_back(); + if (task.finished) { + // We finished searching this group and the groups it reaches without + // finding a problem. + visiting.erase(task.group); + [[maybe_unused]] auto [_, inserted] = + allowedPublicGroupCache.insert({task.group, true}); + assert(inserted); + continue; + } + if (auto it = allowedPublicGroupCache.find(task.group); + it != allowedPublicGroupCache.end()) { + if (it->second) { + // We already know this group is valid. Move on to the next group. + continue; + } else { + // This group is invalid! + markVisitingInvalid(); + return false; + } + } + // Check whether we are already in the process of searching this group. + if (!visiting.insert(task.group).second) { + continue; + } + // We have never seen this group before. Search it. Push a completion task + // first so we will know when we have finished searching. + workList.push_back({task.group, true}); + for (auto heapType : task.group) { + // Look for invalid exact references. + for (auto t : heapType.getTypeChildren()) { + if (t.isExact()) { + markVisitingInvalid(); + return false; + } + } + // Search the referenced groups as well. + for (auto t : heapType.getReferencedHeapTypes()) { + if (!t.isBasic()) { + workList.push_back({t.getRecGroup(), false}); + } + } + } + } + + // We searched all the reachable types and never found an exact reference. + return true; +} + +} // namespace wasm diff --git a/src/ir/public-type-validator.h b/src/ir/public-type-validator.h new file mode 100644 index 00000000000..c18c6a817cd --- /dev/null +++ b/src/ir/public-type-validator.h @@ -0,0 +1,74 @@ +/* + * Copyright 2025 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_ir_public_type_validator_h +#define wasm_ir_public_type_validator_h + +#include "wasm-features.h" +#include "wasm-type.h" + +#include + +namespace wasm { + +// Utility for determining whether it is valid to make a given type public given +// some enabled feature set. Used in the fuzzer for determining whether a +// function can be exported, for instance. +class PublicTypeValidator { +public: + explicit PublicTypeValidator(FeatureSet features) : features(features) {} + + bool isValidPublicType(HeapType type) { + if (features.hasCustomDescriptors()) { + return true; + } + if (type.isBasic()) { + return true; + } + return isValidPublicTypeImpl(type); + } + + bool isValidPublicType(Type type) { + if (features.hasCustomDescriptors()) { + return true; + } + if (type.isBasic()) { + return true; + } + if (type.isTuple()) { + for (auto t : type) { + if (!isValidPublicType(t)) { + return false; + } + } + return true; + } + assert(type.isRef()); + if (type.isExact()) { + return false; + } + return isValidPublicType(type.getHeapType()); + } + +private: + FeatureSet features; + std::unordered_map allowedPublicGroupCache; + bool isValidPublicTypeImpl(HeapType type); +}; + +} // namespace wasm + +#endif // wasm_ir_public_type_validator_h diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 6cf09d164bb..20b1e39fddd 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -341,6 +342,13 @@ class TranslateToFuzzReader { Expression* makeImportSleep(Type type); Expression* makeMemoryHashLogging(); + // We must be careful not to add exports that have invalid public types, such + // as those that reach exact types when custom descriptors is disabled. + PublicTypeValidator publicTypeValidator; + bool isValidPublicType(Type type) { + return publicTypeValidator.isValidPublicType(type); + } + // Function operations. The main processFunctions() loop will call addFunction // as well as modFunction(). void processFunctions(); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index af7313b1aef..3a6b3738015 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -30,7 +30,8 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, std::vector&& input, bool closedWorld) : wasm(wasm), closedWorld(closedWorld), builder(wasm), - random(std::move(input), wasm.features) { + random(std::move(input), wasm.features), + publicTypeValidator(wasm.features) { // - funcref cannot be logged because referenced functions can be inlined or // removed during optimization @@ -1495,11 +1496,13 @@ Function* TranslateToFuzzReader::addFunction() { // outside. bool validExportParams = std::all_of(paramType.begin(), paramType.end(), [&](Type t) { - return t.isDefaultable(); + return t.isDefaultable() && isValidPublicType(t); }); - if (validExportParams && (numAddedFunctions == 0 || oneIn(2)) && - !wasm.getExportOrNull(func->name) && !preserveImportsAndExports) { - wasm.addExport(new Export(func->name, ExternalKind::Function, func->name)); + if (!preserveImportsAndExports && validExportParams && + isValidPublicType(resultType) && (numAddedFunctions == 0 || oneIn(2)) && + !wasm.getExportOrNull(func->name)) { + wasm.addExport( + Builder::makeExport(func->name, func->name, ExternalKind::Function)); } // add some to an elem segment while (oneIn(3) && !random.finished()) { diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 223dbddf79b..d1ef8454a9a 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -18,6 +18,7 @@ set(unittest_SOURCES local-graph.cpp possible-contents.cpp printing.cpp + public-type-validator.cpp scc.cpp stringify.cpp suffix_tree.cpp diff --git a/test/gtest/public-type-validator.cpp b/test/gtest/public-type-validator.cpp new file mode 100644 index 00000000000..564491e309b --- /dev/null +++ b/test/gtest/public-type-validator.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ir/public-type-validator.h" +#include "wasm-type.h" + +#include "gtest/gtest.h" + +using namespace wasm; + +class PublicTypeValidatorTest : public ::testing::Test { +protected: + HeapType EmptyStruct; + HeapType RecursiveStruct; + HeapType InvalidStruct; + HeapType IndirectlyInvalidStruct; + HeapType RecursiveInvalidStruct; + HeapType EmptyStructInGroupWithInvalid; + + void SetUp() override { + TypeBuilder builder(8); + + // Empty struct + HeapType empty = builder[0]; + builder[0] = Struct(); + + // Recursive struct + HeapType recursive = builder[1]; + builder[1] = Struct({Field(Type(recursive, Nullable), Mutable)}); + + // Invalid struct (when custom descriptors are disabled) + HeapType invalid = builder[2]; + builder[2] = Struct({Field(Type(empty, Nullable, Exact), Mutable)}); + + // Indirectly invalid struct + builder[3] = Struct({Field(Type(invalid, Nullable), Mutable)}); + + // Mutually recursive, indirectly invalid struct + builder[4] = Struct({Field(Type(builder[5], Nullable), Mutable)}); + builder[5] = Struct({Field(Type(builder[4], Nullable, Exact), Mutable)}); + builder.createRecGroup(4, 2); + + // Empty struct in group with invalid struct + builder[6] = Struct(); + builder[7] = Struct({Field(Type(invalid, Nullable), Mutable)}); + builder.createRecGroup(6, 2); + + auto result = builder.build(); + auto& built = *result; + + EmptyStruct = built[0]; + RecursiveStruct = built[1]; + InvalidStruct = built[2]; + IndirectlyInvalidStruct = built[3]; + RecursiveInvalidStruct = built[4]; + EmptyStructInGroupWithInvalid = built[6]; + } + + void TearDown() override { wasm::destroyAllTypesForTestingPurposesOnly(); } +}; + +TEST_F(PublicTypeValidatorTest, CustomDescriptorsEnabled) { + PublicTypeValidator validator(FeatureSet::CustomDescriptors); + + EXPECT_TRUE(validator.isValidPublicType(EmptyStruct)); + EXPECT_TRUE(validator.isValidPublicType(RecursiveStruct)); + EXPECT_TRUE(validator.isValidPublicType(InvalidStruct)); + EXPECT_TRUE(validator.isValidPublicType(IndirectlyInvalidStruct)); + EXPECT_TRUE(validator.isValidPublicType(RecursiveInvalidStruct)); + EXPECT_TRUE(validator.isValidPublicType(EmptyStructInGroupWithInvalid)); +} + +TEST_F(PublicTypeValidatorTest, CustomDescriptorsDisabled) { + PublicTypeValidator validator(FeatureSet::MVP); + + EXPECT_TRUE(validator.isValidPublicType(EmptyStruct)); + EXPECT_TRUE(validator.isValidPublicType(RecursiveStruct)); + EXPECT_FALSE(validator.isValidPublicType(InvalidStruct)); + EXPECT_FALSE(validator.isValidPublicType(IndirectlyInvalidStruct)); + EXPECT_FALSE(validator.isValidPublicType(RecursiveInvalidStruct)); + EXPECT_FALSE(validator.isValidPublicType(EmptyStructInGroupWithInvalid)); +} + +TEST_F(PublicTypeValidatorTest, CachedResult) { + PublicTypeValidator validator(FeatureSet::MVP); + + // Check the indirectly invalid type first, then serve the query for the + // directly invalid type from the cache. + EXPECT_FALSE(validator.isValidPublicType(IndirectlyInvalidStruct)); + EXPECT_FALSE(validator.isValidPublicType(InvalidStruct)); + + // We can serve repeated queries from the cache, too. + EXPECT_FALSE(validator.isValidPublicType(IndirectlyInvalidStruct)); +} + +TEST_F(PublicTypeValidatorTest, BasicHeapTypes) { + PublicTypeValidator validator(FeatureSet::MVP); + + EXPECT_TRUE(validator.isValidPublicType(HeapTypes::any)); + EXPECT_TRUE(validator.isValidPublicType(HeapTypes::eq)); + EXPECT_TRUE(validator.isValidPublicType(HeapTypes::ext)); + EXPECT_TRUE(validator.isValidPublicType(HeapTypes::func)); + EXPECT_TRUE(validator.isValidPublicType(HeapTypes::none)); +} + +TEST_F(PublicTypeValidatorTest, Types) { + PublicTypeValidator validator(FeatureSet::MVP); + + EXPECT_TRUE(validator.isValidPublicType(Type::i32)); + EXPECT_TRUE(validator.isValidPublicType(Type::i64)); + EXPECT_TRUE(validator.isValidPublicType(Type::f32)); + EXPECT_TRUE(validator.isValidPublicType(Type::f64)); + EXPECT_TRUE(validator.isValidPublicType(Type::v128)); + + EXPECT_TRUE(validator.isValidPublicType(Type(HeapType::any, Nullable))); + EXPECT_TRUE(validator.isValidPublicType(Type(EmptyStruct, Nullable))); + EXPECT_FALSE(validator.isValidPublicType(Type(EmptyStruct, Nullable, Exact))); + EXPECT_FALSE(validator.isValidPublicType(Type(InvalidStruct, Nullable))); +} From d175e4524b7f43ed3b29bfc0a7f6b04df1cfd27b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 15 May 2025 16:00:46 -0700 Subject: [PATCH 504/622] Allow the fuzzer to use tests with exact types (#7598) Remove the list of tests containing exact types from the list of tests the fuzzer is disallowed from using as initial contents. Also allow fuzzing V8 with exact types. Fix various parts of the fuzzer to ensure we emit valid binaries when initial contents have exact references. --- scripts/fuzz_opt.py | 15 ++++++--------- scripts/test/fuzzing.py | 24 ++---------------------- scripts/test/shared.py | 1 + src/tools/fuzzing/fuzzing.cpp | 31 +++++++++++++++++-------------- src/tools/fuzzing/heap-types.cpp | 3 ++- 5 files changed, 28 insertions(+), 46 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 0cdc976ef86..8f832187f5c 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -151,12 +151,10 @@ def randomize_feature_opts(): # The shared-everything feature is new and we want to fuzz it, but it # also currently disables fuzzing V8, so disable it most of the time. - # Same with custom descriptors and strings - all these cannot be run in - # V8 for now. Relaxed SIMD's nondeterminism disables much but not all of - # our V8 fuzzing, so avoid it too. + # Same with strings. Relaxed SIMD's nondeterminism disables much but not + # all of our V8 fuzzing, so avoid it too. if random.random() < 0.9: FEATURE_OPTS.append('--disable-shared-everything') - FEATURE_OPTS.append('--disable-custom-descriptors') FEATURE_OPTS.append('--disable-strings') FEATURE_OPTS.append('--disable-relaxed-simd') @@ -826,9 +824,8 @@ def run(self, wasm, extra_d8_flags=[]): def can_run(self, wasm): # V8 does not support shared memories when running with # shared-everything enabled, so do not fuzz shared-everything - # for now. It also does not yet support custom descriptors, nor - # strings. - return all_disallowed(['shared-everything', 'custom-descriptors', 'strings']) + # for now. It also does not yet support strings. + return all_disallowed(['shared-everything', 'strings']) def can_compare_to_self(self): # With nans, VM differences can confuse us, so only very simple VMs @@ -1595,7 +1592,7 @@ def can_run_on_wasm(self, wasm): return False # see D8.can_run - return all_disallowed(['shared-everything', 'custom-descriptors', 'strings']) + return all_disallowed(['shared-everything', 'strings']) # Check that the text format round-trips without error. @@ -1798,7 +1795,7 @@ def can_run_on_wasm(self, wasm): return False if NANS: return False - return all_disallowed(['shared-everything', 'custom-descriptors', 'strings']) + return all_disallowed(['shared-everything', 'strings']) # Test --fuzz-preserve-imports-exports, which never modifies imports or exports. diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 580bbb012b4..b074106b271 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -110,33 +110,13 @@ 'dce-stack-switching.wast', 'precompute-stack-switching.wast', 'vacuum-stack-switching.wast', - # TODO: fuzzer support for exact references - 'exact-references.wast', - 'exact-references-lowering.wast', - 'exact-casts.wast', - 'exact-casts-trivial.wast', - 'abstract-type-refining-exact.wast', - 'optimize-instructions-exact.wast', - 'optimize-instructions-all-casts.wast', - 'optimize-instructions-all-casts-exact.wast', - 'local-subtyping-exact.wast', - 'remove-unused-types-exact.wast', - 'coalesce-locals-exact.wast', - 'remove-unused-brs-exact.wast', - 'signature-refining-exact.wast', - 'gufa-cast-all-exact.wast', - 'type-merging-exact.wast', - 'type-refining-exact.wast', - 'type-refining-gufa-exact.wast', - 'type-ssa-exact.wast', - 'minimize-rec-groups-exact.wast', - 'minimize-rec-groups-ignore-exact.wast', - 'public-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', + # TODO: fuzzer support for uninhabitable imported globals + 'exact-references.wast', ] diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 530f386fddc..d4973063707 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -257,6 +257,7 @@ def has_shell_timeout(): '--experimental-wasm-compilation-hints', '--experimental-wasm-stringref', '--experimental-wasm-fp16', + '--experimental-wasm-custom-descriptors', ] # external tools diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 3a6b3738015..9905d00e2aa 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -2177,8 +2177,11 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCompoundRef); } - options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, - &Self::makeRefCast); + // Exact casts are only allowed with custom descriptors enabled. + if (type.isInexact() || wasm.features.hasCustomDescriptors()) { + options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, + &Self::makeRefCast); + } } if (wasm.features.hasGC()) { if (typeStructFields.find(type) != typeStructFields.end()) { @@ -4907,7 +4910,7 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { case BrOnNonNull: { // The sent type is the non-nullable version of the reference, so any ref // of that type is ok, nullable or not. - refType = Type(targetType.getHeapType(), getNullability()); + refType = targetType.with(getNullability()); break; } case BrOnCast: { @@ -4915,14 +4918,17 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { // nullability, so the combination of the two must be a subtype of // targetType. castType = getSubType(targetType); - if (!wasm.features.hasCustomDescriptors()) { - // Exact cast targets disallowed without custom descriptors. - castType = castType.with(Inexact); + if (castType.isExact() && !wasm.features.hasCustomDescriptors()) { + // This exact cast is only valid if its input has the same type (or the + // only possible strict subtype, bottom). + refType = castType; + } else { + // The ref's type must be castable to castType, or we'd not validate. + // But it can also be a subtype, which will trivially also succeed (so + // do that more rarely). Pick subtypes rarely, as they make the cast + // trivial. + refType = oneIn(5) ? getSubType(castType) : getSuperType(castType); } - // The ref's type must be castable to castType, or we'd not validate. But - // it can also be a subtype, which will trivially also succeed (so do that - // more rarely). Pick subtypes rarely, as they make the cast trivial. - refType = oneIn(5) ? getSubType(castType) : getSuperType(castType); if (targetType.isNonNullable()) { // And it must have the right nullability for the target, as mentioned // above: if the target type is non-nullable then either the ref or the @@ -4945,10 +4951,7 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { refType = getSubType(targetType); // See above on BrOnCast, but flipped. castType = oneIn(5) ? getSuperType(refType) : getSubType(refType); - if (!wasm.features.hasCustomDescriptors()) { - // Exact cast targets disallowed without custom descriptors. - castType = castType.with(Inexact); - } + castType = castType.withInexactIfNoCustomDescs(wasm.features); // There is no nullability to adjust: if targetType is non-nullable then // both refType and castType are as well, as subtypes of it. But we can // also allow castType to be nullable (it is not sent to the target). diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 65820e50d8e..4fc7ed91500 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -900,13 +900,14 @@ std::vector Inhabitator::build() { } auto heapType = type.getHeapType(); auto nullability = type.getNullability(); + auto exactness = type.getExactness(); if (auto it = typeIndices.find(heapType); it != typeIndices.end()) { heapType = builder[it->second]; } if (nullables.count(pos)) { nullability = Nullable; } - type = builder.getTempRefType(heapType, nullability); + type = builder.getTempRefType(heapType, nullability, exactness); }; for (size_t i = 0; i < types.size(); ++i) { From f8531ef56e61cb68815c835b294777fbd97f3b0a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 15 May 2025 16:34:03 -0700 Subject: [PATCH 505/622] Update README for exact types (#7600) Mention that ref.func and allocation instructions are always typed as exact internally. Clarify that binary writing will generalize types as necessary. Remove some other outdated information about the supported text format and strings as well. --- README.md | 134 +++++++++++++++++++++++++++++------------------------- 1 file changed, 72 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 19334a96662..b3809263edc 100644 --- a/README.md +++ b/README.md @@ -72,12 +72,10 @@ There are a few differences between Binaryen IR and the WebAssembly language: * Binaryen IR [is a tree][binaryen_ir], i.e., it has hierarchical structure, for convenience of optimization. This differs from the WebAssembly binary format which is a stack machine. - * Consequently Binaryen's text format allows only s-expressions. - WebAssembly's official text format is primarily a linear instruction list - (with s-expression extensions). Binaryen can't read the linear style, but - it can read a wasm text file if it contains only s-expressions. + * Binaryen uses Stack IR to optimize "stacky" code (that can't be represented in structured form). + * When stacky code must be represented in Binaryen IR, such as with multivalue instructions and blocks, it is represented with tuple types that do not exist in the WebAssembly language. In addition to multivalue @@ -85,9 +83,12 @@ There are a few differences between Binaryen IR and the WebAssembly language: but not in WebAssembly. Experiments show that better support for multivalue could enable useful but small code size savings of 1-3%, so it has not been worth changing the core IR structure to support it better. + * Block input values (currently only supported in `catch` blocks in the exception handling feature) are represented as `pop` subexpressions. + * Types and unreachable code + * WebAssembly limits block/if/loop types to none and the concrete value types (i32, i64, f32, f64). Binaryen IR has an unreachable type, and it allows block/if/loop to take it, allowing [local transforms that don't need to @@ -95,94 +96,103 @@ There are a few differences between Binaryen IR and the WebAssembly language: text output is not necessarily valid wasm text. (To get valid wasm text, you can do `--generate-stack-ir --print-stack-ir`, which prints Stack IR, this is guaranteed to be valid for wasm parsers.) - * Binaryen ignores unreachable code when reading WebAssembly binaries. That - means that if you read a wasm file with unreachable code, that code will be - discarded as if it were optimized out (often this is what you want anyhow, - and optimized programs have no unreachable code anyway, but if you write an - unoptimized file and then read it, it may look different). The reason for - this behavior is that unreachable code in WebAssembly has corner cases that - are tricky to handle in Binaryen IR (it can be very unstructured, and - Binaryen IR is more structured than WebAssembly as noted earlier). Note - that Binaryen does support unreachable code in .wat text files, since as we - saw Binaryen only supports s-expressions there, which are structured. + * Binaryen supports a `stringref` type. This is similar to the currently- - frozen [stringref proposal], with the difference that the string type is a + inactive [stringref proposal], with the difference that the string type is a subtype of `externref` rather than `anyref`. Doing so allows toolchains to emit code in a form that uses [js string builtins] which Binaryen can then "lift" into stringref in its internal IR, optimize (for example, a concatenation of "a" and "b" can be optimized at compile time to "ab"), and then "lower" that into js string builtins once more. + * Blocks - * Binaryen IR has only one node that contains a variable-length list of - operands: the block. WebAssembly on the other hand allows lists in loops, - if arms, and the top level of a function. Binaryen's IR has a single - operand for all non-block nodes; this operand may of course be a block. - The motivation for this property is that many passes need special code - for iterating on lists, so having a single IR node with a list simplifies - them. - * As in wasm, blocks and loops may have names. Branch targets in the IR are - resolved by name (as opposed to nesting depth). This has 2 consequences: + + * Binaryen IR has only one control flow structure that contains a + variable-length list of children: the block. WebAssembly on the other hand + allows all control flow structures, such as loops, if arms, and function + bodies, to have multiple children. In Binaryen IR, these other control flow + structures have a single child. This child may of course be a block. The + motivation for this property is that many passes need special code for + iterating on lists of instructions, so having a single IR node with a list + simplifies them. + + * As in the Wasm text format, blocks and loops may have names. Branch targets + in the IR are resolved by name (as opposed to nesting depth). This has 2 + consequences: + * Blocks without names may not be branch targets. + * Names are required to be unique. (Reading .wat files with duplicate names is supported; the names are modified when the IR is constructed). - * As an optimization, a block that is the child of a loop (or if arm, or - function toplevel) and which has no branches targeting it will not be - emitted when generating wasm. Instead its list of operands will be directly - used in the containing node. Such a block is sometimes called an "implicit - block". + + * As an optimization, a block with no name, which can never be a branch + target, will not be emitted when generating wasm. Instead its list of + children will be directly used in the containing control flow structure. + Such a block is sometimes called an "implicit block". + * Reference Types + * The wasm text and binary formats require that a function whose address is taken by `ref.func` must be either in the table, or declared via an `(elem declare func $..)`. Binaryen will emit that data when necessary, but it does not represent it in IR. That is, IR can be worked on without needing to think about declaring function references. - * Binaryen IR allows non-nullable locals in the form that the wasm spec does, - (which was historically nicknamed "1a"), in which a `local.get` must be - structurally dominated by a `local.set` in order to validate (that ensures - we do not read the default value of null). Despite being aligned with the - wasm spec, there are some minor details that you may notice: + + * Binaryen IR allows non-nullable locals in the form that the Wasm spec does, + in which a `local.get` must be structurally dominated by a `local.set` in + order to validate (that ensures we do not read the default value of null). + Despite being aligned with the Wasm spec, there are some minor details that + you may notice: + * A nameless `Block` in Binaryen IR does not interfere with validation. Nameless blocks are never emitted into the binary format (we just emit - their contents), so we ignore them for purposes of non-nullable locals. As - a result, if you read wasm text emitted by Binaryen then you may see what - seems to be code that should not validate per the spec (and may not - validate in wasm text parsers), but that difference will not exist in the - binary format (binaries emitted by Binaryen will always work everywhere, - aside for bugs of course). + their contents), so we ignore them for purposes of validating + non-nullable locals. As a result, if you read wasm text emitted by + Binaryen then you may see what seems to be code that should not validate + per the spec (and may not validate in Wasm text parsers), but that + difference will not exist in the binary format (binaries emitted by + Binaryen will always work everywhere, aside from bugs of course). + * The Binaryen pass runner will automatically fix up validation after each pass (finding things that do not validate and fixing them up, usually by demoting a local to be nullable). As a result you do not need to worry much about this when writing Binaryen passes. For more details see the `requiresNonNullableLocalFixups()` hook in `pass.h` and the `LocalStructuralDominance` class. + * Binaryen IR uses the most refined types possible for references, specifically: - * The IR type of a `ref.func` is always a specific function type, and not - plain `funcref`. It is also non-nullable. + + * The IR type of a `ref.func` is always an exact, non-nullable reference to + a defined function type, and not plain `funcref`, even if no features + beyond basic reference types are enabled. + + * The IR type of allocation instructions such as `struct.new` or + `array.new` is always an exact reference, even if Custom Descriptors are + not enabled. + * Non-nullable types are also used for the type that `try_table` sends on branches (if we branch, a null is never sent), that is, it sends (ref exn) and not (ref null exn). - In both cases if GC is not enabled then we emit the less-refined type in the - binary. When reading a binary, the more refined types will be applied as we - build the IR. + + * As a result, non-nullable and exact references are generally allowed in + the IR even when GC or Custom Descriptors is not enabled. When reading a + binary, the more refined types will be applied as we build the IR. + + In all cases the binary writer will generalize the type as necessary for + the enabled feature set. For example, if only Reference Types is enabled, + all function reference types will be emitted as `funcref`. + * `br_if` output types are more refined in Binaryen IR: they have the type of - the value, when a value flows in. In the wasm spec the type is that of the - branch target, which may be less refined. Using the more refined type here - ensures that we optimize in the best way possible, using all the type - information, but it does mean that some roundtripping operations may look a - little different. In particular, when we emit a `br_if` whose type is more - refined in Binaryen IR then we emit a cast right after it, so that the - output has the right type in the wasm spec. That may cause a few bytes of - extra size in rare cases (we avoid this overhead in the common case where - the `br_if` value is unused). - * Strings - * Binaryen allows string views (`stringview_wtf16` etc.) to be cast using - `ref.cast`. This simplifies the IR, as it allows `ref.cast` to always be - used in all places (and it is lowered to `ref.as_non_null` where possible - in the optimizer). The stringref spec does not seem to allow this though, - and to fix that the binary writer will replace `ref.cast` that casts a - string view to a non-nullable type to `ref.as_non_null`. A `ref.cast` of a - string view that is a no-op is skipped entirely. + the sent value operand, when it exists. In the Wasm spec the type is that + of the branch target, which may be less refined. Using the more refined + type here ensures that we optimize in the best way possible, using all the + type information, but it does mean that some roundtripping operations may + look a little different. In particular, when we emit a `br_if` whose type + is more refined in Binaryen IR, then we emit a cast right after it to + recover the more refined type. That may cause a few bytes of extra size in + rare cases (we avoid this overhead in the common case where the `br_if` + value is unused). As a result, you might notice that round-trip conversions (wasm => Binaryen IR => wasm) change code a little in some corner cases. From f27ff3e1dd620c50cb1a0f617725944031450b38 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 May 2025 09:43:25 -0700 Subject: [PATCH 506/622] Fix Branch Hinting iostream printing (#7603) The code saved and restored std::cout, not the current stream... and the current stream is std::cout in most tests, so this went unnoticed. Printing from wasm-dis works otherwise apparently, and the bug showed up there. --- src/passes/Print.cpp | 4 ++-- test/lit/branch-hinting-printing.wast | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 test/lit/branch-hinting-printing.wast diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 971700c0368..3a405e93981 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2679,11 +2679,11 @@ void PrintSExpression::printCodeAnnotations(Expression* curr) { if (annotation.inline_) { Colors::grey(o); std::ofstream saved; - saved.copyfmt(std::cout); + saved.copyfmt(o); o << "(@" << Annotations::InlineHint << " \"\\" << std::hex << std::setfill('0') << std::setw(2) << int(*annotation.inline_) << "\")\n"; - std::cout.copyfmt(saved); + o.copyfmt(saved); restoreNormalColor(o); doIndent(o, indent); } diff --git a/test/lit/branch-hinting-printing.wast b/test/lit/branch-hinting-printing.wast new file mode 100644 index 00000000000..5ad4a324a0f --- /dev/null +++ b/test/lit/branch-hinting-printing.wast @@ -0,0 +1,21 @@ +;; The branch hint should not cause the later constant to print incorrectly +;; (branch hints are printed in hex, and we should clear that state so later +;; things are not affected). + +(module + (type $0 (func)) + (func $0 (type $0) + (@metadata.code.inline "\00") + (call $0) + ) + (func $1 (type $0) + (drop + (i32.const -18428) + ) + ) +) + +;; RUN: wasm-opt %s -o %t.wasm -g +;; RUN: wasm-dis %t.wasm | filecheck %s + +;; CHECK: (i32.const -18428) From 6431a079507817d5f133570068f1e38d5102041c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 16 May 2025 11:00:31 -0700 Subject: [PATCH 507/622] Preserve exactness optimizing ref.cast + ref.as_non_null (#7605) We would previously drop exactness when making the cast type non-nullable. --- src/passes/OptimizeInstructions.cpp | 2 +- .../lit/passes/optimize-instructions-exact.wast | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index e04988779f8..30a1fbeefee 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2423,7 +2423,7 @@ struct OptimizeInstructions // if (auto* as = curr->ref->dynCast(); as && as->op == RefAsNonNull) { curr->ref = as->value; - curr->type = Type(curr->type.getHeapType(), NonNullable); + curr->type = curr->type.with(NonNullable); } } diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast index cf11805ef52..bd25799cbb3 100644 --- a/test/lit/passes/optimize-instructions-exact.wast +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s -all --disable-custom-descriptors --optimize-instructions -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s (module ;; CHECK: (type $foo (sub (struct))) @@ -63,4 +63,19 @@ ) ) ) + + ;; CHECK: (func $combine-non-null (type $3) (param $foo (ref null $foo)) (result (ref (exact $foo))) + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $combine-non-null (param $foo (ref null $foo)) (result (ref (exact $foo))) + ;; We should not lose the exactness of the cast when we combine the + ;; ref.as_non_null into it. + (ref.cast (ref null (exact $foo)) + (ref.as_non_null + (local.get $foo) + ) + ) + ) ) From 5816d698301aa80dc98a8dec103880d3ff6ef5fb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 May 2025 11:22:23 -0700 Subject: [PATCH 508/622] Fix parsing error on array.new_fixed on non-array (#7604) Fixes #7602 --- src/wasm/wasm-ir-builder.cpp | 3 +++ test/lit/validation/array-new-fixed.wast | 12 ++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 test/lit/validation/array-new-fixed.wast diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 4774ead855f..d738df0c404 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2213,6 +2213,9 @@ Result<> IRBuilder::makeArrayNewElem(HeapType type, Name elem) { Result<> IRBuilder::makeArrayNewFixed(HeapType type, uint32_t arity) { ArrayNewFixed curr(wasm.allocator); + if (!type.isArray()) { + return Err{"expected array type annotation on array.new_fixed"}; + } curr.type = Type(type, NonNullable); curr.values.resize(arity); CHECK_ERR(visitArrayNewFixed(&curr)); diff --git a/test/lit/validation/array-new-fixed.wast b/test/lit/validation/array-new-fixed.wast new file mode 100644 index 00000000000..7b7af61e910 --- /dev/null +++ b/test/lit/validation/array-new-fixed.wast @@ -0,0 +1,12 @@ +;; RUN: not wasm-opt -all %s 2>&1 | filecheck %s + +;; CHECK: expected array type annotation on array.new_fixed + +(module + (type $struct (struct (field anyref))) + + ;; The type here is a struct, not an array, so we error. + (global $struct (ref $struct) (array.new_fixed $struct 1 + (i32.const 64) + )) +) From e89fd0e526ba075b377ccee17c1bf165c5017ae4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 16 May 2025 11:53:34 -0700 Subject: [PATCH 509/622] [NFC] Remove StringLowering::replaceNulls (#7607) Now that `string` is a subtype of `extern`, the null type for strings and externrefs is the same, so we no longer need to fix up nulls in StringLowering. Unblocks #7601. --- src/passes/StringLowering.cpp | 58 ----------------------------------- 1 file changed, 58 deletions(-) diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 84dfb7cb049..993b8de6f87 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -227,9 +227,6 @@ struct StringLowering : public StringGathering { // Replace string.* etc. operations with imported ones. replaceInstructions(module); - // Replace ref.null types as needed. - replaceNulls(module); - // ReFinalize to apply all the above changes. ReFinalize().run(getPassRunner(), module); @@ -495,61 +492,6 @@ struct StringLowering : public StringGathering { replacer.run(getPassRunner(), module); replacer.walkModuleCode(module); } - - // A ref.null of none needs to be noext if it is going to a location of type - // stringref. - void replaceNulls(Module* module) { - // Use SubtypingDiscoverer to find when a ref.null of none flows into a - // place that has been changed from stringref to externref. - struct NullFixer - : public WalkerPass< - ControlFlowWalker>> { - // Hooks for SubtypingDiscoverer. - void noteSubtype(Type, Type) { - // Nothing to do for pure types. - } - void noteSubtype(HeapType, HeapType) { - // Nothing to do for pure types. - } - void noteSubtype(Type, Expression*) { - // Nothing to do for a subtype of an expression. - } - void noteSubtype(Expression* a, Type b) { - // This is the case we care about: if |a| is a null that must be a - // subtype of ext then we fix that up. - if (!b.isRef()) { - return; - } - HeapType top = b.getHeapType().getTop(); - if (top.isMaybeShared(HeapType::ext)) { - if (auto* null = a->dynCast()) { - null->finalize(HeapTypes::noext.getBasic(top.getShared())); - } - } - } - void noteSubtype(Expression* a, Expression* b) { - // Only the type matters of the place we assign to. - noteSubtype(a, b->type); - } - void noteNonFlowSubtype(Expression* a, Type b) { - // Flow or non-flow is the same for us. - noteSubtype(a, b); - } - void noteCast(HeapType, HeapType) { - // Casts do not concern us. - } - void noteCast(Expression*, Type) { - // Casts do not concern us. - } - void noteCast(Expression*, Expression*) { - // Casts do not concern us. - } - }; - - NullFixer fixer; - fixer.run(getPassRunner(), module); - fixer.walkModuleCode(module); - } }; Pass* createStringGatheringPass() { return new StringGathering(); } From 1217a95eb3b3b9a294788ab841ed7363ea8cf173 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 16 May 2025 16:37:54 -0700 Subject: [PATCH 510/622] Fix TupleMake typo in wasm-delegations-fields.def (#7609) Fixes #7560. --- src/wasm-delegations-fields.def | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 7566573beff..e180855c467 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -601,7 +601,7 @@ DELEGATE_FIELD_CASE_START(Pop) DELEGATE_FIELD_CASE_END(Pop) DELEGATE_FIELD_CASE_START(TupleMake) -DELEGATE_FIELD_CHILD_VECTOR(Tuple, operands) +DELEGATE_FIELD_CHILD_VECTOR(TupleMake, operands) DELEGATE_FIELD_CASE_END(TupleMake) DELEGATE_FIELD_CASE_START(TupleExtract) From 45b7110cb91400b26590ec615cc4120b16611c95 Mon Sep 17 00:00:00 2001 From: walkingeyerobot Date: Mon, 19 May 2025 15:00:39 -0400 Subject: [PATCH 511/622] [NFC] Add missing maybe_unused annotation (#7610) This fixes an unused variable warning in opt mode. --- src/ir/public-type-validator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ir/public-type-validator.cpp b/src/ir/public-type-validator.cpp index 5da16e51f28..c5f3705df88 100644 --- a/src/ir/public-type-validator.cpp +++ b/src/ir/public-type-validator.cpp @@ -36,7 +36,8 @@ bool PublicTypeValidator::isValidPublicTypeImpl(HeapType type) { auto markVisitingInvalid = [&]() { for (auto group : visiting) { - auto [_, inserted] = allowedPublicGroupCache.insert({group, false}); + [[maybe_unused]] auto [_, inserted] = + allowedPublicGroupCache.insert({group, false}); assert(inserted); } }; From a2d01d36a538f0d8d8520feb5bd49a30067bf713 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 19 May 2025 13:35:00 -0700 Subject: [PATCH 512/622] Handle stack switching in Unsubtyping (#7608) Add the rather complicated subtyping requirements to SubtypingDiscoverer and test that unsubtyping preserves the necessary subtype relationships. --- scripts/test/fuzzing.py | 1 + src/ir/subtype-exprs.h | 124 ++++- .../passes/unsubtyping-stack-switching.wast | 480 ++++++++++++++++++ 3 files changed, 599 insertions(+), 6 deletions(-) create mode 100644 test/lit/passes/unsubtyping-stack-switching.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index b074106b271..b057f84545b 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -109,6 +109,7 @@ 'coalesce-locals-stack-switching.wast', 'dce-stack-switching.wast', 'precompute-stack-switching.wast', + 'unsubtyping-stack-switching.wast', 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 7d9f30b6778..e689db981e4 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -412,15 +412,127 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStringWTF16Get(StringWTF16Get* curr) {} void visitStringSliceWTF(StringSliceWTF* curr) {} - void visitContNew(ContNew* curr) { WASM_UNREACHABLE("not implemented"); } - void visitContBind(ContBind* curr) { WASM_UNREACHABLE("not implemented"); } - void visitSuspend(Suspend* curr) { WASM_UNREACHABLE("not implemented"); } - void visitResume(Resume* curr) { WASM_UNREACHABLE("not implemented"); } + void visitContNew(ContNew* curr) { + if (!curr->type.isContinuation()) { + return; + } + // The type of the function reference must remain a subtype of the function + // type expected by the continuation. + self()->noteSubtype(curr->func->type.getHeapType(), + curr->type.getHeapType().getContinuation().type); + } + void visitContBind(ContBind* curr) { + if (!curr->cont->type.isContinuation()) { + return; + } + // Each of the bound arguments must remain subtypes of their expected + // parameters. + auto params = curr->cont->type.getHeapType() + .getContinuation() + .type.getSignature() + .params; + assert(curr->operands.size() <= params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], params[i]); + } + } + void visitSuspend(Suspend* curr) { + // The operands must remain subtypes of the parameters given by the tag. + auto params = + self()->getModule()->getTag(curr->tag)->type.getSignature().params; + assert(curr->operands.size() == params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], params[i]); + } + } + void processResumeHandlers(Type contType, + const ArenaVector& handlerTags, + const ArenaVector& handlerBlocks) { + auto contSig = contType.getHeapType().getContinuation().type.getSignature(); + assert(handlerTags.size() == handlerBlocks.size()); + auto& wasm = *self()->getModule(); + // Process each handler in turn. + for (Index i = 0; i < handlerTags.size(); ++i) { + if (!handlerBlocks[i]) { + // Switch handlers do not constrain types in any way. + continue; + } + auto tagSig = wasm.getTag(handlerTags[i])->type.getSignature(); + // The types sent on suspensions with this tag must remain subtypes of the + // types expected at the target block. + auto expected = self()->findBreakTarget(handlerBlocks[i])->type; + assert(tagSig.params.size() + 1 == expected.size()); + for (Index j = 0; j < tagSig.params.size(); ++j) { + self()->noteSubtype(tagSig.params[i], expected[i]); + } + auto nextSig = expected[expected.size() - 1] + .getHeapType() + .getContinuation() + .type.getSignature(); + // The types we send to the next continuation must remain subtypes of the + // types the continuation is expecting based on the tag results. + self()->noteSubtype(nextSig.params, tagSig.results); + // The types returned by the current continuation must remain subtypes of + // the types returned by the next continuation. + self()->noteSubtype(contSig.results, nextSig.results); + } + } + void visitResume(Resume* curr) { + if (!curr->cont->type.isContinuation()) { + return; + } + processResumeHandlers( + curr->cont->type, curr->handlerTags, curr->handlerBlocks); + // The types we send to the resumed continuation must remain subtypes of the + // types expected by the continuation. + auto params = curr->cont->type.getHeapType() + .getContinuation() + .type.getSignature() + .params; + assert(curr->operands.size() == params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], params[i]); + } + } void visitResumeThrow(ResumeThrow* curr) { - WASM_UNREACHABLE("not implemented"); + if (!curr->cont->type.isContinuation()) { + return; + } + processResumeHandlers( + curr->cont->type, curr->handlerTags, curr->handlerBlocks); + // The types we use to create the exception package must remain subtypes of + // the types expected by the exception tag. + auto params = + self()->getModule()->getTag(curr->tag)->type.getSignature().params; + assert(curr->operands.size() == params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], params[i]); + } } void visitStackSwitch(StackSwitch* curr) { - WASM_UNREACHABLE("not implemented"); + if (!curr->cont->type.isContinuation()) { + return; + } + // The types sent when switching must remain subtypes of the types expected + // by the target continuation. + auto contSig = + curr->cont->type.getHeapType().getContinuation().type.getSignature(); + assert(curr->operands.size() + 1 == contSig.params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], contSig.params[i]); + } + // The type returned by the target continuation must remain a subtype of the + // type the current continuation returns as indicated by the tag result. + auto currResult = + self()->getModule()->getTag(curr->tag)->type.getSignature().results; + self()->noteSubtype(contSig.results, currResult); + // The type returned by the current continuation must remain a subtype of + // the type returned by the return continuation. + auto retSig = contSig.params[contSig.params.size() - 1] + .getHeapType() + .getContinuation() + .type.getSignature(); + self()->noteSubtype(currResult, retSig.results); } }; diff --git a/test/lit/passes/unsubtyping-stack-switching.wast b/test/lit/passes/unsubtyping-stack-switching.wast new file mode 100644 index 00000000000..0d6e204cd66 --- /dev/null +++ b/test/lit/passes/unsubtyping-stack-switching.wast @@ -0,0 +1,480 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --unsubtyping --remove-unused-types -all -S -o - | filecheck %s + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (func))) + (type $super (sub (func))) + ;; CHECK: (type $sub (sub $super (func))) + (type $sub (sub $super (func))) + ;; CHECK: (type $cont (cont $super)) + (type $cont (cont $super)) + + ;; CHECK: (elem declare func $cont-new) + + ;; CHECK: (func $cont-new (type $sub) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (cont.new $cont + ;; CHECK-NEXT: (ref.func $cont-new) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-new (type $sub) + (drop + ;; This requires $sub <: $super. + (cont.new $cont + (ref.func $cont-new) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $one (func (param (ref $super)))) + (type $one (func (param (ref $super)))) + ;; CHECK: (type $none (func)) + (type $none (func)) + + ;; CHECK: (type $cont-one (cont $one)) + (type $cont-one (cont $one)) + ;; CHECK: (type $cont-none (cont $none)) + (type $cont-none (cont $none)) + + ;; CHECK: (func $cont-bind (type $none) + ;; CHECK-NEXT: (local $one (ref null $cont-one)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (cont.bind $cont-one $cont-none + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (local.get $one) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-bind + (local $one (ref null $cont-one)) + (drop + ;; This requires $sub <: $super. + (cont.bind $cont-one $cont-none + (struct.new $sub) + (local.get $one) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $2 (func (param (ref null $super)))) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (tag $e (type $2) (param (ref null $super))) + (tag $e (param (ref null $super))) + + ;; CHECK: (func $suspend (type $3) + ;; CHECK-NEXT: (suspend $e + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $suspend + ;; This requires $sub <: $super. + (suspend $e + (struct.new $sub) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func (param (ref null $super)))) + (type $f (func (param (ref null $super)))) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (func $resume-param (type $4) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (resume $cont + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-param + (local $cont (ref null $cont)) + ;; This requires $sub <: $super. + (resume $cont + (struct.new $sub) + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $4 (func (param (ref null $sub)))) + + ;; CHECK: (type $5 (func (result (ref null $super) (ref null $cont)))) + + ;; CHECK: (tag $e (type $4) (param (ref null $sub))) + (tag $e (param (ref null $sub))) + + ;; CHECK: (func $resume-label (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $l (type $5) (result (ref null $super) (ref null $cont)) + ;; CHECK-NEXT: (resume $cont (on $e $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-label + (local $cont (ref null $cont)) + (tuple.drop 2 + (block $l (result (ref null $super) (ref null $cont)) + ;; Sending the tag parameter to the block requires $sub <: $super. + (resume $cont (on $e $l) + (local.get $cont) + ) + (unreachable) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $next-f (func (param (ref null $sub)))) + (type $next-f (func (param (ref null $sub)))) + + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + ;; CHECK: (type $next-cont (cont $next-f)) + (type $next-cont (cont $next-f)) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (tag $e (type $6) (result (ref null $super))) + (tag $e (result (ref null $super))) + + ;; CHECK: (func $resume-tag (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $next-cont)) + ;; CHECK-NEXT: (resume $cont (on $e $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-tag + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $next-cont)) + ;; Based on the tag, the continuation expects a $super back. Based on + ;; the type we give the next continuation, we will send it a $sub back. + ;; This requires $sub <: $super. + (resume $cont (on $e $l) + (local.get $cont) + ) + (ref.null nocont) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func (result (ref null $sub)))) + (type $f (func (result (ref null $sub)))) + ;; CHECK: (type $next-f (func (result (ref null $super)))) + (type $next-f (func (result (ref null $super)))) + + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + ;; CHECK: (type $next-cont (cont $next-f)) + (type $next-cont (cont $next-f)) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (tag $e (type $6)) + (tag $e) + + ;; CHECK: (func $resume-result (type $6) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $next-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (resume $cont (on $e $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-result + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $next-cont)) + (drop + ;; The continuation we're resuming returns a $sub. In the next type we + ;; give it, it returns a $super. This requires $sub <: $super. + (resume $cont (on $e $l) + (local.get $cont) + ) + ) + (ref.null nocont) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $4 (func (param (ref null $super)))) + + ;; CHECK: (tag $e (type $4) (param (ref null $super))) + (tag $e (param (ref null $super))) + + ;; CHECK: (func $resume-throw-param (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (resume_throw $cont $e + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-param + (local $cont (ref null $cont)) + ;; This requires $sub <: $super + (resume_throw $cont $e + (struct.new $sub) + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $4 (func (param (ref null $sub)))) + + ;; CHECK: (type $5 (func (result (ref null $super) (ref null $cont)))) + + ;; CHECK: (tag $e (type $4) (param (ref null $sub))) + (tag $e (param (ref null $sub))) + + ;; CHECK: (func $resume-throw-label (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $l (type $5) (result (ref null $super) (ref null $cont)) + ;; CHECK-NEXT: (resume_throw $cont $e (on $e $l) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-label + (local $cont (ref null $cont)) + (tuple.drop 2 + (block $l (result (ref null $super) (ref null $cont)) + ;; Sending the tag parameter to the block requires $sub <: $super. + (resume_throw $cont $e (on $e $l) + (ref.null none) + (local.get $cont) + ) + (unreachable) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + (rec + ;; CHECK: (type $f (func (param (ref null $super) (ref null $ret-cont)))) + (type $f (func (param (ref null $super) (ref null $ret-cont)))) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $ret-f (func)) + (type $ret-f (func)) + ;; CHECK: (type $ret-cont (cont $ret-f)) + (type $ret-cont (cont $ret-f)) + ) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (tag $e (type $6)) + (tag $e) + + ;; CHECK: (func $switch-param (type $6) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (switch $cont $e + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch-param + (local $cont (ref null $cont)) + ;; The continuation expects a $super, but we send it a $sub. This requires + ;; $sub <: $super. + (switch $cont $e + (struct.new $sub) + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + (rec + ;; CHECK: (type $f (func (param (ref null $ret-cont)) (result (ref null $sub)))) + (type $f (func (param (ref null $ret-cont)) (result (ref null $sub)))) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $ret-f (func (result (ref null $super)))) + (type $ret-f (func (result (ref null $super)))) + ;; CHECK: (type $ret-cont (cont $ret-f)) + (type $ret-cont (cont $ret-f)) + ) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (type $7 (func)) + + ;; CHECK: (tag $e (type $6) (result (ref null $super))) + (tag $e (result (ref null $super))) + + ;; CHECK: (func $switch-target-result (type $7) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (switch $cont $e + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch-target-result + (local $cont (ref null $cont)) + ;; The current continuation returns a $super (as indicated in the tag) and + ;; the target continuation will return a $sub. This requires $sub <: $super. + (switch $cont $e + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + (rec + ;; CHECK: (type $f (func (param (ref null $ret-cont)) (result (ref null $sub)))) + (type $f (func (param (ref null $ret-cont)) (result (ref null $sub)))) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $ret-f (func (result (ref null $super)))) + (type $ret-f (func (result (ref null $super)))) + ;; CHECK: (type $ret-cont (cont $ret-f)) + (type $ret-cont (cont $ret-f)) + ) + + ;; CHECK: (type $6 (func (result (ref null $sub)))) + + ;; CHECK: (type $7 (func)) + + ;; CHECK: (tag $e (type $6) (result (ref null $sub))) + (tag $e (result (ref null $sub))) + + ;; CHECK: (func $switch-return-result (type $7) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (switch $cont $e + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch-return-result + (local $cont (ref null $cont)) + ;; The current continuation returns a $sub (as indicated in the tag) and + ;; after the switch it will be given the return continuation type returning + ;; $super. This requires $sub <: $super. + (switch $cont $e + (local.get $cont) + ) + ) +) From f11598bc7c4441cdd89f421042bd5249b1212926 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 May 2025 14:29:19 -0700 Subject: [PATCH 513/622] [CI] Add a no-asserts build (#7611) All our testing has asserts enabled, to catch issues. This adds a build mode for no-asserts, just to catch compile errors like #7610 --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ src/tools/wasm-reduce.cpp | 2 +- src/wasm/wasm-binary.cpp | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e54a7e494ae..a73a801160f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -455,3 +455,23 @@ jobs: run: | python check.py --binaryen-bin=out/bin lit python check.py --binaryen-bin=out/bin gtest + + # Ensures we can build in no-asserts mode (just building). + build-noasserts: + name: noasserts + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - uses: actions/checkout@v4 + with: + submodules: true + - name: install ninja + run: sudo apt-get install ninja-build + - name: cmake + run: | + mkdir -p out + cmake -S . -B out -G Ninja -DCMAKE_INSTALL_PREFIX=out/install -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DBYN_ENABLE_ASSERTIONS=OFF + - name: build + run: cmake --build out -v diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 43c188bda82..cfc8a3b1208 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -974,7 +974,7 @@ struct Reducer return maxSkip > 2; } - void visitModule(Module* curr) { + void visitModule([[maybe_unused]] Module* curr) { // The initial module given to us is our global object. As we continue to // process things here, we may replace the module, so we should never again // refer to curr. diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 4421a4f471c..c47e1cd6cc2 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -947,7 +947,7 @@ void WasmBinaryWriter::writeNames() { auto substart = startSubsection(BinaryConsts::CustomSections::Subsection::NameLocal); o << U32LEB(functionsWithLocalNames.size()); - Index emitted = 0; + [[maybe_unused]] Index emitted = 0; for (auto& [index, func] : functionsWithLocalNames) { // Pairs of (local index in IR, name). std::vector> localsWithNames; From 5d894fcb7e884e473c7d7a92bd4882519dd948e1 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 19 May 2025 16:02:38 -0700 Subject: [PATCH 514/622] Fix assertion failure in type-refining-gufa (#7612) `filterPackedDataReads` had an assertion that the read field existed, but it was possible for this to not be the case when the reference being read from was null. Avoid the assertion failure by checking for this case and filtering the read results to `none` because a read from null will never return. --- src/ir/possible-contents.cpp | 6 +++ test/lit/passes/type-refining-gufa.wast | 51 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index cf7b7d11506..21b90edee4d 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2825,6 +2825,12 @@ void Flower::filterPackedDataReads(PossibleContents& contents, return; } + // If there is no struct or array to read, no value will ever be returned. + if (ref->type.isNull()) { + contents = PossibleContents::none(); + return; + } + // We are reading data here, so the reference must be a valid struct or // array, otherwise we would never have gotten here. assert(ref->type.isRef()); diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index 7e4a5f55479..fa1f8cfb56d 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -425,3 +425,54 @@ (func $func (type $func) ) ) + +;; Regression test for an assertion failure on packed data reads from null +;; references. +(module + (type $i8 (struct (field i8))) + + ;; NRML: (type $0 (func (param (ref none)) (result i32))) + + ;; NRML: (table $0 1 funcref) + ;; GUFA: (type $0 (func (param (ref none)) (result i32))) + + ;; GUFA: (table $0 1 funcref) + ;; O3O3: (type $0 (func (param (ref none)) (result i32))) + + ;; O3O3: (table $0 1 funcref) + (table $0 1 funcref) + ;; NRML: (elem $0 (i32.const 0) $test) + ;; GUFA: (elem $0 (i32.const 0) $test) + ;; O3O3: (elem $0 (i32.const 0) $test) + (elem $0 (i32.const 0) $test) + + ;; NRML: (export "table" (table $0)) + ;; GUFA: (export "table" (table $0)) + ;; O3O3: (export "table" (table $0)) + (export "table" (table $0)) + + ;; NRML: (func $test (type $0) (param $i8 (ref none)) (result i32) + ;; NRML-NEXT: (block ;; (replaces unreachable StructGet we can't emit) + ;; NRML-NEXT: (drop + ;; NRML-NEXT: (local.get $i8) + ;; NRML-NEXT: ) + ;; NRML-NEXT: (unreachable) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (func $test (type $0) (param $i8 (ref none)) (result i32) + ;; GUFA-NEXT: (block ;; (replaces unreachable StructGet we can't emit) + ;; GUFA-NEXT: (drop + ;; GUFA-NEXT: (local.get $i8) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (unreachable) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; O3O3: (func $test (type $0) (param $0 (ref none)) (result i32) + ;; O3O3-NEXT: (unreachable) + ;; O3O3-NEXT: ) + (func $test (param $i8 (ref none)) (result i32) + (struct.get_s $i8 0 + (local.get $i8) + ) + ) +) From dfab22359f8ce0698cb1c10f9495163cdf9fd470 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 May 2025 16:38:41 -0700 Subject: [PATCH 515/622] [Strings] Fix StringLowering of reference-taken functions (#7613) We manually update the types of functions that have size 1 rec groups, because we need to update imports and exports, even though that changes the ABI - something TypeMapper does not do, but StringLowering wants. See the existing comment for details. That code updated those types, but did just themselves - it did not go through the code to find RefFuncs of them, which left them with the old type, causing errors. To fix this, tell TypeUpdater to map those types (it doesn't know it needs to automatically, since we manually handled them). --- src/passes/StringLowering.cpp | 9 +++- test/lit/passes/string-lowering_types.wast | 61 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 993b8de6f87..9a74bebbeb8 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -288,6 +288,8 @@ struct StringLowering : public StringGathering { Type nnExt = Type(HeapType::ext, NonNullable); void updateTypes(Module* module) { + TypeMapper::TypeUpdates updates; + // TypeMapper will not handle public types, but we do want to modify them as // well: we are modifying the public ABI here. We can't simply tell // TypeMapper to consider them private, as then they'd end up in the new big @@ -324,11 +326,14 @@ struct StringLowering : public StringGathering { for (auto result : func->type.getSignature().results) { results.push_back(fix(result)); } + + // In addition to doing the update, mark it in the map of updates for + // TypeMapper, so RefFuncs with this type get updated. + auto old = func->type; func->type = Signature(params, results); + updates[old] = func->type; } - TypeMapper::TypeUpdates updates; - // Strings turn into externref. updates[HeapType::string] = HeapType::ext; diff --git a/test/lit/passes/string-lowering_types.wast b/test/lit/passes/string-lowering_types.wast index 2274fcaf9f5..cdbb2dc64ed 100644 --- a/test/lit/passes/string-lowering_types.wast +++ b/test/lit/passes/string-lowering_types.wast @@ -71,3 +71,64 @@ (local (ref $private)) ) ) + +;; A function returning a string is taken by reference. We should update the +;; ref.funcs for it, both in the table and in the code, and not error. +(module + ;; CHECK: (type $0 (array (mut i16))) + + ;; CHECK: (type $1 (func (param externref externref) (result i32))) + + ;; CHECK: (type $2 (func (result (ref extern)))) + + ;; CHECK: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + + ;; CHECK: (type $4 (func (param i32) (result (ref extern)))) + + ;; CHECK: (type $5 (func (param externref externref) (result (ref extern)))) + + ;; CHECK: (type $6 (func (param externref (ref null $0) i32) (result i32))) + + ;; CHECK: (type $7 (func (param externref) (result i32))) + + ;; CHECK: (type $8 (func (param externref i32) (result i32))) + + ;; CHECK: (type $9 (func (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) + + ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (table $table 31 31 funcref) + (table $table 31 31 funcref) + + ;; CHECK: (elem $elem (i32.const 0) $func) + (elem $elem (i32.const 0) $func) + + ;; CHECK: (func $func (type $2) (result (ref extern)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $func (result (ref string)) + (drop + (ref.func $func) + ) + (unreachable) + ) +) From bcbabd8bfb843cd9c5bc9df6b57a5b5656fba8db Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 19 May 2025 17:56:20 -0700 Subject: [PATCH 516/622] [custom-descriptors] Initial support for ref.get_desc (#7614) Implement parsing and printing for binary and text as well as typing and validation for ref.get_desc. --- scripts/gen-s-parser.py | 1 + src/gen-s-parser.inc | 6 ++ src/interpreter/interpreter.cpp | 1 + src/ir/ReFinalize.cpp | 1 + src/ir/child-typer.h | 8 +++ src/ir/cost.h | 3 + src/ir/effects.h | 6 +- src/ir/possible-contents.cpp | 4 ++ src/ir/subtype-exprs.h | 1 + src/parser/contexts.h | 10 +++ src/parser/parsers.h | 11 ++++ src/passes/Print.cpp | 5 +- src/passes/TypeGeneralizing.cpp | 2 + src/wasm-binary.h | 1 + src/wasm-builder.h | 6 ++ src/wasm-delegations-fields.def | 4 ++ src/wasm-delegations.def | 1 + src/wasm-interpreter.h | 4 ++ src/wasm-ir-builder.h | 1 + src/wasm.h | 11 ++++ src/wasm/wasm-binary.cpp | 4 ++ src/wasm/wasm-ir-builder.cpp | 18 ++++++ src/wasm/wasm-stack.cpp | 9 +++ src/wasm/wasm-validator.cpp | 8 +++ src/wasm/wasm.cpp | 11 ++++ src/wasm2js.h | 4 ++ test/binaryen.js/kitchen-sink.js.txt | 38 +++++------ test/lit/basic/custom-descriptors.wast | 72 +++++++++++++++++++++ test/lit/parse-bad-get-desc.wast | 15 +++++ test/lit/validation/custom-descriptors.wast | 19 ++++++ 30 files changed, 264 insertions(+), 21 deletions(-) create mode 100644 test/lit/parse-bad-get-desc.wast create mode 100644 test/lit/validation/custom-descriptors.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index ebb7213635a..1ed7d1a189e 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -610,6 +610,7 @@ ("i31.get_u", "makeI31Get(false)"), ("ref.test", "makeRefTest()"), ("ref.cast", "makeRefCast()"), + ("ref.get_desc", "makeRefGetDesc()"), ("br_on_null", "makeBrOnNull()"), ("br_on_non_null", "makeBrOnNull(true)"), ("br_on_cast", "makeBrOnCast()"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index d7d1c5197e3..43d7f460370 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -4764,6 +4764,12 @@ switch (buf[0]) { return Ok{}; } goto parse_error; + case 'g': + if (op == "ref.get_desc"sv) { + CHECK_ERR(makeRefGetDesc(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; case 'i': { switch (buf[5]) { case '3': { diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 71ed2d031c4..33f162985c0 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -245,6 +245,7 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitCallRef(CallRef* curr) { WASM_UNREACHABLE("TODO"); } Flow visitRefTest(RefTest* curr) { WASM_UNREACHABLE("TODO"); } Flow visitRefCast(RefCast* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitRefGetDesc(RefGetDesc* curr) { WASM_UNREACHABLE("TODO"); } Flow visitBrOn(BrOn* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStructNew(StructNew* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStructGet(StructGet* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index df41546ed14..4c098323735 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -147,6 +147,7 @@ void ReFinalize::visitI31Get(I31Get* curr) { curr->finalize(); } void ReFinalize::visitCallRef(CallRef* curr) { curr->finalize(); } void ReFinalize::visitRefTest(RefTest* curr) { curr->finalize(); } void ReFinalize::visitRefCast(RefCast* curr) { curr->finalize(); } +void ReFinalize::visitRefGetDesc(RefGetDesc* curr) { curr->finalize(); } void ReFinalize::visitBrOn(BrOn* curr) { curr->finalize(); if (curr->type == Type::unreachable) { diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 346a10bad40..c195f36141a 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -857,6 +857,14 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->ref, Type(top, Nullable)); } + void visitRefGetDesc(RefGetDesc* curr, + std::optional ht = std::nullopt) { + if (!ht) { + ht = curr->ref->type.getHeapType(); + } + note(&curr->ref, Type(*ht, Nullable)); + } + void visitBrOn(BrOn* curr) { switch (curr->op) { case BrOnNull: diff --git a/src/ir/cost.h b/src/ir/cost.h index 8c15e2d2acc..5e2ebf3e9a9 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -668,6 +668,9 @@ struct CostAnalyzer : public OverriddenVisitor { CostType visitRefCast(RefCast* curr) { return CastCost + nullCheckCost(curr->ref) + visit(curr->ref); } + CostType visitRefGetDesc(RefGetDesc* curr) { + return 1 + nullCheckCost(curr->ref) + visit(curr->ref); + } CostType visitBrOn(BrOn* curr) { // BrOn of a null can be fairly fast, but anything else is a cast check. CostType base = diff --git a/src/ir/effects.h b/src/ir/effects.h index 23290c34005..58778ea0f22 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -849,7 +849,11 @@ class EffectAnalyzer { } void visitRefTest(RefTest* curr) {} void visitRefCast(RefCast* curr) { - // Traps if the ref is not null and the cast fails. + // Traps if the cast fails. + parent.implicitTrap = true; + } + void visitRefGetDesc(RefGetDesc* curr) { + // Traps if the ref is null. parent.implicitTrap = true; } void visitBrOn(BrOn* curr) { parent.breakTargets.insert(curr->name); } diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 21b90edee4d..d604302ce5d 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -703,6 +703,10 @@ struct InfoCollector void visitRefCast(RefCast* curr) { receiveChildValue(curr->ref, curr); } void visitRefTest(RefTest* curr) { addRoot(curr); } + void visitRefGetDesc(RefGetDesc* curr) { + // TODO: Do something more similar to struct.get here + addRoot(curr); + } void visitBrOn(BrOn* curr) { // TODO: optimize when possible handleBreakValue(curr); diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index e689db981e4..ca75332e85d 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -299,6 +299,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteCast(curr->ref, curr->castType); } void visitRefCast(RefCast* curr) { self()->noteCast(curr->ref, curr); } + void visitRefGetDesc(RefGetDesc* curr) {} void visitBrOn(BrOn* curr) { if (curr->op == BrOnCast || curr->op == BrOnCastFail) { self()->noteCast(curr->ref, curr->castType); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index ca46498dde7..6a303a4a533 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -736,6 +736,10 @@ struct NullInstrParserCtx { Result<> makeRefCast(Index, const std::vector&, TypeT) { return Ok{}; } + template + Result<> makeRefGetDesc(Index, const std::vector&, HeapTypeT) { + return Ok{}; + } Result<> makeBrOn(Index, const std::vector&, LabelIdxT, BrOnOp) { return Ok{}; @@ -2591,6 +2595,12 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeRefCast(type)); } + Result<> makeRefGetDesc(Index pos, + const std::vector& annotations, + HeapType type) { + return withLoc(pos, irBuilder.makeRefGetDesc(type)); + } + Result<> makeBrOn(Index pos, const std::vector& annotations, Index label, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 2e2a6da7e39..c80b8fba888 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -233,6 +233,8 @@ Result<> makeRefTest(Ctx&, Index, const std::vector&); template Result<> makeRefCast(Ctx&, Index, const std::vector&); template +Result<> makeRefGetDesc(Ctx&, Index, const std::vector&); +template Result<> makeBrOnNull(Ctx&, Index, const std::vector&, bool onFail = false); template @@ -2218,6 +2220,15 @@ makeRefCast(Ctx& ctx, Index pos, const std::vector& annotations) { return ctx.makeRefCast(pos, annotations, *type); } +template +Result<> makeRefGetDesc(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); + return ctx.makeRefGetDesc(pos, annotations, *type); +} + template Result<> makeBrOnNull(Ctx& ctx, Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 3a405e93981..8f9763427c8 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2211,7 +2211,10 @@ struct PrintExpressionContents printMedium(o, "ref.cast "); printType(curr->type); } - + void visitRefGetDesc(RefGetDesc* curr) { + printMedium(o, "ref.get_desc "); + printHeapTypeName(curr->ref->type.getHeapType()); + } void visitBrOn(BrOn* curr) { switch (curr->op) { case BrOnNull: diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 4dc0cbb444b..622a0ef046f 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -595,6 +595,8 @@ struct TransferFn : OverriddenVisitor { push(Type::none); } + void visitRefGetDesc(RefGetDesc* curr) { WASM_UNREACHABLE("TODO"); } + void visitBrOn(BrOn* curr) { // Like br_if, these instructions do different things to the stack depending // on whether the branch is taken or not. For branches that drop the tested diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 5ee7452060a..fbe151bc472 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1180,6 +1180,7 @@ enum ASTNodes { I31GetS = 0x1d, I31GetU = 0x1e, RefI31Shared = 0x1f, + RefGetDesc = 0x22, // Shared GC Opcodes diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 0393d1a0540..f7eb357c112 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -894,6 +894,12 @@ class Builder { ret->finalize(); return ret; } + RefGetDesc* makeRefGetDesc(Expression* ref) { + auto* ret = wasm.allocator.alloc(); + ret->ref = ref; + ret->finalize(); + return ret; + } BrOn* makeBrOn(BrOnOp op, Name name, Expression* ref, Type castType = Type::none) { auto* ret = wasm.allocator.alloc(); diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index e180855c467..d2165ba9cbc 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -633,6 +633,10 @@ DELEGATE_FIELD_CASE_START(RefCast) DELEGATE_FIELD_CHILD(RefCast, ref) DELEGATE_FIELD_CASE_END(RefCast) +DELEGATE_FIELD_CASE_START(RefGetDesc) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(RefGetDesc, ref) +DELEGATE_FIELD_CASE_END(RefGetDesc) + DELEGATE_FIELD_CASE_START(BrOn) DELEGATE_FIELD_INT(BrOn, op) DELEGATE_FIELD_SCOPE_NAME_USE(BrOn, name) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index 4e78de1a56d..b2491fda092 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -77,6 +77,7 @@ DELEGATE(I31Get); DELEGATE(CallRef); DELEGATE(RefTest); DELEGATE(RefCast); +DELEGATE(RefGetDesc); DELEGATE(BrOn); DELEGATE(StructNew); DELEGATE(StructGet); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index a92aa09f4b4..5c28265f2d5 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1670,6 +1670,10 @@ class ExpressionRunner : public OverriddenVisitor { trap("cast error"); WASM_UNREACHABLE("unreachable"); } + Flow visitRefGetDesc(RefGetDesc* curr) { + NOTE_ENTER("RefGetDesc"); + WASM_UNREACHABLE("unimplemented"); + } Flow visitBrOn(BrOn* curr) { NOTE_ENTER("BrOn"); // BrOnCast* uses the casting infrastructure, so handle them first. diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 16cf7a6483d..10ac7b72efb 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -209,6 +209,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { std::optional inline_ = std::nullopt); Result<> makeRefTest(Type type); Result<> makeRefCast(Type type); + Result<> makeRefGetDesc(HeapType type); Result<> makeBrOn(Index label, BrOnOp op, Type in = Type::none, diff --git a/src/wasm.h b/src/wasm.h index 2af21d933d2..70c0617a914 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -718,6 +718,7 @@ class Expression { CallRefId, RefTestId, RefCastId, + RefGetDescId, BrOnId, StructNewId, StructGetId, @@ -1622,6 +1623,16 @@ class RefCast : public SpecificExpression { Type& getCastType() { return type; } }; +class RefGetDesc : public SpecificExpression { +public: + RefGetDesc() = default; + RefGetDesc(MixedArena& allocator) {} + + Expression* ref; + + void finalize(); +}; + class BrOn : public SpecificExpression { public: BrOn() = default; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index c47e1cd6cc2..9bd4b3f82d8 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4498,6 +4498,10 @@ Result<> WasmBinaryReader::readInst() { auto [heapType, exactness] = getHeapType(); return builder.makeRefCast(Type(heapType, Nullable, exactness)); } + case BinaryConsts::RefGetDesc: { + auto type = getIndexedHeapType(); + return builder.makeRefGetDesc(type); + } case BinaryConsts::BrOnCast: case BinaryConsts::BrOnCastFail: { auto flags = getInt8(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index d738df0c404..5052310f530 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -672,6 +672,13 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } + Result<> visitRefGetDesc(RefGetDesc* curr, + std::optional ht = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitRefGetDesc(curr, ht); + return popConstrainedChildren(children); + } + Result<> visitBreak(Break* curr, std::optional labelType = std::nullopt) { std::vector children; @@ -1991,6 +1998,17 @@ Result<> IRBuilder::makeRefCast(Type type) { return Ok{}; } +Result<> IRBuilder::makeRefGetDesc(HeapType type) { + RefGetDesc curr; + if (!type.getDescriptorType()) { + return Err{"expected type with descriptor"}; + } + CHECK_ERR(ChildPopper{*this}.visitRefGetDesc(&curr, type)); + CHECK_ERR(validateTypeAnnotation(type, curr.ref)); + push(builder.makeRefGetDesc(curr.ref)); + return Ok{}; +} + Result<> IRBuilder::makeBrOn( Index label, BrOnOp op, Type in, Type out, std::optional likely) { BrOn curr; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 889ab5e119f..d0065909905 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2280,6 +2280,15 @@ void BinaryInstWriter::visitRefCast(RefCast* curr) { parent.writeHeapType(curr->type.getHeapType(), curr->type.getExactness()); } +void BinaryInstWriter::visitRefGetDesc(RefGetDesc* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::RefGetDesc); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); +} + void BinaryInstWriter::visitBrOn(BrOn* curr) { switch (curr->op) { case BrOnNull: diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 0285e1e0c3f..67fac8f3fb9 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -495,6 +495,7 @@ struct FunctionValidator : public WalkerPass> { void visitI31Get(I31Get* curr); void visitRefTest(RefTest* curr); void visitRefCast(RefCast* curr); + void visitRefGetDesc(RefGetDesc* curr); void visitBrOn(BrOn* curr); void visitStructNew(StructNew* curr); void visitStructGet(StructGet* curr); @@ -2967,6 +2968,13 @@ void FunctionValidator::visitRefCast(RefCast* curr) { } } +void FunctionValidator::visitRefGetDesc(RefGetDesc* curr) { + shouldBeTrue( + getModule()->features.hasCustomDescriptors(), + curr, + "ref.get_desc requires custom descriptors [--enable-custom-descriptors]"); +} + void FunctionValidator::visitBrOn(BrOn* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 655a3156382..aa91b29da30 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1071,6 +1071,17 @@ void RefCast::finalize() { type = Type::getGreatestLowerBound(type, ref->type); } +void RefGetDesc::finalize() { + if (ref->type == Type::unreachable) { + type = Type::unreachable; + return; + } + + auto desc = ref->type.getHeapType().getDescriptorType(); + assert(desc); + type = Type(*desc, NonNullable, ref->type.getExactness()); +} + void BrOn::finalize() { if (ref->type == Type::unreachable) { type = Type::unreachable; diff --git a/src/wasm2js.h b/src/wasm2js.h index f59180a8237..18b10a87b3f 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2304,6 +2304,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitRefGetDesc(RefGetDesc* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitBrOn(BrOn* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 45c7417dcda..5f9dab4d95f 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -92,25 +92,25 @@ I31GetId: 60 CallRefId: 61 RefTestId: 62 RefCastId: 63 -BrOnId: 64 -StructNewId: 65 -StructGetId: 66 -StructSetId: 67 -ArrayNewId: 70 -ArrayNewFixedId: 73 -ArrayGetId: 74 -ArraySetId: 75 -ArrayLenId: 76 -ArrayCopy: 77 -RefAs: 81 -StringNew: 82 -StringConst: 83 -StringMeasure: 84 -StringEncode: 85 -StringConcat: 86 -StringEq: 87 -StringWTF16Get: 88 -StringSliceWTF: 89 +BrOnId: 65 +StructNewId: 66 +StructGetId: 67 +StructSetId: 68 +ArrayNewId: 71 +ArrayNewFixedId: 74 +ArrayGetId: 75 +ArraySetId: 76 +ArrayLenId: 77 +ArrayCopy: 78 +RefAs: 82 +StringNew: 83 +StringConst: 84 +StringMeasure: 85 +StringEncode: 86 +StringConcat: 87 +StringEq: 88 +StringWTF16Get: 89 +StringSliceWTF: 90 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/lit/basic/custom-descriptors.wast b/test/lit/basic/custom-descriptors.wast index dfec0b22413..ab3b70f7ab0 100644 --- a/test/lit/basic/custom-descriptors.wast +++ b/test/lit/basic/custom-descriptors.wast @@ -36,12 +36,65 @@ ) + ;; CHECK-TEXT: (type $5 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-TEXT: (global $g (ref null $described) (ref.null none)) + ;; CHECK-BIN: (type $5 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-BIN: (global $g (ref null $described) (ref.null none)) (global $g (ref null $described) (ref.null none)) ;; CHECK-TEXT: (global $shared (ref null $shared-describing) (ref.null (shared none))) ;; CHECK-BIN: (global $shared (ref null $shared-describing) (ref.null (shared none))) (global $shared (ref null $shared-describing) (ref.null (shared none))) + + ;; CHECK-TEXT: (func $ref-get-desc (type $5) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $l1 (result (ref $middle)) + ;; CHECK-TEXT-NEXT: (ref.get_desc $described + ;; CHECK-TEXT-NEXT: (local.get $described) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $l2 (result (ref (exact $describing))) + ;; CHECK-TEXT-NEXT: (ref.get_desc $middle + ;; CHECK-TEXT-NEXT: (local.get $middle-exact) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-get-desc (type $5) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block (result (ref $middle)) + ;; CHECK-BIN-NEXT: (ref.get_desc $described + ;; CHECK-BIN-NEXT: (local.get $described) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block (result (ref (exact $describing))) + ;; CHECK-BIN-NEXT: (ref.get_desc $middle + ;; CHECK-BIN-NEXT: (local.get $middle-exact) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $ref-get-desc (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + (drop + (block $l1 (result (ref $middle)) + (ref.get_desc $described + (local.get $described) + ) + ) + ) + (drop + (block $l2 (result (ref (exact $describing))) + (ref.get_desc $middle + (local.get $middle-exact) + ) + ) + ) + ) ) ;; CHECK-BIN-NODEBUG: (rec ;; CHECK-BIN-NODEBUG-NEXT: (type $0 (descriptor $1 (struct))) @@ -55,6 +108,25 @@ ;; CHECK-BIN-NODEBUG: (type $4 (shared (describes $3 (struct)))) +;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref null $0) (ref null (exact $1))))) + ;; CHECK-BIN-NODEBUG: (global $global$0 (ref null $0) (ref.null none)) ;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $4) (ref.null (shared none))) + +;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (ref.get_desc $0 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block (result (ref (exact $2))) +;; CHECK-BIN-NODEBUG-NEXT: (ref.get_desc $1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/parse-bad-get-desc.wast b/test/lit/parse-bad-get-desc.wast new file mode 100644 index 00000000000..9f2ca811c9b --- /dev/null +++ b/test/lit/parse-bad-get-desc.wast @@ -0,0 +1,15 @@ +;; RUN: not wasm-opt %s -S -o - 2>&1 | filecheck %s + +;; CHECK: 10:7: error: expected type with descriptor + +(module + (type $no-descriptor (struct)) + + (func $ref.get_desc-no-descriptor (param (ref null $no-descriptor)) + (drop + (ref.get_desc $no-descriptor + (local.get 0) + ) + ) + ) +) diff --git a/test/lit/validation/custom-descriptors.wast b/test/lit/validation/custom-descriptors.wast new file mode 100644 index 00000000000..f304e792506 --- /dev/null +++ b/test/lit/validation/custom-descriptors.wast @@ -0,0 +1,19 @@ +;; Test that custom descriptors instructions are validated correctly. + +;; RUN: not wasm-opt %s -all 2>&1 | filecheck %s + +;; CHECK: [wasm-validator error in function ref.get_desc-inexact] function body type must match, if function returns + +(module + (rec + (type $described (descriptor $describing (struct))) + (type $describing (describes $described (struct))) + ) + + (func $ref.get_desc-inexact (param (ref null $described)) (result (ref (exact $describing))) + ;; The result should be inexact because the input is inexact. + (ref.get_desc $described + (local.get 0) + ) + ) +) From a19040dfcb71a9aeb4e0fda0dc40ae9ddf82b693 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 May 2025 12:17:10 -0700 Subject: [PATCH 517/622] [Custom Descriptors] Fix exact types in RemoveUnusedBrs handling of SuccessOnlyIfNonNull (#7615) In that case we dropped exactness, leading to an error on the new testcase after optimization. Fix is to use .with(Nullable) --- src/passes/RemoveUnusedBrs.cpp | 9 ++++- .../passes/remove-unused-brs-exact-only.wast | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/remove-unused-brs-exact-only.wast diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index a6a94aa26e0..6f20cfd63af 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -1008,8 +1008,13 @@ struct RemoveUnusedBrs : public WalkerPass> { // (...) // (ref.null bot) // ) - curr->ref = maybeCast( - curr->ref, Type(curr->getSentType().getHeapType(), Nullable)); + // + // A RefCast is added in some cases, but this is still generally + // worth doing as the BrOnNonNull and the appended null may end up + // optimized with surrounding code. + auto* casted = + maybeCast(curr->ref, curr->getSentType().with(Nullable)); + curr->ref = casted; curr->op = BrOnNonNull; curr->castType = Type::none; curr->type = Type::none; diff --git a/test/lit/passes/remove-unused-brs-exact-only.wast b/test/lit/passes/remove-unused-brs-exact-only.wast new file mode 100644 index 00000000000..e096f9517a6 --- /dev/null +++ b/test/lit/passes/remove-unused-brs-exact-only.wast @@ -0,0 +1,38 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --remove-unused-brs -S -o - | filecheck %s + +;; Like "remove-unused-brs-exact.wast", but with exact types in the input so we +;; cannot have a NO_CD mode. + +(module + ;; CHECK: (type $foo (struct)) + (type $foo (struct)) + + ;; CHECK: (func $br_on_cast_to_non_null (type $1) (param $foo (ref null $foo)) (result (ref (exact $foo))) + ;; CHECK-NEXT: (block $block (result (ref (exact $foo))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (br_on_non_null $block + ;; CHECK-NEXT: (ref.cast (ref null (exact $foo)) + ;; CHECK-NEXT: (local.get $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_to_non_null (param $foo (ref null $foo)) (result (ref (exact $foo))) + ;; We can simplify the br_on_cast to a br_on_non_null plus a cast. + (block $block (result (ref (exact $foo))) + (drop + (br_on_cast $block (ref null $foo) (ref (exact $foo)) + (local.get $foo) + ) + ) + (unreachable) + ) + ) +) From 7607db6543028cbb504d5db921be6ad8e11bd6bf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 May 2025 08:31:47 -0700 Subject: [PATCH 518/622] Fix IRBuilder location tracking of If starts (#7617) The old code forgot the If scope when it started the Else, so any if-else would get a binary location span that started in the else (and ends in the right place). To fix this, note the if start location when we have it, and don't trample. Fixes branch hints on if-elses. --- src/wasm/wasm-ir-builder.cpp | 17 ++++- test/lit/branch-hinting.wast | 122 +++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 5052310f530..1fb39b14ec6 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -151,9 +151,18 @@ void IRBuilder::push(Expression* expr) { applyDebugLoc(expr); if (binaryPos && func && lastBinaryPos != *binaryPos) { - func->expressionLocations[expr] = + auto span = BinaryLocations::Span{BinaryLocation(lastBinaryPos - codeSectionOffset), BinaryLocation(*binaryPos - codeSectionOffset)}; + // Some expressions already have their start noted, and we are just seeing + // their last segment (like an Else). + auto [iter, inserted] = func->expressionLocations.insert({expr, span}); + if (!inserted) { + // Just update the end. + iter->second.end = span.end; + // The true start from before is before the start of the current segment. + assert(iter->second.start < span.start); + } lastBinaryPos = *binaryPos; } @@ -933,6 +942,10 @@ Result<> IRBuilder::visitElse() { if (binaryPos && func) { func->delimiterLocations[iff][BinaryLocations::Else] = lastBinaryPos - codeSectionOffset; + + // Note the start of the if (which will be lost as the If is closed and the + // Else begins, but the if spans them both). + func->expressionLocations[iff].start = scope.startPos - codeSectionOffset; } return pushScope(ScopeCtx::makeElse(std::move(scope))); @@ -980,6 +993,8 @@ Result<> IRBuilder::visitCatch(Name tag) { if (binaryPos && func) { auto& delimiterLocs = func->delimiterLocations[tryy]; delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; + // TODO: As in visitElse, we likely need to stash the Try start. Here we + // also need to account for multiple catches. } CHECK_ERR(pushScope(ScopeCtx::makeCatch(std::move(scope), tryy))); diff --git a/test/lit/branch-hinting.wast b/test/lit/branch-hinting.wast index 07277f0bc67..ba8f3716c3f 100644 --- a/test/lit/branch-hinting.wast +++ b/test/lit/branch-hinting.wast @@ -259,6 +259,128 @@ ) ) + ;; CHECK: (func $branch-hints-if-else (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $branch-hints-if-else (type $0) (param $x i32) + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\01") + ;; RTRIP-NEXT: (if + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: (then + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (else + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; SRCMP: (func $branch-hints-if-else (type $0) (param $x i32) + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\01") + ;; SRCMP-NEXT: (if + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: (then + ;; SRCMP-NEXT: (drop + ;; SRCMP-NEXT: (i32.const 1) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: (else + ;; SRCMP-NEXT: (drop + ;; SRCMP-NEXT: (i32.const 0) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + (func $branch-hints-if-else (param $x i32) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (drop + (i32.const 1) + ) + ) + (else + (drop + (i32.const 0) + ) + ) + ) + ) + + ;; CHECK: (func $branch-hints-if-else-result (type $0) (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $branch-hints-if-else-result (type $0) (param $x i32) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (@metadata.code.branch_hint "\01") + ;; RTRIP-NEXT: (if (result i32) + ;; RTRIP-NEXT: (local.get $x) + ;; RTRIP-NEXT: (then + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (else + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; SRCMP: (func $branch-hints-if-else-result (type $0) (param $x i32) + ;; SRCMP-NEXT: (drop + ;; SRCMP-NEXT: (@metadata.code.branch_hint "\01") + ;; SRCMP-NEXT: (if (result i32) + ;; SRCMP-NEXT: (local.get $x) + ;; SRCMP-NEXT: (then + ;; SRCMP-NEXT: (i32.const 1) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: (else + ;; SRCMP-NEXT: (i32.const 0) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + ;; SRCMP-NEXT: ) + (func $branch-hints-if-else-result (param $x i32) + (drop + (@metadata.code.branch_hint "\01") + (if (result i32) + (local.get $x) + (then + (i32.const 1) + ) + (else + (i32.const 0) + ) + ) + ) + ) + ;; CHECK: (func $branch-hints-br_on (type $1) (param $x anyref) ;; CHECK-NEXT: (block $out ;; CHECK-NEXT: (drop From cdcf62b838ecd299c0c999c92dcd8e8ea6434929 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 23 May 2025 07:21:00 -0700 Subject: [PATCH 519/622] Fix missing Exact type ReFinalization in TypeMerging (#7619) If we merge siblings to their parent, they may end up equal: (select (result A)) (B1) (B2) ) => (select (result (exact A))) ;; result is now exact (A) ;; both are (A) ;; now A ) In such cases we must refinalize, as the LUB is now exact. --- src/passes/TypeMerging.cpp | 19 ++++++++++-- test/lit/passes/type-merging-exact.wast | 41 +++++++++++++++++++++++++ test/lit/passes/type-merging.wast | 2 +- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index e072ea38b45..1f2c892f1d8 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -245,9 +245,22 @@ void TypeMerging::run(Module* module_) { // // If we merge siblings, we also need to refinalize because the LUB of merged // siblings is the merged type rather than their common supertype after the - // merge. - bool refinalize = false; - merge(Supertypes); + // merge. This can happen in merge(Siblings), but also in merge(Supertypes), + // since we may end up merging B1 to its super A, and also B2 to the same + // super A, ending up with B1 and B2 now equal - in that case the siblings are + // now both equal (to the parent), allowing an exact LUB: + // + // (select (result A)) + // (B1) + // (B2) + // ) + // => + // (select (result (exact A))) ;; result is now exact + // (A) ;; both are + // (A) ;; now A + // ) + // + bool refinalize = merge(Supertypes); for (int i = 0; i < MAX_ITERATIONS; ++i) { if (!merge(Siblings)) { break; diff --git a/test/lit/passes/type-merging-exact.wast b/test/lit/passes/type-merging-exact.wast index 1ef70067e3c..f9a21804f08 100644 --- a/test/lit/passes/type-merging-exact.wast +++ b/test/lit/passes/type-merging-exact.wast @@ -131,3 +131,44 @@ ) ) ) + +;; Merge two siblings to their parent. We must refinalize here. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (array i8))) + (type $A (sub (array i8))) + (type $B1 (sub $A (array i8))) + (type $B2 (sub $A (array i8))) + ) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result (ref (exact $A))) + ;; CHECK-NEXT: (array.new_default $A + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new_default $A + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; B1 and B2 will turn into A, after which the select can be refined to an + ;; exact type. + (drop + (select (result (ref $A)) + (array.new_default $B1 + (i32.const 0) + ) + (array.new_default $B2 + (i32.const 1) + ) + (i32.const 2) + ) + ) + ) +) + diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast index 8157fce12fa..c009b811bb0 100644 --- a/test/lit/passes/type-merging.wast +++ b/test/lit/passes/type-merging.wast @@ -890,7 +890,7 @@ ) ;; CHECK: (func $test (type $D) (result (ref any) (ref $B)) - ;; CHECK-NEXT: (block $l (type $A) (result (ref any) (ref $B)) + ;; CHECK-NEXT: (block $l ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From df4285ec528fcdb5c40c3c4f7d81abc0f94b0952 Mon Sep 17 00:00:00 2001 From: mcbarton Date: Fri, 23 May 2025 16:45:52 +0100 Subject: [PATCH 520/622] Add Windows 11 arm to CI (#7517) Windows arm Github runners are now available for free for public repositories. --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/create_release.yml | 16 +++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a73a801160f..f0141e663f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest, windows-11-arm] steps: - uses: actions/setup-python@v5 with: @@ -77,7 +77,7 @@ jobs: - name: install ninja (win) run: choco install ninja - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os, 'windows') - name: install v8 run: | @@ -98,7 +98,7 @@ jobs: - name: cmake (win) # -G "Visual Studio 15 2017" run: cmake -S . -B out -DCMAKE_INSTALL_PREFIX=out/install - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os, 'windows') - name: build run: cmake --build out --config Release -v @@ -108,7 +108,7 @@ jobs: - name: strip run: find out/install/ -type f -perm -u=x -exec strip -x {} + - if: matrix.os != 'windows-latest' + if: ${{ !startsWith(matrix.os, 'windows') }} - name: Upload artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 143f0300451..7d6bb6ed74b 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -16,7 +16,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-latest, windows-latest] + os: [macos-latest, windows-latest, windows-11-arm] defaults: run: shell: bash @@ -31,7 +31,7 @@ jobs: - name: install ninja (win) run: choco install ninja - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os, 'windows') - name: mkdir run: mkdir -p out @@ -47,6 +47,11 @@ jobs: run: cmake -S . -B out -DCMAKE_INSTALL_PREFIX=out/install if: matrix.os == 'windows-latest' + - name: cmake (win arm64) + # -G "Visual Studio 15 2017" + run: cmake -S . -B out -DCMAKE_INSTALL_PREFIX=out-arm64/install + if: matrix.os == 'windows-11-arm' + - name: build run: cmake --build out -v --config Release --target install @@ -56,7 +61,7 @@ jobs: - name: strip run: find out*/install/ -type f -perm -u=x -exec strip -x {} + - if: matrix.os != 'windows-latest' + if: ${{ !startsWith(matrix.os, 'windows') }} - name: archive id: archive @@ -73,11 +78,12 @@ jobs: cmake -E sha256sum $TARBALL > $SHASUM echo "TARBALL=$TARBALL" >> $GITHUB_OUTPUT echo "SHASUM=$SHASUM" >> $GITHUB_OUTPUT + if: matrix.os != 'windows-11-arm' - name: archive-arm64 id: archive-arm64 run: | - OSNAME=$(echo ${{ matrix.os }} | sed 's/-latest//') + OSNAME=$(echo ${{ matrix.os }} | sed 's/-latest//' | sed 's/-11-arm//') VERSION=$GITHUB_REF_NAME PKGNAME="binaryen-$VERSION-arm64-$OSNAME" TARBALL=$PKGNAME.tar.gz @@ -89,7 +95,7 @@ jobs: cmake -E sha256sum $TARBALL > $SHASUM echo "TARBALL=$TARBALL" >> $GITHUB_OUTPUT echo "SHASUM=$SHASUM" >> $GITHUB_OUTPUT - if: matrix.os == 'macos-latest' + if: ${{ matrix.os == 'macos-latest' || matrix.os == 'windows-11-arm' }} - name: upload tarball uses: softprops/action-gh-release@v1 From 1625f5b569a422d8135826d8ca5008c3e896e1f7 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 23 May 2025 10:12:35 -0700 Subject: [PATCH 521/622] Export std hash template specializations from dylibs (#7618) Use explicit visibility to force std::hash template specializations to be exported from libbinaryen dynamic library. Currently we are just using the default flags, causing most of our symbols to have "default" visibility, meaning they can all be used from the tool sources. However a recent libc++ change caused functions declared in namespace std (e.g. hash template specializations) to have hidden visibility, inherited. from the namespece (see https://github.com/llvm/llvm-project/pull/131156). So this macro forces them to be exported. Currently it is only applied to the hash specializations that are defined in libbinaryen and used in the tool code. In the future if we want to compile libbinaryen with -fvisibility-hidden or use a DLL on Windows, we'll need to explicitly annotate everything we want to export. But that's probably only useful if we want external users to link against libbinaryen.so (currently we don't; it's only used to reduce our install size). --- CMakeLists.txt | 4 ++++ src/compiler-support.h | 19 +++++++++++++++++++ src/wasm-type-shape.h | 3 ++- src/wasm-type.h | 17 +++++++++-------- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16e2add800f..0279e7d3e43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -462,6 +462,10 @@ if(BUILD_STATIC_LIB) else() message(STATUS "Building libbinaryen as shared library.") add_library(binaryen SHARED) + if(LINUX) + # Disable interposition and resolve Binaryen symbols locally. + add_link_flag("-Bsymbolic") + endif() endif() target_link_libraries(binaryen Threads::Threads) if(BUILD_LLVM_DWARF) diff --git a/src/compiler-support.h b/src/compiler-support.h index a3485919086..50883868335 100644 --- a/src/compiler-support.h +++ b/src/compiler-support.h @@ -31,4 +31,23 @@ #define WASM_BUILTIN_UNREACHABLE __assume(false) #endif +// Forces symbols to be exported from libbinaryen dynamic library. Currently +// we are just using the default flags, causing most of our symbols to have +// "default" visibility, meaning they can all be used from the tool sources. +// However a recent libc++ change caused functions declared in namespace std +// (e.g. hash template specializations) to have hidden visibility, inherited. +// from the namespece (see https://github.com/llvm/llvm-project/pull/131156). +// So this macro forces them to be exported. Currently it is only applied to +// the hash specializations that are defined in libbinaryen and used in the +// tool code. In the future if we want to compile libbinaryen with +// -fvisibility-hidden or use a DLL on Windows, we'll need +// to explicitly annotate everything we want to export. But that's probably +// only useful if we want external users to link against libbinaryen.so +// (currently we don't; it's only used to reduce our install size). +#if defined(__ELF__) || defined(__MACH__) +#define DLLEXPORT [[gnu::visibility("default")]] +#else +#define DLLEXPORT +#endif + #endif // wasm_compiler_support_h diff --git a/src/wasm-type-shape.h b/src/wasm-type-shape.h index e72f28dd530..3fe43b7f36e 100644 --- a/src/wasm-type-shape.h +++ b/src/wasm-type-shape.h @@ -20,6 +20,7 @@ #include #include +#include "compiler-support.h" #include "wasm-features.h" #include "wasm-type.h" @@ -74,7 +75,7 @@ namespace std { template<> class hash { public: - size_t operator()(const wasm::RecGroupShape& shape) const; + DLLEXPORT size_t operator()(const wasm::RecGroupShape& shape) const; }; } // namespace std diff --git a/src/wasm-type.h b/src/wasm-type.h index b5e9a88a3c6..d10893abf61 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -25,6 +25,7 @@ #include #include +#include "compiler-support.h" #include "support/index.h" #include "support/name.h" #include "support/parent_index_iterator.h" @@ -1005,35 +1006,35 @@ namespace std { template<> class hash { public: - size_t operator()(const wasm::Type&) const; + DLLEXPORT size_t operator()(const wasm::Type&) const; }; template<> class hash { public: - size_t operator()(const wasm::Signature&) const; + DLLEXPORT size_t operator()(const wasm::Signature&) const; }; template<> class hash { public: - size_t operator()(const wasm::Continuation&) const; + DLLEXPORT size_t operator()(const wasm::Continuation&) const; }; template<> class hash { public: - size_t operator()(const wasm::Field&) const; + DLLEXPORT size_t operator()(const wasm::Field&) const; }; template<> class hash { public: - size_t operator()(const wasm::Struct&) const; + DLLEXPORT size_t operator()(const wasm::Struct&) const; }; template<> class hash { public: - size_t operator()(const wasm::Array&) const; + DLLEXPORT size_t operator()(const wasm::Array&) const; }; template<> class hash { public: - size_t operator()(const wasm::HeapType&) const; + DLLEXPORT size_t operator()(const wasm::HeapType&) const; }; template<> class hash { public: - size_t operator()(const wasm::RecGroup&) const; + DLLEXPORT size_t operator()(const wasm::RecGroup&) const; }; } // namespace std From 3da0ceb520ba0d7f14b27771b0c73a5313d1be47 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 23 May 2025 13:35:32 -0700 Subject: [PATCH 522/622] [GC] Add missing ReFinalize to GlobalStructInference (#7620) --- src/passes/GlobalStructInference.cpp | 7 +++++ test/lit/passes/gsi.wast | 38 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 387e2c94277..58cfc4dcfba 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -447,6 +447,13 @@ struct GlobalStructInference : public Pass { ret = get; } + // If the type is more refined, we must refinalize. For example, we + // might have a struct.get that normally returns anyref, and know that + // field contains null, so we return nullref. + if (ret->type != curr->type) { + refinalize = true; + } + // This value replaces the struct.get, so it should have the same // source location. debuginfo::copyOriginalToReplacement(curr, ret, getFunction()); diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast index 72149873eab..7ff7bd4cc6d 100644 --- a/test/lit/passes/gsi.wast +++ b/test/lit/passes/gsi.wast @@ -2241,3 +2241,41 @@ ) ) ) + +;; The field has type nullable $A, but we can infer it contains null (as both +;; globals do). This leads to refining the types of the parents. +(module + ;; CHECK: (type $A (struct (field (ref null $A)))) + (type $A (struct (field (ref null $A)))) + + ;; CHECK: (type $1 (func (param (ref $A)) (result (ref $A)))) + + ;; CHECK: (global $global$1 (ref (exact $A)) (struct.new_default $A)) + (global $global$1 (ref (exact $A)) (struct.new_default $A)) + + ;; CHECK: (global $global$2 (ref (exact $A)) (struct.new_default $A)) + (global $global$2 (ref (exact $A)) (struct.new_default $A)) + + ;; CHECK: (func $func (type $1) (param $A (ref $A)) (result (ref $A)) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func (param $A (ref $A)) (result (ref $A)) + ;; The block's result should refine. + (block (result (ref $A)) + (ref.as_non_null + (struct.get $A 0 + (local.get $A) + ) + ) + ) + ) +) From c9ec43dd8e257c8869c91cb14d93e318f7512104 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Sat, 24 May 2025 04:39:10 +0800 Subject: [PATCH 523/622] OptimizeInstructions: Max bits on left and constant on right have no overlap => 0 (#7505) E.g. (i32.and (i32.eqz (i32.load $0 (i32.const 0) ) ) (i32.const 4) ) => (i32.const 0) Fixes: #7481 --- src/passes/OptimizeInstructions.cpp | 38 ++++ .../lit/passes/optimize-instructions-mvp.wast | 210 +++++++++++++++++- 2 files changed, 239 insertions(+), 9 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 30a1fbeefee..65b573c2103 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -839,6 +839,9 @@ struct OptimizeInstructions if (auto* ret = combineAnd(curr)) { return replaceCurrent(ret); } + if (auto* ret = optimizeAndNoOverlappingBits(curr)) { + return replaceCurrent(ret); + } } // for or, we can potentially combine if (curr->op == OrInt32) { @@ -852,6 +855,12 @@ struct OptimizeInstructions return replaceCurrent(ret); } } + if (curr->op == AndInt64) { + if (auto* ret = optimizeAndNoOverlappingBits(curr)) { + return replaceCurrent(ret); + } + } + // relation/comparisons allow for math optimizations if (curr->isRelational()) { if (auto* ret = optimizeRelational(curr)) { @@ -3566,6 +3575,35 @@ struct OptimizeInstructions return nullptr; } + // Bitwise AND of a value with bits in [0, n) and a constant with no bits in + // [0, n) always yields 0. Replace with zero. + Expression* optimizeAndNoOverlappingBits(Binary* curr) { + assert(curr->op == AndInt32 || curr->op == AndInt64); + + auto* left = curr->left; + auto* right = curr->right; + + // Check left's max bits and right is constant. + auto leftMaxBits = Bits::getMaxBits(left, this); + uint64_t maskLeft; + if (!left->type.isNumber() || leftMaxBits == left->type.getByteSize() * 8) { + // If we know nothing useful about the bits on the left, + // we cannot optimize. + return nullptr; + } else { + maskLeft = (1ULL << leftMaxBits) - 1; + } + if (auto* c = right->dynCast()) { + uint64_t constantValue = c->value.getInteger(); + if ((constantValue & maskLeft) == 0) { + return getDroppedChildrenAndAppend( + curr, LiteralUtils::makeZero(left->type, *getModule())); + } + } + + return nullptr; + } + // We can combine `or` operations, e.g. // (x > y) | (x == y) ==> x >= y // (x != 0) | (y != 0) ==> (x | y) != 0 diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 340cce32dcf..1f2bdde5267 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -1770,9 +1770,12 @@ ) ;; CHECK: (func $canonicalize-consts-vars (param $x i32) (param $y i32) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.and - ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -1810,6 +1813,7 @@ ;; CHECK-NEXT: ) (func $canonicalize-consts-vars (param $x i32) (param $y i32) (drop (i32.and (i32.const 1) (i32.const 2))) + (drop (i32.and (i32.const 2) (i32.const 1))) (drop (i32.and (local.get $x) (i32.const 3))) (drop (i32.and (i32.const 4) (local.get $x))) (drop (i32.and (local.get $x) (local.get $y))) @@ -3193,18 +3197,18 @@ (i32.const 24) ) ) - ;; CHECK: (func $sext-24-and-127-128 (result i32) + ;; CHECK: (func $sext-24-and-127-unknown (param $x i32) (result i32) ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 127) - ;; CHECK-NEXT: (i32.const 128) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $sext-24-and-127-128 (result i32) + (func $sext-24-and-127-unknown (param $x i32) (result i32) (i32.shr_s (i32.shl (i32.and ;; takes the min, here it is ok (i32.const 127) - (i32.const 128) + (local.get $x) ) (i32.const 24) ) @@ -7300,7 +7304,7 @@ ) ) ) - ;; CHECK: (func $de-morgan-2 (param $x i32) (param $y i32) + ;; CHECK: (func $de-morgan-2 (param $x i32) (param $y i32) (param $z i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (i32.or @@ -7350,7 +7354,9 @@ ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -7359,7 +7365,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $de-morgan-2 (param $x i32) (param $y i32) + (func $de-morgan-2 (param $x i32) (param $y i32) (param $z i64) (drop (i32.and (i32.eqz (local.get $x)) (i32.eqz (local.get $y))) ) @@ -7376,7 +7382,7 @@ (i32.and (local.get $x) (i32.eqz (local.get $y))) ) (drop - (i32.and (i32.eqz (local.get $x)) (i32.wrap_i64 (i64.const 2))) + (i32.and (i32.eqz (local.get $x)) (i32.wrap_i64 (local.get $z))) ) (drop (i32.and (i32.wrap_i64 (i64.const 1)) (i32.eqz (local.get $y))) @@ -18218,4 +18224,190 @@ (i32.const 1) ) ) + ;; CHECK: (func $add-op-no-overlapping-bits-corner-case (param $0 i32) (param $1 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $add-op-no-overlapping-bits-corner-case (param $0 i32) (param $1 i64) + ;; optimizeAndNoOverlappingBits simplifies AND operations where + ;; - the left value covers bits in [0, n) + ;; - the right operand is a constant with no bits in [0, n) + ;; Result is simplified to zero. + ;; No bit overlaps, so we optimize. + (drop + (i32.and + (i32.const 1) + (i32.const 2) + ) + ) + (drop + (i64.and + (i64.const 1) + (i64.const 2) + ) + ) + (drop + (i64.and + (i64.const 0x7fffffff) + (i64.const 0x80000000) + ) + ) + ;; We know something (but not constant) about the bits + ;; on the left, so we can optimize. + (drop + (i32.and + (i32.and + (local.get $0) + (i32.const 0xff) + ) + (i32.const 0xff00) + ) + ) + (drop + (i64.and + (i64.and + (local.get $1) + (i64.const 0xff) + ) + (i64.const 0xff00) + ) + ) + ) + ;; CHECK: (func $add-op-overlapping-bits-corner-case + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 2147483647) + ;; CHECK-NEXT: (i32.const -2147483647) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.const 2147483647) + ;; CHECK-NEXT: (i64.const 2147483649) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $add-op-overlapping-bits-corner-case + ;; One bit overlaps, so we cannot optimize. + (drop + (i32.and + (i32.const 0x7fffffff) + (i32.const 0x80000001) + ) + ) + (drop + (i64.and + (i64.const 0x7fffffff) + (i64.const 0x80000001) + ) + ) + ) + ;; CHECK: (func $add-op-no-overlapping-skipped + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.const 2147483648) + ;; CHECK-NEXT: (i64.const 2147483647) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $add-op-no-overlapping-skipped + ;; Both-constant cases which do not meet the condition (mask of left has no + ;; overlap with right) is left for Precompute. + (drop + (i32.and + (i32.const 2) + (i32.const 1) + ) + ) + (drop + (i64.and + (i64.const 2) + (i64.const 1) + ) + ) + (drop + (i64.and + (i64.const 0x80000000) + (i64.const 0x7fffffff) + ) + ) + ) + ;; CHECK: (func $add-op-unknown-useful (param $0 i32) (param $1 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const -2147483648) + ;; CHECK-NEXT: (i32.const 2147483647) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.const -9223372036854775808) + ;; CHECK-NEXT: (i64.const 9223372036854775807) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $add-op-unknown-useful (param $0 i32) (param $1 i64) + ;; We know nothing useful about the bits on the left, so we cannot optimize. + (drop + (i32.and + (i32.const 0x80000000) + (i32.const 0x7fffffff) + ) + ) + (drop + (i64.and + (i64.const 0x8000000000000000) + (i64.const 0x7fffffffffffffff) + ) + ) + (drop + (i32.and + (local.get $0) + (i32.const 1) + ) + ) + (drop + (i64.and + (local.get $1) + (i64.const 1) + ) + ) + ) ) From b109a1e103424cde42a04671fb2dcdc6a009f5b7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 May 2025 12:04:08 -0700 Subject: [PATCH 524/622] [SharedEverything] Add array.atomic.get/set (#7621) --- scripts/gen-s-parser.py | 4 +++ src/binaryen-c.cpp | 17 +++++---- src/gen-s-parser.inc | 39 ++++++++++++++++++++ src/ir/possible-contents.cpp | 13 ++++--- src/parser/contexts.h | 17 +++++---- src/parser/parsers.h | 35 ++++++++++++++++-- src/passes/Print.cpp | 18 +++++++--- src/tools/fuzzing/fuzzing.cpp | 11 +++--- src/tools/wasm-ctor-eval.cpp | 6 ++-- src/wasm-binary.h | 4 +++ src/wasm-builder.h | 9 +++-- src/wasm-delegations-fields.def | 2 ++ src/wasm-ir-builder.h | 4 +-- src/wasm.h | 2 ++ src/wasm/wasm-binary.cpp | 22 ++++++++++-- src/wasm/wasm-ir-builder.cpp | 9 ++--- src/wasm/wasm-stack.cpp | 21 ++++++++--- test/lit/basic/gc-atomics.wast | 64 +++++++++++++++++++++++++++++++-- 18 files changed, 248 insertions(+), 49 deletions(-) diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 1ed7d1a189e..c35afc17a2f 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -640,7 +640,11 @@ ("array.get", "makeArrayGet()"), ("array.get_s", "makeArrayGet(true)"), ("array.get_u", "makeArrayGet(false)"), + ("array.atomic.get", "makeAtomicArrayGet()"), + ("array.atomic.get_s", "makeAtomicArrayGet(true)"), + ("array.atomic.get_u", "makeAtomicArrayGet(false)"), ("array.set", "makeArraySet()"), + ("array.atomic.set", "makeAtomicArraySet()"), ("array.len", "makeArrayLen()"), ("array.copy", "makeArrayCopy()"), ("array.fill", "makeArrayFill()"), diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index bd4746313a2..d5f6fbc7e3c 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1822,17 +1822,22 @@ BinaryenExpressionRef BinaryenArrayGet(BinaryenModuleRef module, BinaryenExpressionRef index, BinaryenType type, bool signed_) { - return static_cast( - Builder(*(Module*)module) - .makeArrayGet((Expression*)ref, (Expression*)index, Type(type), signed_)); + return static_cast(Builder(*(Module*)module) + .makeArrayGet((Expression*)ref, + (Expression*)index, + MemoryOrder::Unordered, + Type(type), + signed_)); } BinaryenExpressionRef BinaryenArraySet(BinaryenModuleRef module, BinaryenExpressionRef ref, BinaryenExpressionRef index, BinaryenExpressionRef value) { - return static_cast( - Builder(*(Module*)module) - .makeArraySet((Expression*)ref, (Expression*)index, (Expression*)value)); + return static_cast(Builder(*(Module*)module) + .makeArraySet((Expression*)ref, + (Expression*)index, + (Expression*)value, + MemoryOrder::Unordered)); } BinaryenExpressionRef BinaryenArrayLen(BinaryenModuleRef module, BinaryenExpressionRef ref) { diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 43d7f460370..5baeae40208 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -22,6 +22,45 @@ switch (buf[0]) { goto parse_error; case 'r': { switch (buf[6]) { + case 'a': { + switch (buf[13]) { + case 'g': { + switch (buf[16]) { + case '\0': + if (op == "array.atomic.get"sv) { + CHECK_ERR(makeAtomicArrayGet(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case '_': { + switch (buf[17]) { + case 's': + if (op == "array.atomic.get_s"sv) { + CHECK_ERR(makeAtomicArrayGet(ctx, pos, annotations, true)); + return Ok{}; + } + goto parse_error; + case 'u': + if (op == "array.atomic.get_u"sv) { + CHECK_ERR(makeAtomicArrayGet(ctx, pos, annotations, false)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + default: goto parse_error; + } + } + case 's': + if (op == "array.atomic.set"sv) { + CHECK_ERR(makeAtomicArraySet(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } case 'c': if (op == "array.copy"sv) { CHECK_ERR(makeArrayCopy(ctx, pos, annotations)); diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index d604302ce5d..3c50893274a 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1059,10 +1059,11 @@ struct InfoCollector // part of the main IR, which is potentially confusing during debugging, // however, which is a downside. Builder builder(*getModule()); - auto* get = - builder.makeArrayGet(curr->srcRef, curr->srcIndex, curr->srcRef->type); + auto* get = builder.makeArrayGet( + curr->srcRef, curr->srcIndex, MemoryOrder::Unordered, curr->srcRef->type); visitArrayGet(get); - auto* set = builder.makeArraySet(curr->destRef, curr->destIndex, get); + auto* set = builder.makeArraySet( + curr->destRef, curr->destIndex, get, MemoryOrder::Unordered); visitArraySet(set); } void visitArrayFill(ArrayFill* curr) { @@ -1071,7 +1072,8 @@ struct InfoCollector } // See ArrayCopy, above. Builder builder(*getModule()); - auto* set = builder.makeArraySet(curr->ref, curr->index, curr->value); + auto* set = builder.makeArraySet( + curr->ref, curr->index, curr->value, MemoryOrder::Unordered); visitArraySet(set); } template void visitArrayInit(ArrayInit* curr) { @@ -1092,7 +1094,8 @@ struct InfoCollector Builder builder(*getModule()); auto* get = builder.makeLocalGet(-1, valueType); addRoot(get); - auto* set = builder.makeArraySet(curr->ref, curr->index, get); + auto* set = + builder.makeArraySet(curr->ref, curr->index, get, MemoryOrder::Unordered); visitArraySet(set); } void visitArrayInitData(ArrayInitData* curr) { visitArrayInit(curr); } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 6a303a4a533..fc519119097 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -815,12 +815,13 @@ struct NullInstrParserCtx { return Ok{}; } template - Result<> - makeArrayGet(Index, const std::vector&, HeapTypeT, bool) { + Result<> makeArrayGet( + Index, const std::vector&, HeapTypeT, bool, MemoryOrder) { return Ok{}; } template - Result<> makeArraySet(Index, const std::vector&, HeapTypeT) { + Result<> + makeArraySet(Index, const std::vector&, HeapTypeT, MemoryOrder) { return Ok{}; } Result<> makeArrayLen(Index, const std::vector&) { return Ok{}; } @@ -2693,14 +2694,16 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { Result<> makeArrayGet(Index pos, const std::vector& annotations, HeapType type, - bool signed_) { - return withLoc(pos, irBuilder.makeArrayGet(type, signed_)); + bool signed_, + MemoryOrder order) { + return withLoc(pos, irBuilder.makeArrayGet(type, signed_, order)); } Result<> makeArraySet(Index pos, const std::vector& annotations, - HeapType type) { - return withLoc(pos, irBuilder.makeArraySet(type)); + HeapType type, + MemoryOrder order) { + return withLoc(pos, irBuilder.makeArraySet(type, order)); } Result<> makeArrayLen(Index pos, const std::vector& annotations) { diff --git a/src/parser/parsers.h b/src/parser/parsers.h index c80b8fba888..30b6b8890b7 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -274,8 +274,15 @@ template Result<> makeArrayGet(Ctx&, Index, const std::vector&, bool signed_ = false); template +Result<> makeAtomicArrayGet(Ctx&, + Index, + const std::vector&, + bool signed_ = false); +template Result<> makeArraySet(Ctx&, Index, const std::vector&); template +Result<> makeAtomicArraySet(Ctx&, Index, const std::vector&); +template Result<> makeArrayLen(Ctx&, Index, const std::vector&); template Result<> makeArrayCopy(Ctx&, Index, const std::vector&); @@ -2411,7 +2418,20 @@ Result<> makeArrayGet(Ctx& ctx, bool signed_) { auto type = typeidx(ctx); CHECK_ERR(type); - return ctx.makeArrayGet(pos, annotations, *type, signed_); + return ctx.makeArrayGet( + pos, annotations, *type, signed_, MemoryOrder::Unordered); +} + +template +Result<> makeAtomicArrayGet(Ctx& ctx, + Index pos, + const std::vector& annotations, + bool signed_) { + auto order = memorder(ctx); + CHECK_ERR(order); + auto type = typeidx(ctx); + CHECK_ERR(type); + return ctx.makeArrayGet(pos, annotations, *type, signed_, *order); } template @@ -2419,7 +2439,18 @@ Result<> makeArraySet(Ctx& ctx, Index pos, const std::vector& annotations) { auto type = typeidx(ctx); CHECK_ERR(type); - return ctx.makeArraySet(pos, annotations, *type); + return ctx.makeArraySet(pos, annotations, *type, MemoryOrder::Unordered); +} + +template +Result<> makeAtomicArraySet(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto order = memorder(ctx); + CHECK_ERR(order); + auto type = typeidx(ctx); + CHECK_ERR(type); + return ctx.makeArraySet(pos, annotations, *type, *order); } template diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 8f9763427c8..9269046f86d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2372,19 +2372,29 @@ struct PrintExpressionContents } void visitArrayGet(ArrayGet* curr) { const auto& element = curr->ref->type.getHeapType().getArray().element; + printMedium(o, "array"); + if (curr->order != MemoryOrder::Unordered) { + printMedium(o, ".atomic"); + } if (element.type == Type::i32 && element.packedType != Field::not_packed) { if (curr->signed_) { - printMedium(o, "array.get_s "); + printMedium(o, ".get_s "); } else { - printMedium(o, "array.get_u "); + printMedium(o, ".get_u "); } } else { - printMedium(o, "array.get "); + printMedium(o, ".get "); } + printMemoryOrder(curr->order); printHeapTypeName(curr->ref->type.getHeapType()); } void visitArraySet(ArraySet* curr) { - printMedium(o, "array.set "); + if (curr->order == MemoryOrder::Unordered) { + printMedium(o, "array.set "); + } else { + printMedium(o, "array.atomic.set "); + } + printMemoryOrder(curr->order); printHeapTypeName(curr->ref->type.getHeapType()); } void visitArrayLen(ArrayLen* curr) { printMedium(o, "array.len"); } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 9905d00e2aa..b79d9264ab9 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -5058,14 +5058,16 @@ Expression* TranslateToFuzzReader::makeArrayGet(Type type) { // Only rarely emit a plain get which might trap. See related logic in // ::makePointer(). if (allowOOB && oneIn(10)) { - return builder.makeArrayGet(ref, index, type, signed_); + return builder.makeArrayGet( + ref, index, MemoryOrder::Unordered, type, signed_); } // To avoid a trap, check the length dynamically using this pattern: // // index < array.len ? array[index] : ..some fallback value.. // auto check = makeArrayBoundsCheck(ref, index, funcContext->func, builder); - auto* get = builder.makeArrayGet(check.getRef, check.getIndex, type, signed_); + auto* get = builder.makeArrayGet( + check.getRef, check.getIndex, MemoryOrder::Unordered, type, signed_); auto* fallback = makeTrivial(type); return builder.makeIf(check.condition, get, fallback); } @@ -5083,14 +5085,15 @@ Expression* TranslateToFuzzReader::makeArraySet(Type type) { // Only rarely emit a plain get which might trap. See related logic in // ::makePointer(). if (allowOOB && oneIn(10)) { - return builder.makeArraySet(ref, index, value); + return builder.makeArraySet(ref, index, value, MemoryOrder::Unordered); } // To avoid a trap, check the length dynamically using this pattern: // // if (index < array.len) array[index] = value; // auto check = makeArrayBoundsCheck(ref, index, funcContext->func, builder); - auto* set = builder.makeArraySet(check.getRef, check.getIndex, value); + auto* set = builder.makeArraySet( + check.getRef, check.getIndex, value, MemoryOrder::Unordered); return builder.makeIf(check.condition, set); } diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 481c42cb591..0df95aa8b9e 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -960,8 +960,10 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { set = builder.makeStructSet(index, getGlobal, value, MemoryOrder::Unordered); } else { - set = builder.makeArraySet( - getGlobal, builder.makeConst(int32_t(index)), value); + set = builder.makeArraySet(getGlobal, + builder.makeConst(int32_t(index)), + value, + MemoryOrder::Unordered); } (*startBlock)->list.push_back(set); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index fbe151bc472..17ff420c51c 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1197,6 +1197,10 @@ enum ASTNodes { StructAtomicRMWXor = 0x64, StructAtomicRMWXchg = 0x65, StructAtomicRMWCmpxchg = 0x66, + ArrayAtomicGet = 0x67, + ArrayAtomicGetS = 0x68, + ArrayAtomicGetU = 0x69, + ArrayAtomicSet = 0x6a, // stringref opcodes diff --git a/src/wasm-builder.h b/src/wasm-builder.h index f7eb357c112..f1866ac8717 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1034,6 +1034,7 @@ class Builder { } ArrayGet* makeArrayGet(Expression* ref, Expression* index, + MemoryOrder order, Type type, bool signed_ = false) { auto* ret = wasm.allocator.alloc(); @@ -1041,15 +1042,19 @@ class Builder { ret->index = index; ret->type = type; ret->signed_ = signed_; + ret->order = order; ret->finalize(); return ret; } - ArraySet* - makeArraySet(Expression* ref, Expression* index, Expression* value) { + ArraySet* makeArraySet(Expression* ref, + Expression* index, + Expression* value, + MemoryOrder order) { auto* ret = wasm.allocator.alloc(); ret->ref = ref; ret->index = index; ret->value = value; + ret->order = order; ret->finalize(); return ret; } diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index d2165ba9cbc..4464c29154b 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -703,12 +703,14 @@ DELEGATE_FIELD_CASE_START(ArrayGet) DELEGATE_FIELD_CHILD(ArrayGet, index) DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayGet, ref) DELEGATE_FIELD_INT(ArrayGet, signed_) +DELEGATE_FIELD_INT(ArraySet, order) DELEGATE_FIELD_CASE_END(ArrayGet) DELEGATE_FIELD_CASE_START(ArraySet) DELEGATE_FIELD_CHILD(ArraySet, value) DELEGATE_FIELD_CHILD(ArraySet, index) DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArraySet, ref) +DELEGATE_FIELD_INT(ArraySet, order) DELEGATE_FIELD_CASE_END(ArraySet) DELEGATE_FIELD_CASE_START(ArrayLen) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 10ac7b72efb..3120e8ce696 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -228,8 +228,8 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeArrayNewData(HeapType type, Name data); Result<> makeArrayNewElem(HeapType type, Name elem); Result<> makeArrayNewFixed(HeapType type, uint32_t arity); - Result<> makeArrayGet(HeapType type, bool signed_); - Result<> makeArraySet(HeapType type); + Result<> makeArrayGet(HeapType type, bool signed_, MemoryOrder order); + Result<> makeArraySet(HeapType type, MemoryOrder order); Result<> makeArrayLen(); Result<> makeArrayCopy(HeapType destType, HeapType srcType); Result<> makeArrayFill(HeapType type); diff --git a/src/wasm.h b/src/wasm.h index 70c0617a914..e5752227865 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1778,6 +1778,7 @@ class ArrayGet : public SpecificExpression { Expression* index; // Packed fields have a sign. bool signed_ = false; + MemoryOrder order = MemoryOrder::Unordered; void finalize(); }; @@ -1790,6 +1791,7 @@ class ArraySet : public SpecificExpression { Expression* ref; Expression* index; Expression* value; + MemoryOrder order = MemoryOrder::Unordered; void finalize(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 9bd4b3f82d8..6dd5cc22121 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3771,6 +3771,19 @@ Result<> WasmBinaryReader::readInst() { auto field = getU32LEB(); return builder.makeStructCmpxchg(type, field, order); } + case BinaryConsts::ArrayAtomicGet: + case BinaryConsts::ArrayAtomicGetS: + case BinaryConsts::ArrayAtomicGetU: { + auto order = getMemoryOrder(); + auto type = getIndexedHeapType(); + bool signed_ = op == BinaryConsts::ArrayAtomicGetS; + return builder.makeArrayGet(type, signed_, order); + } + case BinaryConsts::ArrayAtomicSet: { + auto order = getMemoryOrder(); + auto type = getIndexedHeapType(); + return builder.makeArraySet(type, order); + } } return Err{"unknown atomic operation " + std::to_string(op)}; } @@ -4559,11 +4572,14 @@ Result<> WasmBinaryReader::readInst() { } case BinaryConsts::ArrayGet: case BinaryConsts::ArrayGetU: - return builder.makeArrayGet(getIndexedHeapType(), false); + return builder.makeArrayGet( + getIndexedHeapType(), false, MemoryOrder::Unordered); case BinaryConsts::ArrayGetS: - return builder.makeArrayGet(getIndexedHeapType(), true); + return builder.makeArrayGet( + getIndexedHeapType(), true, MemoryOrder::Unordered); case BinaryConsts::ArraySet: - return builder.makeArraySet(getIndexedHeapType()); + return builder.makeArraySet(getIndexedHeapType(), + MemoryOrder::Unordered); case BinaryConsts::ArrayLen: return builder.makeArrayLen(); case BinaryConsts::ArrayCopy: { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 1fb39b14ec6..663114b3107 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2256,20 +2256,21 @@ Result<> IRBuilder::makeArrayNewFixed(HeapType type, uint32_t arity) { return Ok{}; } -Result<> IRBuilder::makeArrayGet(HeapType type, bool signed_) { +Result<> +IRBuilder::makeArrayGet(HeapType type, bool signed_, MemoryOrder order) { ArrayGet curr; CHECK_ERR(ChildPopper{*this}.visitArrayGet(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); push(builder.makeArrayGet( - curr.ref, curr.index, type.getArray().element.type, signed_)); + curr.ref, curr.index, order, type.getArray().element.type, signed_)); return Ok{}; } -Result<> IRBuilder::makeArraySet(HeapType type) { +Result<> IRBuilder::makeArraySet(HeapType type, MemoryOrder order) { ArraySet curr; CHECK_ERR(ChildPopper{*this}.visitArraySet(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - push(builder.makeArraySet(curr.ref, curr.index, curr.value)); + push(builder.makeArraySet(curr.ref, curr.index, curr.value, order)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index d0065909905..988f333b92f 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2455,15 +2455,20 @@ void BinaryInstWriter::visitArrayGet(ArrayGet* curr) { } auto heapType = curr->ref->type.getHeapType(); const auto& field = heapType.getArray().element; + bool atomic = curr->order != MemoryOrder::Unordered; int8_t op; if (field.type != Type::i32 || field.packedType == Field::not_packed) { - op = BinaryConsts::ArrayGet; + op = atomic ? BinaryConsts::ArrayAtomicGet : BinaryConsts::ArrayGet; } else if (curr->signed_) { - op = BinaryConsts::ArrayGetS; + op = atomic ? BinaryConsts::ArrayAtomicGetS : BinaryConsts::ArrayGetS; } else { - op = BinaryConsts::ArrayGetU; + op = atomic ? BinaryConsts::ArrayAtomicGetU : BinaryConsts::ArrayGetU; + } + auto prefix = atomic ? BinaryConsts::AtomicPrefix : BinaryConsts::GCPrefix; + o << int8_t(prefix) << U32LEB(op); + if (atomic) { + parent.writeMemoryOrder(curr->order); } - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(op); parent.writeIndexedHeapType(heapType); } @@ -2472,7 +2477,13 @@ void BinaryInstWriter::visitArraySet(ArraySet* curr) { emitUnreachable(); return; } - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArraySet); + if (curr->order == MemoryOrder::Unordered) { + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArraySet); + } else { + o << int8_t(BinaryConsts::AtomicPrefix) + << U32LEB(BinaryConsts::ArrayAtomicSet); + parent.writeMemoryOrder(curr->order); + } parent.writeIndexedHeapType(curr->ref->type.getHeapType()); } diff --git a/test/lit/basic/gc-atomics.wast b/test/lit/basic/gc-atomics.wast index 7e2d1ef4eda..79a2d89203b 100644 --- a/test/lit/basic/gc-atomics.wast +++ b/test/lit/basic/gc-atomics.wast @@ -8,6 +8,10 @@ (type $struct (struct (field (mut i32)))) ;; CHECK: (type $packed (struct (field (mut i8)))) (type $packed (struct (field (mut i8)))) + ;; CHECK: (type $array (array (mut i32))) + (type $array (array (mut i32))) + ;; CHECK: (type $array-packed (array (mut i8))) + (type $array-packed (array (mut i8))) ;; CHECK: (func $get (type $1) (param $0 (ref null $struct)) (result i32) ;; CHECK-NEXT: (struct.atomic.get $struct 0 @@ -108,7 +112,7 @@ ) ) - ;; CHECK: (func $set (type $4) (param $0 (ref null $struct)) + ;; CHECK: (func $set (type $6) (param $0 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.set $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -121,7 +125,7 @@ ) ) - ;; CHECK: (func $set-seqcst (type $4) (param $0 (ref null $struct)) + ;; CHECK: (func $set-seqcst (type $6) (param $0 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.set $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -134,7 +138,7 @@ ) ) - ;; CHECK: (func $set-acqrel (type $4) (param $0 (ref null $struct)) + ;; CHECK: (func $set-acqrel (type $6) (param $0 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.set acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -425,4 +429,58 @@ (i32.const 2) ) ) + + ;; CHECK: (func $array.get (type $8) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.get $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.get (param (ref null $array)) (result i32) + (array.atomic.get $array + (local.get 0) + (i32.const 42) + ) + ) + + ;; CHECK: (func $array.get_s (type $7) (param $0 (ref null $array-packed)) (result i32) + ;; CHECK-NEXT: (array.atomic.get_s $array-packed + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.get_s (param (ref null $array-packed)) (result i32) + (array.atomic.get_s seqcst $array-packed + (local.get 0) + (i32.const 42) + ) + ) + + ;; CHECK: (func $array.get_u (type $7) (param $0 (ref null $array-packed)) (result i32) + ;; CHECK-NEXT: (array.atomic.get_u acqrel $array-packed + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.get_u (param (ref null $array-packed)) (result i32) + (array.atomic.get_u acqrel $array-packed + (local.get 0) + (i32.const 42) + ) + ) + + ;; CHECK: (func $array.set (type $9) (param $0 (ref null $array)) + ;; CHECK-NEXT: (array.atomic.set acqrel $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.set (param (ref null $array)) + (array.atomic.set acqrel $array + (local.get 0) + (i32.const 42) + (i32.const 1337) + ) + ) ) From 08428b3caf0d4655b3f74d55b507679da323cd0a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 May 2025 12:55:56 -0700 Subject: [PATCH 525/622] OptimizeInstructions: Fix max bits of a local with an unreachable set (#7623) Previously we set such locals to 64 bits, even if they were i32. This interacted with #7505 which compared the bits to the true bits of the type, which led to 64 != 32, which suggested we knew something nontrivial about the bits, and a misoptimization. --- src/passes/OptimizeInstructions.cpp | 1 + .../lit/passes/optimize-instructions-mvp.wast | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 65b573c2103..dc0cff0a717 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -129,6 +129,7 @@ struct LocalScanner : PostWalker { Properties::getFallthrough(curr->value, passOptions, *getModule()); auto& info = localInfo[curr->index]; info.maxBits = std::max(info.maxBits, Bits::getMaxBits(value, this)); + info.maxBits = std::min(info.maxBits, getBitsForType(type)); auto signExtBits = LocalInfo::kUnknown; if (Properties::getSignExtValue(value)) { signExtBits = Properties::getSignExtBits(value); diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 1f2bdde5267..0210447abd3 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -18410,4 +18410,41 @@ ) ) ) + + ;; CHECK: (func $local-unreachable-size-matters + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $temp + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $local-unreachable-size-matters + ;; The local is written 1, then later down we have an unreachable. That + ;; should not confuse us as to the max bits in the local. This is a test for + ;; a bug where any local with an unreachable set got assigned 64 bits, which + ;; is wrong in this case and led to a misoptimization of the |and|. We + ;; should not optimize it away to a 0 (we could in theory optimize it to a + ;; 1, but this pass does not infer locals that precisely). + (local $temp i32) + (local.set $temp + (i32.const 1) + ) + (drop + (i32.and + (local.get $temp) + (i32.const 1) + ) + ) + (local.tee $temp + (unreachable) + ) + ) ) From 18f66046058d1da46e1e536e85cbcef28d4c1e2b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 27 May 2025 13:28:39 -0700 Subject: [PATCH 526/622] [wasm-reduce] Get the return code from pclose (#7624) Rather than executing the reduction command twice for every candidate, once to get the return code and once to capture stdout, just run it once and capture both. --- src/tools/wasm-reduce.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index cfc8a3b1208..56f40dfc9a1 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -185,11 +185,6 @@ struct ProgramResult { void getFromExecution(std::string command) { Timer timer; timer.start(); - // do this using just core stdio.h and stdlib.h, for portability - // sadly this requires two invokes - code = system(("timeout " + std::to_string(timeout) + "s " + command + - " > /dev/null 2> /dev/null") - .c_str()); const int MAX_BUFFER = 1024; char buffer[MAX_BUFFER]; FILE* stream = popen( @@ -199,7 +194,7 @@ struct ProgramResult { while (fgets(buffer, MAX_BUFFER, stream) != NULL) { output.append(buffer); } - pclose(stream); + code = pclose(stream); timer.stop(); time = timer.getTotal() / 2; } From 0ac7afe8715c1095064e30a347d285e0c8d00bc5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 27 May 2025 16:12:34 -0700 Subject: [PATCH 527/622] [wasm-reduce] Fix time calculation (#7627) Now that we only run the reduction command once per candidate rather than twice, we don't need to divide the measured execution time in half. --- src/tools/wasm-reduce.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 56f40dfc9a1..41507b96f20 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -196,7 +196,7 @@ struct ProgramResult { } code = pclose(stream); timer.stop(); - time = timer.getTotal() / 2; + time = timer.getTotal(); } #endif // _WIN32 From da7ed11d501d986dae48c45739523b63fe1263a6 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 28 May 2025 13:28:36 -0700 Subject: [PATCH 528/622] [custom-descriptors] Branching descriptor casts (#7622) Implement basic support for `br_on_cast_desc` and `br_on_cast_desc_fail` as new variants of `BrOn`. Include binary and text parsing and printing as well as validation. Also handle the new operations anywhere the compiler would otherwise complain about a non-exhaustive switch over the `BrCastOp` enum. For validation of type immediates during parsing, relax the requirement that the cast type is a subtype of the input type. Continue validating in the IR that the non-descriptor branching casts are downcasts, but do not validate this for the new descriptor casts. See https://github.com/WebAssembly/custom-descriptors/issues/37 for context. Add new validation and parsing tests in the form of spec tests in preparation for creating an upstream spec test suite. While we're at it, move the ref.get_desc tests to spec tests, add a few test cases, and fix a bug when the ref.get_desc operand is null. --- scripts/gen-s-parser.py | 6 +- scripts/test/fuzzing.py | 2 + src/gen-s-parser.inc | 34 ++- src/ir/ReFinalize.cpp | 14 +- src/ir/child-typer.h | 15 +- src/ir/cost.h | 17 +- src/ir/properties.h | 2 +- src/parser/parsers.h | 8 +- src/passes/Print.cpp | 26 ++- src/wasm-binary.h | 2 + src/wasm-builder.h | 10 +- src/wasm-delegations-fields.def | 24 +- src/wasm-interpreter.h | 82 ++++--- src/wasm.h | 7 + src/wasm/wasm-binary.cpp | 9 +- src/wasm/wasm-ir-builder.cpp | 51 ++-- src/wasm/wasm-stack.cpp | 48 ++-- src/wasm/wasm-validator.cpp | 90 +++++-- src/wasm/wasm.cpp | 56 +++-- test/lit/basic/custom-descriptors.wast | 246 +++++++++++++++++++- test/lit/parse-bad-get-desc.wast | 15 -- test/lit/validation/custom-descriptors.wast | 19 -- test/spec/br_on_cast_desc.wast | 223 ++++++++++++++++++ test/spec/ref.get_cast.wast | 55 +++++ 24 files changed, 875 insertions(+), 186 deletions(-) delete mode 100644 test/lit/parse-bad-get-desc.wast delete mode 100644 test/lit/validation/custom-descriptors.wast create mode 100644 test/spec/br_on_cast_desc.wast create mode 100644 test/spec/ref.get_cast.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index c35afc17a2f..0448ebbace4 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -613,8 +613,10 @@ ("ref.get_desc", "makeRefGetDesc()"), ("br_on_null", "makeBrOnNull()"), ("br_on_non_null", "makeBrOnNull(true)"), - ("br_on_cast", "makeBrOnCast()"), - ("br_on_cast_fail", "makeBrOnCast(true)"), + ("br_on_cast", "makeBrOnCast(BrOnCast)"), + ("br_on_cast_fail", "makeBrOnCast(BrOnCastFail)"), + ("br_on_cast_desc", "makeBrOnCast(BrOnCastDesc)"), + ("br_on_cast_desc_fail", "makeBrOnCast(BrOnCastDescFail)"), ("struct.new", "makeStructNew(false)"), ("struct.new_default", "makeStructNew(true)"), ("struct.get", "makeStructGet()"), diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index b057f84545b..7975b1074b4 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -113,6 +113,8 @@ 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', + 'br_on_cast_desc.wast', + 'ref.get_cast.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 5baeae40208..69d7c772748 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -209,16 +209,38 @@ switch (buf[0]) { switch (buf[10]) { case '\0': if (op == "br_on_cast"sv) { - CHECK_ERR(makeBrOnCast(ctx, pos, annotations)); + CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCast)); return Ok{}; } goto parse_error; - case '_': - if (op == "br_on_cast_fail"sv) { - CHECK_ERR(makeBrOnCast(ctx, pos, annotations, true)); - return Ok{}; + case '_': { + switch (buf[11]) { + case 'd': { + switch (buf[15]) { + case '\0': + if (op == "br_on_cast_desc"sv) { + CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastDesc)); + return Ok{}; + } + goto parse_error; + case '_': + if (op == "br_on_cast_desc_fail"sv) { + CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastDescFail)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + case 'f': + if (op == "br_on_cast_fail"sv) { + CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastFail)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } default: goto parse_error; } } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 4c098323735..2efad1bb006 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -151,7 +151,7 @@ void ReFinalize::visitRefGetDesc(RefGetDesc* curr) { curr->finalize(); } void ReFinalize::visitBrOn(BrOn* curr) { curr->finalize(); if (curr->type == Type::unreachable) { - replaceUntaken(curr->ref, nullptr); + replaceUntaken(curr->ref, curr->desc); } else { updateBreakValueType(curr->name, curr->getSentType()); } @@ -218,11 +218,11 @@ void ReFinalize::updateBreakValueType(Name name, Type type) { } // Replace an untaken branch/switch with an unreachable value. -// A condition may also exist and may or may not be unreachable. -void ReFinalize::replaceUntaken(Expression* value, Expression* condition) { +// Another child may also exist and may or may not be unreachable. +void ReFinalize::replaceUntaken(Expression* value, Expression* otherChild) { assert(value->type == Type::unreachable); auto* replacement = value; - if (condition) { + if (otherChild) { Builder builder(*getModule()); // Even if we have // (block @@ -233,10 +233,10 @@ void ReFinalize::replaceUntaken(Expression* value, Expression* condition) { // the value is unreachable, and necessary since the type of // the condition did not have an impact before (the break/switch // type was unreachable), and might not fit in. - if (condition->type.isConcrete()) { - condition = builder.makeDrop(condition); + if (otherChild->type.isConcrete()) { + otherChild = builder.makeDrop(otherChild); } - replacement = builder.makeSequence(value, condition); + replacement = builder.makeSequence(value, otherChild); assert(replacement->type.isBasic() && "Basic type expected"); } replaceCurrent(replacement); diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index c195f36141a..e588e72b1c1 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -865,16 +865,25 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->ref, Type(*ht, Nullable)); } - void visitBrOn(BrOn* curr) { + void visitBrOn(BrOn* curr, std::optional target = std::nullopt) { switch (curr->op) { case BrOnNull: case BrOnNonNull: noteAnyReference(&curr->ref); return; case BrOnCast: - case BrOnCastFail: { - auto top = curr->castType.getHeapType().getTop(); + case BrOnCastFail: + case BrOnCastDesc: + case BrOnCastDescFail: { + if (!target) { + target = curr->castType; + } + auto top = target->getHeapType().getTop(); note(&curr->ref, Type(top, Nullable)); + if (curr->op == BrOnCastDesc || curr->op == BrOnCastDescFail) { + auto descriptor = *target->getHeapType().getDescriptorType(); + note(&curr->desc, Type(descriptor, Nullable)); + } return; } } diff --git a/src/ir/cost.h b/src/ir/cost.h index 5e2ebf3e9a9..cfdf3f09b1b 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -673,9 +673,20 @@ struct CostAnalyzer : public OverriddenVisitor { } CostType visitBrOn(BrOn* curr) { // BrOn of a null can be fairly fast, but anything else is a cast check. - CostType base = - curr->op == BrOnNull || curr->op == BrOnNonNull ? 2 : CastCost; - return base + nullCheckCost(curr->ref) + maybeVisit(curr->ref); + switch (curr->op) { + case BrOnNull: + case BrOnNonNull: + return 2 + nullCheckCost(curr->ref) + visit(curr->ref); + case BrOnCast: + case BrOnCastFail: + return CastCost + visit(curr->ref); + case BrOnCastDesc: + case BrOnCastDescFail: + // These are not as expensive as full casts, since they just do a + // identity check on the descriptor. + return 2 + visit(curr->ref) + visit(curr->desc); + } + WASM_UNREACHABLE("unexpected op"); } CostType visitStructNew(StructNew* curr) { CostType ret = AllocationCost + curr->operands.size(); diff --git a/src/ir/properties.h b/src/ir/properties.h index 4fb0c2b2145..e522b6654ca 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -521,7 +521,7 @@ inline bool hasUnwritableTypeImmediate(Expression* curr) { #define DELEGATE_ID curr->_id #define DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) \ - { \ + if (curr->cast()->field) { \ auto type = curr->cast()->field->type; \ if (type == Type::unreachable || type.isNull()) { \ return true; \ diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 30b6b8890b7..937822ae65a 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -238,8 +238,7 @@ template Result<> makeBrOnNull(Ctx&, Index, const std::vector&, bool onFail = false); template -Result<> -makeBrOnCast(Ctx&, Index, const std::vector&, bool onFail = false); +Result<> makeBrOnCast(Ctx&, Index, const std::vector&, BrOnOp op); template Result<> makeStructNew(Ctx&, Index, const std::vector&, bool default_); @@ -2251,15 +2250,14 @@ template Result<> makeBrOnCast(Ctx& ctx, Index pos, const std::vector& annotations, - bool onFail) { + BrOnOp op) { auto label = labelidx(ctx); CHECK_ERR(label); auto in = reftype(ctx); CHECK_ERR(in); auto out = reftype(ctx); CHECK_ERR(out); - return ctx.makeBrOn( - pos, annotations, *label, onFail ? BrOnCastFail : BrOnCast, *in, *out); + return ctx.makeBrOn(pos, annotations, *label, op, *in, *out); } template diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 9269046f86d..7af4c4a1075 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2226,7 +2226,26 @@ struct PrintExpressionContents curr->name.print(o); return; case BrOnCast: - printMedium(o, "br_on_cast "); + case BrOnCastDesc: + case BrOnCastFail: + case BrOnCastDescFail: + switch (curr->op) { + case BrOnCast: + printMedium(o, "br_on_cast"); + break; + case BrOnCastFail: + printMedium(o, "br_on_cast_fail"); + break; + case BrOnCastDesc: + printMedium(o, "br_on_cast_desc"); + break; + case BrOnCastDescFail: + printMedium(o, "br_on_cast_desc_fail"); + break; + default: + WASM_UNREACHABLE("unexpected op"); + } + o << ' '; curr->name.print(o); o << ' '; if (curr->ref->type == Type::unreachable) { @@ -2240,8 +2259,9 @@ struct PrintExpressionContents o << ' '; printType(curr->castType); return; - case BrOnCastFail: - printMedium(o, "br_on_cast_fail "); + printMedium(o, + curr->op == BrOnCastFail ? "br_on_cast_fail " + : "br_on_cast_desc_fail "); curr->name.print(o); o << ' '; if (curr->ref->type == Type::unreachable) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 17ff420c51c..4dfc1df034d 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1174,6 +1174,8 @@ enum ASTNodes { RefCastNull = 0x17, BrOnCast = 0x18, BrOnCastFail = 0x19, + BrOnCastDesc = 0x25, + BrOnCastDescFail = 0x26, AnyConvertExtern = 0x1a, ExternConvertAny = 0x1b, RefI31 = 0x1c, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index f1866ac8717..588c2b0330c 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -900,12 +900,18 @@ class Builder { ret->finalize(); return ret; } - BrOn* - makeBrOn(BrOnOp op, Name name, Expression* ref, Type castType = Type::none) { + BrOn* makeBrOn(BrOnOp op, + Name name, + Expression* ref, + Type castType = Type::none, + Expression* desc = nullptr) { + assert((desc && (op == BrOnCastDesc || op == BrOnCastDescFail)) || + (!desc && op != BrOnCastDesc && op != BrOnCastDescFail)); auto* ret = wasm.allocator.alloc(); ret->op = op; ret->name = name; ret->ref = ref; + ret->desc = desc; ret->castType = castType; ret->finalize(); return ret; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 4464c29154b..cc2074e6966 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -46,6 +46,14 @@ // present (like a Return's value). If you do not define this then // DELEGATE_FIELD_CHILD is called. // +// DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) - The previous two +// cases combined. If you do not define this, but you define exactly one of +// DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD and DELEGATE_FIELD_OPTIONAL_CHILD, that +// defined macro will be called. Defining both +// DELEGATE_FIELD_IMMEDIATE_TYPE_CHILD and DELEGATE_FIELD_OPTIONAL_CHILD without +// defining DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD is an error. If +// neither of the other macros are defined, then DELEGATE_FIELD_CHILD is called. +// // DELEGATE_FIELD_CHILD_VECTOR(id, field) - called for a variable-sized vector // of child pointers. If this is not defined, and DELEGATE_GET_FIELD is, then // DELEGATE_FIELD_CHILD is called on them. @@ -56,7 +64,7 @@ // DELEGATE_FIELD_INT_ARRAY(id, field) - called for a std::array of fixed size // of integer values (like a SIMD mask). If this is not defined, and // DELEGATE_GET_FIELD is, then DELEGATE_FIELD_INT is called on them. - +// // DELEGATE_FIELD_INT_VECTOR(id, field) - called for a variable-sized vector // of integer values. If this is not defined, and DELEGATE_GET_FIELD is, then // DELEGATE_FIELD_INT is called on them. @@ -113,6 +121,18 @@ #error please define DELEGATE_FIELD_CHILD(id, field) #endif +#ifndef DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD +#if defined(DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD) && !defined(DELEGATE_FIELD_OPTIONAL_CHILD) +#define DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) +#elif !defined(DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD) && defined(DELEGATE_FIELD_OPTIONAL_CHILD) +#define DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_OPTIONAL_CHILD(id, field) +#elif !defined(DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD) && !defined(DELEGATE_FIELD_OPTIONAL_CHILD) +#define DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_CHILD(id, field) +#else +#error please define DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) +#endif +#endif + #ifndef DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD #define DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_CHILD(id, field) #endif @@ -641,6 +661,7 @@ DELEGATE_FIELD_CASE_START(BrOn) DELEGATE_FIELD_INT(BrOn, op) DELEGATE_FIELD_SCOPE_NAME_USE(BrOn, name) DELEGATE_FIELD_TYPE(BrOn, castType) +DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(BrOn, desc) DELEGATE_FIELD_CHILD(BrOn, ref) DELEGATE_FIELD_CASE_END(BrOn) @@ -844,6 +865,7 @@ DELEGATE_FIELD_MAIN_END #undef DELEGATE_FIELD_CHILD #undef DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD #undef DELEGATE_FIELD_OPTIONAL_CHILD +#undef DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD #undef DELEGATE_FIELD_CHILD_VECTOR #undef DELEGATE_FIELD_INT #undef DELEGATE_FIELD_INT_ARRAY diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5c28265f2d5..3e49a8d640e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1677,48 +1677,58 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitBrOn(BrOn* curr) { NOTE_ENTER("BrOn"); // BrOnCast* uses the casting infrastructure, so handle them first. - if (curr->op == BrOnCast || curr->op == BrOnCastFail) { - auto cast = doCast(curr); - if (auto* breaking = cast.getBreaking()) { - return *breaking; - } else if (auto* original = cast.getFailure()) { - if (curr->op == BrOnCast) { - return *original; + switch (curr->op) { + case BrOnCast: + case BrOnCastFail: { + auto cast = doCast(curr); + if (auto* breaking = cast.getBreaking()) { + return *breaking; + } else if (auto* original = cast.getFailure()) { + if (curr->op == BrOnCast) { + return *original; + } else { + return Flow(curr->name, *original); + } } else { - return Flow(curr->name, *original); + auto* result = cast.getSuccess(); + assert(result); + if (curr->op == BrOnCast) { + return Flow(curr->name, *result); + } else { + return *result; + } } - } else { - auto* result = cast.getSuccess(); - assert(result); - if (curr->op == BrOnCast) { - return Flow(curr->name, *result); + } + case BrOnCastDesc: + case BrOnCastDescFail: + WASM_UNREACHABLE("TODO"); + case BrOnNull: + case BrOnNonNull: { + // Otherwise we are just checking for null. + Flow flow = visit(curr->ref); + if (flow.breaking()) { + return flow; + } + const auto& value = flow.getSingleValue(); + NOTE_EVAL1(value); + if (curr->op == BrOnNull) { + // BrOnNull does not propagate the value if it takes the branch. + if (value.isNull()) { + return Flow(curr->name); + } + // If the branch is not taken, we return the non-null value. + return {value}; } else { - return *result; + // BrOnNonNull does not return a value if it does not take the branch. + if (value.isNull()) { + return Flow(); + } + // If the branch is taken, we send the non-null value. + return Flow(curr->name, value); } } } - // Otherwise we are just checking for null. - Flow flow = visit(curr->ref); - if (flow.breaking()) { - return flow; - } - const auto& value = flow.getSingleValue(); - NOTE_EVAL1(value); - if (curr->op == BrOnNull) { - // BrOnNull does not propagate the value if it takes the branch. - if (value.isNull()) { - return Flow(curr->name); - } - // If the branch is not taken, we return the non-null value. - return {value}; - } else { - // BrOnNonNull does not return a value if it does not take the branch. - if (value.isNull()) { - return Flow(); - } - // If the branch is taken, we send the non-null value. - return Flow(curr->name, value); - } + WASM_UNREACHABLE("unexpected op"); } Flow visitStructNew(StructNew* curr) { NOTE_ENTER("StructNew"); diff --git a/src/wasm.h b/src/wasm.h index e5752227865..77719cc1010 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -608,6 +608,8 @@ enum BrOnOp { BrOnNonNull, BrOnCast, BrOnCastFail, + BrOnCastDesc, + BrOnCastDescFail, }; enum StringNewOp { @@ -1641,6 +1643,11 @@ class BrOn : public SpecificExpression { BrOnOp op; Name name; Expression* ref; + + // Only used for br_on_cast_desc{,_fail} + Expression* desc; + + // Only used for br_on_cast{,_desc}{,_fail} Type castType; void finalize(); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 6dd5cc22121..90e3cd0c331 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4516,7 +4516,9 @@ Result<> WasmBinaryReader::readInst() { return builder.makeRefGetDesc(type); } case BinaryConsts::BrOnCast: - case BinaryConsts::BrOnCastFail: { + case BinaryConsts::BrOnCastFail: + case BinaryConsts::BrOnCastDesc: + case BinaryConsts::BrOnCastDescFail: { auto flags = getInt8(); auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) ? Nullable @@ -4529,7 +4531,10 @@ Result<> WasmBinaryReader::readInst() { auto [dstType, dstExact] = getHeapType(); auto in = Type(srcType, srcNull, srcExact); auto cast = Type(dstType, dstNull, dstExact); - auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; + auto kind = op == BinaryConsts::BrOnCast ? BrOnCast + : op == BinaryConsts::BrOnCastFail ? BrOnCastFail + : op == BinaryConsts::BrOnCastDesc ? BrOnCastDesc + : BrOnCastDescFail; return builder.makeBrOn(label, kind, in, cast); } case BinaryConsts::StructNew: diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 663114b3107..b7962326ff0 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -37,17 +37,17 @@ namespace wasm { namespace { -Result<> validateTypeAnnotation(HeapType type, Expression* child) { - if (child->type == Type::unreachable) { - return Ok{}; - } - if (!child->type.isRef() || - !HeapType::isSubType(child->type.getHeapType(), type)) { - return Err{"invalid reference type on stack"}; +Result<> validateTypeAnnotation(Type type, Expression* child) { + if (!Type::isSubType(child->type, type)) { + return Err{"invalid type on stack"}; } return Ok{}; } +Result<> validateTypeAnnotation(HeapType type, Expression* child) { + return validateTypeAnnotation(Type(type, Nullable), child); +} + } // anonymous namespace Result IRBuilder::addScratchLocal(Type type) { @@ -2029,14 +2029,29 @@ Result<> IRBuilder::makeBrOn( BrOn curr; curr.op = op; curr.castType = out; + curr.desc = nullptr; CHECK_ERR(visitBrOn(&curr)); - if (out != Type::none) { - if (!Type::isSubType(out, in)) { - return Err{"output type is not a subtype of the input type"}; - } - if (!Type::isSubType(curr.ref->type, in)) { - return Err{"expected input to match input type annotation"}; + + // Validate type immediates before we forget them. + switch (op) { + case BrOnNull: + case BrOnNonNull: + break; + case BrOnCastDesc: + case BrOnCastDescFail: { + assert(out.isRef()); + auto descriptor = out.getHeapType().getDescriptorType(); + if (!descriptor) { + return Err{"cast target must have descriptor"}; + } + CHECK_ERR(validateTypeAnnotation(out.with(*descriptor).with(Nullable), + curr.desc)); } + [[fallthrough]]; + case BrOnCast: + case BrOnCastFail: + assert(in.isRef()); + CHECK_ERR(validateTypeAnnotation(in, curr.ref)); } // Extra values need to be sent in a scratch local. @@ -2050,6 +2065,8 @@ Result<> IRBuilder::makeBrOn( case BrOnNonNull: case BrOnCast: case BrOnCastFail: + case BrOnCastDesc: + case BrOnCastDescFail: // Modeled as sending one value. if (extraArity == 0) { return Err{"br_on target does not expect a value"}; @@ -2069,6 +2086,8 @@ Result<> IRBuilder::makeBrOn( break; case BrOnCast: case BrOnCastFail: + case BrOnCastDesc: + case BrOnCastDescFail: testType = in; break; } @@ -2082,7 +2101,7 @@ Result<> IRBuilder::makeBrOn( auto name = getLabelName(label); CHECK_ERR(name); - auto* br = builder.makeBrOn(op, *name, curr.ref, out); + auto* br = builder.makeBrOn(op, *name, curr.ref, out, curr.desc); addBranchHint(br, likely); push(br); return Ok{}; @@ -2106,7 +2125,7 @@ Result<> IRBuilder::makeBrOn( // Perform the branch. CHECK_ERR(visitBrOn(&curr)); - auto* br = builder.makeBrOn(op, extraLabel, curr.ref, out); + auto* br = builder.makeBrOn(op, extraLabel, curr.ref, out, curr.desc); addBranchHint(br, likely); push(br); @@ -2127,6 +2146,7 @@ Result<> IRBuilder::makeBrOn( case BrOnNonNull: WASM_UNREACHABLE("unexpected op"); case BrOnCast: + case BrOnCastDesc: if (out.isNullable()) { resultType = Type(in.getHeapType(), NonNullable); } else { @@ -2134,6 +2154,7 @@ Result<> IRBuilder::makeBrOn( } break; case BrOnCastFail: + case BrOnCastDescFail: if (in.isNonNullable()) { resultType = Type(out.getHeapType(), NonNullable); } else { diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 988f333b92f..1d21cd7900f 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2290,6 +2290,11 @@ void BinaryInstWriter::visitRefGetDesc(RefGetDesc* curr) { } void BinaryInstWriter::visitBrOn(BrOn* curr) { + bool hasDesc = curr->op == BrOnCastDesc || curr->op == BrOnCastDescFail; + if (hasDesc && curr->desc->type.isNull()) { + emitUnreachable(); + return; + } switch (curr->op) { case BrOnNull: o << int8_t(BinaryConsts::BrOnNull); @@ -2300,27 +2305,30 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { o << U32LEB(getBreakIndex(curr->name)); return; case BrOnCast: - case BrOnCastFail: { - o << int8_t(BinaryConsts::GCPrefix); - if (curr->op == BrOnCast) { - o << U32LEB(BinaryConsts::BrOnCast); - } else { - o << U32LEB(BinaryConsts::BrOnCastFail); - } - assert(curr->ref->type.isRef()); - assert(Type::isSubType(curr->castType, curr->ref->type)); - uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | - (curr->castType.isNullable() ? 2 : 0); - o << flags; - o << U32LEB(getBreakIndex(curr->name)); - parent.writeHeapType(curr->ref->type.getHeapType(), - curr->ref->type.getExactness()); - parent.writeHeapType(curr->castType.getHeapType(), - curr->castType.getExactness()); - return; - } + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCast); + break; + case BrOnCastFail: + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCastFail); + break; + case BrOnCastDesc: + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCastDesc); + break; + case BrOnCastDescFail: + o << int8_t(BinaryConsts::GCPrefix) + << U32LEB(BinaryConsts::BrOnCastDescFail); + break; } - WASM_UNREACHABLE("invalid br_on_*"); + assert(curr->ref->type.isRef()); + assert(hasDesc || Type::isSubType(curr->castType, curr->ref->type)); + uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | + (curr->castType.isNullable() ? 2 : 0); + o << flags; + o << U32LEB(getBreakIndex(curr->name)); + parent.writeHeapType(curr->ref->type.getHeapType(), + curr->ref->type.getExactness()); + parent.writeHeapType(curr->castType.getHeapType(), + curr->castType.getExactness()); + return; } void BinaryInstWriter::visitStructNew(StructNew* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 67fac8f3fb9..906f01f1b5f 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2976,20 +2976,20 @@ void FunctionValidator::visitRefGetDesc(RefGetDesc* curr) { } void FunctionValidator::visitBrOn(BrOn* curr) { - shouldBeTrue(getModule()->features.hasGC(), - curr, - "br_on_cast requires gc [--enable-gc]"); + shouldBeTrue( + getModule()->features.hasGC(), curr, "br_on* requires gc [--enable-gc]"); if (curr->ref->type == Type::unreachable) { return; } if (!shouldBeTrue( - curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type")) { + curr->ref->type.isRef(), curr, "br_on* ref must have ref type")) { return; } - if (curr->op == BrOnCast || curr->op == BrOnCastFail) { + if (curr->op != BrOnNull && curr->op != BrOnNonNull) { + // Common validation for all br_on_cast* if (!shouldBeTrue(curr->castType.isRef(), curr, - "br_on_cast must have reference cast type")) { + "br_on_cast* must have reference cast type")) { return; } shouldBeEqual( @@ -2997,25 +2997,71 @@ void FunctionValidator::visitBrOn(BrOn* curr) { curr->ref->type.getHeapType().getBottom(), curr, "br_on_cast* target type and ref type must have a common supertype"); - shouldBeSubType( - curr->castType, - curr->ref->type, - curr, - "br_on_cast* target type must be a subtype of its input type"); - // See comment about exactness on visitRefTest. - if (!getModule()->features.hasCustomDescriptors()) { - shouldBeTrue(curr->castType.isInexact() || - curr->castType.with(Nullable) == - curr->ref->type.with(Nullable), + } + switch (curr->op) { + case BrOnNull: + case BrOnNonNull: + shouldBeEqual(curr->castType, + Type(Type::none), + curr, + "non-cast br_on* must not set castType field"); + break; + case BrOnCastDesc: + case BrOnCastDescFail: { + shouldBeTrue(getModule()->features.hasCustomDescriptors(), curr, - "br_on_cast* to exact type requires custom descriptors " + "br_on_cast_desc* requires custom descriptors " "[--enable-custom-descriptors]"); + if (!shouldBeTrue(curr->desc && curr->desc->type.isRef(), + curr, + "br_on_cast_desc* descriptor must have ref type")) { + return; + } + auto descriptor = curr->desc->type.getHeapType(); + if (!descriptor.isBottom()) { + auto described = descriptor.getDescribedType(); + if (!shouldBeTrue( + bool(described), + curr, + "br_on_cast_desc* descriptor should have a described type")) { + return; + } + shouldBeEqual( + *described, + curr->castType.getHeapType(), + curr, + "br_on_cast_desc* cast type should be described by descriptor"); + shouldBeEqual( + curr->castType.getExactness(), + curr->desc->type.getExactness(), + curr, + "br_on_cast_desc* cast exactness should match descriptor exactness"); + shouldBeTrue(curr->ref->type.isNullable() || + curr->castType.isNonNullable(), + curr, + "br_on_cast_desc* with non-nullable ref should have " + "non-nullable cast type"); + } + break; + } + case BrOnCast: + case BrOnCastFail: { + shouldBeSubType( + curr->castType, + curr->ref->type, + curr, + "br_on_cast* target type must be a subtype of its input type"); + // See comment about exactness on visitRefTest. + if (!getModule()->features.hasCustomDescriptors()) { + shouldBeTrue(curr->castType.isInexact() || + curr->castType.with(Nullable) == + curr->ref->type.with(Nullable), + curr, + "br_on_cast* to exact type requires custom descriptors " + "[--enable-custom-descriptors]"); + } + break; } - } else { - shouldBeEqual(curr->castType, - Type(Type::none), - curr, - "non-cast br_on* must not set intendedType field"); } noteBreak(curr->name, curr->getSentType(), curr); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index aa91b29da30..2fc1238a255 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1077,34 +1077,59 @@ void RefGetDesc::finalize() { return; } - auto desc = ref->type.getHeapType().getDescriptorType(); - assert(desc); - type = Type(*desc, NonNullable, ref->type.getExactness()); + if (ref->type.isNull()) { + // The operation will trap. Model it as returning an uninhabitable type. + type = ref->type.with(NonNullable); + } else { + auto desc = ref->type.getHeapType().getDescriptorType(); + assert(desc); + type = Type(*desc, NonNullable, ref->type.getExactness()); + } } void BrOn::finalize() { - if (ref->type == Type::unreachable) { + if (ref->type == Type::unreachable || + (desc && desc->type == Type::unreachable)) { type = Type::unreachable; return; } if (op == BrOnCast || op == BrOnCastFail) { - // The cast type must be a subtype of the input type. If we've refined the - // input type so that this is no longer true, we can fix it by similarly - // refining the cast type in a way that will not change the cast behavior. + // If we've refined the input type so that it is no longer a subtype of the + // cast type, we can improve the cast type in a way that will not change the + // cast behavior. This satisfies the constraint we had before Custom + // Descriptors that the cast type is a subtype of the input type. castType = Type::getGreatestLowerBound(castType, ref->type); assert(castType.isRef()); + } else if (op == BrOnCastDesc || op == BrOnCastDescFail) { + if (desc->type.isNull()) { + // Cast will never be executed and the instruction will not be emitted. + // Model this with an uninhabitable cast type. + castType = desc->type.with(NonNullable); + } else { + // The cast heap type and exactness is determined by the descriptor's + // type. Its nullability can be improved if the input value is + // non-nullable. + auto heapType = desc->type.getHeapType().getDescribedType(); + assert(heapType); + auto exactness = desc->type.getExactness(); + castType = castType.with(*heapType).with(exactness); + if (ref->type.isNonNullable()) { + castType = castType.with(NonNullable); + } + } } switch (op) { case BrOnNull: // If we do not branch, we flow out the existing value as non-null. type = ref->type.with(NonNullable); - break; + return; case BrOnNonNull: // If we do not branch, we flow out nothing (the spec could also have had // us flow out the null, but it does not). type = Type::none; - break; + return; case BrOnCast: + case BrOnCastDesc: if (castType.isNullable()) { // Nulls take the branch, so the result is non-nullable. type = ref->type.with(NonNullable); @@ -1113,8 +1138,9 @@ void BrOn::finalize() { // the input is. type = ref->type; } - break; + return; case BrOnCastFail: + case BrOnCastDescFail: if (castType.isNullable()) { // Nulls do not take the branch, so the result is non-nullable only if // the input is. @@ -1123,10 +1149,9 @@ void BrOn::finalize() { // Nulls take the branch, so the result is non-nullable. type = castType; } - break; - default: - WASM_UNREACHABLE("invalid br_on_*"); + return; } + WASM_UNREACHABLE("invalid br_on_*"); } Type BrOn::getSentType() { @@ -1143,6 +1168,7 @@ Type BrOn::getSentType() { // BrOnNonNull sends the non-nullable type on the branch. return ref->type.with(NonNullable); case BrOnCast: + case BrOnCastDesc: // The same as the result type of br_on_cast_fail. if (castType.isNullable()) { return castType.with(ref->type.getNullability()); @@ -1150,6 +1176,7 @@ Type BrOn::getSentType() { return castType; } case BrOnCastFail: + case BrOnCastDescFail: // The same as the result type of br_on_cast (if reachable). if (ref->type == Type::unreachable) { return Type::unreachable; @@ -1159,9 +1186,8 @@ Type BrOn::getSentType() { } else { return ref->type; } - default: - WASM_UNREACHABLE("invalid br_on_*"); } + WASM_UNREACHABLE("invalid br_on_*"); } void StructNew::finalize() { diff --git a/test/lit/basic/custom-descriptors.wast b/test/lit/basic/custom-descriptors.wast index ab3b70f7ab0..7cef783b9f8 100644 --- a/test/lit/basic/custom-descriptors.wast +++ b/test/lit/basic/custom-descriptors.wast @@ -25,8 +25,16 @@ ) (rec + ;; CHECK-TEXT: (type $3 (func (param anyref (ref null $describing)))) + + ;; CHECK-TEXT: (type $4 (func (param anyref) (result anyref))) + ;; CHECK-TEXT: (rec ;; CHECK-TEXT-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) + ;; CHECK-BIN: (type $3 (func (param anyref (ref null $describing)))) + + ;; CHECK-BIN: (type $4 (func (param anyref) (result anyref))) + ;; CHECK-BIN: (rec ;; CHECK-BIN-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) (type $shared-described (shared (descriptor $shared-describing (struct)))) @@ -36,10 +44,14 @@ ) - ;; CHECK-TEXT: (type $5 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-TEXT: (type $7 (func (param (ref null $described) (ref null (exact $middle))))) + + ;; CHECK-TEXT: (type $8 (func)) ;; CHECK-TEXT: (global $g (ref null $described) (ref.null none)) - ;; CHECK-BIN: (type $5 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-BIN: (type $7 (func (param (ref null $described) (ref null (exact $middle))))) + + ;; CHECK-BIN: (type $8 (func)) ;; CHECK-BIN: (global $g (ref null $described) (ref.null none)) (global $g (ref null $described) (ref.null none)) @@ -47,7 +59,7 @@ ;; CHECK-BIN: (global $shared (ref null $shared-describing) (ref.null (shared none))) (global $shared (ref null $shared-describing) (ref.null (shared none))) - ;; CHECK-TEXT: (func $ref-get-desc (type $5) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-TEXT: (func $ref-get-desc (type $7) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l1 (result (ref $middle)) ;; CHECK-TEXT-NEXT: (ref.get_desc $described @@ -63,7 +75,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-get-desc (type $5) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-BIN: (func $ref-get-desc (type $7) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result (ref $middle)) ;; CHECK-BIN-NEXT: (ref.get_desc $described @@ -95,6 +107,162 @@ ) ) ) + + ;; CHECK-TEXT: (func $ref-get-desc-null (type $8) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefGetDesc we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-get-desc-null (type $8) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $ref-get-desc-null + (drop + (ref.get_desc $described + (ref.null none) + ) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast-desc (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $l (result (ref null $middle)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_desc $l anyref (ref null $middle) + ;; CHECK-TEXT-NEXT: (local.get $any) + ;; CHECK-TEXT-NEXT: (local.get $descriptor) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-desc (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block (result (ref null $middle)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_desc $block anyref (ref null $middle) + ;; CHECK-BIN-NEXT: (local.get $any) + ;; CHECK-BIN-NEXT: (local.get $descriptor) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $br-on-cast-desc (param $any anyref) (param $descriptor (ref null $describing)) + (drop + (block $l (result (ref null $middle)) + (br_on_cast_desc $l anyref (ref null $middle) + (local.get $any) + (local.get $descriptor) + ) + (unreachable) + ) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast-desc-fail (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $l (result anyref) + ;; CHECK-TEXT-NEXT: (br_on_cast_desc_fail $l anyref (ref null $middle) + ;; CHECK-TEXT-NEXT: (local.get $any) + ;; CHECK-TEXT-NEXT: (local.get $descriptor) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-desc-fail (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (br_on_cast_desc_fail $block anyref (ref null $middle) + ;; CHECK-BIN-NEXT: (local.get $any) + ;; CHECK-BIN-NEXT: (local.get $descriptor) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $br-on-cast-desc-fail (param $any anyref) (param $descriptor (ref null $describing)) + (drop + (block $l (result anyref) + (br_on_cast_desc_fail $l anyref (ref null $middle) + (local.get $any) + (local.get $descriptor) + ) + ) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast-desc-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable BrOn we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-desc-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $br-on-cast-desc-null (param anyref) (result anyref) + (br_on_cast_desc 0 anyref (ref null $described) + (ref.null none) + (ref.null none) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast-desc-fail-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable BrOn we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-desc-fail-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $br-on-cast-desc-fail-null (param anyref) (result anyref) + (br_on_cast_desc_fail 0 anyref (ref null $described) + (ref.null none) + (ref.null none) + ) + ) + + + ;; TODO: upcast, unreachable ref, null ref, unreachable desc, null desc, exact desc, immediates are not refs ) ;; CHECK-BIN-NODEBUG: (rec ;; CHECK-BIN-NODEBUG-NEXT: (type $0 (descriptor $1 (struct))) @@ -103,18 +271,24 @@ ;; CHECK-BIN-NODEBUG: (type $2 (describes $1 (struct))) +;; CHECK-BIN-NODEBUG: (type $3 (func (param anyref (ref null $2)))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param anyref) (result anyref))) + ;; CHECK-BIN-NODEBUG: (rec -;; CHECK-BIN-NODEBUG-NEXT: (type $3 (shared (descriptor $4 (struct)))) +;; CHECK-BIN-NODEBUG-NEXT: (type $5 (shared (descriptor $6 (struct)))) + +;; CHECK-BIN-NODEBUG: (type $6 (shared (describes $5 (struct)))) -;; CHECK-BIN-NODEBUG: (type $4 (shared (describes $3 (struct)))) +;; CHECK-BIN-NODEBUG: (type $7 (func (param (ref null $0) (ref null (exact $1))))) -;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref null $0) (ref null (exact $1))))) +;; CHECK-BIN-NODEBUG: (type $8 (func)) ;; CHECK-BIN-NODEBUG: (global $global$0 (ref null $0) (ref.null none)) -;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $4) (ref.null (shared none))) +;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $6) (ref.null (shared none))) -;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) +;; CHECK-BIN-NODEBUG: (func $0 (type $7) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result (ref $1)) ;; CHECK-BIN-NODEBUG-NEXT: (ref.get_desc $0 @@ -130,3 +304,57 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $8) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $3) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_desc $block anyref (ref null $1) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $3 (type $3) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_desc_fail $block anyref (ref null $1) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $4 (type $4) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $5 (type $4) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/parse-bad-get-desc.wast b/test/lit/parse-bad-get-desc.wast deleted file mode 100644 index 9f2ca811c9b..00000000000 --- a/test/lit/parse-bad-get-desc.wast +++ /dev/null @@ -1,15 +0,0 @@ -;; RUN: not wasm-opt %s -S -o - 2>&1 | filecheck %s - -;; CHECK: 10:7: error: expected type with descriptor - -(module - (type $no-descriptor (struct)) - - (func $ref.get_desc-no-descriptor (param (ref null $no-descriptor)) - (drop - (ref.get_desc $no-descriptor - (local.get 0) - ) - ) - ) -) diff --git a/test/lit/validation/custom-descriptors.wast b/test/lit/validation/custom-descriptors.wast deleted file mode 100644 index f304e792506..00000000000 --- a/test/lit/validation/custom-descriptors.wast +++ /dev/null @@ -1,19 +0,0 @@ -;; Test that custom descriptors instructions are validated correctly. - -;; RUN: not wasm-opt %s -all 2>&1 | filecheck %s - -;; CHECK: [wasm-validator error in function ref.get_desc-inexact] function body type must match, if function returns - -(module - (rec - (type $described (descriptor $describing (struct))) - (type $describing (describes $described (struct))) - ) - - (func $ref.get_desc-inexact (param (ref null $described)) (result (ref (exact $describing))) - ;; The result should be inexact because the input is inexact. - (ref.get_desc $described - (local.get 0) - ) - ) -) diff --git a/test/spec/br_on_cast_desc.wast b/test/spec/br_on_cast_desc.wast new file mode 100644 index 00000000000..6a9546eae0a --- /dev/null +++ b/test/spec/br_on_cast_desc.wast @@ -0,0 +1,223 @@ +(module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + + ;; br_on_cast_desc + + (func $br_on_cast_desc-unreachable (result anyref) + (unreachable) + (br_on_cast_desc 0 anyref (ref null $super)) + ) + (func $br_on_cast_desc-null (param anyref) (result anyref) + (br_on_cast_desc 0 anyref (ref null $super) + (ref.null none) + (ref.null none) + ) + ) + (func $br_on_cast_desc-upcast (param $sub (ref null $sub)) (param $super.desc (ref null $super.desc)) (result (ref null $super)) + (br_on_cast_desc 0 (ref null $sub) (ref null $super) + (local.get $sub) + (local.get $super.desc) + ) + ) + (func $br_on_cast_desc-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result (ref null (exact $super))) + ;; The sent type exact because the descriptor is exact. + (br_on_cast_desc 0 anyref (ref null $super) + (local.get $any) + (local.get $super.desc) + ) + (unreachable) + ) + + ;; br_on_cast_desc_fail + + (func $br_on_cast_desc_fail-unreachable (result anyref) + (unreachable) + (br_on_cast_desc_fail 0 anyref (ref null $super)) + ) + (func $br_on_cast_desc_fail-null (param anyref) (result anyref) + (br_on_cast_desc_fail 0 anyref (ref null $super) + (ref.null none) + (ref.null none) + ) + ) + (func $br_on_cast_desc_fail-upcast (param $sub (ref null $sub)) (param $super.desc (ref null $super.desc)) (result (ref null $super)) + (br_on_cast_desc_fail 0 (ref null $sub) (ref null $super) + (local.get $sub) + (local.get $super.desc) + ) + ) + (func $br_on_cast_desc_fail-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result anyref) + (block (result (ref null (exact $super))) + ;; The result type can be exact because the descriptor is exact. + (br_on_cast_desc_fail 1 anyref (ref null (exact $super)) + (local.get $any) + (local.get $super.desc) + ) + ) + ) +) + +(assert_malformed + ;; Input type must be a reference. + (module quote "(module (rec (type $struct (descriptor $desc (struct))) (type $desc (describes $struct (struct)))) (func (result anyref) (unreachable) (br_on_cast_desc 0 i32 (ref null $struct))))") + "expected reftype" +) + +(assert_malformed + ;; Input type must be a reference. + (module quote "(module (rec (type $struct (descriptor $desc (struct))) (type $desc (describes $struct (struct)))) (func (result anyref) (unreachable) (br_on_cast_desc_fail 0 i32 (ref null $struct))))") + "expected reftype" +) + +(assert_malformed + ;; Cast type must be a reference. + (module quote "(module (func (unreachable) (br_on_cast_desc 0 anyref i32)))") + "expected reftype" +) + +(assert_malformed + ;; Cast type must be a reference. + (module quote "(module (func (unreachable) (br_on_cast_desc_fail 0 anyref i32)))") + "expected reftype" +) + +(assert_invalid + (module + (type (struct)) + (func (result anyref) + (unreachable) + ;; Cannot do a descriptor cast to a type without a descriptor. + (br_on_cast_desc 0 anyref (ref null 0)) + ) + ) + "cast target must have descriptor" +) + +(assert_invalid + (module + (type (struct)) + (func (result anyref) + (unreachable) + ;; Cannot do a descriptor cast to a type without a descriptor. + (br_on_cast_desc_fail 0 anyref (ref null 0)) + ) + ) + "cast target must have descriptor" +) + +(assert_invalid + (module + (rec + (type (descriptor 1 (struct))) + (type (describes 0 (struct))) + ) + (func (param anyref) (result anyref) + (br_on_cast_desc 0 eqref (ref null 0) + ;; This should be an eqref but is an anyref. + (local.get 0) + (ref.null none) + ) + ) + ) + "invalid reference type on stack" +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param anyref) (result anyref) + (br_on_cast_desc_fail 0 eqref (ref null $struct) + ;; This should be an eqref but is an anyref. + (local.get 0) + (ref.null none) + ) + ) + ) + "invalid reference type on stack" +) + +(assert_invalid + (module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + (func (param $any anyref) (param $super.desc (ref null $super.desc)) (result anyref) + (br_on_cast_desc 0 anyref (ref null $sub) + (local.get $any) + ;; This should be a $sub.desc but it is a $super.desc. + (local.get $super.desc) + ) + ) + ) + "invalid reference type on stack" +) + +(assert_invalid + (module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + (func (param $any anyref) (param $super.desc (ref null $super.desc)) (result anyref) + (br_on_cast_desc_fail 0 anyref (ref null $sub) + (local.get $any) + ;; This should be a $sub.desc but it is a $super.desc. + (local.get $super.desc) + ) + ) + ) + "invalid reference type on stack" +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param $any anyref) (param $desc (ref null $desc)) (result (ref null (exact $struct))) + ;; The sent type is not exact because the descriptor is not exact. + (br_on_cast_desc 0 anyref (ref null $struct) + (local.get $any) + (local.get $descriptor) + ) + (unreachable) + ) + ) + "break type must be a subtype" +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param $any anyref) (param $desc (ref null $desc)) (result anyref) + (block (result (ref null (exact $struct))) + ;; The result type can be exact because the descriptor is exact. + (br_on_cast_desc_fail 1 anyref (ref null (exact $struct)) + (local.get $any) + (local.get $desc) + ) + ) + ) + ) + "function body type must match" +) diff --git a/test/spec/ref.get_cast.wast b/test/spec/ref.get_cast.wast new file mode 100644 index 00000000000..4c01209ad3e --- /dev/null +++ b/test/spec/ref.get_cast.wast @@ -0,0 +1,55 @@ +(module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func $unreachable (param $struct (ref null $struct)) (result (ref $desc)) + (ref.get_desc $struct + (unreachable) + ) + ) + (func $null (param $struct (ref null $struct)) (result (ref $desc)) + (ref.get_desc $struct + (ref.null none) + ) + ) + (func $inexact (param $struct (ref null $struct)) (result (ref $desc)) + (ref.get_desc $struct + (local.get $struct) + ) + ) + (func $exact (param $struct (ref null (exact $struct))) (result (ref (exact $desc))) + (ref.get_desc $struct + (local.get $struct) + ) + ) +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param $struct (ref null $struct)) (result (ref (exact $desc))) + ;; The result is not exact if the input is not exact. + (ref.get_desc $struct + (local.get $struct) + ) + ) + ) + "function body must match" +) + +(assert_invalid + (module + (type $struct (struct)) + (func (param $struct (ref null $struct)) (result anyref) + ;; The type must have a descriptor + (ref.get_desc $struct + (local.get $struct) + ) + ) + ) + "expected type with descriptor" +) From bb17db39ec5adb307f2d46ee4ee314d96b9a7456 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 28 May 2025 13:38:12 -0700 Subject: [PATCH 529/622] Fix assertion failure in TupleOptimization (#7629) We previously counted a local use for all `local.tee` operands to `tuple.extract`, even if the `local.tee` was unreachable and had a non-tuple local. This led to an assertion failure later. Fix the issue by disregarding `local.tee` operands of `tuple.extract` if they don't have tuple locals. --- src/passes/TupleOptimization.cpp | 7 +++++-- test/lit/passes/tuple-optimization.wast | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index c4fd51b16da..0a9584e09fd 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -150,8 +150,11 @@ struct TupleOptimization : public WalkerPass> { void visitTupleExtract(TupleExtract* curr) { // We need the input to be a local, either from a tee or a get. - if (auto* set = curr->tuple->dynCast()) { - validUses[set->index]++; + if (auto* tee = curr->tuple->dynCast()) { + // The tee might not be of a tuple local if it is unreachable. + if (getFunction()->getLocalType(tee->index).isTuple()) { + validUses[tee->index]++; + } } else if (auto* get = curr->tuple->dynCast()) { validUses[get->index]++; } diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index e0d68848e8a..6ee5cc829b5 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -1064,4 +1064,23 @@ ) ) ) + + ;; CHECK: (func $unreachable.tuple.extract (type $3) (result i32) + ;; CHECK-NEXT: (local $tuple (tuple i32 i64)) + ;; CHECK-NEXT: (local $non-tuple i32) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $non-tuple + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable.tuple.extract (result i32) + (local $tuple (tuple i32 i64)) + (local $non-tuple i32) + (tuple.extract 2 0 + (local.tee $non-tuple + (unreachable) + ) + ) + ) ) From b3a14c2ceaffd95ac49365822c39a8595750762f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 29 May 2025 07:39:00 -0700 Subject: [PATCH 530/622] [custom-descriptors] ref.cast_desc (#7630) Add an optional `desc` child to `RefCast` for use in the new `ref.cast_desc` instruction. Add support for parsing, printing, and validating the new instruction. Fix a few minor issues left over from adding the branching descriptor casts along the way. --- scripts/gen-s-parser.py | 3 +- scripts/test/fuzzing.py | 1 + src/gen-s-parser.inc | 21 +- src/ir/child-typer.h | 15 +- src/parser/contexts.h | 7 +- src/parser/parsers.h | 10 +- src/passes/Print.cpp | 6 +- src/wasm-binary.h | 2 + src/wasm-builder.h | 4 + src/wasm-delegations-fields.def | 1 + src/wasm-ir-builder.h | 2 +- src/wasm.h | 3 + src/wasm/wasm-binary.cpp | 15 +- src/wasm/wasm-ir-builder.cpp | 35 ++- src/wasm/wasm-stack.cpp | 18 +- src/wasm/wasm-validator.cpp | 34 +++ src/wasm/wasm.cpp | 22 +- test/lit/basic/custom-descriptors.wast | 283 ++++++++++++++++++++++--- test/spec/br_on_cast_desc.wast | 18 +- test/spec/ref.cast_desc.wast | 165 ++++++++++++++ 20 files changed, 591 insertions(+), 74 deletions(-) create mode 100644 test/spec/ref.cast_desc.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 0448ebbace4..809741c128e 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -609,7 +609,8 @@ ("i31.get_s", "makeI31Get(true)"), ("i31.get_u", "makeI31Get(false)"), ("ref.test", "makeRefTest()"), - ("ref.cast", "makeRefCast()"), + ("ref.cast", "makeRefCast(false)"), + ("ref.cast_desc", "makeRefCast(true)"), ("ref.get_desc", "makeRefGetDesc()"), ("br_on_null", "makeBrOnNull()"), ("br_on_non_null", "makeBrOnNull(true)"), diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 7975b1074b4..164ff2cb915 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -115,6 +115,7 @@ 'custom-descriptors.wast', 'br_on_cast_desc.wast', 'ref.get_cast.wast', + 'ref.cast_desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 69d7c772748..23c01d67eb2 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -4807,12 +4807,23 @@ switch (buf[0]) { return Ok{}; } goto parse_error; - case 'c': - if (op == "ref.cast"sv) { - CHECK_ERR(makeRefCast(ctx, pos, annotations)); - return Ok{}; + case 'c': { + switch (buf[8]) { + case '\0': + if (op == "ref.cast"sv) { + CHECK_ERR(makeRefCast(ctx, pos, annotations, false)); + return Ok{}; + } + goto parse_error; + case '_': + if (op == "ref.cast_desc"sv) { + CHECK_ERR(makeRefCast(ctx, pos, annotations, true)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } case 'e': if (op == "ref.eq"sv) { CHECK_ERR(makeRefEq(ctx, pos, annotations)); diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index e588e72b1c1..71f9c2edf8d 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -852,9 +852,17 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->ref, Type(top, Nullable)); } - void visitRefCast(RefCast* curr) { + void visitRefCast(RefCast* curr, std::optional target = std::nullopt) { auto top = curr->type.getHeapType().getTop(); note(&curr->ref, Type(top, Nullable)); + if (curr->desc) { + if (!target) { + target = curr->type; + } + auto desc = target->getHeapType().getDescriptorType(); + assert(desc); + note(&curr->desc, Type(*desc, Nullable, curr->type.getExactness())); + } } void visitRefGetDesc(RefGetDesc* curr, @@ -881,8 +889,9 @@ template struct ChildTyper : OverriddenVisitor { auto top = target->getHeapType().getTop(); note(&curr->ref, Type(top, Nullable)); if (curr->op == BrOnCastDesc || curr->op == BrOnCastDescFail) { - auto descriptor = *target->getHeapType().getDescriptorType(); - note(&curr->desc, Type(descriptor, Nullable)); + auto desc = target->getHeapType().getDescriptorType(); + assert(desc); + note(&curr->desc, Type(*desc, Nullable)); } return; } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index fc519119097..c1cfa5dbbb1 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -733,7 +733,7 @@ struct NullInstrParserCtx { return Ok{}; } template - Result<> makeRefCast(Index, const std::vector&, TypeT) { + Result<> makeRefCast(Index, const std::vector&, TypeT, bool) { return Ok{}; } template @@ -2592,8 +2592,9 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { Result<> makeRefCast(Index pos, const std::vector& annotations, - Type type) { - return withLoc(pos, irBuilder.makeRefCast(type)); + Type type, + bool isDesc) { + return withLoc(pos, irBuilder.makeRefCast(type, isDesc)); } Result<> makeRefGetDesc(Index pos, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 937822ae65a..a16b3a801e6 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -231,7 +231,7 @@ Result<> makeI31Get(Ctx&, Index, const std::vector&, bool signed_); template Result<> makeRefTest(Ctx&, Index, const std::vector&); template -Result<> makeRefCast(Ctx&, Index, const std::vector&); +Result<> makeRefCast(Ctx&, Index, const std::vector&, bool isDesc); template Result<> makeRefGetDesc(Ctx&, Index, const std::vector&); template @@ -2219,11 +2219,13 @@ makeRefTest(Ctx& ctx, Index pos, const std::vector& annotations) { } template -Result<> -makeRefCast(Ctx& ctx, Index pos, const std::vector& annotations) { +Result<> makeRefCast(Ctx& ctx, + Index pos, + const std::vector& annotations, + bool isDesc) { auto type = reftype(ctx); CHECK_ERR(type); - return ctx.makeRefCast(pos, annotations, *type); + return ctx.makeRefCast(pos, annotations, *type, isDesc); } template diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 7af4c4a1075..8d76a425778 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2208,7 +2208,11 @@ struct PrintExpressionContents printType(curr->castType); } void visitRefCast(RefCast* curr) { - printMedium(o, "ref.cast "); + if (curr->desc) { + printMedium(o, "ref.cast_desc "); + } else { + printMedium(o, "ref.cast "); + } printType(curr->type); } void visitRefGetDesc(RefGetDesc* curr) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 4dfc1df034d..88a29ba90ea 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1172,6 +1172,8 @@ enum ASTNodes { RefTestNull = 0x15, RefCast = 0x16, RefCastNull = 0x17, + RefCastDesc = 0x23, + RefCastDescNull = 0x24, BrOnCast = 0x18, BrOnCastFail = 0x19, BrOnCastDesc = 0x25, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 588c2b0330c..9429108e499 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -888,8 +888,12 @@ class Builder { return ret; } RefCast* makeRefCast(Expression* ref, Type type) { + return makeRefCast(ref, nullptr, type); + } + RefCast* makeRefCast(Expression* ref, Expression* desc, Type type) { auto* ret = wasm.allocator.alloc(); ret->ref = ref; + ret->desc = desc; ret->type = type; ret->finalize(); return ret; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index cc2074e6966..b6932f6d1b9 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -650,6 +650,7 @@ DELEGATE_FIELD_CHILD(RefTest, ref) DELEGATE_FIELD_CASE_END(RefTest) DELEGATE_FIELD_CASE_START(RefCast) +DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(RefCast, desc) DELEGATE_FIELD_CHILD(RefCast, ref) DELEGATE_FIELD_CASE_END(RefCast) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 3120e8ce696..d801750fa57 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -208,7 +208,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { bool isReturn, std::optional inline_ = std::nullopt); Result<> makeRefTest(Type type); - Result<> makeRefCast(Type type); + Result<> makeRefCast(Type type, bool isDesc); Result<> makeRefGetDesc(HeapType type); Result<> makeBrOn(Index label, BrOnOp op, diff --git a/src/wasm.h b/src/wasm.h index 77719cc1010..9a78c7c6ba5 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1620,6 +1620,9 @@ class RefCast : public SpecificExpression { Expression* ref; + // Used only for ref.cast_desc. + Expression* desc; + void finalize(); Type& getCastType() { return type; } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 90e3cd0c331..4c570360b82 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4505,11 +4505,22 @@ Result<> WasmBinaryReader::readInst() { } case BinaryConsts::RefCast: { auto [heapType, exactness] = getHeapType(); - return builder.makeRefCast(Type(heapType, NonNullable, exactness)); + return builder.makeRefCast(Type(heapType, NonNullable, exactness), + false); } case BinaryConsts::RefCastNull: { auto [heapType, exactness] = getHeapType(); - return builder.makeRefCast(Type(heapType, Nullable, exactness)); + return builder.makeRefCast(Type(heapType, Nullable, exactness), + false); + } + case BinaryConsts::RefCastDesc: { + auto [heapType, exactness] = getHeapType(); + return builder.makeRefCast(Type(heapType, NonNullable, exactness), + true); + } + case BinaryConsts::RefCastDescNull: { + auto [heapType, exactness] = getHeapType(); + return builder.makeRefCast(Type(heapType, Nullable, exactness), true); } case BinaryConsts::RefGetDesc: { auto type = getIndexedHeapType(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index b7962326ff0..93f47aead92 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2005,11 +2005,28 @@ Result<> IRBuilder::makeRefTest(Type type) { return Ok{}; } -Result<> IRBuilder::makeRefCast(Type type) { +Result<> IRBuilder::makeRefCast(Type type, bool isDesc) { + std::optional descriptor; + if (isDesc) { + assert(type.isRef()); + descriptor = type.getHeapType().getDescriptorType(); + if (!descriptor) { + return Err{"cast target must have descriptor"}; + } + } + RefCast curr; curr.type = type; + // Placeholder value to differentiate ref.cast_desc. + curr.desc = isDesc ? &curr : nullptr; CHECK_ERR(visitRefCast(&curr)); - push(builder.makeRefCast(curr.ref, type)); + + if (isDesc) { + CHECK_ERR( + validateTypeAnnotation(type.with(*descriptor).with(Nullable), curr.desc)); + } + + push(builder.makeRefCast(curr.ref, curr.desc, type)); return Ok{}; } @@ -2026,6 +2043,15 @@ Result<> IRBuilder::makeRefGetDesc(HeapType type) { Result<> IRBuilder::makeBrOn( Index label, BrOnOp op, Type in, Type out, std::optional likely) { + std::optional descriptor; + if (op == BrOnCastDesc || op == BrOnCastDescFail) { + assert(out.isRef()); + descriptor = out.getHeapType().getDescriptorType(); + if (!descriptor) { + return Err{"cast target must have descriptor"}; + } + } + BrOn curr; curr.op = op; curr.castType = out; @@ -2039,11 +2065,6 @@ Result<> IRBuilder::makeBrOn( break; case BrOnCastDesc: case BrOnCastDescFail: { - assert(out.isRef()); - auto descriptor = out.getHeapType().getDescriptorType(); - if (!descriptor) { - return Err{"cast target must have descriptor"}; - } CHECK_ERR(validateTypeAnnotation(out.with(*descriptor).with(Nullable), curr.desc)); } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 1d21cd7900f..aeb8359116f 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -81,7 +81,7 @@ void BinaryInstWriter::visitBreak(Break* curr) { // are casting, and to emit the proper thing. RefCast cast; cast.type = to; - cast.ref = nullptr; + cast.ref = cast.desc = nullptr; visitRefCast(&cast); }; @@ -2271,11 +2271,23 @@ void BinaryInstWriter::visitRefTest(RefTest* curr) { } void BinaryInstWriter::visitRefCast(RefCast* curr) { + if (curr->desc && curr->desc->type.isNull()) { + emitUnreachable(); + return; + } o << int8_t(BinaryConsts::GCPrefix); if (curr->type.isNullable()) { - o << U32LEB(BinaryConsts::RefCastNull); + if (curr->desc) { + o << U32LEB(BinaryConsts::RefCastDescNull); + } else { + o << U32LEB(BinaryConsts::RefCastNull); + } } else { - o << U32LEB(BinaryConsts::RefCast); + if (curr->desc) { + o << U32LEB(BinaryConsts::RefCastDesc); + } else { + o << U32LEB(BinaryConsts::RefCast); + } } parent.writeHeapType(curr->type.getHeapType(), curr->type.getExactness()); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 906f01f1b5f..a3d658369d7 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2966,6 +2966,40 @@ void FunctionValidator::visitRefCast(RefCast* curr) { "ref.cast to exact type requires custom descriptors " "[--enable-custom-descriptors]"); } + + if (!curr->desc) { + return; + } + + shouldBeTrue(getModule()->features.hasCustomDescriptors(), + curr, + "ref.cast_desc requires custom descriptors " + "[--enable-custom-descriptors]"); + if (!shouldBeTrue(curr->desc && curr->desc->type.isRef(), + curr, + "ref.cast_desc descriptor must have ref type")) { + return; + } + auto descriptor = curr->desc->type.getHeapType(); + if (descriptor.isBottom()) { + return; + } + + auto described = descriptor.getDescribedType(); + if (!shouldBeTrue(bool(described), + curr, + "ref.cast_desc descriptor should have a described type")) { + return; + } + shouldBeEqual(*described, + curr->type.getHeapType(), + curr, + "ref.cast_desc cast type should be described by descriptor"); + shouldBeEqual( + curr->type.getExactness(), + curr->desc->type.getExactness(), + curr, + "ref.cast_desc cast exactness should match descriptor exactness"); } void FunctionValidator::visitRefGetDesc(RefGetDesc* curr) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 2fc1238a255..6ece8bfab8c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1052,11 +1052,31 @@ void RefTest::finalize() { } void RefCast::finalize() { - if (ref->type == Type::unreachable) { + if (ref->type == Type::unreachable || + (desc && desc->type == Type::unreachable)) { type = Type::unreachable; return; } + if (desc) { + if (desc->type.isNull()) { + // Cast will never be executed and the instruction will not be emitted. + // Model this with an uninhabitable cast type. + type = desc->type.with(NonNullable); + return; + } + // The cast heap type and exactness is determined by the descriptor's type. + // Its nullability can be improved if the input valus is non-nullable. + auto heapType = desc->type.getHeapType().getDescribedType(); + assert(heapType); + auto exactness = desc->type.getExactness(); + type = type.with(*heapType).with(exactness); + if (ref->type.isNonNullable()) { + type = type.with(NonNullable); + } + return; + } + // We reach this before validation, so the input type might be totally wrong. // Return early in this case to avoid doing the wrong thing below. if (!ref->type.isRef()) { diff --git a/test/lit/basic/custom-descriptors.wast b/test/lit/basic/custom-descriptors.wast index 7cef783b9f8..b8ced993e3c 100644 --- a/test/lit/basic/custom-descriptors.wast +++ b/test/lit/basic/custom-descriptors.wast @@ -25,15 +25,23 @@ ) (rec - ;; CHECK-TEXT: (type $3 (func (param anyref (ref null $describing)))) + ;; CHECK-TEXT: (type $3 (func (param anyref) (result anyref))) - ;; CHECK-TEXT: (type $4 (func (param anyref) (result anyref))) + ;; CHECK-TEXT: (type $4 (func (param anyref (ref null $describing)))) + + ;; CHECK-TEXT: (type $5 (func (result anyref))) + + ;; CHECK-TEXT: (type $6 (func (param anyref (ref null (exact $middle))) (result (ref null (exact $described))))) ;; CHECK-TEXT: (rec ;; CHECK-TEXT-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) - ;; CHECK-BIN: (type $3 (func (param anyref (ref null $describing)))) + ;; CHECK-BIN: (type $3 (func (param anyref) (result anyref))) + + ;; CHECK-BIN: (type $4 (func (param anyref (ref null $describing)))) + + ;; CHECK-BIN: (type $5 (func (result anyref))) - ;; CHECK-BIN: (type $4 (func (param anyref) (result anyref))) + ;; CHECK-BIN: (type $6 (func (param anyref (ref null (exact $middle))) (result (ref null (exact $described))))) ;; CHECK-BIN: (rec ;; CHECK-BIN-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) @@ -44,14 +52,18 @@ ) - ;; CHECK-TEXT: (type $7 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-TEXT: (type $9 (func (param (ref null $described) (ref null (exact $middle))))) - ;; CHECK-TEXT: (type $8 (func)) + ;; CHECK-TEXT: (type $10 (func)) + + ;; CHECK-TEXT: (type $11 (func (param (ref any) (ref null $middle)) (result (ref null $described)))) ;; CHECK-TEXT: (global $g (ref null $described) (ref.null none)) - ;; CHECK-BIN: (type $7 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-BIN: (type $9 (func (param (ref null $described) (ref null (exact $middle))))) + + ;; CHECK-BIN: (type $10 (func)) - ;; CHECK-BIN: (type $8 (func)) + ;; CHECK-BIN: (type $11 (func (param (ref any) (ref null $middle)) (result (ref null $described)))) ;; CHECK-BIN: (global $g (ref null $described) (ref.null none)) (global $g (ref null $described) (ref.null none)) @@ -59,7 +71,7 @@ ;; CHECK-BIN: (global $shared (ref null $shared-describing) (ref.null (shared none))) (global $shared (ref null $shared-describing) (ref.null (shared none))) - ;; CHECK-TEXT: (func $ref-get-desc (type $7) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-TEXT: (func $ref-get-desc (type $9) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l1 (result (ref $middle)) ;; CHECK-TEXT-NEXT: (ref.get_desc $described @@ -75,7 +87,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-get-desc (type $7) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-BIN: (func $ref-get-desc (type $9) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result (ref $middle)) ;; CHECK-BIN-NEXT: (ref.get_desc $described @@ -108,7 +120,7 @@ ) ) - ;; CHECK-TEXT: (func $ref-get-desc-null (type $8) + ;; CHECK-TEXT: (func $ref-get-desc-null (type $10) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefGetDesc we can't emit) ;; CHECK-TEXT-NEXT: (drop @@ -118,7 +130,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-get-desc-null (type $8) + ;; CHECK-BIN: (func $ref-get-desc-null (type $10) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) @@ -134,7 +146,7 @@ ) ) - ;; CHECK-TEXT: (func $br-on-cast-desc (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-TEXT: (func $br-on-cast-desc (type $4) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l (result (ref null $middle)) ;; CHECK-TEXT-NEXT: (drop @@ -147,7 +159,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-desc (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-BIN: (func $br-on-cast-desc (type $4) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block (result (ref null $middle)) ;; CHECK-BIN-NEXT: (drop @@ -172,7 +184,7 @@ ) ) - ;; CHECK-TEXT: (func $br-on-cast-desc-fail (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-TEXT: (func $br-on-cast-desc-fail (type $4) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l (result anyref) ;; CHECK-TEXT-NEXT: (br_on_cast_desc_fail $l anyref (ref null $middle) @@ -182,7 +194,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-desc-fail (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-BIN: (func $br-on-cast-desc-fail (type $4) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block (result anyref) ;; CHECK-BIN-NEXT: (br_on_cast_desc_fail $block anyref (ref null $middle) @@ -203,7 +215,7 @@ ) ) - ;; CHECK-TEXT: (func $br-on-cast-desc-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-TEXT: (func $br-on-cast-desc-null (type $3) (param $0 anyref) (result anyref) ;; CHECK-TEXT-NEXT: (block $label (result anyref) ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable BrOn we can't emit) ;; CHECK-TEXT-NEXT: (drop @@ -216,7 +228,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-desc-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-BIN: (func $br-on-cast-desc-null (type $3) (param $0 anyref) (result anyref) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) @@ -232,7 +244,7 @@ ) ) - ;; CHECK-TEXT: (func $br-on-cast-desc-fail-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-TEXT: (func $br-on-cast-desc-fail-null (type $3) (param $0 anyref) (result anyref) ;; CHECK-TEXT-NEXT: (block $label (result anyref) ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable BrOn we can't emit) ;; CHECK-TEXT-NEXT: (drop @@ -245,7 +257,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-desc-fail-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-BIN: (func $br-on-cast-desc-fail-null (type $3) (param $0 anyref) (result anyref) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) @@ -261,8 +273,156 @@ ) ) + ;; CHECK-TEXT: (func $ref-cast-desc-null-unreachable (type $5) (result anyref) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast-desc-null-unreachable (type $5) (result anyref) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $ref-cast-desc-null-unreachable (result anyref) + (unreachable) + (ref.cast_desc (ref null $described)) + ) + + ;; CHECK-TEXT: (func $ref-cast-desc-null-null (type $3) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast-desc-null-null (type $3) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $ref-cast-desc-null-null (param anyref) (result anyref) + (ref.cast_desc (ref null $described) + (ref.null none) + (ref.null none) + ) + ) + + ;; CHECK-TEXT: (func $ref-cast-desc-null-exact (type $6) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; CHECK-TEXT-NEXT: (ref.cast_desc (ref null (exact $described)) + ;; CHECK-TEXT-NEXT: (local.get $any) + ;; CHECK-TEXT-NEXT: (local.get $middle) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast-desc-null-exact (type $6) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; CHECK-BIN-NEXT: (ref.cast_desc (ref null (exact $described)) + ;; CHECK-BIN-NEXT: (local.get $any) + ;; CHECK-BIN-NEXT: (local.get $middle) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $ref-cast-desc-null-exact (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; The cast type is exact because the descriptor is exact. + (ref.cast_desc (ref null (exact $described)) + (local.get $any) + (local.get $middle) + ) + ) - ;; TODO: upcast, unreachable ref, null ref, unreachable desc, null desc, exact desc, immediates are not refs + ;; CHECK-TEXT: (func $ref-cast-desc-nn-unreachable (type $5) (result anyref) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast-desc-nn-unreachable (type $5) (result anyref) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $ref-cast-desc-nn-unreachable (result anyref) + (unreachable) + (ref.cast_desc (ref $described)) + ) + + ;; CHECK-TEXT: (func $ref-cast-desc-nn-null (type $3) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast-desc-nn-null (type $3) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $ref-cast-desc-nn-null (param anyref) (result anyref) + (ref.cast_desc (ref $described) + (ref.null none) + (ref.null none) + ) + ) + + ;; CHECK-TEXT: (func $ref-cast-desc-nn-exact (type $6) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; CHECK-TEXT-NEXT: (ref.cast_desc (ref (exact $described)) + ;; CHECK-TEXT-NEXT: (local.get $any) + ;; CHECK-TEXT-NEXT: (local.get $middle) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast-desc-nn-exact (type $6) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; CHECK-BIN-NEXT: (ref.cast_desc (ref (exact $described)) + ;; CHECK-BIN-NEXT: (local.get $any) + ;; CHECK-BIN-NEXT: (local.get $middle) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $ref-cast-desc-nn-exact (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; The cast type is exact because the descriptor is exact. + (ref.cast_desc (ref (exact $described)) + (local.get $any) + (local.get $middle) + ) + ) + + ;; CHECK-TEXT: (func $ref-cast-desc-nn-to-null (type $11) (param $any-nn (ref any)) (param $middle (ref null $middle)) (result (ref null $described)) + ;; CHECK-TEXT-NEXT: (ref.cast_desc (ref $described) + ;; CHECK-TEXT-NEXT: (local.get $any-nn) + ;; CHECK-TEXT-NEXT: (local.get $middle) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast-desc-nn-to-null (type $11) (param $any-nn (ref any)) (param $middle (ref null $middle)) (result (ref null $described)) + ;; CHECK-BIN-NEXT: (ref.cast_desc (ref $described) + ;; CHECK-BIN-NEXT: (local.get $any-nn) + ;; CHECK-BIN-NEXT: (local.get $middle) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $ref-cast-desc-nn-to-null (param $any-nn (ref any)) (param $middle (ref null $middle)) (result (ref null $described)) + (ref.cast_desc (ref null $described) + (local.get $any-nn) + (local.get $middle) + ) + ) ) ;; CHECK-BIN-NODEBUG: (rec ;; CHECK-BIN-NODEBUG-NEXT: (type $0 (descriptor $1 (struct))) @@ -271,24 +431,30 @@ ;; CHECK-BIN-NODEBUG: (type $2 (describes $1 (struct))) -;; CHECK-BIN-NODEBUG: (type $3 (func (param anyref (ref null $2)))) +;; CHECK-BIN-NODEBUG: (type $3 (func (param anyref) (result anyref))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param anyref (ref null $2)))) -;; CHECK-BIN-NODEBUG: (type $4 (func (param anyref) (result anyref))) +;; CHECK-BIN-NODEBUG: (type $5 (func (result anyref))) + +;; CHECK-BIN-NODEBUG: (type $6 (func (param anyref (ref null (exact $1))) (result (ref null (exact $0))))) ;; CHECK-BIN-NODEBUG: (rec -;; CHECK-BIN-NODEBUG-NEXT: (type $5 (shared (descriptor $6 (struct)))) +;; CHECK-BIN-NODEBUG-NEXT: (type $7 (shared (descriptor $8 (struct)))) + +;; CHECK-BIN-NODEBUG: (type $8 (shared (describes $7 (struct)))) -;; CHECK-BIN-NODEBUG: (type $6 (shared (describes $5 (struct)))) +;; CHECK-BIN-NODEBUG: (type $9 (func (param (ref null $0) (ref null (exact $1))))) -;; CHECK-BIN-NODEBUG: (type $7 (func (param (ref null $0) (ref null (exact $1))))) +;; CHECK-BIN-NODEBUG: (type $10 (func)) -;; CHECK-BIN-NODEBUG: (type $8 (func)) +;; CHECK-BIN-NODEBUG: (type $11 (func (param (ref any) (ref null $1)) (result (ref null $0)))) ;; CHECK-BIN-NODEBUG: (global $global$0 (ref null $0) (ref.null none)) -;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $6) (ref.null (shared none))) +;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $8) (ref.null (shared none))) -;; CHECK-BIN-NODEBUG: (func $0 (type $7) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) +;; CHECK-BIN-NODEBUG: (func $0 (type $9) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result (ref $1)) ;; CHECK-BIN-NODEBUG-NEXT: (ref.get_desc $0 @@ -305,7 +471,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $1 (type $8) +;; CHECK-BIN-NODEBUG: (func $1 (type $10) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -314,7 +480,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $2 (type $3) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG: (func $2 (type $4) (param $0 anyref) (param $1 (ref null $2)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (drop @@ -328,7 +494,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $3 (type $3) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG: (func $3 (type $4) (param $0 anyref) (param $1 (ref null $2)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_desc_fail $block anyref (ref null $1) @@ -339,7 +505,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $4 (type $4) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG: (func $4 (type $3) (param $0 anyref) (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -349,7 +515,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $5 (type $4) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG: (func $5 (type $3) (param $0 anyref) (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -358,3 +524,52 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $6 (type $5) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $7 (type $3) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $8 (type $6) (param $0 anyref) (param $1 (ref null (exact $1))) (result (ref null (exact $0))) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast_desc (ref null (exact $0)) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $9 (type $5) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $10 (type $3) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $11 (type $6) (param $0 anyref) (param $1 (ref null (exact $1))) (result (ref null (exact $0))) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast_desc (ref (exact $0)) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $12 (type $11) (param $0 (ref any)) (param $1 (ref null $1)) (result (ref null $0)) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast_desc (ref $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/spec/br_on_cast_desc.wast b/test/spec/br_on_cast_desc.wast index 6a9546eae0a..834ddcb1f9a 100644 --- a/test/spec/br_on_cast_desc.wast +++ b/test/spec/br_on_cast_desc.wast @@ -26,7 +26,7 @@ ) ) (func $br_on_cast_desc-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result (ref null (exact $super))) - ;; The sent type exact because the descriptor is exact. + ;; The sent type is exact because the descriptor is exact. (br_on_cast_desc 0 anyref (ref null $super) (local.get $any) (local.get $super.desc) @@ -77,13 +77,13 @@ (assert_malformed ;; Cast type must be a reference. - (module quote "(module (func (unreachable) (br_on_cast_desc 0 anyref i32)))") + (module quote "(module (func (unreachable) (br_on_cast_desc 0 anyref i32) (unreachable)))") "expected reftype" ) (assert_malformed ;; Cast type must be a reference. - (module quote "(module (func (unreachable) (br_on_cast_desc_fail 0 anyref i32)))") + (module quote "(module (func (unreachable) (br_on_cast_desc_fail 0 anyref i32) (unreachable)))") "expected reftype" ) @@ -125,7 +125,7 @@ ) ) ) - "invalid reference type on stack" + "invalid type on stack" ) (assert_invalid @@ -142,7 +142,7 @@ ) ) ) - "invalid reference type on stack" + "invalid type on stack" ) (assert_invalid @@ -162,7 +162,7 @@ ) ) ) - "invalid reference type on stack" + "invalid type on stack" ) (assert_invalid @@ -182,7 +182,7 @@ ) ) ) - "invalid reference type on stack" + "invalid type on stack" ) (assert_invalid @@ -192,10 +192,10 @@ (type $desc (describes $struct (struct))) ) (func (param $any anyref) (param $desc (ref null $desc)) (result (ref null (exact $struct))) - ;; The sent type is not exact because the descriptor is not exact. + ;; The sent type cannnot be exact because the descriptor is not exact. (br_on_cast_desc 0 anyref (ref null $struct) (local.get $any) - (local.get $descriptor) + (local.get $desc) ) (unreachable) ) diff --git a/test/spec/ref.cast_desc.wast b/test/spec/ref.cast_desc.wast new file mode 100644 index 00000000000..58e72777b23 --- /dev/null +++ b/test/spec/ref.cast_desc.wast @@ -0,0 +1,165 @@ +(module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + + ;; ref.cast_desc (ref null ht) + + (func $ref.cast_desc-null-unreachable (result anyref) + (unreachable) + (ref.cast_desc (ref null $super)) + ) + (func $ref.cast_desc-null-null (param anyref) (result anyref) + (ref.cast_desc (ref null $super) + (ref.null none) + (ref.null none) + ) + ) + (func $ref.cast_desc-null-upcast (param $sub (ref null $sub)) (param $super.desc (ref null $super.desc)) (result (ref null $super)) + (ref.cast_desc (ref null $super) + (local.get $sub) + (local.get $super.desc) + ) + ) + (func $ref.cast_desc-null-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result (ref null (exact $super))) + ;; The cast type is exact because the descriptor is exact. + (ref.cast_desc (ref null (exact $super)) + (local.get $any) + (local.get $super.desc) + ) + ) + + ;; ref.cast_desc (ref ht) + + (func $ref.cast_desc-nn-unreachable (result anyref) + (unreachable) + (ref.cast_desc (ref $super)) + ) + (func $ref.cast_desc-nn-null (param anyref) (result anyref) + (ref.cast_desc (ref $super) + (ref.null none) + (ref.null none) + ) + ) + (func $ref.cast_desc-nn-upcast (param $sub (ref null $sub)) (param $super.desc (ref null $super.desc)) (result (ref null $super)) + (ref.cast_desc (ref $super) + (local.get $sub) + (local.get $super.desc) + ) + ) + (func $ref.cast_desc-nn-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result (ref null (exact $super))) + ;; The cast type is exact because the descriptor is exact. + (ref.cast_desc (ref (exact $super)) + (local.get $any) + (local.get $super.desc) + ) + ) +) + +(assert_malformed + ;; Cast type must be a reference. + (module quote "(module (func (unreachable) (ref.cast_desc i32) (unreachable)))") + "expected reftype" +) + +(assert_invalid + (module + (type (struct)) + (func (result anyref) + (unreachable) + ;; Cannot do a descriptor cast to a type without a descriptor. + (ref.cast_desc (ref null 0)) + ) + ) + "cast target must have descriptor" +) + +(assert_invalid + (module + (type (struct)) + (func (result anyref) + (unreachable) + ;; Cannot do a descriptor cast to a type without a descriptor. + (ref.cast_desc (ref 0)) + ) + ) + "cast target must have descriptor" +) + +(assert_invalid + (module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + (func (param $super.desc (ref null $super.desc)) (result anyref) + (ref.cast_desc (ref null $sub) + (local.get 0) + ;; This should be a $sub.desc but it is a $super.desc. + (local.get $super.desc) + ) + ) + ) + "invalid type on stack" +) + +(assert_invalid + (module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + (func (param $super.desc (ref null $super.desc)) (result anyref) + (ref.cast_desc (ref $sub) + (local.get 0) + ;; This should be a $sub.desc but it is a $super.desc. + (local.get $super.desc) + ) + ) + ) + "invalid type on stack" +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param $any anyref) (param $desc (ref null $desc)) (result anyref) + ;; The cast type cannot be exact because the descriptor is not exact. + (ref.cast_desc (ref null (exact $struct)) + (local.get $any) + (local.get $desc) + ) + ) + ) + "invalid type on stack" +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param $any anyref) (param $desc (ref null $desc)) (result anyref) + ;; The cast type cannot be exact because the descriptor is not exact. + (ref.cast_desc (ref (exact $struct)) + (local.get $any) + (local.get $desc) + ) + ) + ) + "invalid type on stack" +) From 14d117fce25d37f89c99425e97b4495b77266460 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 29 May 2025 09:01:13 -0700 Subject: [PATCH 531/622] [CD] Fix TypeSSA on exact parameters in unreachable calls (#7628) TypeSSA ignored unreachable instructions, but we do validate parts of them, like calls - a later unreachable param does not disable validation or other params (see new testcase). Remove the ignoring of such code in TypeSSA and add proper skipping of unreachable code, where necessary, in ChildTyper (as an option). --- src/ir/child-typer.h | 94 +++++++++++++++++++++++ src/passes/TypeSSA.cpp | 16 +--- test/lit/passes/type-ssa-exact.wast | 113 ++++++++++++++++++---------- test/lit/passes/type-ssa.wast | 30 ++++++++ 4 files changed, 202 insertions(+), 51 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 71f9c2edf8d..309d60ca30e 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -65,6 +65,14 @@ namespace wasm { // the types of children. For example, it does not report the constraint that // two non-reference children of `select` must have the same type because that // would require inspecting the types of those children. +// +// The skipUnreachable() hook function determines the behavior on code that +// we cannot process due to unreachability. For example, an unreachable +// StructNew has no struct type defined, so we cannot apply its heap type. By +// default we do not skip such unreachable code, and error in such such cases if +// the type is not provided (by passing the heap type as mentioned above, as a +// parameter to the visit* method). Optimization passes can often skip +// unreachable code (leaving it for DCE), while other operations might not. template struct ChildTyper : OverriddenVisitor { Module& wasm; Function* func; @@ -109,6 +117,8 @@ template struct ChildTyper : OverriddenVisitor { Type getLabelType(Name label) { return self().getLabelType(label); } + bool skipUnreachable() { return false; } + void visitNop(Nop* curr) {} void visitBlock(Block* curr) { @@ -197,6 +207,9 @@ template struct ChildTyper : OverriddenVisitor { } void visitAtomicRMW(AtomicRMW* curr) { + if (self().skipUnreachable() && curr->type == Type::unreachable) { + return; + } assert(curr->type == Type::i32 || curr->type == Type::i64); notePointer(&curr->ptr, curr->memory); note(&curr->value, curr->type); @@ -823,6 +836,9 @@ template struct ChildTyper : OverriddenVisitor { void visitTupleExtract(TupleExtract* curr, std::optional arity = std::nullopt) { if (!arity) { + if (self().skipUnreachable() && !curr->tuple->type.isTuple()) { + return; + } assert(curr->tuple->type.isTuple()); arity = curr->tuple->type.size(); } @@ -837,6 +853,9 @@ template struct ChildTyper : OverriddenVisitor { void visitCallRef(CallRef* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->target->type.isRef()) { + return; + } ht = curr->target->type.getHeapType().getSignature(); } auto params = ht->getSignature().params; @@ -848,17 +867,26 @@ template struct ChildTyper : OverriddenVisitor { } void visitRefTest(RefTest* curr) { + if (self().skipUnreachable() && !curr->castType.isRef()) { + return; + } auto top = curr->castType.getHeapType().getTop(); note(&curr->ref, Type(top, Nullable)); } void visitRefCast(RefCast* curr, std::optional target = std::nullopt) { + if (self().skipUnreachable() && !curr->type.isRef()) { + return; + } auto top = curr->type.getHeapType().getTop(); note(&curr->ref, Type(top, Nullable)); if (curr->desc) { if (!target) { target = curr->type; } + if (self().skipUnreachable() && !target->isRef()) { + return; + } auto desc = target->getHeapType().getDescriptorType(); assert(desc); note(&curr->desc, Type(*desc, Nullable, curr->type.getExactness())); @@ -868,6 +896,9 @@ template struct ChildTyper : OverriddenVisitor { void visitRefGetDesc(RefGetDesc* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } note(&curr->ref, Type(*ht, Nullable)); @@ -886,6 +917,9 @@ template struct ChildTyper : OverriddenVisitor { if (!target) { target = curr->castType; } + if (self().skipUnreachable() && !target->isRef()) { + return; + } auto top = target->getHeapType().getTop(); note(&curr->ref, Type(top, Nullable)); if (curr->op == BrOnCastDesc || curr->op == BrOnCastDescFail) { @@ -903,6 +937,9 @@ template struct ChildTyper : OverriddenVisitor { if (curr->isWithDefault()) { return; } + if (self().skipUnreachable() && !curr->type.isRef()) { + return; + } const auto& fields = curr->type.getHeapType().getStruct().fields; assert(fields.size() == curr->operands.size()); for (size_t i = 0; i < fields.size(); ++i) { @@ -913,6 +950,9 @@ template struct ChildTyper : OverriddenVisitor { void visitStructGet(StructGet* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } note(&curr->ref, Type(*ht, Nullable)); @@ -921,6 +961,9 @@ template struct ChildTyper : OverriddenVisitor { void visitStructSet(StructSet* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } const auto& fields = ht->getStruct().fields; @@ -932,6 +975,9 @@ template struct ChildTyper : OverriddenVisitor { void visitStructRMW(StructRMW* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } const auto& fields = ht->getStruct().fields; @@ -943,6 +989,9 @@ template struct ChildTyper : OverriddenVisitor { void visitStructCmpxchg(StructCmpxchg* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } const auto& fields = ht->getStruct().fields; @@ -954,6 +1003,9 @@ template struct ChildTyper : OverriddenVisitor { void visitArrayNew(ArrayNew* curr) { if (!curr->isWithDefault()) { + if (self().skipUnreachable() && !curr->type.isRef()) { + return; + } note(&curr->init, curr->type.getHeapType().getArray().element.type); } note(&curr->size, Type::i32); @@ -970,6 +1022,9 @@ template struct ChildTyper : OverriddenVisitor { } void visitArrayNewFixed(ArrayNewFixed* curr) { + if (self().skipUnreachable() && !curr->type.isRef()) { + return; + } auto type = curr->type.getHeapType().getArray().element.type; for (auto& expr : curr->values) { note(&expr, type); @@ -979,6 +1034,9 @@ template struct ChildTyper : OverriddenVisitor { void visitArrayGet(ArrayGet* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } note(&curr->ref, Type(*ht, Nullable)); @@ -988,6 +1046,9 @@ template struct ChildTyper : OverriddenVisitor { void visitArraySet(ArraySet* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } auto type = ht->getArray().element.type; @@ -1004,9 +1065,15 @@ template struct ChildTyper : OverriddenVisitor { std::optional dest = std::nullopt, std::optional src = std::nullopt) { if (!dest) { + if (self().skipUnreachable() && !curr->destRef->type.isRef()) { + return; + } dest = curr->destRef->type.getHeapType(); } if (!src) { + if (self().skipUnreachable() && !curr->srcRef->type.isRef()) { + return; + } src = curr->srcRef->type.getHeapType(); } note(&curr->destRef, Type(*dest, Nullable)); @@ -1019,6 +1086,9 @@ template struct ChildTyper : OverriddenVisitor { void visitArrayFill(ArrayFill* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } auto type = ht->getArray().element.type; @@ -1031,6 +1101,9 @@ template struct ChildTyper : OverriddenVisitor { void visitArrayInitData(ArrayInitData* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } note(&curr->ref, Type(*ht, Nullable)); @@ -1042,6 +1115,9 @@ template struct ChildTyper : OverriddenVisitor { void visitArrayInitElem(ArrayInitElem* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } ht = curr->ref->type.getHeapType(); } note(&curr->ref, Type(*ht, Nullable)); @@ -1093,6 +1169,9 @@ template struct ChildTyper : OverriddenVisitor { void visitStringEncode(StringEncode* curr, std::optional ht = std::nullopt) { if (!ht) { + if (self().skipUnreachable() && !curr->array->type.isRef()) { + return; + } ht = curr->array->type.getHeapType(); } note(&curr->str, Type(HeapType::string, Nullable)); @@ -1129,9 +1208,15 @@ template struct ChildTyper : OverriddenVisitor { std::optional src = std::nullopt, std::optional dest = std::nullopt) { if (!src.has_value()) { + if (self().skipUnreachable() && !curr->cont->type.isRef()) { + return; + } src = curr->cont->type.getHeapType(); } if (!dest.has_value()) { + if (self().skipUnreachable() && !curr->type.isRef()) { + return; + } dest = curr->type.getHeapType(); } auto sourceParams = src->getContinuation().type.getSignature().params; @@ -1155,6 +1240,9 @@ template struct ChildTyper : OverriddenVisitor { void visitResume(Resume* curr, std::optional ct = std::nullopt) { if (!ct.has_value()) { + if (self().skipUnreachable() && !curr->cont->type.isRef()) { + return; + } ct = curr->cont->type.getHeapType(); } assert(ct->isContinuation()); @@ -1169,6 +1257,9 @@ template struct ChildTyper : OverriddenVisitor { void visitResumeThrow(ResumeThrow* curr, std::optional ct = std::nullopt) { if (!ct.has_value()) { + if (self().skipUnreachable() && !curr->cont->type.isRef()) { + return; + } ct = curr->cont->type.getHeapType(); } assert(ct->isContinuation()); @@ -1183,6 +1274,9 @@ template struct ChildTyper : OverriddenVisitor { void visitStackSwitch(StackSwitch* curr, std::optional ct = std::nullopt) { if (!ct.has_value()) { + if (self().skipUnreachable() && !curr->cont->type.isRef()) { + return; + } ct = curr->cont->type.getHeapType(); } assert(ct->isContinuation()); diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index a29bf879590..1bfd815292d 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -215,18 +215,6 @@ struct Analyzer return; } - // Also do not let unreachable instructions inhibit optimization, as long as - // they are unreachable because of an unreachable child. (Some other - // unreachable instructions, such as a return_call, can still require an - // exact operand and may inhibit optimization.) - if (curr->type == Type::unreachable) { - for (auto* child : ChildIterator(curr)) { - if (child->type == Type::unreachable) { - return; - } - } - } - struct ExactChildTyper : ChildTyper { Analyzer& parent; ExactChildTyper(Analyzer& parent) @@ -249,6 +237,10 @@ struct Analyzer void noteAnyI16ArrayReferenceType(Expression**) {} Type getLabelType(Name label) { WASM_UNREACHABLE("unexpected branch"); } + + // Skip unreachable code. If we cannot compute a constraint due to + // unreachability, we can ignore it. + bool skipUnreachable() { return true; } } typer(*this); typer.visit(curr); } diff --git a/test/lit/passes/type-ssa-exact.wast b/test/lit/passes/type-ssa-exact.wast index a53b65493b7..6c443ec9f55 100644 --- a/test/lit/passes/type-ssa-exact.wast +++ b/test/lit/passes/type-ssa-exact.wast @@ -1,5 +1,5 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: wasm-opt %s -all --type-ssa --preserve-type-order -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --type-ssa --preserve-type-order -S -o - | filecheck %s (module (rec @@ -290,43 +290,41 @@ ;; CHECK: (type $used-in-ret-call-ok_14 (sub $used-in-ret-call-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-ret-call-unreachable_15 (sub $used-in-ret-call-unreachable (struct (field anyref)))) + ;; CHECK: (type $used-in-throw-ok_15 (sub $used-in-throw-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-throw-ok_16 (sub $used-in-throw-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-local-set-ok_16 (sub $used-in-local-set-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-local-set-ok_17 (sub $used-in-local-set-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-local-set-tuple-ok_17 (sub $used-in-local-set-tuple-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-local-set-tuple-ok_18 (sub $used-in-local-set-tuple-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-global-set-ok_18 (sub $used-in-global-set-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-global-set-ok_19 (sub $used-in-global-set-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-table-set-ok_19 (sub $used-in-table-set-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-table-set-ok_20 (sub $used-in-table-set-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-struct-new-ok_20 (sub $used-in-struct-new-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-struct-new-ok_21 (sub $used-in-struct-new-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-struct-set-ok_21 (sub $used-in-struct-set-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-struct-set-ok_22 (sub $used-in-struct-set-ok (struct (field anyref)))) + ;; CHECK: (type $array-new-exact_22 (sub $array-new-exact (array (ref null (exact $used-in-array-new))))) - ;; CHECK: (type $array-new-exact_23 (sub $array-new-exact (array (ref null (exact $used-in-array-new))))) + ;; CHECK: (type $array-new-inexact_23 (sub $array-new-inexact (array (ref null $used-in-array-new-ok)))) ;; CHECK: (type $array-new-inexact_24 (sub $array-new-inexact (array (ref null $used-in-array-new-ok)))) - ;; CHECK: (type $array-new-inexact_25 (sub $array-new-inexact (array (ref null $used-in-array-new-ok)))) + ;; CHECK: (type $used-in-array-new-ok_25 (sub $used-in-array-new-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-array-new-ok_26 (sub $used-in-array-new-ok (struct (field anyref)))) + ;; CHECK: (type $array-new-fixed-exact_26 (sub $array-new-fixed-exact (array (ref null (exact $used-in-array-new-fixed))))) - ;; CHECK: (type $array-new-fixed-exact_27 (sub $array-new-fixed-exact (array (ref null (exact $used-in-array-new-fixed))))) + ;; CHECK: (type $array-new-fixed-inexact_27 (sub $array-new-fixed-inexact (array (ref null $used-in-array-new-fixed-ok)))) ;; CHECK: (type $array-new-fixed-inexact_28 (sub $array-new-fixed-inexact (array (ref null $used-in-array-new-fixed-ok)))) - ;; CHECK: (type $array-new-fixed-inexact_29 (sub $array-new-fixed-inexact (array (ref null $used-in-array-new-fixed-ok)))) + ;; CHECK: (type $used-in-array-new-fixed-ok_29 (sub $used-in-array-new-fixed-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-array-new-fixed-ok_30 (sub $used-in-array-new-fixed-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-array-set-ok_30 (sub $used-in-array-set-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-array-set-ok_31 (sub $used-in-array-set-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-array-fill-ok_31 (sub $used-in-array-fill-ok (struct (field anyref)))) - ;; CHECK: (type $used-in-array-fill-ok_32 (sub $used-in-array-fill-ok (struct (field anyref)))) - - ;; CHECK: (type $used-in-array-copy-ok_33 (sub $used-in-array-copy-ok (struct (field anyref)))) + ;; CHECK: (type $used-in-array-copy-ok_32 (sub $used-in-array-copy-ok (struct (field anyref)))) ;; CHECK: (global $global-exact (mut (ref null (exact $used-in-global-set))) (ref.null none)) (global $global-exact (mut (ref null (exact $used-in-global-set))) (ref.null none)) @@ -989,7 +987,7 @@ ;; CHECK: (func $ret-call-unreachable (type $93) (param $used (ref (exact $used-in-ret-call-unreachable))) (param $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-ret-call-unreachable_15 + ;; CHECK-NEXT: (struct.new $used-in-ret-call-unreachable ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1005,7 +1003,7 @@ ) ) ;; The callee parameter is exact, but since the call is unreachable (with an - ;; unreachable child), it does not inhibit optimization. + ;; unreachable child), we do not optimize. (return_call $ret-call-unreachable (local.get $used) (unreachable) @@ -1038,7 +1036,7 @@ ;; CHECK-NEXT: (local.get $used) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-throw-ok_16 + ;; CHECK-NEXT: (struct.new $used-in-throw-ok_15 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1083,7 +1081,7 @@ ;; CHECK-NEXT: (local.get $used) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-local-set-ok_17 + ;; CHECK-NEXT: (struct.new $used-in-local-set-ok_16 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1138,7 +1136,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-local-set-tuple-ok_18 + ;; CHECK-NEXT: (struct.new $used-in-local-set-tuple-ok_17 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1184,7 +1182,7 @@ ;; CHECK-NEXT: (local.get $used) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-global-set-ok_19 + ;; CHECK-NEXT: (struct.new $used-in-global-set-ok_18 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1229,7 +1227,7 @@ ;; CHECK-NEXT: (local.get $used) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-table-set-ok_20 + ;; CHECK-NEXT: (struct.new $used-in-table-set-ok_19 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1282,7 +1280,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-struct-new-ok_21 + ;; CHECK-NEXT: (struct.new $used-in-struct-new-ok_20 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1330,7 +1328,7 @@ ;; CHECK-NEXT: (local.get $used) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-struct-set-ok_22 + ;; CHECK-NEXT: (struct.new $used-in-struct-set-ok_21 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1355,7 +1353,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (array.new $array-new-exact_23 + ;; CHECK-NEXT: (array.new $array-new-exact_22 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1389,19 +1387,19 @@ ;; CHECK: (func $array-new-ok (type $108) (param $used (ref null (exact $used-in-array-new-ok))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (array.new $array-new-inexact_24 + ;; CHECK-NEXT: (array.new $array-new-inexact_23 ;; CHECK-NEXT: (local.get $used) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (array.new $array-new-inexact_25 + ;; CHECK-NEXT: (array.new $array-new-inexact_24 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-array-new-ok_26 + ;; CHECK-NEXT: (struct.new $used-in-array-new-ok_25 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1434,7 +1432,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (array.new $array-new-fixed-exact_27 + ;; CHECK-NEXT: (array.new $array-new-fixed-exact_26 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1467,18 +1465,18 @@ ;; CHECK: (func $array-new-fixed-ok (type $110) (param $used (ref null (exact $used-in-array-new-fixed-ok))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (array.new_fixed $array-new-fixed-inexact_28 1 + ;; CHECK-NEXT: (array.new_fixed $array-new-fixed-inexact_27 1 ;; CHECK-NEXT: (local.get $used) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (array.new $array-new-fixed-inexact_29 + ;; CHECK-NEXT: (array.new $array-new-fixed-inexact_28 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-array-new-fixed-ok_30 + ;; CHECK-NEXT: (struct.new $used-in-array-new-fixed-ok_29 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1535,7 +1533,7 @@ ;; CHECK-NEXT: (local.get $used) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-array-set-ok_31 + ;; CHECK-NEXT: (struct.new $used-in-array-set-ok_30 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1588,7 +1586,7 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-array-fill-ok_32 + ;; CHECK-NEXT: (struct.new $used-in-array-fill-ok_31 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1616,7 +1614,7 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new $used-in-array-copy-ok_33 + ;; CHECK-NEXT: (struct.new $used-in-array-copy-ok_32 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1692,3 +1690,40 @@ ) ) ) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $func (func (param (ref (exact $array)) i32))) + (type $func (func (param (ref (exact $array)) i32))) + ;; CHECK: (type $array (sub (array v128))) + (type $array (sub (array v128))) + ) + ;; CHECK: (type $2 (func)) + + ;; CHECK: (func $caller (type $2) + ;; CHECK-NEXT: (call $called + ;; CHECK-NEXT: (array.new_default $array + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + ;; This call is unreachable, but we cannot optimize here, as it would break + ;; validation: the called function expects an exact ref to $array. + (call $called + (array.new_default $array + (i32.const 0) + ) + (unreachable) + ) + ) + ;; CHECK: (func $called (type $func) (param $0 (ref (exact $array))) (param $1 i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $called (type $func) (param $0 (ref (exact $array))) (param $1 i32) + (nop) + ) +) + diff --git a/test/lit/passes/type-ssa.wast b/test/lit/passes/type-ssa.wast index 617c004865c..bd7a3409a1f 100644 --- a/test/lit/passes/type-ssa.wast +++ b/test/lit/passes/type-ssa.wast @@ -30,6 +30,10 @@ ;; CHECK: (global $h (ref $struct) (struct.new $struct_5 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) + + ;; CHECK: (memory $0 16 17) + (memory $0 16 17) + (global $h (ref $struct) (struct.new $struct (i32.const 42) )) @@ -69,6 +73,32 @@ ) ) ) + + ;; CHECK: (func $tuple-unreachable (type $1) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tuple-unreachable + ;; We should not error on this. + (tuple.extract 2 0 + (unreachable) + ) + ) + + ;; CHECK: (func $atomic-unreachable (type $1) + ;; CHECK-NEXT: (i32.atomic.rmw.sub offset=4 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $atomic-unreachable + ;; We should not error on this. + (i32.atomic.rmw.sub offset=4 + (unreachable) + (unreachable) + ) + ) ) ;; The same module as before, except that now the type is final, so we cannot From ada15819a8e635867661f0a9014bf9614cf42933 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 29 May 2025 16:40:56 -0700 Subject: [PATCH 532/622] [NFC] Improve wasm-opt help text (#7631) Add an example + wiki links. --- src/tools/wasm-opt.cpp | 16 +++++++++++++++- test/lit/help/wasm-opt.test | 14 +++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index a59bda728ef..d94b30ef4c9 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -95,7 +95,21 @@ int main(int argc, const char* argv[]) { const std::string WasmOptOption = "wasm-opt options"; - OptimizationOptions options("wasm-opt", "Read, write, and optimize files"); + OptimizationOptions options("wasm-opt", + R"(Read, write, and optimize files. + +Example usage: + + wasm-opt input.wasm -O3 -o output.wasm + +This reads an input wasm file, optimizes with -O3, and writes out the result. + +For more on how to optimize effectively, see + + https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook + https://github.com/WebAssembly/binaryen/wiki/GC-Optimization-Guidebook + )"); + options .add("--output", "-o", diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index ac98198d2dd..34a2ab2f25c 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -2,7 +2,19 @@ ;; CHECK: ================================================================================ ;; CHECK-NEXT: wasm-opt INFILE ;; CHECK-NEXT: -;; CHECK-NEXT: Read, write, and optimize files +;; CHECK-NEXT: Read, write, and optimize files. +;; CHECK-NEXT: +;; CHECK-NEXT: Example usage: +;; CHECK-NEXT: +;; CHECK-NEXT: wasm-opt input.wasm -O3 -o output.wasm +;; CHECK-NEXT: +;; CHECK-NEXT: This reads an input wasm file, optimizes with -O3, and writes out the result. +;; CHECK-NEXT: +;; CHECK-NEXT: For more on how to optimize effectively, see +;; CHECK-NEXT: +;; CHECK-NEXT: https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook +;; CHECK-NEXT: https://github.com/WebAssembly/binaryen/wiki/GC-Optimization-Guidebook +;; CHECK-NEXT: ;; CHECK-NEXT: ================================================================================ ;; CHECK-NEXT: ;; CHECK-NEXT: From cc1cc3d6b4d98a660c6fe4de10f383ff0b52d06b Mon Sep 17 00:00:00 2001 From: Marcin Kolny Date: Mon, 2 Jun 2025 17:11:20 +0100 Subject: [PATCH 533/622] InstrumentMemory: Allow filtering by instruction, and instrument memory.grow (#7388) --- src/passes/InstrumentMemory.cpp | 65 +++++++- test/lit/passes/instrument-memory-filter.wast | 154 ++++++++++++++++++ test/lit/passes/instrument-memory-gc.wast | 4 + test/lit/passes/instrument-memory.wast | 29 +++- test/lit/passes/instrument-memory64.wast | 27 ++- 5 files changed, 268 insertions(+), 11 deletions(-) create mode 100644 test/lit/passes/instrument-memory-filter.wast diff --git a/src/passes/InstrumentMemory.cpp b/src/passes/InstrumentMemory.cpp index 9bba5f537c3..d3b9f80f298 100644 --- a/src/passes/InstrumentMemory.cpp +++ b/src/passes/InstrumentMemory.cpp @@ -59,6 +59,7 @@ #include "asmjs/shared-constants.h" #include "shared-constants.h" +#include "support/string.h" #include #include #include @@ -93,14 +94,26 @@ static Name array_set_val_f32("array_set_val_f32"); static Name array_set_val_f64("array_set_val_f64"); static Name array_get_index("array_get_index"); static Name array_set_index("array_set_index"); +static Name memory_grow_pre("memory_grow_pre"); +static Name memory_grow_post("memory_grow_post"); // TODO: Add support for atomicRMW/cmpxchg -struct InstrumentMemory : public WalkerPass> { - // Adds calls to new imports. - bool addsEffects() override { return true; } +using InstructionFilter = std::optional>; + +#define CHECK_EXPRESSION(expr) \ + do { \ + if (filter && !filter->count(expr)) { \ + return; \ + } \ + } while (false) +struct AddInstrumentation : public WalkerPass> { + explicit AddInstrumentation(InstructionFilter filter) + : filter(std::move(filter)) {} void visitLoad(Load* curr) { + CHECK_EXPRESSION("load"); + id++; Builder builder(*getModule()); auto mem = getModule()->getMemory(curr->memory); @@ -134,6 +147,8 @@ struct InstrumentMemory : public WalkerPass> { } void visitStore(Store* curr) { + CHECK_EXPRESSION("store"); + id++; Builder builder(*getModule()); auto mem = getModule()->getMemory(curr->memory); @@ -167,6 +182,8 @@ struct InstrumentMemory : public WalkerPass> { } void visitStructGet(StructGet* curr) { + CHECK_EXPRESSION("struct.get"); + Builder builder(*getModule()); Name target; if (curr->type == Type::i32) { @@ -185,6 +202,8 @@ struct InstrumentMemory : public WalkerPass> { } void visitStructSet(StructSet* curr) { + CHECK_EXPRESSION("struct.set"); + Builder builder(*getModule()); Name target; if (curr->value->type == Type::i32) { @@ -205,6 +224,8 @@ struct InstrumentMemory : public WalkerPass> { } void visitArrayGet(ArrayGet* curr) { + CHECK_EXPRESSION("array.get"); + Builder builder(*getModule()); curr->index = builder.makeCall(array_get_index, @@ -227,6 +248,8 @@ struct InstrumentMemory : public WalkerPass> { } void visitArraySet(ArraySet* curr) { + CHECK_EXPRESSION("array.set"); + Builder builder(*getModule()); curr->index = builder.makeCall(array_set_index, @@ -250,10 +273,28 @@ struct InstrumentMemory : public WalkerPass> { curr->value->type); } + void visitMemoryGrow(MemoryGrow* curr) { + CHECK_EXPRESSION("memory.grow"); + + id++; + Builder builder(*getModule()); + auto addressType = getModule()->getMemory(curr->memory)->addressType; + curr->delta = + builder.makeCall(memory_grow_pre, + {builder.makeConst(int32_t(id)), curr->delta}, + addressType); + replaceCurrent(builder.makeCall( + memory_grow_post, {builder.makeConst(int32_t(id)), curr}, addressType)); + } + void visitModule(Module* curr) { auto addressType = curr->memories.empty() ? Type::i32 : curr->memories[0]->addressType; + // Grow. + addImport(curr, memory_grow_pre, {Type::i32, addressType}, addressType); + addImport(curr, memory_grow_post, {Type::i32, addressType}, addressType); + // Load. addImport(curr, load_ptr, @@ -300,7 +341,8 @@ struct InstrumentMemory : public WalkerPass> { } private: - Index id; + Index id = 0; + InstructionFilter filter; void addImport(Module* curr, Name name, Type params, Type results) { auto import = Builder::makeFunction(name, Signature(params, results), {}); @@ -310,6 +352,21 @@ struct InstrumentMemory : public WalkerPass> { } }; +struct InstrumentMemory : Pass { + // Adds calls to new imports. + bool addsEffects() override { return true; } + + void run(Module* module) override { + auto arg = getArgumentOrDefault("instrument-memory", ""); + InstructionFilter instructions; + if (arg.size() > 0) { + String::Split s(arg, ","); + instructions = std::unordered_set{s.begin(), s.end()}; + } + AddInstrumentation(std::move(instructions)).run(getPassRunner(), module); + } +}; + Pass* createInstrumentMemoryPass() { return new InstrumentMemory(); } } // namespace wasm diff --git a/test/lit/passes/instrument-memory-filter.wast b/test/lit/passes/instrument-memory-filter.wast new file mode 100644 index 00000000000..5dd1aa190dd --- /dev/null +++ b/test/lit/passes/instrument-memory-filter.wast @@ -0,0 +1,154 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --instrument-memory="load,memory.grow" -S -o - | filecheck %s + +;; The test validates the instruction filter used in this pass. +(module + (memory 256 256) + ;; CHECK: (type $0 (func (param i32 i32) (result i32))) + + ;; CHECK: (type $1 (func)) + (type $1 (func)) + ;; CHECK: (type $2 (func (param i32 i32 i32 i32) (result i32))) + + ;; CHECK: (type $3 (func (param i32 i64) (result i64))) + + ;; CHECK: (type $4 (func (param i32 f32) (result f32))) + + ;; CHECK: (type $5 (func (param i32 f64) (result f64))) + + ;; CHECK: (import "env" "memory_grow_pre" (func $memory_grow_pre (param i32 i32) (result i32))) + + ;; CHECK: (import "env" "memory_grow_post" (func $memory_grow_post (param i32 i32) (result i32))) + + ;; CHECK: (import "env" "load_ptr" (func $load_ptr (param i32 i32 i32 i32) (result i32))) + + ;; CHECK: (import "env" "load_val_i32" (func $load_val_i32 (param i32 i32) (result i32))) + + ;; CHECK: (import "env" "load_val_i64" (func $load_val_i64 (param i32 i64) (result i64))) + + ;; CHECK: (import "env" "load_val_f32" (func $load_val_f32 (param i32 f32) (result f32))) + + ;; CHECK: (import "env" "load_val_f64" (func $load_val_f64 (param i32 f64) (result f64))) + + ;; CHECK: (import "env" "store_ptr" (func $store_ptr (param i32 i32 i32 i32) (result i32))) + + ;; CHECK: (import "env" "store_val_i32" (func $store_val_i32 (param i32 i32) (result i32))) + + ;; CHECK: (import "env" "store_val_i64" (func $store_val_i64 (param i32 i64) (result i64))) + + ;; CHECK: (import "env" "store_val_f32" (func $store_val_f32 (param i32 f32) (result f32))) + + ;; CHECK: (import "env" "store_val_f64" (func $store_val_f64 (param i32 f64) (result f64))) + + ;; CHECK: (memory $0 256 256) + + ;; CHECK: (func $A + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $load_val_i32 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.load8_s + ;; CHECK-NEXT: (call $load_ptr + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $load_val_i32 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.load8_u + ;; CHECK-NEXT: (call $load_ptr + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $load_val_i64 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i64.load16_s offset=8 align=1 + ;; CHECK-NEXT: (call $load_ptr + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $load_val_i64 + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i64.load32_s offset=10 align=2 + ;; CHECK-NEXT: (call $load_ptr + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $A (type $1) + ;; "*.load*" instructions in this function are instrumented because of the + ;; "load" filter included in the command. + (drop (i32.load8_s (i32.const 0))) + (drop (i32.load8_u (i32.const 0))) + (drop (i64.load16_s offset=8 align=1 (i32.const 0))) + (drop (i64.load32_s offset=10 align=2 (i32.const 0))) + ) + + ;; CHECK: (func $B + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $memory_grow_post + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (call $memory_grow_pre + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $B (type $1) + ;; "memory.grow" instructions in this function are instrumented because of the + ;; "memory.grow" filter included in the command. + (drop (memory.grow (i32.const 4))) + ) + + ;; CHECK: (func $C + ;; CHECK-NEXT: (i32.store8 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store16 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.store16 offset=5 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.store offset=9 align=2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (f64.const 9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $C (type $1) + ;; "*.store*" instructions in this function are not instrumented because the + ;; filter is non-empty and doesn't specify "store" instructions. + (i32.store8 (i32.const 0) (i32.const 1)) + (i32.store16 (i32.const 0) (i32.const 2)) + (i64.store16 offset=5 align=2 (i32.const 0) (i64.const 5)) + (f64.store offset=9 align=2 (i32.const 0) (f64.const 9)) + ) +) diff --git a/test/lit/passes/instrument-memory-gc.wast b/test/lit/passes/instrument-memory-gc.wast index 2e824b2abc5..6cc6ef0112c 100644 --- a/test/lit/passes/instrument-memory-gc.wast +++ b/test/lit/passes/instrument-memory-gc.wast @@ -26,6 +26,10 @@ ;; CHECK: (type $8 (func (param (ref $array)))) + ;; CHECK: (import "env" "memory_grow_pre" (func $memory_grow_pre (type $0) (param i32 i32) (result i32))) + + ;; CHECK: (import "env" "memory_grow_post" (func $memory_grow_post (type $0) (param i32 i32) (result i32))) + ;; CHECK: (import "env" "load_ptr" (func $load_ptr (type $6) (param i32 i32 i32 i32) (result i32))) ;; CHECK: (import "env" "load_val_i32" (func $load_val_i32 (type $0) (param i32 i32) (result i32))) diff --git a/test/lit/passes/instrument-memory.wast b/test/lit/passes/instrument-memory.wast index f776d2af13f..e5aa5bb2734 100644 --- a/test/lit/passes/instrument-memory.wast +++ b/test/lit/passes/instrument-memory.wast @@ -5,17 +5,21 @@ (module (memory 256 256) + ;; CHECK: (type $0 (func (param i32 i32) (result i32))) + ;; CHECK: (type $1 (func)) (type $1 (func)) ;; CHECK: (type $2 (func (param i32 i32 i32 i32) (result i32))) - ;; CHECK: (type $3 (func (param i32 i32) (result i32))) + ;; CHECK: (type $3 (func (param i32 i64) (result i64))) + + ;; CHECK: (type $4 (func (param i32 f32) (result f32))) - ;; CHECK: (type $4 (func (param i32 i64) (result i64))) + ;; CHECK: (type $5 (func (param i32 f64) (result f64))) - ;; CHECK: (type $5 (func (param i32 f32) (result f32))) + ;; CHECK: (import "env" "memory_grow_pre" (func $memory_grow_pre (param i32 i32) (result i32))) - ;; CHECK: (type $6 (func (param i32 f64) (result f64))) + ;; CHECK: (import "env" "memory_grow_post" (func $memory_grow_post (param i32 i32) (result i32))) ;; CHECK: (import "env" "load_ptr" (func $load_ptr (param i32 i32 i32 i32) (result i32))) @@ -676,4 +680,21 @@ (f32.store offset=8 align=2 (i32.const 0) (f32.const 8)) (f64.store offset=9 align=2 (i32.const 0) (f64.const 9)) ) + + ;; CHECK: (func $C + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $memory_grow_post + ;; CHECK-NEXT: (i32.const 47) + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (call $memory_grow_pre + ;; CHECK-NEXT: (i32.const 47) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $C (type $1) + (drop (memory.grow (i32.const 4))) + ) ) diff --git a/test/lit/passes/instrument-memory64.wast b/test/lit/passes/instrument-memory64.wast index 5ceb37fe22c..5ab9c38699a 100644 --- a/test/lit/passes/instrument-memory64.wast +++ b/test/lit/passes/instrument-memory64.wast @@ -5,17 +5,21 @@ (module (memory i64 256 256) + ;; CHECK: (type $0 (func (param i32 i64) (result i64))) + ;; CHECK: (type $1 (func)) (type $1 (func)) ;; CHECK: (type $2 (func (param i32 i32 i64 i64) (result i64))) ;; CHECK: (type $3 (func (param i32 i32) (result i32))) - ;; CHECK: (type $4 (func (param i32 i64) (result i64))) + ;; CHECK: (type $4 (func (param i32 f32) (result f32))) + + ;; CHECK: (type $5 (func (param i32 f64) (result f64))) - ;; CHECK: (type $5 (func (param i32 f32) (result f32))) + ;; CHECK: (import "env" "memory_grow_pre" (func $memory_grow_pre (param i32 i64) (result i64))) - ;; CHECK: (type $6 (func (param i32 f64) (result f64))) + ;; CHECK: (import "env" "memory_grow_post" (func $memory_grow_post (param i32 i64) (result i64))) ;; CHECK: (import "env" "load_ptr" (func $load_ptr (param i32 i32 i64 i64) (result i64))) @@ -676,4 +680,21 @@ (f32.store offset=8 align=2 (i64.const 0) (f32.const 8)) (f64.store offset=9 align=2 (i64.const 0) (f64.const 9)) ) + + ;; CHECK: (func $C + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $memory_grow_post + ;; CHECK-NEXT: (i32.const 47) + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (call $memory_grow_pre + ;; CHECK-NEXT: (i32.const 47) + ;; CHECK-NEXT: (i64.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $C (type $1) + (drop (memory.grow (i64.const 4))) + ) ) From 537b4d49844620e2ff42f37ebccb4bee327e7aab Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Mon, 2 Jun 2025 13:19:37 -0700 Subject: [PATCH 534/622] Revert "Export std hash template specializations from dylibs (#7618)" (#7635) This reverts most of commit 1625f5b569a422d8135826d8ca5008c3e896e1f7. The upstream commit that caused the issue has been reverted. The change to use "-Bsymbolic" for the DSO is independently useful. --- src/compiler-support.h | 19 ------------------- src/wasm-type-shape.h | 3 +-- src/wasm-type.h | 17 ++++++++--------- 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/compiler-support.h b/src/compiler-support.h index 50883868335..a3485919086 100644 --- a/src/compiler-support.h +++ b/src/compiler-support.h @@ -31,23 +31,4 @@ #define WASM_BUILTIN_UNREACHABLE __assume(false) #endif -// Forces symbols to be exported from libbinaryen dynamic library. Currently -// we are just using the default flags, causing most of our symbols to have -// "default" visibility, meaning they can all be used from the tool sources. -// However a recent libc++ change caused functions declared in namespace std -// (e.g. hash template specializations) to have hidden visibility, inherited. -// from the namespece (see https://github.com/llvm/llvm-project/pull/131156). -// So this macro forces them to be exported. Currently it is only applied to -// the hash specializations that are defined in libbinaryen and used in the -// tool code. In the future if we want to compile libbinaryen with -// -fvisibility-hidden or use a DLL on Windows, we'll need -// to explicitly annotate everything we want to export. But that's probably -// only useful if we want external users to link against libbinaryen.so -// (currently we don't; it's only used to reduce our install size). -#if defined(__ELF__) || defined(__MACH__) -#define DLLEXPORT [[gnu::visibility("default")]] -#else -#define DLLEXPORT -#endif - #endif // wasm_compiler_support_h diff --git a/src/wasm-type-shape.h b/src/wasm-type-shape.h index 3fe43b7f36e..e72f28dd530 100644 --- a/src/wasm-type-shape.h +++ b/src/wasm-type-shape.h @@ -20,7 +20,6 @@ #include #include -#include "compiler-support.h" #include "wasm-features.h" #include "wasm-type.h" @@ -75,7 +74,7 @@ namespace std { template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::RecGroupShape& shape) const; + size_t operator()(const wasm::RecGroupShape& shape) const; }; } // namespace std diff --git a/src/wasm-type.h b/src/wasm-type.h index d10893abf61..b5e9a88a3c6 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -25,7 +25,6 @@ #include #include -#include "compiler-support.h" #include "support/index.h" #include "support/name.h" #include "support/parent_index_iterator.h" @@ -1006,35 +1005,35 @@ namespace std { template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::Type&) const; + size_t operator()(const wasm::Type&) const; }; template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::Signature&) const; + size_t operator()(const wasm::Signature&) const; }; template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::Continuation&) const; + size_t operator()(const wasm::Continuation&) const; }; template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::Field&) const; + size_t operator()(const wasm::Field&) const; }; template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::Struct&) const; + size_t operator()(const wasm::Struct&) const; }; template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::Array&) const; + size_t operator()(const wasm::Array&) const; }; template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::HeapType&) const; + size_t operator()(const wasm::HeapType&) const; }; template<> class hash { public: - DLLEXPORT size_t operator()(const wasm::RecGroup&) const; + size_t operator()(const wasm::RecGroup&) const; }; } // namespace std From efd572c8d8be9e2c5221eb5e4e76c3096ffe02ed Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Mon, 2 Jun 2025 14:15:04 -0700 Subject: [PATCH 535/622] Mark a variable only used in an assert (#7636) This allows building without asserts, with -Wunused-but-set-variable --- src/ir/possible-contents.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 520abe58850..8054d9ca325 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -332,7 +332,7 @@ class PossibleContents { WASM_UNREACHABLE("TODO: use Literals"); } else if (std::get_if(&value)) { WASM_UNREACHABLE("TODO"); - } else if (auto* cone = std::get_if(&value)) { + } else if ([[maybe_unused]] auto* cone = std::get_if(&value)) { // Return a full cone of the appropriate type, as we lack depth info for // the separate items in the tuple (tuples themselves have no subtyping, // so the tuple's depth must be 0, i.e., an exact type). From 4f9374c46f120489e3a16f92cd7b2f7c5a27c11e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 2 Jun 2025 16:50:56 -0700 Subject: [PATCH 536/622] [Shared-Everything] ArrayRMW and ArrayCmpxchg (#7632) --- scripts/gen-s-parser.py | 7 + src/gen-s-parser.inc | 57 ++++++ src/interpreter/interpreter.cpp | 2 + src/ir/ReFinalize.cpp | 2 + src/ir/child-typer.h | 34 +++- src/ir/cost.h | 9 + src/ir/effects.h | 26 +++ src/ir/possible-contents.cpp | 18 ++ src/ir/subtype-exprs.h | 22 ++- src/parser/contexts.h | 30 +++ src/parser/parsers.h | 37 ++++ src/passes/Heap2Local.cpp | 12 ++ src/passes/OptimizeInstructions.cpp | 12 ++ src/passes/Print.cpp | 20 ++ src/passes/TypeGeneralizing.cpp | 4 + src/wasm-binary.h | 7 + src/wasm-builder.h | 28 +++ src/wasm-delegations-fields.def | 16 ++ src/wasm-delegations.def | 2 + src/wasm-interpreter.h | 75 ++++++++ src/wasm-ir-builder.h | 2 + src/wasm.h | 30 +++ src/wasm/wasm-binary.cpp | 20 ++ src/wasm/wasm-ir-builder.cpp | 32 ++++ src/wasm/wasm-stack.cpp | 41 ++++ src/wasm/wasm-validator.cpp | 108 +++++++++++ src/wasm/wasm.cpp | 30 ++- src/wasm2js.h | 8 + test/binaryen.js/kitchen-sink.js.txt | 18 +- test/lit/basic/gc-atomics.wast | 163 ++++++++++++++-- .../optimize-instructions-struct-rmw.wast | 178 ++++++++++++------ 31 files changed, 964 insertions(+), 86 deletions(-) diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 809741c128e..2b0ae08526c 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -653,6 +653,13 @@ ("array.fill", "makeArrayFill()"), ("array.init_data", "makeArrayInitData()"), ("array.init_elem", "makeArrayInitElem()"), + ("array.atomic.rmw.add", "makeArrayRMW(RMWAdd)"), + ("array.atomic.rmw.sub", "makeArrayRMW(RMWSub)"), + ("array.atomic.rmw.and", "makeArrayRMW(RMWAnd)"), + ("array.atomic.rmw.or", "makeArrayRMW(RMWOr)"), + ("array.atomic.rmw.xor", "makeArrayRMW(RMWXor)"), + ("array.atomic.rmw.xchg", "makeArrayRMW(RMWXchg)"), + ("array.atomic.rmw.cmpxchg", "makeArrayCmpxchg()"), ("ref.as_non_null", "makeRefAs(RefAsNonNull)"), ("extern.internalize", "makeRefAs(AnyConvertExtern)"), # Deprecated ("extern.externalize", "makeRefAs(ExternConvertAny)"), # Deprecated diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 23c01d67eb2..ee406b4bfd7 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -52,6 +52,63 @@ switch (buf[0]) { default: goto parse_error; } } + case 'r': { + switch (buf[17]) { + case 'a': { + switch (buf[18]) { + case 'd': + if (op == "array.atomic.rmw.add"sv) { + CHECK_ERR(makeArrayRMW(ctx, pos, annotations, RMWAdd)); + return Ok{}; + } + goto parse_error; + case 'n': + if (op == "array.atomic.rmw.and"sv) { + CHECK_ERR(makeArrayRMW(ctx, pos, annotations, RMWAnd)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + case 'c': + if (op == "array.atomic.rmw.cmpxchg"sv) { + CHECK_ERR(makeArrayCmpxchg(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case 'o': + if (op == "array.atomic.rmw.or"sv) { + CHECK_ERR(makeArrayRMW(ctx, pos, annotations, RMWOr)); + return Ok{}; + } + goto parse_error; + case 's': + if (op == "array.atomic.rmw.sub"sv) { + CHECK_ERR(makeArrayRMW(ctx, pos, annotations, RMWSub)); + return Ok{}; + } + goto parse_error; + case 'x': { + switch (buf[18]) { + case 'c': + if (op == "array.atomic.rmw.xchg"sv) { + CHECK_ERR(makeArrayRMW(ctx, pos, annotations, RMWXchg)); + return Ok{}; + } + goto parse_error; + case 'o': + if (op == "array.atomic.rmw.xor"sv) { + CHECK_ERR(makeArrayRMW(ctx, pos, annotations, RMWXor)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + default: goto parse_error; + } + } case 's': if (op == "array.atomic.set"sv) { CHECK_ERR(makeAtomicArraySet(ctx, pos, annotations)); diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 33f162985c0..a3a8e0ae5b1 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -263,6 +263,8 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitArrayFill(ArrayFill* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayInitData(ArrayInitData* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayInitElem(ArrayInitElem* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayRMW(ArrayRMW* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayCmpxchg(ArrayCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } Flow visitRefAs(RefAs* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStringNew(StringNew* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStringConst(StringConst* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 2efad1bb006..bd61d84022f 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -172,6 +172,8 @@ void ReFinalize::visitArrayCopy(ArrayCopy* curr) { curr->finalize(); } void ReFinalize::visitArrayFill(ArrayFill* curr) { curr->finalize(); } void ReFinalize::visitArrayInitData(ArrayInitData* curr) { curr->finalize(); } void ReFinalize::visitArrayInitElem(ArrayInitElem* curr) { curr->finalize(); } +void ReFinalize::visitArrayRMW(ArrayRMW* curr) { curr->finalize(); } +void ReFinalize::visitArrayCmpxchg(ArrayCmpxchg* curr) { curr->finalize(); } void ReFinalize::visitRefAs(RefAs* curr) { curr->finalize(); } void ReFinalize::visitStringNew(StringNew* curr) { curr->finalize(); } void ReFinalize::visitStringConst(StringConst* curr) { curr->finalize(); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 309d60ca30e..0436da48420 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -997,8 +997,9 @@ template struct ChildTyper : OverriddenVisitor { const auto& fields = ht->getStruct().fields; assert(curr->index < fields.size()); note(&curr->ref, Type(*ht, Nullable)); - note(&curr->expected, fields[curr->index].type); - note(&curr->replacement, fields[curr->index].type); + auto type = fields[curr->index].type; + note(&curr->expected, type.isRef() ? Type(HeapType::eq, Nullable) : type); + note(&curr->replacement, type); } void visitArrayNew(ArrayNew* curr) { @@ -1126,6 +1127,35 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->size, Type::i32); } + void visitArrayRMW(ArrayRMW* curr, + std::optional ht = std::nullopt) { + if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } + ht = curr->ref->type.getHeapType(); + } + auto type = ht->getArray().element.type; + note(&curr->ref, Type(*ht, Nullable)); + note(&curr->index, Type::i32); + note(&curr->value, type); + } + + void visitArrayCmpxchg(ArrayCmpxchg* curr, + std::optional ht = std::nullopt) { + if (!ht) { + if (self().skipUnreachable() && !curr->ref->type.isRef()) { + return; + } + ht = curr->ref->type.getHeapType(); + } + auto type = ht->getArray().element.type; + note(&curr->ref, Type(*ht, Nullable)); + note(&curr->index, Type::i32); + note(&curr->expected, type.isRef() ? Type(HeapType::eq, Nullable) : type); + note(&curr->replacement, type); + } + void visitRefAs(RefAs* curr) { switch (curr->op) { case RefAsNonNull: diff --git a/src/ir/cost.h b/src/ir/cost.h index cfdf3f09b1b..5b4ad6dbc12 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -752,6 +752,15 @@ struct CostAnalyzer : public OverriddenVisitor { return 6 + visit(curr->ref) + visit(curr->index) + visit(curr->offset) + visit(curr->size); } + CostType visitArrayRMW(ArrayRMW* curr) { + return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + + visit(curr->index) + visit(curr->value); + } + CostType visitArrayCmpxchg(ArrayCmpxchg* curr) { + return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + + visit(curr->index) + visit(curr->expected) + + visit(curr->replacement); + } CostType visitRefAs(RefAs* curr) { return 1 + visit(curr->value); } CostType visitStringNew(StringNew* curr) { return 8 + visit(curr->ref) + maybeVisit(curr->start) + diff --git a/src/ir/effects.h b/src/ir/effects.h index 58778ea0f22..c4960e3e112 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1000,6 +1000,32 @@ class EffectAnalyzer { } void visitArrayInitData(ArrayInitData* curr) { visitArrayInit(curr); } void visitArrayInitElem(ArrayInitElem* curr) { visitArrayInit(curr); } + void visitArrayRMW(ArrayRMW* curr) { + if (curr->ref->type.isNull()) { + parent.trap = true; + return; + } + parent.readsArray = true; + parent.writesArray = true; + if (curr->ref->type.isNullable()) { + parent.implicitTrap = true; + } + assert(curr->order != MemoryOrder::Unordered); + parent.isAtomic = true; + } + void visitArrayCmpxchg(ArrayCmpxchg* curr) { + if (curr->ref->type.isNull()) { + parent.trap = true; + return; + } + parent.readsArray = true; + parent.writesArray = true; + if (curr->ref->type.isNullable()) { + parent.implicitTrap = true; + } + assert(curr->order != MemoryOrder::Unordered); + parent.isAtomic = true; + } void visitRefAs(RefAs* curr) { if (curr->op == AnyConvertExtern || curr->op == ExternConvertAny) { // These conversions are infallible. diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 3c50893274a..5d3a7cdb070 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1100,6 +1100,22 @@ struct InfoCollector } void visitArrayInitData(ArrayInitData* curr) { visitArrayInit(curr); } void visitArrayInitElem(ArrayInitElem* curr) { visitArrayInit(curr); } + void visitArrayRMW(ArrayRMW* curr) { + if (curr->ref->type == Type::unreachable) { + return; + } + // TODO: Model the modification part of the RMW in addition to the read and + // the write. + addRoot(curr); + } + void visitArrayCmpxchg(ArrayCmpxchg* curr) { + if (curr->ref->type == Type::unreachable) { + return; + } + // TODO: Model the modification part of the RMW in addition to the read and + // the write. + addRoot(curr); + } void visitStringNew(StringNew* curr) { if (curr->type == Type::unreachable) { return; @@ -1618,6 +1634,8 @@ void TNHOracle::scan(Function* func, void visitArrayInitElem(ArrayInitElem* curr) { notePossibleTrap(curr->ref); } + void visitArrayRMW(ArrayRMW* curr) { notePossibleTrap(curr->ref); } + void visitArrayCmpxchg(ArrayCmpxchg* curr) { notePossibleTrap(curr->ref); } void visitFunction(Function* curr) { // In optimized TNH code, a function that always traps will be turned diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index ca75332e85d..5ae5a99da40 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -337,8 +337,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { return; } const auto& fields = curr->ref->type.getHeapType().getStruct().fields; - self()->noteSubtype(curr->expected, fields[curr->index].type); - self()->noteSubtype(curr->replacement, fields[curr->index].type); + auto type = fields[curr->index].type; + self()->noteSubtype(curr->expected, + type.isRef() ? Type(HeapType::eq, Nullable) : type); + self()->noteSubtype(curr->replacement, type); } void visitArrayNew(ArrayNew* curr) { if (!curr->type.isArray() || curr->isWithDefault()) { @@ -399,6 +401,22 @@ struct SubtypingDiscoverer : public OverriddenVisitor { auto* seg = self()->getModule()->getElementSegment(curr->segment); self()->noteSubtype(seg->type, array.element.type); } + void visitArrayRMW(ArrayRMW* curr) { + if (!curr->ref->type.isArray()) { + return; + } + auto array = curr->ref->type.getHeapType().getArray(); + self()->noteSubtype(curr->value, array.element.type); + } + void visitArrayCmpxchg(ArrayCmpxchg* curr) { + if (!curr->ref->type.isArray()) { + return; + } + auto type = curr->ref->type.getHeapType().getArray().element.type; + self()->noteSubtype(curr->expected, + type.isRef() ? Type(HeapType::eq, Nullable) : type); + self()->noteSubtype(curr->replacement, type); + } void visitRefAs(RefAs* curr) { if (curr->op == RefAsNonNull) { self()->noteCast(curr->value, curr); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index c1cfa5dbbb1..48234829b5a 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -848,6 +848,21 @@ struct NullInstrParserCtx { ElemIdxT) { return Ok{}; } + template + Result<> makeArrayRMW(Index, + const std::vector&, + AtomicRMWOp, + HeapTypeT, + MemoryOrder) { + return Ok{}; + } + template + Result<> makeArrayCmpxchg(Index, + const std::vector&, + HeapTypeT, + MemoryOrder) { + return Ok{}; + } Result<> makeRefAs(Index, const std::vector&, RefAsOp) { return Ok{}; } @@ -2738,6 +2753,21 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeArrayInitElem(type, elem)); } + Result<> makeArrayRMW(Index pos, + const std::vector& annotations, + AtomicRMWOp op, + HeapType type, + MemoryOrder order) { + return withLoc(pos, irBuilder.makeArrayRMW(op, type, order)); + } + + Result<> makeArrayCmpxchg(Index pos, + const std::vector& annotations, + HeapType type, + MemoryOrder order) { + return withLoc(pos, irBuilder.makeArrayCmpxchg(type, order)); + } + Result<> makeRefAs(Index pos, const std::vector& annotations, RefAsOp op) { return withLoc(pos, irBuilder.makeRefAs(op)); diff --git a/src/parser/parsers.h b/src/parser/parsers.h index a16b3a801e6..dd4cbab2f5d 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -292,6 +292,10 @@ Result<> makeArrayInitData(Ctx&, Index, const std::vector&); template Result<> makeArrayInitElem(Ctx&, Index, const std::vector&); template +Result<> makeArrayRMW(AtomicRMWOp, Index, const std::vector&); +template +Result<> makeArrayCmpxchg(Index, const std::vector&); +template Result<> makeRefAs(Ctx&, Index, const std::vector&, RefAsOp op); template Result<> @@ -2499,6 +2503,39 @@ Result<> makeArrayInitElem(Ctx& ctx, return ctx.makeArrayInitElem(pos, annotations, *type, *elem); } +template +Result<> makeArrayRMW(Ctx& ctx, + Index pos, + const std::vector& annotations, + AtomicRMWOp op) { + auto order1 = memorder(ctx); + CHECK_ERR(order1); + auto order2 = memorder(ctx); + CHECK_ERR(order2); + if (*order1 != *order2) { + return ctx.in.err(pos, "array.atomic.rmw memory orders must be identical"); + } + auto type = typeidx(ctx); + CHECK_ERR(type); + return ctx.makeArrayRMW(pos, annotations, op, *type, *order1); +} + +template +Result<> makeArrayCmpxchg(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto order1 = memorder(ctx); + CHECK_ERR(order1); + auto order2 = memorder(ctx); + CHECK_ERR(order2); + if (*order1 != *order2) { + return ctx.in.err(pos, "array.atomic.rmw memory orders must be identical"); + } + auto type = typeidx(ctx); + CHECK_ERR(type); + return ctx.makeArrayCmpxchg(pos, annotations, *type, *order1); +} + template Result<> makeRefAs(Ctx& ctx, Index pos, diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index d71d27bafd6..731b3b0de36 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -448,6 +448,18 @@ struct EscapeAnalyzer { escapes = false; fullyConsumes = true; } + void visitArrayRMW(ArrayRMW* curr) { + if (curr->ref == child) { + escapes = false; + fullyConsumes = true; + } + } + void visitArrayCmpxchg(ArrayCmpxchg* curr) { + if (curr->ref == child || curr->expected == child) { + escapes = false; + fullyConsumes = true; + } + } // TODO other GC operations } checker; diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index dc0cff0a717..1da16844197 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2240,6 +2240,18 @@ struct OptimizeInstructions trapOnNull(curr, curr->destRef) || trapOnNull(curr, curr->srcRef); } + void visitArrayRMW(ArrayRMW* curr) { + skipNonNullCast(curr->ref, curr); + trapOnNull(curr, curr->ref); + // TODO: more opts like StructRMW + } + + void visitArrayCmpxchg(ArrayCmpxchg* curr) { + skipNonNullCast(curr->ref, curr); + trapOnNull(curr, curr->ref); + // TODO: more opts like StructCmpxchg + } + void visitRefCast(RefCast* curr) { // Note we must check the ref's type here and not our own, since we only // refinalize at the end, which means our type may not have been updated yet diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 8d76a425778..a882451abab 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2444,6 +2444,26 @@ struct PrintExpressionContents o << ' '; curr->segment.print(o); } + void visitArrayRMW(ArrayRMW* curr) { + prepareColor(o); + o << "array.atomic.rmw."; + printAtomicRMWOp(curr->op); + restoreNormalColor(o); + o << ' '; + printMemoryOrder(curr->order); + printMemoryOrder(curr->order); + auto heapType = curr->ref->type.getHeapType(); + printHeapTypeName(heapType); + } + void visitArrayCmpxchg(ArrayCmpxchg* curr) { + prepareColor(o); + o << "array.atomic.rmw.cmpxchg "; + restoreNormalColor(o); + printMemoryOrder(curr->order); + printMemoryOrder(curr->order); + auto heapType = curr->ref->type.getHeapType(); + printHeapTypeName(heapType); + } void visitRefAs(RefAs* curr) { switch (curr->op) { case RefAsNonNull: diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 622a0ef046f..97ab84cb65e 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -727,6 +727,10 @@ struct TransferFn : OverriddenVisitor { } } + void visitArrayRMW(ArrayRMW* curr) { WASM_UNREACHABLE("TODO"); } + + void visitArrayCmpxchg(ArrayCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } + HeapType generalizeArrayType(HeapType type, std::optional reqFieldType = std::nullopt) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 88a29ba90ea..ecd7079c586 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1205,6 +1205,13 @@ enum ASTNodes { ArrayAtomicGetS = 0x68, ArrayAtomicGetU = 0x69, ArrayAtomicSet = 0x6a, + ArrayAtomicRMWAdd = 0x6b, + ArrayAtomicRMWSub = 0x6c, + ArrayAtomicRMWAnd = 0x6d, + ArrayAtomicRMWOr = 0x6e, + ArrayAtomicRMWXor = 0x6f, + ArrayAtomicRMWXchg = 0x70, + ArrayAtomicRMWCmpxchg = 0x71, // stringref opcodes diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 9429108e499..70ec6a5f2fd 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1128,6 +1128,34 @@ class Builder { ret->finalize(); return ret; } + ArrayRMW* makeArrayRMW(AtomicRMWOp op, + Expression* ref, + Expression* index, + Expression* value, + MemoryOrder order) { + auto* ret = wasm.allocator.alloc(); + ret->op = op; + ret->ref = ref; + ret->index = index; + ret->value = value; + ret->order = order; + ret->finalize(); + return ret; + } + ArrayCmpxchg* makeArrayCmpxchg(Expression* ref, + Expression* index, + Expression* expected, + Expression* replacement, + MemoryOrder order) { + auto* ret = wasm.allocator.alloc(); + ret->ref = ref; + ret->index = index; + ret->expected = expected; + ret->replacement = replacement; + ret->order = order; + ret->finalize(); + return ret; + } RefAs* makeRefAs(RefAsOp op, Expression* value) { auto* ret = wasm.allocator.alloc(); ret->op = op; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index b6932f6d1b9..b9201d4d09d 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -770,6 +770,22 @@ DELEGATE_FIELD_CHILD(ArrayInitElem, index) DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayInitElem, ref) DELEGATE_FIELD_CASE_END(ArrayInitElem) +DELEGATE_FIELD_CASE_START(ArrayRMW) +DELEGATE_FIELD_INT(ArrayRMW, op) +DELEGATE_FIELD_CHILD(ArrayRMW, value) +DELEGATE_FIELD_CHILD(ArrayRMW, index) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayRMW, ref) +DELEGATE_FIELD_INT(ArrayRMW, order) +DELEGATE_FIELD_CASE_END(ArrayRMW) + +DELEGATE_FIELD_CASE_START(ArrayCmpxchg) +DELEGATE_FIELD_CHILD(ArrayCmpxchg, replacement) +DELEGATE_FIELD_CHILD(ArrayCmpxchg, expected) +DELEGATE_FIELD_CHILD(ArrayCmpxchg, index) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayCmpxchg, ref) +DELEGATE_FIELD_INT(ArrayCmpxchg, order) +DELEGATE_FIELD_CASE_END(ArrayCmpxchg) + DELEGATE_FIELD_CASE_START(RefAs) DELEGATE_FIELD_INT(RefAs, op) DELEGATE_FIELD_CHILD(RefAs, value) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index b2491fda092..1c804432b47 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -95,6 +95,8 @@ DELEGATE(ArrayCopy); DELEGATE(ArrayFill); DELEGATE(ArrayInitData); DELEGATE(ArrayInitElem); +DELEGATE(ArrayRMW); +DELEGATE(ArrayCmpxchg); DELEGATE(RefAs); DELEGATE(StringNew); DELEGATE(StringConst); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 3e49a8d640e..22a76b5c83e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2086,6 +2086,81 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitArrayInitData(ArrayInitData* curr) { WASM_UNREACHABLE("unimp"); } Flow visitArrayInitElem(ArrayInitElem* curr) { WASM_UNREACHABLE("unimp"); } + Flow visitArrayRMW(ArrayRMW* curr) { + NOTE_ENTER("ArrayRMW"); + Flow ref = self()->visit(curr->ref); + if (ref.breaking()) { + return ref; + } + Flow index = self()->visit(curr->index); + if (index.breaking()) { + return index; + } + Flow value = self()->visit(curr->value); + if (value.breaking()) { + return value; + } + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + size_t indexVal = index.getSingleValue().getUnsigned(); + auto& field = data->values[indexVal]; + auto oldVal = field; + auto newVal = value.getSingleValue(); + switch (curr->op) { + case RMWAdd: + field = field.add(newVal); + break; + case RMWSub: + field = field.sub(newVal); + break; + case RMWAnd: + field = field.and_(newVal); + break; + case RMWOr: + field = field.or_(newVal); + break; + case RMWXor: + field = field.xor_(newVal); + break; + case RMWXchg: + field = newVal; + break; + } + return oldVal; + } + + Flow visitArrayCmpxchg(ArrayCmpxchg* curr) { + NOTE_ENTER("ArrayCmpxchg"); + Flow ref = self()->visit(curr->ref); + if (ref.breaking()) { + return ref; + } + Flow index = self()->visit(curr->index); + if (index.breaking()) { + return index; + } + Flow expected = self()->visit(curr->expected); + if (expected.breaking()) { + return expected; + } + Flow replacement = self()->visit(curr->replacement); + if (replacement.breaking()) { + return replacement; + } + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + size_t indexVal = index.getSingleValue().getUnsigned(); + auto& field = data->values[indexVal]; + auto oldVal = field; + if (field == expected.getSingleValue()) { + field = replacement.getSingleValue(); + } + return oldVal; + } Flow visitRefAs(RefAs* curr) { NOTE_ENTER("RefAs"); Flow flow = visit(curr->value); diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index d801750fa57..8ffe622d151 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -235,6 +235,8 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeArrayFill(HeapType type); Result<> makeArrayInitData(HeapType type, Name data); Result<> makeArrayInitElem(HeapType type, Name elem); + Result<> makeArrayRMW(AtomicRMWOp op, HeapType type, MemoryOrder order); + Result<> makeArrayCmpxchg(HeapType type, MemoryOrder order); Result<> makeRefAs(RefAsOp op); Result<> makeStringNew(StringNewOp op); Result<> makeStringConst(Name string); diff --git a/src/wasm.h b/src/wasm.h index 9a78c7c6ba5..2120db589aa 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -738,6 +738,8 @@ class Expression { ArrayFillId, ArrayInitDataId, ArrayInitElemId, + ArrayRMWId, + ArrayCmpxchgId, RefAsId, StringNewId, StringConstId, @@ -1871,6 +1873,34 @@ class ArrayInitElem : public SpecificExpression { void finalize(); }; +class ArrayRMW : public SpecificExpression { +public: + ArrayRMW() = default; + ArrayRMW(MixedArena& allocator) {} + + AtomicRMWOp op; + Expression* ref; + Expression* index; + Expression* value; + MemoryOrder order; + + void finalize(); +}; + +class ArrayCmpxchg : public SpecificExpression { +public: + ArrayCmpxchg() = default; + ArrayCmpxchg(MixedArena& allocator) {} + + Expression* ref; + Expression* index; + Expression* expected; + Expression* replacement; + MemoryOrder order; + + void finalize(); +}; + class RefAs : public SpecificExpression { public: RefAs() = default; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 4c570360b82..ba7eaa76e73 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3784,6 +3784,26 @@ Result<> WasmBinaryReader::readInst() { auto type = getIndexedHeapType(); return builder.makeArraySet(type, order); } + +#define ARRAY_RMW(op) \ + case BinaryConsts::ArrayAtomicRMW##op: { \ + auto order = getMemoryOrder(true); \ + auto type = getIndexedHeapType(); \ + return builder.makeArrayRMW(RMW##op, type, order); \ + } + + ARRAY_RMW(Add) + ARRAY_RMW(Sub) + ARRAY_RMW(And) + ARRAY_RMW(Or) + ARRAY_RMW(Xor) + ARRAY_RMW(Xchg) + + case BinaryConsts::ArrayAtomicRMWCmpxchg: { + auto order = getMemoryOrder(true); + auto type = getIndexedHeapType(); + return builder.makeArrayCmpxchg(type, order); + } } return Err{"unknown atomic operation " + std::to_string(op)}; } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 93f47aead92..b21bf836c1a 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -667,6 +667,20 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } + Result<> visitArrayRMW(ArrayRMW* curr, + std::optional ht = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitArrayRMW(curr, ht); + return popConstrainedChildren(children); + } + + Result<> visitArrayCmpxchg(ArrayCmpxchg* curr, + std::optional ht = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitArrayCmpxchg(curr, ht); + return popConstrainedChildren(children); + } + Result<> visitStringEncode(StringEncode* curr, std::optional ht = std::nullopt) { std::vector children; @@ -2369,6 +2383,24 @@ Result<> IRBuilder::makeArrayInitElem(HeapType type, Name elem) { return Ok{}; } +Result<> +IRBuilder::makeArrayRMW(AtomicRMWOp op, HeapType type, MemoryOrder order) { + ArrayRMW curr; + CHECK_ERR(ChildPopper{*this}.visitArrayRMW(&curr, type)); + CHECK_ERR(validateTypeAnnotation(type, curr.ref)); + push(builder.makeArrayRMW(op, curr.ref, curr.index, curr.value, order)); + return Ok{}; +} + +Result<> IRBuilder::makeArrayCmpxchg(HeapType type, MemoryOrder order) { + ArrayCmpxchg curr; + CHECK_ERR(ChildPopper{*this}.visitArrayCmpxchg(&curr, type)); + CHECK_ERR(validateTypeAnnotation(type, curr.ref)); + push(builder.makeArrayCmpxchg( + curr.ref, curr.index, curr.expected, curr.replacement, order)); + return Ok{}; +} + Result<> IRBuilder::makeRefAs(RefAsOp op) { RefAs curr; curr.op = op; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index aeb8359116f..a5631dbf711 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2552,6 +2552,47 @@ void BinaryInstWriter::visitArrayInitElem(ArrayInitElem* curr) { o << U32LEB(parent.getElementSegmentIndex(curr->segment)); } +void BinaryInstWriter::visitArrayRMW(ArrayRMW* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + o << int8_t(BinaryConsts::AtomicPrefix); + switch (curr->op) { + case RMWAdd: + o << U32LEB(BinaryConsts::ArrayAtomicRMWAdd); + break; + case RMWSub: + o << U32LEB(BinaryConsts::ArrayAtomicRMWSub); + break; + case RMWAnd: + o << U32LEB(BinaryConsts::ArrayAtomicRMWAnd); + break; + case RMWOr: + o << U32LEB(BinaryConsts::ArrayAtomicRMWOr); + break; + case RMWXor: + o << U32LEB(BinaryConsts::ArrayAtomicRMWXor); + break; + case RMWXchg: + o << U32LEB(BinaryConsts::ArrayAtomicRMWXchg); + break; + } + parent.writeMemoryOrder(curr->order, /*isRMW=*/true); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); +} + +void BinaryInstWriter::visitArrayCmpxchg(ArrayCmpxchg* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + o << int8_t(BinaryConsts::AtomicPrefix) + << U32LEB(BinaryConsts::ArrayAtomicRMWCmpxchg); + parent.writeMemoryOrder(curr->order, /*isRMW=*/true); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); +} + void BinaryInstWriter::visitRefAs(RefAs* curr) { switch (curr->op) { case RefAsNonNull: diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a3d658369d7..2a3cb45a91e 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -515,6 +515,8 @@ struct FunctionValidator : public WalkerPass> { template void visitArrayInit(ArrayInit* curr); void visitArrayInitData(ArrayInitData* curr); void visitArrayInitElem(ArrayInitElem* curr); + void visitArrayRMW(ArrayRMW* curr); + void visitArrayCmpxchg(ArrayCmpxchg* curr); void visitStringNew(StringNew* curr); void visitStringConst(StringConst* curr); void visitStringMeasure(StringMeasure* curr); @@ -3692,6 +3694,112 @@ void FunctionValidator::visitArrayInitElem(ArrayInitElem* curr) { "array.init_elem segment type must match destination type"); } +void FunctionValidator::visitArrayRMW(ArrayRMW* curr) { + auto expected = + FeatureSet::GC | FeatureSet::Atomics | FeatureSet::SharedEverything; + if (!shouldBeTrue(expected <= getModule()->features, + curr, + "array.atomic.rmw requires additional features ")) { + getStream() << getMissingFeaturesList(*getModule(), expected) << '\n'; + } + if (curr->ref->type == Type::unreachable) { + return; + } + if (!shouldBeTrue(curr->ref->type.isRef(), + curr->ref, + "array.atomic.rmw ref must be a reference type")) { + return; + } + auto type = curr->ref->type.getHeapType(); + if (type.isMaybeShared(HeapType::none)) { + return; + } + if (!shouldBeTrue( + type.isArray(), curr->ref, "array.atomic.rmw ref must be a array")) { + return; + } + const auto& element = type.getArray().element; + shouldBeEqual(element.mutable_, + Mutable, + curr, + "array.atomic.rmw element must be mutable"); + shouldBeFalse( + element.isPacked(), curr, "array.atomic.rmw element must not be packed"); + bool isAny = + element.type.isRef() && + Type::isSubType( + element.type, + Type(HeapTypes::any.getBasic(element.type.getHeapType().getShared()), + Nullable)); + if (!shouldBeTrue(element.type == Type::i32 || element.type == Type::i64 || + (isAny && curr->op == RMWXchg), + curr, + "array.atomic.rmw element type invalid for operation")) { + return; + } + shouldBeSubType(curr->value->type, + element.type, + curr, + "array.atomic.rmw value must have the proper type"); +} + +void FunctionValidator::visitArrayCmpxchg(ArrayCmpxchg* curr) { + auto expected = + FeatureSet::GC | FeatureSet::Atomics | FeatureSet::SharedEverything; + if (!shouldBeTrue(expected <= getModule()->features, + curr, + "array.atomic.rmw requires additional features ")) { + getStream() << getMissingFeaturesList(*getModule(), expected) << '\n'; + } + if (curr->ref->type == Type::unreachable) { + return; + } + if (!shouldBeTrue(curr->ref->type.isRef(), + curr->ref, + "array.atomic.rmw ref must be a reference type")) { + return; + } + auto type = curr->ref->type.getHeapType(); + if (type.isMaybeShared(HeapType::none)) { + return; + } + if (!shouldBeTrue( + type.isArray(), curr->ref, "array.atomic.rmw ref must be a array")) { + return; + } + const auto& element = type.getArray().element; + shouldBeEqual(element.mutable_, + Mutable, + curr, + "array.atomic.rmw element must be mutable"); + shouldBeFalse( + element.isPacked(), curr, "array.atomic.rmw element must not be packed"); + + Type expectedExpectedType; + if (element.type == Type::i32) { + expectedExpectedType = Type::i32; + } else if (element.type == Type::i64) { + expectedExpectedType = Type::i64; + } else if (element.type.isRef()) { + expectedExpectedType = Type( + HeapTypes::eq.getBasic(element.type.getHeapType().getShared()), Nullable); + } else { + shouldBeTrue( + false, curr, "array.atomic.rmw element type invalid for operation"); + return; + } + shouldBeSubType( + curr->expected->type, + expectedExpectedType, + curr, + "array.atomic.rmw.cmpxchg expected value must have the proper type"); + shouldBeSubType( + curr->replacement->type, + element.type, + curr, + "array.atomic.rmw.cmpxchg replacement value must have the proper type"); +} + void FunctionValidator::visitStringNew(StringNew* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 6ece8bfab8c..bde52702e12 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1256,9 +1256,7 @@ void StructCmpxchg::finalize() { replacement->type == Type::unreachable) { type = Type::unreachable; } else if (ref->type.isNull()) { - // Like StructRMW, but the most precise possible field type is the LUB of - // the expected and replacement values. - type = Type::getLeastUpperBound(expected->type, replacement->type); + type = replacement->type; } else { type = ref->type.getHeapType().getStruct().fields[index].type; } @@ -1362,6 +1360,32 @@ void ArrayInitElem::finalize() { } } +void ArrayRMW::finalize() { + if (ref->type == Type::unreachable || index->type == Type::unreachable || + value->type == Type::unreachable) { + type = Type::unreachable; + } else if (ref->type.isNull()) { + // We have no array type to read the field off of, but the most precise + // possible option is the type of the value we are using to make the + // modification. + type = value->type; + } else { + type = ref->type.getHeapType().getArray().element.type; + } +} + +void ArrayCmpxchg::finalize() { + if (ref->type == Type::unreachable || index->type == Type::unreachable || + expected->type == Type::unreachable || + replacement->type == Type::unreachable) { + type = Type::unreachable; + } else if (ref->type.isNull()) { + type = replacement->type; + } else { + type = ref->type.getHeapType().getArray().element.type; + } +} + void RefAs::finalize() { // An unreachable child means we are unreachable. Also set ourselves to // unreachable when the child is invalid (say, it is an i32 or some other non- diff --git a/src/wasm2js.h b/src/wasm2js.h index 18b10a87b3f..ab6e62b88ea 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2376,6 +2376,14 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitArrayRMW(ArrayRMW* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } + Ref visitArrayCmpxchg(ArrayCmpxchg* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitStringNew(StringNew* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 5f9dab4d95f..d9c3ff97ac5 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -102,15 +102,15 @@ ArrayGetId: 75 ArraySetId: 76 ArrayLenId: 77 ArrayCopy: 78 -RefAs: 82 -StringNew: 83 -StringConst: 84 -StringMeasure: 85 -StringEncode: 86 -StringConcat: 87 -StringEq: 88 -StringWTF16Get: 89 -StringSliceWTF: 90 +RefAs: 84 +StringNew: 85 +StringConst: 86 +StringMeasure: 87 +StringEncode: 88 +StringConcat: 89 +StringEq: 90 +StringWTF16Get: 91 +StringSliceWTF: 92 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/lit/basic/gc-atomics.wast b/test/lit/basic/gc-atomics.wast index 79a2d89203b..3e7a3686dae 100644 --- a/test/lit/basic/gc-atomics.wast +++ b/test/lit/basic/gc-atomics.wast @@ -46,7 +46,7 @@ ) ) - ;; CHECK: (func $get-s (type $3) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-s (type $5) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_s $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -57,7 +57,7 @@ ) ) - ;; CHECK: (func $get-s-seqcst (type $3) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-s-seqcst (type $5) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_s $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -68,7 +68,7 @@ ) ) - ;; CHECK: (func $get-s-acqrel (type $3) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-s-acqrel (type $5) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_s acqrel $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -79,7 +79,7 @@ ) ) - ;; CHECK: (func $get-u (type $3) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-u (type $5) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_u $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -90,7 +90,7 @@ ) ) - ;; CHECK: (func $get-u-seqcst (type $3) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-u-seqcst (type $5) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_u $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -101,7 +101,7 @@ ) ) - ;; CHECK: (func $get-u-acqrel (type $3) (param $0 (ref null $packed)) (result i32) + ;; CHECK: (func $get-u-acqrel (type $5) (param $0 (ref null $packed)) (result i32) ;; CHECK-NEXT: (struct.atomic.get_u acqrel $packed 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -112,7 +112,7 @@ ) ) - ;; CHECK: (func $set (type $6) (param $0 (ref null $struct)) + ;; CHECK: (func $set (type $7) (param $0 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.set $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -125,7 +125,7 @@ ) ) - ;; CHECK: (func $set-seqcst (type $6) (param $0 (ref null $struct)) + ;; CHECK: (func $set-seqcst (type $7) (param $0 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.set $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -138,7 +138,7 @@ ) ) - ;; CHECK: (func $set-acqrel (type $6) (param $0 (ref null $struct)) + ;; CHECK: (func $set-acqrel (type $7) (param $0 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.set acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -430,7 +430,7 @@ ) ) - ;; CHECK: (func $array.get (type $8) (param $0 (ref null $array)) (result i32) + ;; CHECK: (func $array.get (type $4) (param $0 (ref null $array)) (result i32) ;; CHECK-NEXT: (array.atomic.get $array ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 42) @@ -443,7 +443,7 @@ ) ) - ;; CHECK: (func $array.get_s (type $7) (param $0 (ref null $array-packed)) (result i32) + ;; CHECK: (func $array.get_s (type $8) (param $0 (ref null $array-packed)) (result i32) ;; CHECK-NEXT: (array.atomic.get_s $array-packed ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 42) @@ -456,7 +456,7 @@ ) ) - ;; CHECK: (func $array.get_u (type $7) (param $0 (ref null $array-packed)) (result i32) + ;; CHECK: (func $array.get_u (type $8) (param $0 (ref null $array-packed)) (result i32) ;; CHECK-NEXT: (array.atomic.get_u acqrel $array-packed ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 42) @@ -483,4 +483,143 @@ (i32.const 1337) ) ) + + ;; CHECK: (func $array-rmw-add (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.add $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-add (param (ref null $array)) (result i32) + (array.atomic.rmw.add $array + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $array-rmw-sub (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.sub $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-sub (param (ref null $array)) (result i32) + (array.atomic.rmw.sub $array + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $array-rmw-and (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.and $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-and (param (ref null $array)) (result i32) + (array.atomic.rmw.and $array + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $array-rmw-or (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.or $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-or (param (ref null $array)) (result i32) + (array.atomic.rmw.or $array + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $array-rmw-xor (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.xor $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-xor (param (ref null $array)) (result i32) + (array.atomic.rmw.xor $array + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $array-rmw-xchg (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.xchg $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-xchg (param (ref null $array)) (result i32) + (array.atomic.rmw.xchg $array + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $array-rmw-xchg-acqrel (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.xchg acqrel acqrel $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-xchg-acqrel (param (ref null $array)) (result i32) + (array.atomic.rmw.xchg acqrel acqrel $array + (local.get 0) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $array-rmw-cmpxchg (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.cmpxchg $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-cmpxchg (param (ref null $array)) (result i32) + (array.atomic.rmw.cmpxchg $array + (local.get 0) + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + ) + + ;; CHECK: (func $array-rmw-cmpxchg-acqrel (type $4) (param $0 (ref null $array)) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.cmpxchg acqrel acqrel $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-cmpxchg-acqrel (param (ref null $array)) (result i32) + (array.atomic.rmw.cmpxchg acqrel acqrel $array + (local.get 0) + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + ) ) diff --git a/test/lit/passes/optimize-instructions-struct-rmw.wast b/test/lit/passes/optimize-instructions-struct-rmw.wast index 0eb5da2fcc5..e1b3c54f452 100644 --- a/test/lit/passes/optimize-instructions-struct-rmw.wast +++ b/test/lit/passes/optimize-instructions-struct-rmw.wast @@ -16,7 +16,10 @@ ;; CHECK: (type $unshared-struct (struct (field (mut (ref null $unshared-struct))))) (type $unshared-struct (struct (field (mut (ref null $unshared-struct))))) - ;; CHECK: (func $rmw-skip-non-null-cast (type $6) (param $0 (ref null $i32)) (param $1 i32) (result i32) + ;; CHECK: (type $array (array (mut i32))) + (type $array (array (mut i32))) + + ;; CHECK: (func $rmw-skip-non-null-cast (type $7) (param $0 (ref null $i32)) (param $1 i32) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.add $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -31,7 +34,7 @@ ) ) - ;; CHECK: (func $cmpxchg-skip-non-null-cast (type $7) (param $0 (ref null $i32)) (param $1 i32) (param $2 i32) (result i32) + ;; CHECK: (func $cmpxchg-skip-non-null-cast (type $8) (param $0 (ref null $i32)) (param $1 i32) (param $2 i32) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -48,7 +51,43 @@ ) ) - ;; CHECK: (func $rmw-trap-on-null (type $8) (result i32) + ;; CHECK: (func $array-rmw-skip-non-null-cast (type $9) (param $0 (ref null $array)) (param $1 i32) (param $2 i32) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.add $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-rmw-skip-non-null-cast (param (ref null $array) i32 i32) (result i32) + (array.atomic.rmw.add $array + (ref.as_non_null + (local.get 0) + ) + (local.get 1) + (local.get 2) + ) + ) + + ;; CHECK: (func $array-cmpxchg-skip-non-null-cast (type $10) (param $0 (ref null $array)) (param $1 i32) (param $2 i32) (param $3 i32) (result i32) + ;; CHECK-NEXT: (array.atomic.rmw.cmpxchg $array + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-cmpxchg-skip-non-null-cast (param (ref null $array) i32 i32 i32) (result i32) + (array.atomic.rmw.cmpxchg $array + (ref.as_non_null + (local.get 0) + ) + (local.get 1) + (local.get 2) + (local.get 3) + ) + ) + + ;; CHECK: (func $rmw-trap-on-null (type $11) (result i32) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $rmw-trap-on-null (result i32) @@ -58,7 +97,7 @@ ) ) - ;; CHECK: (func $cmpxchg-trap-on-null (type $8) (result i32) + ;; CHECK: (func $cmpxchg-trap-on-null (type $11) (result i32) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $cmpxchg-trap-on-null (result i32) @@ -69,7 +108,30 @@ ) ) - ;; CHECK: (func $rmw-add-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $array-rmw-trap-on-null (type $11) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $array-rmw-trap-on-null (result i32) + (array.atomic.rmw.add $array + (ref.null none) + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $array-cmpxchg-trap-on-null (type $11) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $array-cmpxchg-trap-on-null (result i32) + (array.atomic.rmw.cmpxchg $array + (ref.null none) + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + ) + + ;; CHECK: (func $rmw-add-i32-ident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop @@ -87,7 +149,7 @@ ) ) - ;; CHECK: (func $rmw-add-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-add-i32-noident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.add acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 1) @@ -101,7 +163,7 @@ ) ) - ;; CHECK: (func $rmw-sub-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-sub-i32-ident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop @@ -118,7 +180,7 @@ ) ) - ;; CHECK: (func $rmw-sub-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-sub-i32-noident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.sub acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 1) @@ -131,7 +193,7 @@ ) ) - ;; CHECK: (func $rmw-and-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-and-i32-ident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop @@ -148,7 +210,7 @@ ) ) - ;; CHECK: (func $rmw-and-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-and-i32-noident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.and acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -161,7 +223,7 @@ ) ) - ;; CHECK: (func $rmw-or-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-or-i32-ident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop @@ -178,7 +240,7 @@ ) ) - ;; CHECK: (func $rmw-or-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-or-i32-noident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.or acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const -1) @@ -191,7 +253,7 @@ ) ) - ;; CHECK: (func $rmw-xor-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-xor-i32-ident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (drop @@ -208,7 +270,7 @@ ) ) - ;; CHECK: (func $rmw-xor-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-xor-i32-noident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.xor acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const -1) @@ -221,7 +283,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-i32-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-xchg-i32-ident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (struct.get $i32 0 @@ -239,7 +301,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-i32-noident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-xchg-i32-noident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -252,7 +314,7 @@ ) ) - ;; CHECK: (func $cmpxchg-i32-ident (type $6) (param $0 (ref null $i32)) (param $1 i32) (result i32) + ;; CHECK: (func $cmpxchg-i32-ident (type $7) (param $0 (ref null $i32)) (param $1 i32) (result i32) ;; CHECK-NEXT: (struct.atomic.get acqrel $i32 0 ;; CHECK-NEXT: (block (result (ref null $i32)) ;; CHECK-NEXT: (block @@ -275,7 +337,7 @@ ) ) - ;; CHECK: (func $cmpxchg-i32-noident (type $6) (param $0 (ref null $i32)) (param $1 i32) (result i32) + ;; CHECK: (func $cmpxchg-i32-noident (type $7) (param $0 (ref null $i32)) (param $1 i32) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -290,7 +352,7 @@ ) ) - ;; CHECK: (func $rmw-add-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-add-i64-ident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop @@ -308,7 +370,7 @@ ) ) - ;; CHECK: (func $rmw-add-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-add-i64-noident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.rmw.add acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 1) @@ -322,7 +384,7 @@ ) ) - ;; CHECK: (func $rmw-sub-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-sub-i64-ident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop @@ -339,7 +401,7 @@ ) ) - ;; CHECK: (func $rmw-sub-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-sub-i64-noident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.rmw.sub acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 1) @@ -352,7 +414,7 @@ ) ) - ;; CHECK: (func $rmw-and-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-and-i64-ident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop @@ -369,7 +431,7 @@ ) ) - ;; CHECK: (func $rmw-and-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-and-i64-noident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.rmw.and acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 0) @@ -382,7 +444,7 @@ ) ) - ;; CHECK: (func $rmw-or-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-or-i64-ident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop @@ -399,7 +461,7 @@ ) ) - ;; CHECK: (func $rmw-or-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-or-i64-noident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.rmw.or acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const -1) @@ -412,7 +474,7 @@ ) ) - ;; CHECK: (func $rmw-xor-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-xor-i64-ident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (drop @@ -429,7 +491,7 @@ ) ) - ;; CHECK: (func $rmw-xor-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-xor-i64-noident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.rmw.xor acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const -1) @@ -442,7 +504,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-i64-ident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-xchg-i64-ident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (struct.get $i64 0 @@ -460,7 +522,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-i64-noident (type $10) (param $0 (ref null $i64)) (result i64) + ;; CHECK: (func $rmw-xchg-i64-noident (type $13) (param $0 (ref null $i64)) (result i64) ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 0) @@ -473,7 +535,7 @@ ) ) - ;; CHECK: (func $cmpxchg-i64-ident (type $11) (param $0 (ref null $i64)) (param $1 i64) (result i64) + ;; CHECK: (func $cmpxchg-i64-ident (type $14) (param $0 (ref null $i64)) (param $1 i64) (result i64) ;; CHECK-NEXT: (struct.atomic.get acqrel $i64 0 ;; CHECK-NEXT: (block (result (ref null $i64)) ;; CHECK-NEXT: (block @@ -496,7 +558,7 @@ ) ) - ;; CHECK: (func $cmpxchg-i64-noident (type $11) (param $0 (ref null $i64)) (param $1 i64) (result i64) + ;; CHECK: (func $cmpxchg-i64-noident (type $14) (param $0 (ref null $i64)) (param $1 i64) (result i64) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $i64 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i64.const 0) @@ -511,7 +573,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-ref-ident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK: (func $rmw-xchg-ref-ident (type $15) (param $0 (ref null $struct)) (result (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (struct.get $struct 0 @@ -529,7 +591,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-ref-noident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK: (func $rmw-xchg-ref-noident (type $15) (param $0 (ref null $struct)) (result (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.rmw.xchg acqrel acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $0) @@ -542,7 +604,7 @@ ) ) - ;; CHECK: (func $cmpxchg-ref-ident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK: (func $cmpxchg-ref-ident (type $15) (param $0 (ref null $struct)) (result (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.get acqrel $struct 0 ;; CHECK-NEXT: (block (result (ref null $struct)) ;; CHECK-NEXT: (block @@ -565,7 +627,7 @@ ) ) - ;; CHECK: (func $cmpxchg-ref-ident-null (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK: (func $cmpxchg-ref-ident-null (type $15) (param $0 (ref null $struct)) (result (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.get acqrel $struct 0 ;; CHECK-NEXT: (block (result (ref null $struct)) ;; CHECK-NEXT: (block @@ -588,7 +650,7 @@ ) ) - ;; CHECK: (func $cmpxchg-ref-noident (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK: (func $cmpxchg-ref-noident (type $15) (param $0 (ref null $struct)) (result (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg acqrel acqrel $struct 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (ref.null (shared none)) @@ -603,7 +665,7 @@ ) ) - ;; CHECK: (func $rmw-add-i32-seqcst-ident (type $9) (param $0 (ref null $i32)) (result i32) + ;; CHECK: (func $rmw-add-i32-seqcst-ident (type $12) (param $0 (ref null $i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.rmw.add $i32 0 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -617,7 +679,7 @@ ) ) - ;; CHECK: (func $rmw-add-i32-unshared-ident (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $rmw-add-i32-unshared-ident (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.get $unshared-i32 0 ;; CHECK-NEXT: (block (result (ref null $unshared-i32)) ;; CHECK-NEXT: (drop @@ -636,7 +698,7 @@ ) ) - ;; CHECK: (func $cmpxchg-i32-unshared-ident (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $cmpxchg-i32-unshared-ident (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (struct.atomic.get $unshared-i32 0 ;; CHECK-NEXT: (block (result (ref null $unshared-i32)) ;; CHECK-NEXT: (block @@ -660,7 +722,7 @@ ) ) - ;; CHECK: (func $rmw-add-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $rmw-add-i32-lower (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -691,7 +753,7 @@ ) ) - ;; CHECK: (func $rmw-sub-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $rmw-sub-i32-lower (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -722,7 +784,7 @@ ) ) - ;; CHECK: (func $rmw-and-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $rmw-and-i32-lower (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -753,7 +815,7 @@ ) ) - ;; CHECK: (func $rmw-or-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $rmw-or-i32-lower (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -784,7 +846,7 @@ ) ) - ;; CHECK: (func $rmw-xor-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $rmw-xor-i32-lower (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -815,7 +877,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $rmw-xchg-i32-lower (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -843,7 +905,7 @@ ) ) - ;; CHECK: (func $cmpxchg-i32-lower (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $cmpxchg-i32-lower (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -883,7 +945,7 @@ ) ) - ;; CHECK: (func $rmw-add-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK: (func $rmw-add-i64-lower (type $17) (param $0 (ref null $unshared-i64)) (result i64) ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) @@ -914,7 +976,7 @@ ) ) - ;; CHECK: (func $rmw-sub-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK: (func $rmw-sub-i64-lower (type $17) (param $0 (ref null $unshared-i64)) (result i64) ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) @@ -945,7 +1007,7 @@ ) ) - ;; CHECK: (func $rmw-and-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK: (func $rmw-and-i64-lower (type $17) (param $0 (ref null $unshared-i64)) (result i64) ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) @@ -976,7 +1038,7 @@ ) ) - ;; CHECK: (func $rmw-or-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK: (func $rmw-or-i64-lower (type $17) (param $0 (ref null $unshared-i64)) (result i64) ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) @@ -1007,7 +1069,7 @@ ) ) - ;; CHECK: (func $rmw-xor-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK: (func $rmw-xor-i64-lower (type $17) (param $0 (ref null $unshared-i64)) (result i64) ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) @@ -1038,7 +1100,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK: (func $rmw-xchg-i64-lower (type $17) (param $0 (ref null $unshared-i64)) (result i64) ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) @@ -1066,7 +1128,7 @@ ) ) - ;; CHECK: (func $cmpxchg-i64-lower (type $14) (param $0 (ref null $unshared-i64)) (result i64) + ;; CHECK: (func $cmpxchg-i64-lower (type $17) (param $0 (ref null $unshared-i64)) (result i64) ;; CHECK-NEXT: (local $1 (ref null $unshared-i64)) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) @@ -1106,7 +1168,7 @@ ) ) - ;; CHECK: (func $rmw-xchg-ref-lower (type $15) (param $0 (ref null $unshared-struct)) (result (ref null $unshared-struct)) + ;; CHECK: (func $rmw-xchg-ref-lower (type $18) (param $0 (ref null $unshared-struct)) (result (ref null $unshared-struct)) ;; CHECK-NEXT: (local $1 (ref null $unshared-struct)) ;; CHECK-NEXT: (local $2 (ref null $unshared-struct)) ;; CHECK-NEXT: (local $3 (ref null $unshared-struct)) @@ -1134,7 +1196,7 @@ ) ) - ;; CHECK: (func $cmpxchg-ref-lower (type $15) (param $0 (ref null $unshared-struct)) (result (ref null $unshared-struct)) + ;; CHECK: (func $cmpxchg-ref-lower (type $18) (param $0 (ref null $unshared-struct)) (result (ref null $unshared-struct)) ;; CHECK-NEXT: (local $1 (ref null $unshared-struct)) ;; CHECK-NEXT: (local $2 (ref null $unshared-struct)) ;; CHECK-NEXT: (local $3 (ref null $unshared-struct)) @@ -1174,7 +1236,7 @@ ) ) - ;; CHECK: (func $rmw-add-i32-acqrel (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $rmw-add-i32-acqrel (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -1206,7 +1268,7 @@ ) ) - ;; CHECK: (func $cmpxchg-i32-acqrel (type $13) (param $0 (ref null $unshared-i32)) (result i32) + ;; CHECK: (func $cmpxchg-i32-acqrel (type $16) (param $0 (ref null $unshared-i32)) (result i32) ;; CHECK-NEXT: (local $1 (ref null $unshared-i32)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) From b245afb16f069e8e6bb31be2b47596bad269eb49 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Wed, 4 Jun 2025 01:02:15 +0800 Subject: [PATCH 537/622] OptimizeInstructions: Extend getMaxbits to handle Blocks (#7590) E.g. (i32.and (i32.load (i32.const 0) ) (block (result i32) (i32.store (i32.const 0) (i32.const 0) ) (i32.const 0) ) Interactions between the and's arms prevent other optimizations from helping here, but we can see the and is with 0. Fixes: #7559 --- src/ir/bits.h | 4 + src/passes/OptimizeInstructions.cpp | 9 +- .../lit/passes/optimize-instructions-mvp.wast | 105 +++++++++++++++++- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/src/ir/bits.h b/src/ir/bits.h index e8f2b4f50cc..6add10a8be9 100644 --- a/src/ir/bits.h +++ b/src/ir/bits.h @@ -456,6 +456,10 @@ Index getMaxBits(Expression* curr, if (LoadUtils::isSignRelevant(load) && !load->signed_) { return 8 * load->bytes; } + } else if (auto* block = curr->dynCast()) { + if (!block->name.is() && !block->list.empty() && block->type.isConcrete()) { + return getMaxBits(block->list.back(), localInfoProvider); + } } switch (curr->type.getBasic()) { case Type::i32: diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 1da16844197..6e7a3833ddd 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2608,8 +2608,13 @@ struct OptimizeInstructions } Index getMaxBitsForLocal(LocalGet* get) { - // check what we know about the local - return localInfo[get->index].maxBits; + // check what we know about the local (we may know nothing, if this local + // was added after the pass scanned for locals; in that case, full + // optimization may require another cycle) + if (get->index < localInfo.size()) { + return localInfo[get->index].maxBits; + } + return getBitsForType(get->type); } private: diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 0210447abd3..61afa766927 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -8745,36 +8745,129 @@ (i32.const 0) ) ) - ;; CHECK: (func $andZero (param $0 i32) (result i32) + ;; CHECK: (func $andZero (param $0 i32) (param $1 i64) (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (call $andZero - ;; CHECK-NEXT: (i32.const 1234) + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $andZero (param $0 i32) (result i32) + (func $andZero (param $0 i32) (param $1 i64) (result i32) (drop (i32.and (local.get $0) (i32.const 0) ) ) + (drop + (i64.and + (local.get $1) + (i64.const 0) + ) + ) + ;; side effects. we must keep the tee, but + ;; can drop it. (drop (i32.and - (call $andZero (i32.const 1234)) ;; side effects, we must keep this, but - ;; can drop it. + (local.tee $0 + (i32.const 1) + ) (i32.const 0) ) ) + (drop + (i64.and + (local.tee $1 + (i64.const 1) + ) + (i64.const 0) + ) + ) + ;; We can optimize out the |and| even if the 0 is at the end of a block. + (drop + (i32.and + (local.tee $0 + (i32.const 1) + ) + (block (result i32) + (local.set $0 + (i32.const 1) + ) + (i32.const 0) + ) + ) + ) + (drop + (i64.and + (local.tee $1 + (i64.const 1) + ) + (block (result i64) + (local.set $1 + (i64.const 1) + ) + (i64.const 0) + ) + ) + ) (unreachable) ) ;; CHECK: (func $abstract-additions (param $x32 i32) (param $x64 i64) (param $y32 f32) (param $y64 f64) From 700fa15b0ce8a3613702e21d2ed7ce3f028f18a6 Mon Sep 17 00:00:00 2001 From: CountBleck Date: Wed, 4 Jun 2025 09:10:28 -0700 Subject: [PATCH 538/622] [NFC] Remove BinaryenStringNewSetTry from C API (#7633) The definition for this function was removed in #6589. --- src/binaryen-c.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 67948d8b79c..60e88d78b29 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -2643,8 +2643,6 @@ BINARYEN_API BinaryenExpressionRef BinaryenStringNewGetEnd(BinaryenExpressionRef expr); BINARYEN_API void BinaryenStringNewSetEnd(BinaryenExpressionRef expr, BinaryenExpressionRef endExpr); -BINARYEN_API void BinaryenStringNewSetTry(BinaryenExpressionRef expr, - bool try_); // StringConst From 8c82b6884483315011541e4519afdcb7fd46df68 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Jun 2025 09:54:16 -0700 Subject: [PATCH 539/622] [Branch Hints][DWARF] Fix hints on stacky code, by making IRBuilder aware of synthetic code (#7640) Synthetic code, like for scratch locals, has no binary location. Before, we thought it did, which messed up location tracking for surrounding code. --- src/passes/Outlining.cpp | 2 +- src/wasm-ir-builder.h | 14 ++++- src/wasm/wasm-ir-builder.cpp | 44 +++++++------ test/lit/branch-hinting-stack-ir.wast | 65 ++++++++++++++++++++ test/passes/fannkuch3_manyopts_dwarf.bin.txt | 8 +-- 5 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 test/lit/branch-hinting-stack-ir.wast diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index 80bbcd05071..fff19225104 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -128,7 +128,7 @@ struct ReconstructStringifyWalker // visitIfStart(), which will expect to be able to pop the condition. // This is always okay to do because the correct condition was installed // onto the If when the outer scope was visited. - existingBuilder.push(curr->iff->condition); + existingBuilder.pushSynthetic(curr->iff->condition); ODBG(desc = "If Start at "); ASSERT_OK(existingBuilder.visitIfStart(curr->iff)); } else if (reason.getElseStart()) { diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 8ffe622d151..e854491f6af 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -55,9 +55,21 @@ class IRBuilder : public UnifiedExpressionVisitor> { // initialized to initialize the child fields and refinalize it. Result<> visit(Expression*); + // The origin of an expression. + enum class Origin { + // The expression originates in the binary we are reading. We track binary + // locations for such instructions where necessary (for code annotations, + // etc.). + Binary = 0, + // The expression was synthetically added by the IRBuilder (e.g. a local.get + // of a scratch local). + Synthetic = 1 + }; + // Like visit, but pushes the expression onto the stack as-is without popping // any children or refinalization. - void push(Expression*); + void push(Expression*, Origin origin = Origin::Binary); + void pushSynthetic(Expression* expr) { push(expr, Origin::Synthetic); } // Set the debug location to be attached to the next visited, created, or // pushed instruction. diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index b21bf836c1a..62a897517df 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -79,7 +79,7 @@ MaybeResult IRBuilder::hoistLastValue() { if (type == Type::unreachable) { // Make sure the top of the stack also has an unreachable expression. if (stack.back()->type != Type::unreachable) { - push(builder.makeUnreachable()); + pushSynthetic(builder.makeUnreachable()); } return HoistedVal{Index(index), nullptr}; } @@ -88,7 +88,7 @@ MaybeResult IRBuilder::hoistLastValue() { CHECK_ERR(scratchIdx); expr = builder.makeLocalSet(*scratchIdx, expr); auto* get = builder.makeLocalGet(*scratchIdx, type); - push(get); + pushSynthetic(get); return HoistedVal{Index(index), get}; } @@ -107,7 +107,7 @@ Result<> IRBuilder::packageHoistedValue(const HoistedVal& hoisted, scope.exprStack.end()); auto* block = builder.makeBlock(exprs, type); scope.exprStack.resize(hoisted.valIndex); - push(block); + pushSynthetic(block); }; auto type = scope.exprStack.back()->type; @@ -137,33 +137,37 @@ Result<> IRBuilder::packageHoistedValue(const HoistedVal& hoisted, scratchIdx = *scratch; } for (Index i = 1, size = type.size(); i < size; ++i) { - push(builder.makeTupleExtract(builder.makeLocalGet(scratchIdx, type), i)); + pushSynthetic( + builder.makeTupleExtract(builder.makeLocalGet(scratchIdx, type), i)); } return Ok{}; } -void IRBuilder::push(Expression* expr) { +void IRBuilder::push(Expression* expr, Origin origin) { auto& scope = getScope(); if (expr->type == Type::unreachable) { scope.unreachable = true; } scope.exprStack.push_back(expr); - applyDebugLoc(expr); - if (binaryPos && func && lastBinaryPos != *binaryPos) { - auto span = - BinaryLocations::Span{BinaryLocation(lastBinaryPos - codeSectionOffset), - BinaryLocation(*binaryPos - codeSectionOffset)}; - // Some expressions already have their start noted, and we are just seeing - // their last segment (like an Else). - auto [iter, inserted] = func->expressionLocations.insert({expr, span}); - if (!inserted) { - // Just update the end. - iter->second.end = span.end; - // The true start from before is before the start of the current segment. - assert(iter->second.start < span.start); + if (origin == Origin::Binary) { + applyDebugLoc(expr); + if (binaryPos && func && lastBinaryPos != *binaryPos) { + auto span = + BinaryLocations::Span{BinaryLocation(lastBinaryPos - codeSectionOffset), + BinaryLocation(*binaryPos - codeSectionOffset)}; + // Some expressions already have their start noted, and we are just seeing + // their last segment (like an Else). + auto [iter, inserted] = func->expressionLocations.insert({expr, span}); + if (!inserted) { + // Just update the end. + iter->second.end = span.end; + // The true start from before is before the start of the current + // segment. + assert(iter->second.start < span.start); + } + lastBinaryPos = *binaryPos; } - lastBinaryPos = *binaryPos; } DBG(std::cerr << "After pushing " << ShallowExpression{expr} << ":\n"); @@ -1018,7 +1022,7 @@ Result<> IRBuilder::visitCatch(Name tag) { // Note that we have a pop to help determine later whether we need to run // the fixup for pops within blocks. scopeStack[0].notePop(); - push(builder.makePop(params)); + pushSynthetic(builder.makePop(params)); } return Ok{}; diff --git a/test/lit/branch-hinting-stack-ir.wast b/test/lit/branch-hinting-stack-ir.wast new file mode 100644 index 00000000000..b658a0ffeda --- /dev/null +++ b/test/lit/branch-hinting-stack-ir.wast @@ -0,0 +1,65 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Enable StackIR opts with --shrink-level=1, and verify we can roundtrip an +;; annotation on stacky code. + +;; RUN: wasm-opt -all --shrink-level=1 --roundtrip %s -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (func $empty-if (type $0) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $empty-if + (local $1 i32) + ;; Several stack IR opts work here, leading to the if arms being empty (nops + ;; removed) and the local.set/get vanishing, leaving stacky code like this: + ;; + ;; i32.const 0 ;; read by the if, past the other const and drop + ;; i32.const 1 + ;; drop + ;; if + ;; else + ;; end + ;; + ;; As a result we have a segment before us that was heavily modified (with the + ;; local.set), and the if body is empty. This should not cause an error when + ;; computing the if's binary location for the hint, and the hint should + ;; remain. + (local.set $1 + (i32.const 0) + ) + (drop + (i32.const 1) + ) + (@metadata.code.branch_hint "\00") + (if + (local.get $1) + (then + (nop) + ) + (else + (nop) + ) + ) + ) +) + diff --git a/test/passes/fannkuch3_manyopts_dwarf.bin.txt b/test/passes/fannkuch3_manyopts_dwarf.bin.txt index 6feceabfffc..78fd4ed1193 100644 --- a/test/passes/fannkuch3_manyopts_dwarf.bin.txt +++ b/test/passes/fannkuch3_manyopts_dwarf.bin.txt @@ -4902,6 +4902,7 @@ file_names[ 4]: ;; code offset: 0xaf (local.get $2) ) + ;; code offset: 0xbd (br_if $label1 (block (result i32) (local.set $scratch @@ -4918,7 +4919,6 @@ file_names[ 4]: ;; code offset: 0xb9 (local.get $1) ) - ;; code offset: 0xbd (local.get $scratch) ) ) @@ -5428,6 +5428,7 @@ file_names[ 4]: ;; code offset: 0x234 (local.get $2) ) + ;; code offset: 0x242 (br_if $label7 (block (result i32) (local.set $scratch_18 @@ -5444,7 +5445,6 @@ file_names[ 4]: ;; code offset: 0x23e (local.get $1) ) - ;; code offset: 0x242 (local.get $scratch_18) ) ) @@ -6252,6 +6252,7 @@ file_names[ 4]: ;; code offset: 0x4a7 (local.get $2) ) + ;; code offset: 0x4b5 (br_if $label3 (block (result i32) (local.set $scratch @@ -6268,7 +6269,6 @@ file_names[ 4]: ;; code offset: 0x4b1 (local.get $0) ) - ;; code offset: 0x4b5 (local.get $scratch) ) ) @@ -6515,6 +6515,7 @@ file_names[ 4]: ;; code offset: 0x565 (local.get $2) ) + ;; code offset: 0x573 (br_if $label7 (block (result i32) (local.set $scratch_10 @@ -6531,7 +6532,6 @@ file_names[ 4]: ;; code offset: 0x56f (local.get $0) ) - ;; code offset: 0x573 (local.get $scratch_10) ) ) From a1fa20534ae97d1a7ad11911a011eca2fd6f9862 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Jun 2025 16:56:54 -0700 Subject: [PATCH 540/622] OptimizeInstructions: Fix merging of exact cast + ref.as_non_null (#7646) We must not forget exactness there. --- src/passes/OptimizeInstructions.cpp | 4 ++-- .../passes/optimize-instructions-exact.wast | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 6e7a3833ddd..0b1280a45d1 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1759,7 +1759,7 @@ struct OptimizeInstructions } } if (canOptimize) { - cast->type = Type(cast->type.getHeapType(), NonNullable); + cast->type = cast->type.with(NonNullable); } } } @@ -2583,7 +2583,7 @@ struct OptimizeInstructions // The cast cannot be non-nullable, or we would have handled this right // above by just removing the ref.as, since it would not be needed. assert(!cast->type.isNonNullable()); - cast->type = Type(cast->type.getHeapType(), NonNullable); + cast->type = cast->type.with(NonNullable); replaceCurrent(cast); } } diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast index bd25799cbb3..cda2210f02b 100644 --- a/test/lit/passes/optimize-instructions-exact.wast +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -6,7 +6,7 @@ ;; CHECK: (type $foo (sub (struct))) (type $foo (sub (struct))) - ;; CHECK: (func $ref-cast-exact-fallthrough (type $1) (param $exact (ref (exact $foo))) (result (ref $foo)) + ;; CHECK: (func $ref-cast-exact-fallthrough (type $2) (param $exact (ref (exact $foo))) (result (ref $foo)) ;; CHECK-NEXT: (local $inexact (ref $foo)) ;; CHECK-NEXT: (local $2 (ref (exact $foo))) ;; CHECK-NEXT: (drop @@ -29,7 +29,7 @@ ) ) - ;; CHECK: (func $prefer-exactness (type $2) (param $exact-null (ref null (exact $foo))) (result (ref $foo)) + ;; CHECK: (func $prefer-exactness (type $3) (param $exact-null (ref null (exact $foo))) (result (ref $foo)) ;; CHECK-NEXT: (local $inexact-nn (ref $foo)) ;; CHECK-NEXT: (local $inexact-null (ref null $foo)) ;; CHECK-NEXT: (local $3 (ref null (exact $foo))) @@ -64,7 +64,7 @@ ) ) - ;; CHECK: (func $combine-non-null (type $3) (param $foo (ref null $foo)) (result (ref (exact $foo))) + ;; CHECK: (func $combine-non-null (type $1) (param $foo (ref null $foo)) (result (ref (exact $foo))) ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) ;; CHECK-NEXT: (local.get $foo) ;; CHECK-NEXT: ) @@ -78,4 +78,18 @@ ) ) ) + + ;; CHECK: (func $combine-non-null-reverse (type $1) (param $foo (ref null $foo)) (result (ref (exact $foo))) + ;; CHECK-NEXT: (ref.cast (ref (exact $foo)) + ;; CHECK-NEXT: (local.get $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $combine-non-null-reverse (param $foo (ref null $foo)) (result (ref (exact $foo))) + ;; As above, but flipped. + (ref.as_non_null + (ref.cast (ref null (exact $foo)) + (local.get $foo) + ) + ) + ) ) From 2b989ae0e1d724c532ce7022b2a7085347b25d82 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 10 Jun 2025 08:59:23 +0200 Subject: [PATCH 541/622] [custom-descriptors] Update struct.new parsing and validation (#7643) When allocating structs that have custom descriptors, struct.new and struct.new_default must take an additional operand for the descriptor. --------- Co-authored-by: Alon Zakai --- scripts/test/fuzzing.py | 3 +- src/ir/child-typer.h | 17 +- src/ir/cost.h | 1 + src/wasm-builder.h | 15 +- src/wasm-delegations-fields.def | 1 + src/wasm.h | 2 + src/wasm/wasm-ir-builder.cpp | 8 +- src/wasm/wasm-validator.cpp | 12 ++ test/lit/basic/custom-descriptors.wast | 175 +++++++++++++----- .../{ref.get_cast.wast => ref.get_desc.wast} | 0 test/spec/struct.new-desc.wast | 123 ++++++++++++ 11 files changed, 294 insertions(+), 63 deletions(-) rename test/spec/{ref.get_cast.wast => ref.get_desc.wast} (100%) create mode 100644 test/spec/struct.new-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 164ff2cb915..4a880fc78e6 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -114,8 +114,9 @@ # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', 'br_on_cast_desc.wast', - 'ref.get_cast.wast', + 'ref.get_desc.wast', 'ref.cast_desc.wast', + 'struct.new-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 0436da48420..b922e286cc5 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -934,16 +934,19 @@ template struct ChildTyper : OverriddenVisitor { } void visitStructNew(StructNew* curr) { - if (curr->isWithDefault()) { - return; - } if (self().skipUnreachable() && !curr->type.isRef()) { return; } - const auto& fields = curr->type.getHeapType().getStruct().fields; - assert(fields.size() == curr->operands.size()); - for (size_t i = 0; i < fields.size(); ++i) { - note(&curr->operands[i], fields[i].type); + if (!curr->isWithDefault()) { + const auto& fields = curr->type.getHeapType().getStruct().fields; + assert(fields.size() == curr->operands.size()); + for (size_t i = 0; i < fields.size(); ++i) { + note(&curr->operands[i], fields[i].type); + } + } + auto desc = curr->type.getHeapType().getDescriptorType(); + if (desc) { + note(&curr->descriptor, Type(*desc, NonNullable, Exact)); } } diff --git a/src/ir/cost.h b/src/ir/cost.h index 5b4ad6dbc12..014bbcd0bdc 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -693,6 +693,7 @@ struct CostAnalyzer : public OverriddenVisitor { for (auto* child : curr->operands) { ret += visit(child); } + ret += maybeVisit(curr->descriptor); return ret; } CostType visitStructGet(StructGet* curr) { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 70ec6a5f2fd..d4eca47ad3f 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -921,23 +921,32 @@ class Builder { return ret; } StructNew* makeStructNew(HeapType type, - std::initializer_list args) { + std::initializer_list args, + Expression* descriptor = nullptr) { auto* ret = wasm.allocator.alloc(); ret->operands.set(args); + ret->descriptor = descriptor; ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } - StructNew* makeStructNew(HeapType type, ExpressionList&& args) { + StructNew* makeStructNew(HeapType type, + ExpressionList&& args, + Expression* descriptor = nullptr) { auto* ret = wasm.allocator.alloc(); ret->operands = std::move(args); + ret->descriptor = descriptor; ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; } - template StructNew* makeStructNew(HeapType type, const T& args) { + template + StructNew* makeStructNew(HeapType type, + const T& args, + Expression* descriptor = nullptr) { auto* ret = wasm.allocator.alloc(); ret->operands.set(args); + ret->descriptor = descriptor; ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index b9201d4d09d..7128a100b20 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -667,6 +667,7 @@ DELEGATE_FIELD_CHILD(BrOn, ref) DELEGATE_FIELD_CASE_END(BrOn) DELEGATE_FIELD_CASE_START(StructNew) +DELEGATE_FIELD_OPTIONAL_CHILD(StructNew, descriptor) DELEGATE_FIELD_CHILD_VECTOR(StructNew, operands) DELEGATE_FIELD_CASE_END(StructNew) diff --git a/src/wasm.h b/src/wasm.h index 2120db589aa..f94990e9594 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1672,6 +1672,8 @@ class StructNew : public SpecificExpression { // case, and binaryen doesn't guarantee roundtripping binaries anyhow. ExpressionList operands; + Expression* descriptor = nullptr; + bool isWithDefault() { return operands.empty(); } void finalize(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 62a897517df..402fa448d9a 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2214,15 +2214,17 @@ Result<> IRBuilder::makeBrOn( Result<> IRBuilder::makeStructNew(HeapType type) { StructNew curr(wasm.allocator); curr.type = Type(type, NonNullable, Exact); - // Differentiate from struct.new_default with a non-empty expression list. curr.operands.resize(type.getStruct().fields.size()); CHECK_ERR(visitStructNew(&curr)); - push(builder.makeStructNew(type, std::move(curr.operands))); + push(builder.makeStructNew(type, std::move(curr.operands), curr.descriptor)); return Ok{}; } Result<> IRBuilder::makeStructNewDefault(HeapType type) { - push(builder.makeStructNew(type, {})); + StructNew curr(wasm.allocator); + curr.type = Type(type, NonNullable, Exact); + CHECK_ERR(visitStructNew(&curr)); + push(builder.makeStructNew(type, {}, curr.descriptor)); return Ok{}; } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 2a3cb45a91e..d39e6cda7ff 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3147,6 +3147,18 @@ void FunctionValidator::visitStructNew(StructNew* curr) { } } } + + auto descType = curr->type.getHeapType().getDescriptorType(); + if (!descType) { + shouldBeFalse(curr->descriptor, + curr, + "struct.new of type without descriptor should lack one"); + } else { + shouldBeSubType(curr->descriptor->type, + Type(*descType, Nullable, Exact), + curr, + "struct.new descriptor operand should have proper type"); + } } void FunctionValidator::visitStructGet(StructGet* curr) { diff --git a/test/lit/basic/custom-descriptors.wast b/test/lit/basic/custom-descriptors.wast index b8ced993e3c..f1d302dbad5 100644 --- a/test/lit/basic/custom-descriptors.wast +++ b/test/lit/basic/custom-descriptors.wast @@ -27,21 +27,37 @@ (rec ;; CHECK-TEXT: (type $3 (func (param anyref) (result anyref))) - ;; CHECK-TEXT: (type $4 (func (param anyref (ref null $describing)))) + ;; CHECK-TEXT: (rec + ;; CHECK-TEXT-NEXT: (type $pair (descriptor $pair.desc (struct (field i32) (field i64)))) + ;; CHECK-BIN: (type $3 (func (param anyref) (result anyref))) + + ;; CHECK-BIN: (rec + ;; CHECK-BIN-NEXT: (type $pair (descriptor $pair.desc (struct (field i32) (field i64)))) + (type $pair (descriptor $pair.desc (struct (field i32 i64)))) + ;; CHECK-TEXT: (type $pair.desc (describes $pair (struct))) + ;; CHECK-BIN: (type $pair.desc (describes $pair (struct))) + (type $pair.desc (describes $pair (struct))) + ) + + (rec + + ;; CHECK-TEXT: (type $6 (func (param anyref (ref null $describing)))) + + ;; CHECK-TEXT: (type $7 (func (result anyref))) - ;; CHECK-TEXT: (type $5 (func (result anyref))) + ;; CHECK-TEXT: (type $8 (func (param anyref (ref null (exact $middle))) (result (ref null (exact $described))))) - ;; CHECK-TEXT: (type $6 (func (param anyref (ref null (exact $middle))) (result (ref null (exact $described))))) + ;; CHECK-TEXT: (type $9 (func (result (ref (exact $pair))))) ;; CHECK-TEXT: (rec ;; CHECK-TEXT-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) - ;; CHECK-BIN: (type $3 (func (param anyref) (result anyref))) + ;; CHECK-BIN: (type $6 (func (param anyref (ref null $describing)))) - ;; CHECK-BIN: (type $4 (func (param anyref (ref null $describing)))) + ;; CHECK-BIN: (type $7 (func (result anyref))) - ;; CHECK-BIN: (type $5 (func (result anyref))) + ;; CHECK-BIN: (type $8 (func (param anyref (ref null (exact $middle))) (result (ref null (exact $described))))) - ;; CHECK-BIN: (type $6 (func (param anyref (ref null (exact $middle))) (result (ref null (exact $described))))) + ;; CHECK-BIN: (type $9 (func (result (ref (exact $pair))))) ;; CHECK-BIN: (rec ;; CHECK-BIN-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) @@ -52,18 +68,19 @@ ) - ;; CHECK-TEXT: (type $9 (func (param (ref null $described) (ref null (exact $middle))))) - ;; CHECK-TEXT: (type $10 (func)) + ;; CHECK-TEXT: (type $12 (func (param (ref null $described) (ref null (exact $middle))))) + + ;; CHECK-TEXT: (type $13 (func)) - ;; CHECK-TEXT: (type $11 (func (param (ref any) (ref null $middle)) (result (ref null $described)))) + ;; CHECK-TEXT: (type $14 (func (param (ref any) (ref null $middle)) (result (ref null $described)))) ;; CHECK-TEXT: (global $g (ref null $described) (ref.null none)) - ;; CHECK-BIN: (type $9 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-BIN: (type $12 (func (param (ref null $described) (ref null (exact $middle))))) - ;; CHECK-BIN: (type $10 (func)) + ;; CHECK-BIN: (type $13 (func)) - ;; CHECK-BIN: (type $11 (func (param (ref any) (ref null $middle)) (result (ref null $described)))) + ;; CHECK-BIN: (type $14 (func (param (ref any) (ref null $middle)) (result (ref null $described)))) ;; CHECK-BIN: (global $g (ref null $described) (ref.null none)) (global $g (ref null $described) (ref.null none)) @@ -71,7 +88,7 @@ ;; CHECK-BIN: (global $shared (ref null $shared-describing) (ref.null (shared none))) (global $shared (ref null $shared-describing) (ref.null (shared none))) - ;; CHECK-TEXT: (func $ref-get-desc (type $9) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-TEXT: (func $ref-get-desc (type $12) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l1 (result (ref $middle)) ;; CHECK-TEXT-NEXT: (ref.get_desc $described @@ -87,7 +104,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-get-desc (type $9) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-BIN: (func $ref-get-desc (type $12) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result (ref $middle)) ;; CHECK-BIN-NEXT: (ref.get_desc $described @@ -120,7 +137,7 @@ ) ) - ;; CHECK-TEXT: (func $ref-get-desc-null (type $10) + ;; CHECK-TEXT: (func $ref-get-desc-null (type $13) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefGetDesc we can't emit) ;; CHECK-TEXT-NEXT: (drop @@ -130,7 +147,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-get-desc-null (type $10) + ;; CHECK-BIN: (func $ref-get-desc-null (type $13) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: ) @@ -146,7 +163,7 @@ ) ) - ;; CHECK-TEXT: (func $br-on-cast-desc (type $4) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-TEXT: (func $br-on-cast-desc (type $6) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l (result (ref null $middle)) ;; CHECK-TEXT-NEXT: (drop @@ -159,7 +176,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-desc (type $4) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-BIN: (func $br-on-cast-desc (type $6) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block (result (ref null $middle)) ;; CHECK-BIN-NEXT: (drop @@ -184,7 +201,7 @@ ) ) - ;; CHECK-TEXT: (func $br-on-cast-desc-fail (type $4) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-TEXT: (func $br-on-cast-desc-fail (type $6) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l (result anyref) ;; CHECK-TEXT-NEXT: (br_on_cast_desc_fail $l anyref (ref null $middle) @@ -194,7 +211,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $br-on-cast-desc-fail (type $4) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-BIN: (func $br-on-cast-desc-fail (type $6) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block (result anyref) ;; CHECK-BIN-NEXT: (br_on_cast_desc_fail $block anyref (ref null $middle) @@ -273,7 +290,7 @@ ) ) - ;; CHECK-TEXT: (func $ref-cast-desc-null-unreachable (type $5) (result anyref) + ;; CHECK-TEXT: (func $ref-cast-desc-null-unreachable (type $7) (result anyref) ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefCast we can't emit) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (unreachable) @@ -284,7 +301,7 @@ ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast-desc-null-unreachable (type $5) (result anyref) + ;; CHECK-BIN: (func $ref-cast-desc-null-unreachable (type $7) (result anyref) ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $ref-cast-desc-null-unreachable (result anyref) @@ -319,13 +336,13 @@ ) ) - ;; CHECK-TEXT: (func $ref-cast-desc-null-exact (type $6) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; CHECK-TEXT: (func $ref-cast-desc-null-exact (type $8) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) ;; CHECK-TEXT-NEXT: (ref.cast_desc (ref null (exact $described)) ;; CHECK-TEXT-NEXT: (local.get $any) ;; CHECK-TEXT-NEXT: (local.get $middle) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast-desc-null-exact (type $6) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; CHECK-BIN: (func $ref-cast-desc-null-exact (type $8) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) ;; CHECK-BIN-NEXT: (ref.cast_desc (ref null (exact $described)) ;; CHECK-BIN-NEXT: (local.get $any) ;; CHECK-BIN-NEXT: (local.get $middle) @@ -339,7 +356,7 @@ ) ) - ;; CHECK-TEXT: (func $ref-cast-desc-nn-unreachable (type $5) (result anyref) + ;; CHECK-TEXT: (func $ref-cast-desc-nn-unreachable (type $7) (result anyref) ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefCast we can't emit) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (unreachable) @@ -350,7 +367,7 @@ ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast-desc-nn-unreachable (type $5) (result anyref) + ;; CHECK-BIN: (func $ref-cast-desc-nn-unreachable (type $7) (result anyref) ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $ref-cast-desc-nn-unreachable (result anyref) @@ -385,13 +402,13 @@ ) ) - ;; CHECK-TEXT: (func $ref-cast-desc-nn-exact (type $6) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; CHECK-TEXT: (func $ref-cast-desc-nn-exact (type $8) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) ;; CHECK-TEXT-NEXT: (ref.cast_desc (ref (exact $described)) ;; CHECK-TEXT-NEXT: (local.get $any) ;; CHECK-TEXT-NEXT: (local.get $middle) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast-desc-nn-exact (type $6) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) + ;; CHECK-BIN: (func $ref-cast-desc-nn-exact (type $8) (param $any anyref) (param $middle (ref null (exact $middle))) (result (ref null (exact $described))) ;; CHECK-BIN-NEXT: (ref.cast_desc (ref (exact $described)) ;; CHECK-BIN-NEXT: (local.get $any) ;; CHECK-BIN-NEXT: (local.get $middle) @@ -405,13 +422,13 @@ ) ) - ;; CHECK-TEXT: (func $ref-cast-desc-nn-to-null (type $11) (param $any-nn (ref any)) (param $middle (ref null $middle)) (result (ref null $described)) + ;; CHECK-TEXT: (func $ref-cast-desc-nn-to-null (type $14) (param $any-nn (ref any)) (param $middle (ref null $middle)) (result (ref null $described)) ;; CHECK-TEXT-NEXT: (ref.cast_desc (ref $described) ;; CHECK-TEXT-NEXT: (local.get $any-nn) ;; CHECK-TEXT-NEXT: (local.get $middle) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast-desc-nn-to-null (type $11) (param $any-nn (ref any)) (param $middle (ref null $middle)) (result (ref null $described)) + ;; CHECK-BIN: (func $ref-cast-desc-nn-to-null (type $14) (param $any-nn (ref any)) (param $middle (ref null $middle)) (result (ref null $described)) ;; CHECK-BIN-NEXT: (ref.cast_desc (ref $described) ;; CHECK-BIN-NEXT: (local.get $any-nn) ;; CHECK-BIN-NEXT: (local.get $middle) @@ -423,6 +440,45 @@ (local.get $middle) ) ) + + ;; CHECK-TEXT: (func $struct-new (type $9) (result (ref (exact $pair))) + ;; CHECK-TEXT-NEXT: (struct.new $pair + ;; CHECK-TEXT-NEXT: (i32.const 0) + ;; CHECK-TEXT-NEXT: (i64.const 1) + ;; CHECK-TEXT-NEXT: (struct.new_default $pair.desc) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $struct-new (type $9) (result (ref (exact $pair))) + ;; CHECK-BIN-NEXT: (struct.new $pair + ;; CHECK-BIN-NEXT: (i32.const 0) + ;; CHECK-BIN-NEXT: (i64.const 1) + ;; CHECK-BIN-NEXT: (struct.new_default $pair.desc) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $struct-new (result (ref (exact $pair))) + (struct.new $pair + (i32.const 0) + (i64.const 1) + (struct.new $pair.desc) + ) + ) + + ;; CHECK-TEXT: (func $struct-new-default (type $9) (result (ref (exact $pair))) + ;; CHECK-TEXT-NEXT: (struct.new_default $pair + ;; CHECK-TEXT-NEXT: (struct.new_default $pair.desc) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $struct-new-default (type $9) (result (ref (exact $pair))) + ;; CHECK-BIN-NEXT: (struct.new_default $pair + ;; CHECK-BIN-NEXT: (struct.new_default $pair.desc) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $struct-new-default (result (ref (exact $pair))) + (struct.new_default $pair + (struct.new $pair.desc) + ) + ) + ) ;; CHECK-BIN-NODEBUG: (rec ;; CHECK-BIN-NODEBUG-NEXT: (type $0 (descriptor $1 (struct))) @@ -433,28 +489,35 @@ ;; CHECK-BIN-NODEBUG: (type $3 (func (param anyref) (result anyref))) -;; CHECK-BIN-NODEBUG: (type $4 (func (param anyref (ref null $2)))) +;; CHECK-BIN-NODEBUG: (rec +;; CHECK-BIN-NODEBUG-NEXT: (type $4 (descriptor $5 (struct (field i32) (field i64)))) + +;; CHECK-BIN-NODEBUG: (type $5 (describes $4 (struct))) + +;; CHECK-BIN-NODEBUG: (type $6 (func (param anyref (ref null $2)))) + +;; CHECK-BIN-NODEBUG: (type $7 (func (result anyref))) -;; CHECK-BIN-NODEBUG: (type $5 (func (result anyref))) +;; CHECK-BIN-NODEBUG: (type $8 (func (param anyref (ref null (exact $1))) (result (ref null (exact $0))))) -;; CHECK-BIN-NODEBUG: (type $6 (func (param anyref (ref null (exact $1))) (result (ref null (exact $0))))) +;; CHECK-BIN-NODEBUG: (type $9 (func (result (ref (exact $4))))) ;; CHECK-BIN-NODEBUG: (rec -;; CHECK-BIN-NODEBUG-NEXT: (type $7 (shared (descriptor $8 (struct)))) +;; CHECK-BIN-NODEBUG-NEXT: (type $10 (shared (descriptor $11 (struct)))) -;; CHECK-BIN-NODEBUG: (type $8 (shared (describes $7 (struct)))) +;; CHECK-BIN-NODEBUG: (type $11 (shared (describes $10 (struct)))) -;; CHECK-BIN-NODEBUG: (type $9 (func (param (ref null $0) (ref null (exact $1))))) +;; CHECK-BIN-NODEBUG: (type $12 (func (param (ref null $0) (ref null (exact $1))))) -;; CHECK-BIN-NODEBUG: (type $10 (func)) +;; CHECK-BIN-NODEBUG: (type $13 (func)) -;; CHECK-BIN-NODEBUG: (type $11 (func (param (ref any) (ref null $1)) (result (ref null $0)))) +;; CHECK-BIN-NODEBUG: (type $14 (func (param (ref any) (ref null $1)) (result (ref null $0)))) ;; CHECK-BIN-NODEBUG: (global $global$0 (ref null $0) (ref.null none)) -;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $8) (ref.null (shared none))) +;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $11) (ref.null (shared none))) -;; CHECK-BIN-NODEBUG: (func $0 (type $9) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) +;; CHECK-BIN-NODEBUG: (func $0 (type $12) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result (ref $1)) ;; CHECK-BIN-NODEBUG-NEXT: (ref.get_desc $0 @@ -471,7 +534,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $1 (type $10) +;; CHECK-BIN-NODEBUG: (func $1 (type $13) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -480,7 +543,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $2 (type $4) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG: (func $2 (type $6) (param $0 anyref) (param $1 (ref null $2)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (drop @@ -494,7 +557,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $3 (type $4) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG: (func $3 (type $6) (param $0 anyref) (param $1 (ref null $2)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_desc_fail $block anyref (ref null $1) @@ -525,7 +588,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $6 (type $5) (result anyref) +;; CHECK-BIN-NODEBUG: (func $6 (type $7) (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -539,14 +602,14 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $8 (type $6) (param $0 anyref) (param $1 (ref null (exact $1))) (result (ref null (exact $0))) +;; CHECK-BIN-NODEBUG: (func $8 (type $8) (param $0 anyref) (param $1 (ref null (exact $1))) (result (ref null (exact $0))) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast_desc (ref null (exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $9 (type $5) (result anyref) +;; CHECK-BIN-NODEBUG: (func $9 (type $7) (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -560,16 +623,30 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $11 (type $6) (param $0 anyref) (param $1 (ref null (exact $1))) (result (ref null (exact $0))) +;; CHECK-BIN-NODEBUG: (func $11 (type $8) (param $0 anyref) (param $1 (ref null (exact $1))) (result (ref null (exact $0))) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast_desc (ref (exact $0)) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $12 (type $11) (param $0 (ref any)) (param $1 (ref null $1)) (result (ref null $0)) +;; CHECK-BIN-NODEBUG: (func $12 (type $14) (param $0 (ref any)) (param $1 (ref null $1)) (result (ref null $0)) ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast_desc (ref $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $13 (type $9) (result (ref (exact $4))) +;; CHECK-BIN-NODEBUG-NEXT: (struct.new $4 +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) +;; CHECK-BIN-NODEBUG-NEXT: (i64.const 1) +;; CHECK-BIN-NODEBUG-NEXT: (struct.new_default $5) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $14 (type $9) (result (ref (exact $4))) +;; CHECK-BIN-NODEBUG-NEXT: (struct.new_default $4 +;; CHECK-BIN-NODEBUG-NEXT: (struct.new_default $5) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/spec/ref.get_cast.wast b/test/spec/ref.get_desc.wast similarity index 100% rename from test/spec/ref.get_cast.wast rename to test/spec/ref.get_desc.wast diff --git a/test/spec/struct.new-desc.wast b/test/spec/struct.new-desc.wast new file mode 100644 index 00000000000..653a6404cbe --- /dev/null +++ b/test/spec/struct.new-desc.wast @@ -0,0 +1,123 @@ +(module + (rec + (type $pair (descriptor $pair.desc (struct (field i32 i64)))) + (type $pair.desc (describes $pair (struct))) + ) + (func $struct.new (result (ref (exact $pair))) + (local $desc (ref null (exact $pair.desc))) + (struct.new $pair + (i32.const 0) + (i64.const 1) + (local.get $desc) + ) + ) + (func $struct.new_default (result (ref (exact $pair))) + (local $desc (ref null (exact $pair.desc))) + (struct.new_default $pair + (local.get $desc) + ) + ) +) + +(assert_invalid + (module + (rec + (type $pair (descriptor $pair.desc (struct (field i32 i64)))) + (type $pair.desc (describes $pair (struct))) + ) + (func (result (ref (exact $pair))) + ;; The descriptor operand is missing. + (struct.new $pair + (i32.const 0) + (i64.const 1) + ) + ) + ) + "popping from empty stack" +) + +(assert_invalid + (module + (rec + (type $pair (descriptor $pair.desc (struct (field i32 i64)))) + (type $pair.desc (describes $pair (struct))) + ) + (func (result (ref (exact $pair))) + The descriptor operand is missing. + (struct.new_default $pair) + ) + ) + "popping from empty stack" +) + +(assert_invalid + (module + (rec + (type $pair (descriptor $pair.desc (struct (field i32 i64)))) + (type $pair.desc (describes $pair (struct))) + ) + (func (result (ref (exact $pair))) + ;; The descriptor needs to be exact. + (local $desc (ref null $pair.desc)) + (struct.new $pair + (i32.const 0) + (i64.const 1) + (local.get $desc) + ) + ) + ) + "struct.new descriptor operand should have proper type" +) + +(assert_invalid + (module + (rec + (type $pair (descriptor $pair.desc (struct (field i32 i64)))) + (type $pair.desc (describes $pair (struct))) + ) + (func (result (ref (exact $pair))) + ;; The descriptor needs to be exact. + (local $desc (ref null $pair.desc)) + (struct.new_default $pair + (local.get $desc) + ) + ) + ) + "struct.new descriptor operand should have proper type" +) + +(assert_invalid + (module + (rec + (type $pair (descriptor $pair.desc (struct (field i32 i64)))) + (type $pair.desc (describes $pair (struct))) + (type $other (struct)) + ) + (func (result (ref (exact $pair))) + ;; The descriptor has the wrong heap type. + (struct.new $pair + (i32.const 0) + (i64.const 1) + (struct.new $other) + ) + ) + ) + "struct.new descriptor operand should have proper type" +) + +(assert_invalid + (module + (rec + (type $pair (descriptor $pair.desc (struct (field i32 i64)))) + (type $pair.desc (describes $pair (struct))) + (type $other (struct)) + ) + (func (result (ref (exact $pair))) + ;; The descriptor has the wrong heap type. + (struct.new_default $pair + (struct.new $other) + ) + ) + ) + "struct.new descriptor operand should have proper type" +) From ab14353dc981327786712bb96521a156b81cd2f5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 12 Jun 2025 11:18:10 -0700 Subject: [PATCH 542/622] Precompute: Skip RefGetDesc for now (#7651) This allows users to optimize custom descriptors content without erroring, unblocking them for now. --- src/passes/Precompute.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index f101cc525b6..6b307730344 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -240,6 +240,12 @@ class PrecomputingExpressionRunner // string.encode_wtf16_array anyhow.) return Flow(NONCONSTANT_FLOW); } + + Flow visitRefGetDesc(RefGetDesc* curr) { + // TODO: Implement this. For now, return nonconstant so that we skip it and + // do not error. + return Flow(NONCONSTANT_FLOW); + } }; struct Precompute From ba0c818db8daff362e47f857b46e2a573fb83282 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 12 Jun 2025 11:18:31 -0700 Subject: [PATCH 543/622] [CustomDescriptors] RemoveUnusedModuleElements: Fix StructNew optimization of descriptor (#7650) We have very special optimizations for StructNew in that pass: we see if they have reads (struct.get) and remove them if not. That explicit handling of StructNew was not aware of the new descriptor field, and so it could error, removing things that were still needed. --- scripts/test/fuzzing.py | 1 + src/passes/RemoveUnusedModuleElements.cpp | 7 ++++ ...used-module-elements-refs-descriptors.wast | 37 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 test/lit/passes/remove-unused-module-elements-refs-descriptors.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 4a880fc78e6..cea7fa8e5bb 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -112,6 +112,7 @@ 'unsubtyping-stack-switching.wast', 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors + 'remove-unused-module-elements-refs-descriptors.wast', 'custom-descriptors.wast', 'br_on_cast_desc.wast', 'ref.get_desc.wast', diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index be924f35221..cbb1180231e 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -482,6 +482,13 @@ struct Analyzer { } auto* new_ = curr->cast(); + + // Use the descriptor right now, normally. (We only have special + // optimization for struct.new operands, below.) + if (new_->descriptor) { + use(new_->descriptor); + } + auto type = new_->type.getHeapType(); for (Index i = 0; i < new_->operands.size(); i++) { diff --git a/test/lit/passes/remove-unused-module-elements-refs-descriptors.wast b/test/lit/passes/remove-unused-module-elements-refs-descriptors.wast new file mode 100644 index 00000000000..b894f4ec2f4 --- /dev/null +++ b/test/lit/passes/remove-unused-module-elements-refs-descriptors.wast @@ -0,0 +1,37 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements --closed-world -all -S -o - | filecheck %s + +;; The global.get of $global is the descriptor, not a field of the struct. +;; We should not try to optimize it away and error. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $B (sub (descriptor $A (struct)))) + (type $B (sub (descriptor $A (struct)))) + ;; CHECK: (type $A (sub (describes $B (struct)))) + (type $A (sub (describes $B (struct)))) + ) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (global $global (ref (exact $A)) (struct.new_default $A)) + (global $global (ref (exact $A)) (struct.new $A)) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $B + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $export (export "export") + (drop + (struct.new $B + (global.get $global) + ) + ) + ) +) + From 21a915f4b7e85cdea685074ca998b80fd305024f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 12 Jun 2025 15:28:25 -0700 Subject: [PATCH 544/622] [CustomDescriptors] TypeUpdating: Sort descriptors properly when sorting types (#7649) The spec constrains the order of described/descriptor type pairs. --- scripts/test/fuzzing.py | 1 + src/ir/type-updating.cpp | 100 ++++++++------ .../remove-unused-types-descriptors.wast | 126 ++++++++++++++++++ 3 files changed, 184 insertions(+), 43 deletions(-) create mode 100644 test/lit/passes/remove-unused-types-descriptors.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index cea7fa8e5bb..6968fb8e4b8 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -118,6 +118,7 @@ 'ref.get_desc.wast', 'ref.cast_desc.wast', 'struct.new-desc.wast', + 'remove-unused-types-descriptors.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index a05b316823c..87edc69f7c4 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -48,59 +48,73 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( std::unordered_set additionalSet(additionalPrivateTypes.begin(), additionalPrivateTypes.end()); - std::vector>> privateSupertypes; - privateSupertypes.reserve(typeInfo.size()); + // Check if a type is private, given the info for it. + auto isPublicGivenInfo = [&](HeapType type, auto& info) { + return info.visibility != ModuleUtils::Visibility::Private && + !additionalSet.count(type); + }; + + // Check if a type is private, looking for its info (if there is none, it is + // not private). + auto isPublic = [&](HeapType type) { + auto it = typeInfo.find(type); + if (it == typeInfo.end()) { + return false; + } + return isPublicGivenInfo(type, it->second); + }; + + // For each type, note all the predecessors it must have, i.e., that must + // appear before it. That includes supertypes and described types. + std::vector>> privatePreds; + privatePreds.reserve(typeInfo.size()); for (auto& [type, info] : typeInfo) { - if (info.visibility != ModuleUtils::Visibility::Private && - !additionalSet.count(type)) { + if (isPublicGivenInfo(type, info)) { continue; } - privateSupertypes.push_back({type, {}}); + privatePreds.push_back({type, {}}); - if (auto super = getDeclaredSuperType(type)) { - auto it = typeInfo.find(*super); - // Record the supertype only if it is among the private types. - if ((it != typeInfo.end() && - it->second.visibility == ModuleUtils::Visibility::Private) || - additionalSet.count(*super)) { - privateSupertypes.back().second.push_back(*super); - } + // Check for a (private) supertype. + if (auto super = getDeclaredSuperType(type); super && !isPublic(*super)) { + privatePreds.back().second.push_back(*super); + } + + // Check for a (private) described type. + if (auto desc = type.getDescribedType()) { + // It is not possible for a a described type to be public while its + // descriptor is private, or vice versa. + assert(!isPublic(*desc)); + privatePreds.back().second.push_back(*desc); } } - // Topological sort to have subtypes first. This is the opposite of the - // order we need, so the comparison is the opposite of what we ultimately - // want. std::vector sorted; if (wasm.typeIndices.empty()) { - sorted = TopologicalSort::sortOf(privateSupertypes.begin(), - privateSupertypes.end()); + sorted = TopologicalSort::sortOf(privatePreds.begin(), privatePreds.end()); } else { - sorted = - TopologicalSort::minSortOf(privateSupertypes.begin(), - privateSupertypes.end(), - [&](Index a, Index b) { - auto typeA = privateSupertypes[a].first; - auto typeB = privateSupertypes[b].first; - // Preserve type order. - auto itA = wasm.typeIndices.find(typeA); - auto itB = wasm.typeIndices.find(typeB); - bool hasA = itA != wasm.typeIndices.end(); - bool hasB = itB != wasm.typeIndices.end(); - if (hasA != hasB) { - // Types with preserved indices must be - // sorted before (after in this reversed - // comparison) types without indices to - // maintain transitivity. - return !hasA; - } - if (hasA && *itA != *itB) { - return !(itA->second < itB->second); - } - // Break ties by the arbitrary order we - // have collected the types in. - return a > b; - }); + sorted = TopologicalSort::minSortOf( + privatePreds.begin(), privatePreds.end(), [&](Index a, Index b) { + auto typeA = privatePreds[a].first; + auto typeB = privatePreds[b].first; + // Preserve type order. + auto itA = wasm.typeIndices.find(typeA); + auto itB = wasm.typeIndices.find(typeB); + bool hasA = itA != wasm.typeIndices.end(); + bool hasB = itB != wasm.typeIndices.end(); + if (hasA != hasB) { + // Types with preserved indices must be + // sorted before (after in this reversed + // comparison) types without indices to + // maintain transitivity. + return !hasA; + } + if (hasA && *itA != *itB) { + return !(itA->second < itB->second); + } + // Break ties by the arbitrary order we + // have collected the types in. + return a > b; + }); } std::reverse(sorted.begin(), sorted.end()); Index i = 0; diff --git a/test/lit/passes/remove-unused-types-descriptors.wast b/test/lit/passes/remove-unused-types-descriptors.wast new file mode 100644 index 00000000000..00c04b973d6 --- /dev/null +++ b/test/lit/passes/remove-unused-types-descriptors.wast @@ -0,0 +1,126 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --remove-unused-types --closed-world -all -S -o - | filecheck %s + +;; Use described the most, middle next, and describing least. +;; The unused type lets remove-unused-types work and sort the types, so we +;; can test the sort is valid. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described (descriptor $middle (struct))) + (type $described (descriptor $middle (struct))) + ;; CHECK: (type $middle (describes $described (descriptor $describing (struct)))) + (type $middle (describes $described (descriptor $describing (struct)))) + ;; CHECK: (type $describing (describes $middle (struct))) + (type $describing (describes $middle (struct))) + + (type $unused (struct)) + ) + + ;; CHECK: (type $3 (func (param anyref))) + + ;; CHECK: (func $uses (type $3) (param $x anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $described) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $described) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $described) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $middle) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $middle) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $describing) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $uses (param $x anyref) + (drop (ref.cast (ref $described) (local.get $x))) + (drop (ref.cast (ref $described) (local.get $x))) + (drop (ref.cast (ref $described) (local.get $x))) + + (drop (ref.cast (ref $middle) (local.get $x))) + (drop (ref.cast (ref $middle) (local.get $x))) + + (drop (ref.cast (ref $describing) (local.get $x))) + ) +) + +;; As above, but with use counts flipped. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described (descriptor $middle (struct))) + (type $described (descriptor $middle (struct))) + ;; CHECK: (type $middle (describes $described (descriptor $describing (struct)))) + (type $middle (describes $described (descriptor $describing (struct)))) + ;; CHECK: (type $describing (describes $middle (struct))) + (type $describing (describes $middle (struct))) + + (type $unused (struct)) + ) + + ;; CHECK: (type $3 (func (param anyref))) + + ;; CHECK: (func $uses (type $3) (param $x anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $described) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $middle) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $middle) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $describing) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $describing) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $describing) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $uses (param $x anyref) + (drop (ref.cast (ref $described) (local.get $x))) + + (drop (ref.cast (ref $middle) (local.get $x))) + (drop (ref.cast (ref $middle) (local.get $x))) + + (drop (ref.cast (ref $describing) (local.get $x))) + (drop (ref.cast (ref $describing) (local.get $x))) + (drop (ref.cast (ref $describing) (local.get $x))) + ) +) + From 9fed19c931cafb272395a9923d8efc997bbb988e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Fri, 13 Jun 2025 17:22:37 +0200 Subject: [PATCH 545/622] Fix wabt setup script (#7655) On Linux we use "ubuntu" as the platform string when searching for a wabt release tarball and search for a file that ends with "-ubuntu.tar.gz". However wabt release tarballs mention Ubuntu version as well, e.g. the current release name is "wabt-1.0.37-ubuntu-20.04.tar.gz". The current check with `endswith` does not match the current release tarball names. Update the name matching to use the regex `^wabt-.*-ubuntu-.*.tar.gz$`, which matches version numbers after the "ubuntu" part. --- third_party/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/third_party/setup.py b/third_party/setup.py index 8baaa02db30..b10a684e373 100755 --- a/third_party/setup.py +++ b/third_party/setup.py @@ -184,9 +184,10 @@ def wabt_determine_platform(): def wabt_determine_release(platform): + platform_regex = re.compile(r"^wabt-.*-ubuntu-.*.tar.gz$") data = fetch_json('https://api.github.com/repos/WebAssembly/wabt/releases/latest') for asset in data['assets']: - if asset['name'].endswith('-' + platform + '.tar.gz'): + if platform_regex.match(asset['name']): return asset['browser_download_url'] print('Cannot determine release') return '' From c2b7a042890cf48bbd5ca08d0a28ba510d99dedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Fri, 13 Jun 2025 18:44:32 +0200 Subject: [PATCH 546/622] Fix platform regex when installing wabt (#7656) --- third_party/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/setup.py b/third_party/setup.py index b10a684e373..2e4e2c38a7b 100755 --- a/third_party/setup.py +++ b/third_party/setup.py @@ -184,7 +184,7 @@ def wabt_determine_platform(): def wabt_determine_release(platform): - platform_regex = re.compile(r"^wabt-.*-ubuntu-.*.tar.gz$") + platform_regex = re.compile(r"^wabt-.*-%s.*.tar.gz$" % platform) data = fetch_json('https://api.github.com/repos/WebAssembly/wabt/releases/latest') for asset in data['assets']: if platform_regex.match(asset['name']): From c91c0520a617ce7b72315451645dba0792ea59f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 16 Jun 2025 15:08:22 +0200 Subject: [PATCH 547/622] Fix table.init text format parsing (#7654) `table.init` text format has two forms: - `table.init tableidx elemidx` - `table.init elemidx` (`tableidx` is implicitly 0) Because the optional `tableidx` is not the last (second) argument, we have to parse one or two immediate arguments, and depending on how many we parsed decide which one is `elemidx`. Currently the code assumes first immediate argument is always a table, which does not handle the second form. Before this PR, `wasm-shell` fails to parse this functions in spec tests: https://github.com/WebAssembly/testsuite/blob/e05365077e13a1d86ffe77acfb1a835b7aa78422/bulk.wast#L207-L211 After the PR we're able to parse it, but we still can't parse the whole file, because of other issues. Error when parsing `bulk.wast`, before this PR: ``` 209:6: error: expected elem index or identifier ``` After this PR: ``` 250:33: error: unrecognized instruction ``` (The new error is about `elem.drop`, which we don't support yet, see #7209) --- scripts/test/shared.py | 4 ++-- src/parser/parsers.h | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index d4973063707..237f59a9b6d 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -416,12 +416,12 @@ def get_tests(test_dir, extensions=[], recursive=False): 'address.wast', # 64-bit offset allowed by memory64 'align.wast', # Alignment bit 6 used by multi-memory 'binary.wast', # memory.grow reserved byte a LEB in multi-memory - 'bulk.wast', # Requires table.init abbreviation with implicit table + 'bulk.wast', # Requires support for elem.drop 'comments.wast', # Issue with carriage returns being treated as newlines 'const.wast', # Hex float constant not recognized as out of range 'conversions.wast', # Promoted NaN should be canonical 'data.wast', # Constant global references allowed by GC - 'elem.wast', # Requires table.init abbreviation with implicit table + 'elem.wast', # Requires modeling empty declarative segments 'f32.wast', # Adding -0 and -nan should give a canonical NaN 'f64.wast', # Adding -0 and -nan should give a canonical NaN 'float_exprs.wast', # Adding 0 and NaN should give canonical NaN diff --git a/src/parser/parsers.h b/src/parser/parsers.h index dd4cbab2f5d..7b2b5d00d3b 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -2130,10 +2130,33 @@ makeTableCopy(Ctx& ctx, Index pos, const std::vector& annotations) { template Result<> makeTableInit(Ctx& ctx, Index pos, const std::vector& annotations) { + // Note: binary and text formats for `table.init` are different. In both + // formats the table index is optional (with 0 as the default). When both the + // table and elem index are specified, the elem index comes first in the + // binary format, but second in the text format. + + auto reset = ctx.in.getPos(); + + auto retry = [&]() -> Result<> { + // We're unable to parse the two argument format. Try one argument format + // with just elem index. + WithPosition with(ctx, reset); + auto elem = elemidx(ctx); + CHECK_ERR(elem); + MaybeResult table = ctx.getTableFromIdx(0); + return ctx.makeTableInit(pos, annotations, table.getPtr(), *elem); + }; + auto table = maybeTableidx(ctx); - CHECK_ERR(table); - auto elem = elemidx(ctx); - CHECK_ERR(elem); + if (table.getErr()) { + return retry(); + } + + auto elem = maybeElemidx(ctx); + if (elem.getErr() || !elem) { + return retry(); + } + return ctx.makeTableInit(pos, annotations, table.getPtr(), *elem); } @@ -2850,13 +2873,22 @@ template Result globalidx(Ctx& ctx) { // elemidx ::= x:u32 => x // | v:id => x (if elems[x] = v) -template Result elemidx(Ctx& ctx) { +template +MaybeResult maybeElemidx(Ctx& ctx) { if (auto x = ctx.in.takeU32()) { return ctx.getElemFromIdx(*x); } if (auto id = ctx.in.takeID()) { return ctx.getElemFromName(*id); } + return {}; +} + +template Result elemidx(Ctx& ctx) { + if (auto idx = maybeElemidx(ctx)) { + CHECK_ERR(idx); + return *idx; + } return ctx.in.err("expected elem index or identifier"); } From e73224ae2bb7feb29fb3d0de105ddd123e00a008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Jun 2025 20:06:34 +0200 Subject: [PATCH 548/622] Implement `elem.drop` (#7658) Fixes #7209. --- scripts/gen-s-parser.py | 1 + scripts/test/shared.py | 2 - src/gen-s-parser.inc | 41 ++++++++----- src/interpreter/interpreter.cpp | 1 + src/ir/ReFinalize.cpp | 1 + src/ir/child-typer.h | 2 + src/ir/cost.h | 1 + src/ir/effects.h | 1 + src/ir/possible-contents.cpp | 1 + src/ir/subtype-exprs.h | 1 + src/parser/contexts.h | 9 +++ src/parser/parsers.h | 10 +++ src/passes/Memory64Lowering.cpp | 2 + src/passes/Print.cpp | 4 ++ src/passes/TypeGeneralizing.cpp | 2 + src/wasm-binary.h | 1 + src/wasm-builder.h | 6 ++ src/wasm-delegations-fields.def | 4 ++ src/wasm-delegations.def | 1 + src/wasm-interpreter.h | 12 ++++ src/wasm-ir-builder.h | 1 + src/wasm.h | 11 ++++ src/wasm/wasm-binary.cpp | 4 ++ src/wasm/wasm-ir-builder.cpp | 5 ++ src/wasm/wasm-stack.cpp | 5 ++ src/wasm/wasm-validator.cpp | 9 +++ src/wasm/wasm.cpp | 2 + src/wasm2js.h | 4 ++ test/binaryen.js/exception-handling.js.txt | 8 +-- test/binaryen.js/kitchen-sink.js.txt | 58 +++++++++--------- test/lit/basic/elem-drop.wast | 71 ++++++++++++++++++++++ 31 files changed, 231 insertions(+), 50 deletions(-) create mode 100644 test/lit/basic/elem-drop.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 2b0ae08526c..df5121428e8 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -582,6 +582,7 @@ ("table.fill", "makeTableFill()"), ("table.copy", "makeTableCopy()"), ("table.init", "makeTableInit()"), + ("elem.drop", "makeElemDrop()"), # exception handling instructions ("try", "makeTry()"), ("try_table", "makeTryTable()"), diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 237f59a9b6d..a5702ddb4b0 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -416,7 +416,6 @@ def get_tests(test_dir, extensions=[], recursive=False): 'address.wast', # 64-bit offset allowed by memory64 'align.wast', # Alignment bit 6 used by multi-memory 'binary.wast', # memory.grow reserved byte a LEB in multi-memory - 'bulk.wast', # Requires support for elem.drop 'comments.wast', # Issue with carriage returns being treated as newlines 'const.wast', # Hex float constant not recognized as out of range 'conversions.wast', # Promoted NaN should be canonical @@ -450,7 +449,6 @@ def get_tests(test_dir, extensions=[], recursive=False): 'type-equivalence.wast', # Recursive types allowed by GC 'unreached-invalid.wast', # Requires more precise unreachable validation 'array.wast', # Requires support for table default elements - 'array_init_elem.wast', # Requires support for elem.drop 'br_if.wast', # Requires more precise branch validation 'br_on_cast.wast', # Requires host references to not be externalized i31refs 'br_on_cast_fail.wast', # Requires host references to not be externalized i31refs diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index ee406b4bfd7..2dccad00271 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -401,25 +401,36 @@ switch (buf[0]) { } } case 'e': { - switch (buf[7]) { - case 'c': - if (op == "extern.convert_any"sv) { - CHECK_ERR(makeRefAs(ctx, pos, annotations, ExternConvertAny)); - return Ok{}; - } - goto parse_error; - case 'e': - if (op == "extern.externalize"sv) { - CHECK_ERR(makeRefAs(ctx, pos, annotations, ExternConvertAny)); + switch (buf[1]) { + case 'l': + if (op == "elem.drop"sv) { + CHECK_ERR(makeElemDrop(ctx, pos, annotations)); return Ok{}; } goto parse_error; - case 'i': - if (op == "extern.internalize"sv) { - CHECK_ERR(makeRefAs(ctx, pos, annotations, AnyConvertExtern)); - return Ok{}; + case 'x': { + switch (buf[7]) { + case 'c': + if (op == "extern.convert_any"sv) { + CHECK_ERR(makeRefAs(ctx, pos, annotations, ExternConvertAny)); + return Ok{}; + } + goto parse_error; + case 'e': + if (op == "extern.externalize"sv) { + CHECK_ERR(makeRefAs(ctx, pos, annotations, ExternConvertAny)); + return Ok{}; + } + goto parse_error; + case 'i': + if (op == "extern.internalize"sv) { + CHECK_ERR(makeRefAs(ctx, pos, annotations, AnyConvertExtern)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } default: goto parse_error; } } diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index a3a8e0ae5b1..e9df1bab4d3 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -233,6 +233,7 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitTableFill(TableFill* curr) { WASM_UNREACHABLE("TODO"); } Flow visitTableCopy(TableCopy* curr) { WASM_UNREACHABLE("TODO"); } Flow visitTableInit(TableInit* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitElemDrop(ElemDrop* curr) { WASM_UNREACHABLE("TODO"); } Flow visitTry(Try* curr) { WASM_UNREACHABLE("TODO"); } Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("TODO"); } Flow visitThrow(Throw* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index bd61d84022f..05d45233e35 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -127,6 +127,7 @@ void ReFinalize::visitTableGrow(TableGrow* curr) { curr->finalize(); } void ReFinalize::visitTableFill(TableFill* curr) { curr->finalize(); } void ReFinalize::visitTableCopy(TableCopy* curr) { curr->finalize(); } void ReFinalize::visitTableInit(TableInit* curr) { curr->finalize(); } +void ReFinalize::visitElemDrop(ElemDrop* curr) { curr->finalize(); } void ReFinalize::visitTry(Try* curr) { curr->finalize(); } void ReFinalize::visitTryTable(TryTable* curr) { curr->finalize(); diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index b922e286cc5..f802266e33a 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -804,6 +804,8 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->size, Type::i32); } + void visitElemDrop(ElemDrop* curr) {} + void visitTry(Try* curr) { note(&curr->body, curr->type); for (auto& expr : curr->catchBodies) { diff --git a/src/ir/cost.h b/src/ir/cost.h index 014bbcd0bdc..3827dfb2460 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -631,6 +631,7 @@ struct CostAnalyzer : public OverriddenVisitor { CostType visitTableInit(TableInit* curr) { return 6 + visit(curr->dest) + visit(curr->offset) + visit(curr->size); } + CostType visitElemDrop(ElemDrop* curr) { return 6; } CostType visitTry(Try* curr) { // We assume no exception will be thrown in most cases return visit(curr->body); diff --git a/src/ir/effects.h b/src/ir/effects.h index c4960e3e112..504c1cc6488 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -782,6 +782,7 @@ class EffectAnalyzer { parent.writesTable = true; parent.implicitTrap = true; } + void visitElemDrop(ElemDrop* curr) { parent.writesTable = true; } void visitTry(Try* curr) { if (curr->delegateTarget.is()) { parent.delegateTargets.insert(curr->delegateTarget); diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 5d3a7cdb070..d36d57c6172 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -675,6 +675,7 @@ struct InfoCollector void visitTableFill(TableFill* curr) { addRoot(curr); } void visitTableCopy(TableCopy* curr) { addRoot(curr); } void visitTableInit(TableInit* curr) {} + void visitElemDrop(ElemDrop* curr) {} void visitNop(Nop* curr) {} void visitUnreachable(Unreachable* curr) {} diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 5ae5a99da40..0e26ca96ee8 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -243,6 +243,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(seg->type, self()->getModule()->getTable(curr->table)->type); } + void visitElemDrop(ElemDrop* curr) {} void visitTry(Try* curr) { self()->noteSubtype(curr->body, curr); for (auto* body : curr->catchBodies) { diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 48234829b5a..f27a7128738 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -700,6 +700,9 @@ struct NullInstrParserCtx { makeTableInit(Index, const std::vector&, TableIdxT*, ElemIdxT) { return Ok{}; } + Result<> makeElemDrop(Index, const std::vector&, ElemIdxT) { + return Ok{}; + } Result<> makeThrow(Index, const std::vector&, TagIdxT) { return Ok{}; } @@ -2545,6 +2548,12 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeTableInit(elem, *t)); } + Result<> makeElemDrop(Index pos, + const std::vector& annotations, + Name elem) { + return withLoc(pos, irBuilder.makeElemDrop(elem)); + } + Result<> makeThrow(Index pos, const std::vector& annotations, Name tag) { return withLoc(pos, irBuilder.makeThrow(tag)); diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 7b2b5d00d3b..1d1974a9d27 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -209,6 +209,8 @@ Result<> makeTableCopy(Ctx&, Index, const std::vector&); template Result<> makeTableInit(Ctx&, Index, const std::vector&); template +Result<> makeElemDrop(Ctx&, Index, const std::vector&); +template Result<> makeThrow(Ctx&, Index, const std::vector&); template Result<> makeRethrow(Ctx&, Index, const std::vector&); @@ -2160,6 +2162,14 @@ makeTableInit(Ctx& ctx, Index pos, const std::vector& annotations) { return ctx.makeTableInit(pos, annotations, table.getPtr(), *elem); } +template +Result<> +makeElemDrop(Ctx& ctx, Index pos, const std::vector& annotations) { + auto elem = elemidx(ctx); + CHECK_ERR(elem); + return ctx.makeElemDrop(pos, annotations, *elem); +} + template Result<> makeThrow(Ctx& ctx, Index pos, const std::vector& annotations) { diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index 714f3aa5b34..1506c016eea 100644 --- a/src/passes/Memory64Lowering.cpp +++ b/src/passes/Memory64Lowering.cpp @@ -238,6 +238,8 @@ struct Memory64Lowering : public WalkerPass> { wrapTableAddress64(curr->dest, curr->table); } + void visitElemDrop(ElemDrop* curr) {} + void visitCallIndirect(CallIndirect* curr) { wrapTableAddress64(curr->target, curr->table); } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index a882451abab..f60642a23c8 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2132,6 +2132,10 @@ struct PrintExpressionContents o << ' '; curr->segment.print(o); } + void visitElemDrop(ElemDrop* curr) { + printMedium(o, "elem.drop "); + curr->segment.print(o); + } void visitTry(Try* curr) { printMedium(o, "try"); if (curr->name.is()) { diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 97ab84cb65e..5ede9fa019b 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -501,6 +501,8 @@ struct TransferFn : OverriddenVisitor { void visitTableInit(TableInit* curr) {} + void visitElemDrop(ElemDrop* curr) {} + void visitTry(Try* curr) { WASM_UNREACHABLE("TODO"); } void visitTryTable(TryTable* curr) { WASM_UNREACHABLE("TODO"); } void visitThrow(Throw* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index ecd7079c586..857ebe0d540 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1118,6 +1118,7 @@ enum ASTNodes { TableFill = 0x11, TableCopy = 0x0e, TableInit = 0x0c, + ElemDrop = 0x0d, RefNull = 0xd0, RefIsNull = 0xd1, RefFunc = 0xd2, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index d4eca47ad3f..7bee64c2407 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -768,6 +768,12 @@ class Builder { ret->finalize(); return ret; } + ElemDrop* makeElemDrop(Name segment) { + auto* ret = wasm.allocator.alloc(); + ret->segment = segment; + ret->finalize(); + return ret; + } private: Try* makeTry(Name name, diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 7128a100b20..e2627e72fc1 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -582,6 +582,10 @@ DELEGATE_FIELD_NAME_KIND(TableInit, segment, ModuleItemKind::ElementSegment) DELEGATE_FIELD_NAME_KIND(TableInit, table, ModuleItemKind::Table) DELEGATE_FIELD_CASE_END(TableInit) +DELEGATE_FIELD_CASE_START(ElemDrop) +DELEGATE_FIELD_NAME_KIND(ElemDrop, segment, ModuleItemKind::ElementSegment) +DELEGATE_FIELD_CASE_END(ElemDrop) + DELEGATE_FIELD_CASE_START(Try) DELEGATE_FIELD_SCOPE_NAME_USE(Try, delegateTarget) DELEGATE_FIELD_CHILD_VECTOR(Try, catchBodies) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index 1c804432b47..568b16883c5 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -65,6 +65,7 @@ DELEGATE(TableGrow); DELEGATE(TableFill); DELEGATE(TableCopy); DELEGATE(TableInit); +DELEGATE(ElemDrop); DELEGATE(Try); DELEGATE(TryTable); DELEGATE(Throw); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 22a76b5c83e..062d0b7b847 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1557,6 +1557,7 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitTableFill(TableFill* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTableCopy(TableCopy* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTableInit(TableInit* curr) { WASM_UNREACHABLE("unimp"); } + Flow visitElemDrop(ElemDrop* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTry(Try* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("unimp"); } Flow visitThrow(Throw* curr) { @@ -2670,6 +2671,10 @@ class ConstantExpressionRunner : public ExpressionRunner { NOTE_ENTER("TableInit"); return Flow(NONCONSTANT_FLOW); } + Flow visitElemDrop(ElemDrop* curr) { + NOTE_ENTER("ElemDrop"); + return Flow(NONCONSTANT_FLOW); + } Flow visitLoad(Load* curr) { NOTE_ENTER("Load"); return Flow(NONCONSTANT_FLOW); @@ -3648,6 +3653,13 @@ class ModuleRunnerBase : public ExpressionRunner { return {}; } + Flow visitElemDrop(ElemDrop* curr) { + Module& wasm = *self()->getModule(); + ElementSegment* seg = wasm.getElementSegment(curr->segment); + seg->data.clear(); + return {}; + } + Flow visitLocalGet(LocalGet* curr) { NOTE_ENTER("LocalGet"); auto index = curr->index; diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index e854491f6af..540d75983c5 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -202,6 +202,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeTableFill(Name table); Result<> makeTableCopy(Name destTable, Name srcTable); Result<> makeTableInit(Name elem, Name table); + Result<> makeElemDrop(Name elem); Result<> makeTry(Name label, Signature sig); Result<> makeTryTable(Name label, Signature sig, diff --git a/src/wasm.h b/src/wasm.h index f94990e9594..e3ca5f47997 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -708,6 +708,7 @@ class Expression { TableFillId, TableCopyId, TableInitId, + ElemDropId, TryId, TryTableId, ThrowId, @@ -1471,6 +1472,16 @@ class TableInit : public SpecificExpression { void finalize(); }; +class ElemDrop : public SpecificExpression { +public: + ElemDrop() = default; + ElemDrop(MixedArena& allocator) : ElemDrop() {} + + Name segment; + + void finalize(); +}; + // 'try' from the old (Phase 3) EH proposal class Try : public SpecificExpression { public: diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index ba7eaa76e73..08c6e19e298 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3856,6 +3856,10 @@ Result<> WasmBinaryReader::readInst() { auto table = getTableName(getU32LEB()); return builder.makeTableInit(elem, table); } + case BinaryConsts::ElemDrop: { + auto elem = getElemName(getU32LEB()); + return builder.makeElemDrop(elem); + } case BinaryConsts::F32_F16LoadMem: { auto [mem, align, offset] = getMemarg(); return builder.makeLoad(2, false, offset, align, Type::f32, mem); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 402fa448d9a..c1d323e5e9d 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1901,6 +1901,11 @@ Result<> IRBuilder::makeTableInit(Name elem, Name table) { return Ok{}; } +Result<> IRBuilder::makeElemDrop(Name segment) { + push(builder.makeElemDrop(segment)); + return Ok{}; +} + Result<> IRBuilder::makeTry(Name label, Signature sig) { auto* tryy = wasm.allocator.alloc(); tryy->type = sig.results; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index a5631dbf711..406a75b600f 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2128,6 +2128,11 @@ void BinaryInstWriter::visitTableInit(TableInit* curr) { o << U32LEB(parent.getTableIndex(curr->table)); } +void BinaryInstWriter::visitElemDrop(ElemDrop* curr) { + o << int8_t(BinaryConsts::MiscPrefix) << U32LEB(BinaryConsts::ElemDrop); + o << U32LEB(parent.getElementSegmentIndex(curr->segment)); +} + void BinaryInstWriter::visitTry(Try* curr) { breakStack.push_back(curr->name); o << int8_t(BinaryConsts::Try); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index d39e6cda7ff..d36ca80f38c 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -481,6 +481,7 @@ struct FunctionValidator : public WalkerPass> { void visitTableFill(TableFill* curr); void visitTableCopy(TableCopy* curr); void visitTableInit(TableInit* curr); + void visitElemDrop(ElemDrop* curr); void noteDelegate(Name name, Expression* curr); void noteRethrow(Name name, Expression* curr); void visitTry(Try* curr); @@ -2549,6 +2550,14 @@ void FunctionValidator::visitTableInit(TableInit* curr) { curr->size->type, Type(Type::i32), curr, "table.init size must be valid"); } +void FunctionValidator::visitElemDrop(ElemDrop* curr) { + shouldBeTrue(getModule()->features.hasBulkMemory(), + curr, + "elem.drop requires bulk-memory [--enable-bulk-memory]"); + auto* segment = getModule()->getElementSegment(curr->segment); + shouldBeTrue(!!segment, curr, "elem.drop segment must exist"); +} + void FunctionValidator::noteDelegate(Name name, Expression* curr) { if (name != DELEGATE_CALLER_TARGET) { shouldBeTrue(delegateTargetNames.count(name) != 0, diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index bde52702e12..c95ee8a296f 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -891,6 +891,8 @@ void TableInit::finalize() { } } +void ElemDrop::finalize() { type = Type::none; } + void Try::finalize(std::optional type_) { if (type_) { type = *type_; diff --git a/src/wasm2js.h b/src/wasm2js.h index ab6e62b88ea..ddc839e6f60 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2252,6 +2252,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitElemDrop(ElemDrop* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitTry(Try* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/binaryen.js/exception-handling.js.txt b/test/binaryen.js/exception-handling.js.txt index 992fbb4c479..d0a6367cb29 100644 --- a/test/binaryen.js/exception-handling.js.txt +++ b/test/binaryen.js/exception-handling.js.txt @@ -34,7 +34,7 @@ ) ) -getExpressionInfo(throw) = {"id":54,"type":1,"tag":"e"} -getExpressionInfo(rethrow) = {"id":55,"type":1,"target":"l0"} -getExpressionInfo(try_catch) = {"id":52,"type":1,"name":"l0","hasCatchAll":false,"delegateTarget":null,"isDelegate":false} -getExpressionInfo(try_delegate) = {"id":52,"type":0,"name":"try_outer","hasCatchAll":true,"delegateTarget":null,"isDelegate":false} +getExpressionInfo(throw) = {"id":55,"type":1,"tag":"e"} +getExpressionInfo(rethrow) = {"id":56,"type":1,"target":"l0"} +getExpressionInfo(try_catch) = {"id":53,"type":1,"name":"l0","hasCatchAll":false,"delegateTarget":null,"isDelegate":false} +getExpressionInfo(try_delegate) = {"id":53,"type":0,"name":"try_outer","hasCatchAll":true,"delegateTarget":null,"isDelegate":false} diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index d9c3ff97ac5..03714168237 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -82,35 +82,35 @@ TableGetId: 45 TableSetId: 46 TableSizeId: 47 TableGrowId: 48 -TryId: 52 -ThrowId: 54 -RethrowId: 55 -TupleMakeId: 57 -TupleExtractId: 58 -RefI31Id: 59 -I31GetId: 60 -CallRefId: 61 -RefTestId: 62 -RefCastId: 63 -BrOnId: 65 -StructNewId: 66 -StructGetId: 67 -StructSetId: 68 -ArrayNewId: 71 -ArrayNewFixedId: 74 -ArrayGetId: 75 -ArraySetId: 76 -ArrayLenId: 77 -ArrayCopy: 78 -RefAs: 84 -StringNew: 85 -StringConst: 86 -StringMeasure: 87 -StringEncode: 88 -StringConcat: 89 -StringEq: 90 -StringWTF16Get: 91 -StringSliceWTF: 92 +TryId: 53 +ThrowId: 55 +RethrowId: 56 +TupleMakeId: 58 +TupleExtractId: 59 +RefI31Id: 60 +I31GetId: 61 +CallRefId: 62 +RefTestId: 63 +RefCastId: 64 +BrOnId: 66 +StructNewId: 67 +StructGetId: 68 +StructSetId: 69 +ArrayNewId: 72 +ArrayNewFixedId: 75 +ArrayGetId: 76 +ArraySetId: 77 +ArrayLenId: 78 +ArrayCopy: 79 +RefAs: 85 +StringNew: 86 +StringConst: 87 +StringMeasure: 88 +StringEncode: 89 +StringConcat: 90 +StringEq: 91 +StringWTF16Get: 92 +StringSliceWTF: 93 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/lit/basic/elem-drop.wast b/test/lit/basic/elem-drop.wast new file mode 100644 index 00000000000..db7f6afa7bd --- /dev/null +++ b/test/lit/basic/elem-drop.wast @@ -0,0 +1,71 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + (table 1 funcref) + ;; CHECK-TEXT: (type $0 (func)) + + ;; CHECK-TEXT: (table $0 1 funcref) + + ;; CHECK-TEXT: (elem $p func $f) + + ;; CHECK-TEXT: (elem $a (i32.const 0) $f) + + ;; CHECK-TEXT: (func $f (type $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (type $0 (func)) + + ;; CHECK-BIN: (table $0 1 funcref) + + ;; CHECK-BIN: (elem $p func $f) + + ;; CHECK-BIN: (elem $a (i32.const 0) $f) + + ;; CHECK-BIN: (func $f (type $0) + ;; CHECK-BIN-NEXT: ) + (func $f) + (elem $p funcref (ref.func $f)) + (elem $a (table 0) (i32.const 0) func $f) + + ;; CHECK-TEXT: (func $drop_passive (type $0) + ;; CHECK-TEXT-NEXT: (elem.drop $p) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $drop_passive (type $0) + ;; CHECK-BIN-NEXT: (elem.drop $p) + ;; CHECK-BIN-NEXT: ) + (func $drop_passive (elem.drop $p)) + + ;; CHECK-TEXT: (func $drop_active (type $0) + ;; CHECK-TEXT-NEXT: (elem.drop $a) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $drop_active (type $0) + ;; CHECK-BIN-NEXT: (elem.drop $a) + ;; CHECK-BIN-NEXT: ) + (func $drop_active (elem.drop $a)) +) +;; CHECK-BIN-NODEBUG: (type $0 (func)) + +;; CHECK-BIN-NODEBUG: (table $0 1 funcref) + +;; CHECK-BIN-NODEBUG: (elem $0 func $0) + +;; CHECK-BIN-NODEBUG: (elem $1 (i32.const 0) $0) + +;; CHECK-BIN-NODEBUG: (func $0 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (elem.drop $0) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (elem.drop $1) +;; CHECK-BIN-NODEBUG-NEXT: ) From 1c517463c056ffbae59ec0835f4aa6fbbd275516 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Jun 2025 20:15:38 +0200 Subject: [PATCH 549/622] Avoid assertions when parsing incorrect type annotations (#7662) Audit child-typer.h for all uses of `getStruct()`, `getArray()`, `getSignature()`, and `getContinuation()` on the passed type immediates and make sure that their calling factory methods in IRBuilder guard against type annotations that would cause assertion failures in these calls. --- src/wasm/wasm-ir-builder.cpp | 43 +++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index c1d323e5e9d..2c282684974 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1500,6 +1500,9 @@ Result<> IRBuilder::makeCallIndirect(Name table, HeapType type, bool isReturn, std::optional inline_) { + if (!type.isSignature()) { + return Err{"expected function type annotation on call_indirect"}; + } CallIndirect curr(wasm.allocator); curr.heapType = type; curr.operands.resize(type.getSignature().params.size()); @@ -2005,6 +2008,9 @@ Result<> IRBuilder::makeI31Get(bool signed_) { Result<> IRBuilder::makeCallRef(HeapType type, bool isReturn, std::optional inline_) { + if (!type.isSignature()) { + return Err{"expected function type annotation on call_ref"}; + } CallRef curr(wasm.allocator); if (!type.isSignature()) { return Err{"expected function type"}; @@ -2217,6 +2223,9 @@ Result<> IRBuilder::makeBrOn( } Result<> IRBuilder::makeStructNew(HeapType type) { + if (!type.isStruct()) { + return Err{"expected struct type annotation on struct.new"}; + } StructNew curr(wasm.allocator); curr.type = Type(type, NonNullable, Exact); curr.operands.resize(type.getStruct().fields.size()); @@ -2248,6 +2257,9 @@ Result<> IRBuilder::makeStructGet(HeapType type, Result<> IRBuilder::makeStructSet(HeapType type, Index field, MemoryOrder order) { + if (!type.isStruct()) { + return Err{"expected struct type annotation on struct.set"}; + } StructSet curr; curr.index = field; CHECK_ERR(ChildPopper{*this}.visitStructSet(&curr, type)); @@ -2260,6 +2272,9 @@ Result<> IRBuilder::makeStructRMW(AtomicRMWOp op, HeapType type, Index field, MemoryOrder order) { + if (!type.isStruct()) { + return Err{"expected struct type annotation on struct.atomic.rmw"}; + } StructRMW curr; curr.index = field; CHECK_ERR(ChildPopper{*this}.visitStructRMW(&curr, type)); @@ -2270,6 +2285,9 @@ Result<> IRBuilder::makeStructRMW(AtomicRMWOp op, Result<> IRBuilder::makeStructCmpxchg(HeapType type, Index field, MemoryOrder order) { + if (!type.isStruct()) { + return Err{"expected struct type annotation on struct.atomic.rmw"}; + } StructCmpxchg curr; curr.index = field; CHECK_ERR(ChildPopper{*this}.visitStructCmpxchg(&curr, type)); @@ -2280,6 +2298,9 @@ IRBuilder::makeStructCmpxchg(HeapType type, Index field, MemoryOrder order) { } Result<> IRBuilder::makeArrayNew(HeapType type) { + if (!type.isArray()) { + return Err{"expected array type annotation on array.new"}; + } ArrayNew curr; curr.type = Type(type, NonNullable, Exact); // Differentiate from array.new_default with dummy initializer. @@ -2312,10 +2333,10 @@ Result<> IRBuilder::makeArrayNewElem(HeapType type, Name elem) { } Result<> IRBuilder::makeArrayNewFixed(HeapType type, uint32_t arity) { - ArrayNewFixed curr(wasm.allocator); if (!type.isArray()) { return Err{"expected array type annotation on array.new_fixed"}; } + ArrayNewFixed curr(wasm.allocator); curr.type = Type(type, NonNullable); curr.values.resize(arity); CHECK_ERR(visitArrayNewFixed(&curr)); @@ -2334,6 +2355,9 @@ IRBuilder::makeArrayGet(HeapType type, bool signed_, MemoryOrder order) { } Result<> IRBuilder::makeArraySet(HeapType type, MemoryOrder order) { + if (!type.isArray()) { + return Err{"expected array type annotation on array.set"}; + } ArraySet curr; CHECK_ERR(ChildPopper{*this}.visitArraySet(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); @@ -2359,6 +2383,9 @@ Result<> IRBuilder::makeArrayCopy(HeapType destType, HeapType srcType) { } Result<> IRBuilder::makeArrayFill(HeapType type) { + if (!type.isArray()) { + return Err{"expected array type annotation on array.fill"}; + } ArrayFill curr; CHECK_ERR(ChildPopper{*this}.visitArrayFill(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); @@ -2396,6 +2423,9 @@ Result<> IRBuilder::makeArrayInitElem(HeapType type, Name elem) { Result<> IRBuilder::makeArrayRMW(AtomicRMWOp op, HeapType type, MemoryOrder order) { + if (!type.isArray()) { + return Err{"expected array type annotation on array.atomic.rmw"}; + } ArrayRMW curr; CHECK_ERR(ChildPopper{*this}.visitArrayRMW(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); @@ -2404,6 +2434,9 @@ IRBuilder::makeArrayRMW(AtomicRMWOp op, HeapType type, MemoryOrder order) { } Result<> IRBuilder::makeArrayCmpxchg(HeapType type, MemoryOrder order) { + if (!type.isArray()) { + return Err{"expected array type annotation on array.atomic.rmw"}; + } ArrayCmpxchg curr; CHECK_ERR(ChildPopper{*this}.visitArrayCmpxchg(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); @@ -2500,7 +2533,7 @@ Result<> IRBuilder::makeContNew(HeapType type) { Result<> IRBuilder::makeContBind(HeapType sourceType, HeapType targetType) { if (!sourceType.isContinuation() || !targetType.isContinuation()) { - return Err{"expected continuation types"}; + return Err{"expected continuation type annotations on cont.bind"}; } ContBind curr(wasm.allocator); @@ -2590,7 +2623,7 @@ IRBuilder::makeResume(HeapType ct, return Err{"the sizes of tags and labels must be equal"}; } if (!ct.isContinuation()) { - return Err{"expected continuation type"}; + return Err{"expected continuation type annotation on resume"}; } Resume curr(wasm.allocator); @@ -2623,7 +2656,7 @@ IRBuilder::makeResumeThrow(HeapType ct, return Err{"the sizes of tags and labels must be equal"}; } if (!ct.isContinuation()) { - return Err{"expected continuation type"}; + return Err{"expected continuation type annotation on resume_throw"}; } ResumeThrow curr(wasm.allocator); @@ -2649,7 +2682,7 @@ IRBuilder::makeResumeThrow(HeapType ct, Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { if (!ct.isContinuation()) { - return Err{"expected continuation type"}; + return Err{"expected continuation type annotation on switch"}; } StackSwitch curr(wasm.allocator); curr.tag = tag; From bb23d2093aa6fc7d786bf39fb3ec20777ddf6c11 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 18 Jun 2025 03:48:44 +0200 Subject: [PATCH 550/622] [Custom Descriptors] Update Unsubtyping (#7664) When we have types A and B with descriptors A.desc and B.desc, A <: B implies A.desc <: B.desc. Update Unsubypting to preserve the implication and avoid generating invalid types. --- scripts/test/fuzzing.py | 1 + src/passes/Unsubtyping.cpp | 5 +++ test/lit/passes/unsubtyping-desc.wast | 64 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 test/lit/passes/unsubtyping-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 6968fb8e4b8..be1ab82e1e8 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -119,6 +119,7 @@ 'ref.cast_desc.wast', 'struct.new-desc.wast', 'remove-unused-types-descriptors.wast', + 'unsubtyping-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index 8d76f348a5a..729dd18d488 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -290,6 +290,11 @@ struct Unsubtyping case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } + if (auto desc = type.getDescriptorType()) { + if (auto superDesc = super.getDescriptorType()) { + noteSubtype(*desc, *superDesc); + } + } } // Analyze all casts at once. diff --git a/test/lit/passes/unsubtyping-desc.wast b/test/lit/passes/unsubtyping-desc.wast new file mode 100644 index 00000000000..b33c8ba6464 --- /dev/null +++ b/test/lit/passes/unsubtyping-desc.wast @@ -0,0 +1,64 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --unsubtyping --remove-unused-types -all -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; CHECK: (type $B (sub (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub (describes $B (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) + ) + + ;; There is nothing requiring the subtype relationship, so we should optimize. + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $B (ref null $B) (ref.null none)) + (global $B (ref null $B) (ref.null none)) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) + ) + + ;; Now we require B <: A, which implies B.desc <: A.desc. + ;; CHECK: (global $B (ref null $B) (ref.null none)) + (global $B (ref null $B) (ref.null none)) + ;; CHECK: (global $A (ref null $A) (global.get $B)) + (global $A (ref null $A) (global.get $B)) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; CHECK: (type $B (sub (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) + ) + + ;; Now we directly require B.desc <: A.desc. This does *not* imply B <: A, so + ;; we can optimize $B (but not $B.desc). + ;; CHECK: (global $B.desc (ref null $B.desc) (ref.null none)) + (global $B.desc (ref null $B.desc) (ref.null none)) + ;; CHECK: (global $A.desc (ref null $A.desc) (global.get $B.desc)) + (global $A.desc (ref null $A.desc) (global.get $B.desc)) +) From 02338c927aaeca12683767d5d6ead33fa67840e7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 18 Jun 2025 18:01:16 +0200 Subject: [PATCH 551/622] [NFC] Remove stale comment about unsafe casts (#7665) We no longer support unsafe casts, so this comment is not meaningful.k --- src/passes/OptimizeCasts.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/passes/OptimizeCasts.cpp b/src/passes/OptimizeCasts.cpp index efa00948b55..c4487f8b4b6 100644 --- a/src/passes/OptimizeCasts.cpp +++ b/src/passes/OptimizeCasts.cpp @@ -342,11 +342,6 @@ struct EarlyCastFinder // change the best cast to move. bestMove.bestCast = curr; } - // We don't care about the safety of the cast at present. If there are - // two casts with the same type one being safe and one being unsafe, the - // first cast that we visit will be chosen to be moved. Perhaps in the - // future we can consider prioritizing unsafe casts over safe ones since - // users may be more interested in that. } } } From 436000709be7f1ef3998c7e1f0bdd77077d12c75 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 19 Jun 2025 01:41:25 +0200 Subject: [PATCH 552/622] [Custom Descriptors] Update TypeMerging (#7666) Update type merging to consider the existence of descriptor and described types as part of the top-level structure of a type. This prevents types with and without descriptors from incorrectly being merged together. --- scripts/test/fuzzing.py | 1 + src/passes/TypeMerging.cpp | 10 ++++ test/lit/passes/type-merging-desc.wast | 75 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 test/lit/passes/type-merging-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index be1ab82e1e8..cfaf8c998aa 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -120,6 +120,7 @@ 'struct.new-desc.wast', 'remove-unused-types-descriptors.wast', 'unsubtyping-desc.wast', + 'type-merging-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 1f2c892f1d8..f47a212f30c 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -591,6 +591,13 @@ bool shapeEq(HeapType a, HeapType b) { if (a.isShared() != b.isShared()) { return false; } + // Ignore supertype because we want to be able to merge into parents. + if (!!a.getDescriptorType() != !!b.getDescriptorType()) { + return false; + } + if (!!a.getDescribedType() != !!b.getDescribedType()) { + return false; + } auto aKind = a.getKind(); auto bKind = b.getKind(); if (aKind != bKind) { @@ -614,6 +621,9 @@ bool shapeEq(HeapType a, HeapType b) { size_t shapeHash(HeapType a) { size_t digest = hash(a.isOpen()); rehash(digest, a.isShared()); + // Ignore supertype because we want to be able to merge into parents. + rehash(digest, !!a.getDescriptorType()); + rehash(digest, !!a.getDescribedType()); auto kind = a.getKind(); rehash(digest, kind); switch (kind) { diff --git a/test/lit/passes/type-merging-desc.wast b/test/lit/passes/type-merging-desc.wast new file mode 100644 index 00000000000..ba09ab87481 --- /dev/null +++ b/test/lit/passes/type-merging-desc.wast @@ -0,0 +1,75 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-merging --remove-unused-types -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct)) + (type $A (struct)) + ;; CHECK: (type $B (descriptor $C (struct))) + (type $B (descriptor $C (struct))) + ;; CHECK: (type $C (describes $B (struct))) + (type $C (describes $B (struct))) + ) + + ;; The types have different shapes and should not be merged. + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $B (ref null $B) (ref.null none)) + (global $B (ref null $B) (ref.null none)) + ;; CHECK: (global $C (ref null $C) (ref.null none)) + (global $C (ref null $C) (ref.null none)) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (descriptor $A.desc (struct (field i32)))) + (type $A (descriptor $A.desc (struct (field i32)))) + ;; CHECK: (type $A.desc (describes $A (struct))) + (type $A.desc (describes $A (struct))) + + ;; CHECK: (type $B (descriptor $B.desc (struct (field f64)))) + (type $B (descriptor $B.desc (struct (field f64)))) + ;; CHECK: (type $B.desc (describes $B (struct))) + (type $B.desc (describes $B (struct))) + ) + + ;; $A and $B have different shapes and should not be merged, so therefore + ;; $A.desc and $B.desc should not be merged. + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) + (global $A.desc (ref null $A.desc) (ref.null none)) + ;; CHECK: (global $B (ref null $B) (ref.null none)) + (global $B (ref null $B) (ref.null none)) + ;; CHECK: (global $B.desc (ref null $A.desc) (ref.null none)) + (global $B.desc (ref null $B.desc) (ref.null none)) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (descriptor $A.desc (struct))) + (type $A (descriptor $A.desc (struct))) + ;; CHECK: (type $A.desc (describes $A (struct (field i32)))) + (type $A.desc (describes $A (struct (field i32)))) + + ;; CHECK: (type $B (descriptor $B.desc (struct))) + (type $B (descriptor $B.desc (struct))) + ;; CHECK: (type $B.desc (describes $B (struct (field f64)))) + (type $B.desc (describes $B (struct (field f64)))) + ) + + ;; $A.desc and $B.desc have different shapes and should not be merged, so + ;; therefore $A and $B should not be merged. + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) + (global $A.desc (ref null $A.desc) (ref.null none)) + ;; CHECK: (global $B (ref null $A) (ref.null none)) + (global $B (ref null $B) (ref.null none)) + ;; CHECK: (global $B.desc (ref null $B.desc) (ref.null none)) + (global $B.desc (ref null $B.desc) (ref.null none)) +) From 2578b418ee3a2bc6893aa2e88757033c42bacb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Fri, 20 Jun 2025 17:09:42 +0200 Subject: [PATCH 553/622] Fix elem.drop interpretation (#7668) Don't drop the segment in the module as the module can be used again later. Instead add the segment to the dropped segments set. Fixes the fuzzing issue described in https://github.com/WebAssembly/binaryen/pull/7658#issuecomment-2985124026. --- src/wasm-interpreter.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 062d0b7b847..29334d90130 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3654,9 +3654,8 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitElemDrop(ElemDrop* curr) { - Module& wasm = *self()->getModule(); ElementSegment* seg = wasm.getElementSegment(curr->segment); - seg->data.clear(); + droppedElementSegments.insert(seg->name); return {}; } From 6ea7fc20b54e4c629c9df251bdd6bbb722fbef72 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Jun 2025 21:16:37 +0200 Subject: [PATCH 554/622] [Custom Descriptors] Update Heap2Local (#7667) When optimizing allocations of structs with descriptors, store the descriptor in a local the same way we store normal fields. Update ref.get_desc on optimized allocations to simply get the local holding the descriptor. Lower ref.cast_desc to unreachable when the optimized allocation flows in as the descriptor because it cannot have also been the descriptor on the cast value without being considered to have escaped. When the optimized allocation is itself the cast value, compare the local containing its descriptor to the target descriptor to determine whether the cast would have succeeded. --- scripts/test/fuzzing.py | 1 + src/passes/Heap2Local.cpp | 175 ++++++---- test/lit/passes/heap2local-desc.wast | 464 +++++++++++++++++++++++++++ 3 files changed, 578 insertions(+), 62 deletions(-) create mode 100644 test/lit/passes/heap2local-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index cfaf8c998aa..5857367ecfd 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -121,6 +121,7 @@ 'remove-unused-types-descriptors.wast', 'unsubtyping-desc.wast', 'type-merging-desc.wast', + 'heap2local-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 731b3b0de36..9dc76f04950 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -395,13 +395,23 @@ struct EscapeAnalyzer { // Whether the cast succeeds or fails, it does not escape. escapes = false; - // If the cast fails then the allocation is fully consumed and does not - // flow any further (instead, we trap). - if (!Type::isSubType(allocation->type, curr->type)) { + if (curr->ref == child) { + // If the cast fails then the allocation is fully consumed and does + // not flow any further (instead, we trap). + if (!Type::isSubType(allocation->type, curr->type)) { + fullyConsumes = true; + } + } else { + assert(curr->desc == child); fullyConsumes = true; } } + void visitRefGetDesc(RefGetDesc* curr) { + escapes = false; + fullyConsumes = true; + } + // GC operations. void visitStructSet(StructSet* curr) { // The reference does not escape (but the value is stored to memory and @@ -598,10 +608,14 @@ struct Struct2Local : PostWalker { : allocation(allocation), analyzer(analyzer), func(func), wasm(wasm), builder(wasm), fields(allocation->type.getHeapType().getStruct().fields) { - // Allocate locals to store the allocation's fields in. + // Allocate locals to store the allocation's fields and descriptor in. for (auto field : fields) { localIndexes.push_back(builder.addVar(func, field.type)); } + if (allocation->descriptor) { + localIndexes.push_back( + builder.addVar(func, allocation->descriptor->type)); + } // Replace the things we need to using the visit* methods. walk(func->body); @@ -708,61 +722,54 @@ struct Struct2Local : PostWalker { // First, assign the initial values to the new locals. std::vector contents; - if (!allocation->isWithDefault()) { - // We must assign the initial values to temp indexes, then copy them - // over all at once. If instead we did set them as we go, then we might - // hit a problem like this: - // - // (local.set X (new_X)) - // (local.set Y (block (result ..) - // (.. (local.get X) ..) ;; returns new_X, wrongly - // (new_Y) - // ) - // - // Note how we assign to the local X and use it during the assignment to - // the local Y - but we should still see the old value of X, not new_X. - // Temp locals X', Y' can ensure that: - // - // (local.set X' (new_X)) - // (local.set Y' (block (result ..) - // (.. (local.get X) ..) ;; returns the proper, old X - // (new_Y) - // ) - // .. - // (local.set X (local.get X')) - // (local.set Y (local.get Y')) - std::vector tempIndexes; - + // We might be in a loop, so the locals representing the struct fields might + // already have values. Furthermore, the computation of the new field values + // might depend on the old field values. If we naively assign the new values + // to the locals as they are computed, the computation of a later field may + // use the new value of an earlier field where it should have used the old + // value of the earlier field. To avoid this problem, we store all the + // nontrivial new values in temp locals, and only once they have fully been + // computed do we copy them into the locals representing the fields. + std::vector tempIndexes; + Index numTemps = + (curr->isWithDefault() ? 0 : fields.size()) + bool(curr->descriptor); + tempIndexes.reserve(numTemps); + + // Create the temp variables. + if (!curr->isWithDefault()) { for (auto field : fields) { tempIndexes.push_back(builder.addVar(func, field.type)); } + } + if (curr->descriptor) { + tempIndexes.push_back(builder.addVar(func, curr->descriptor->type)); + } - // Store the initial values into the temp locals. - for (Index i = 0; i < tempIndexes.size(); i++) { + // Store the initial values into the temp locals. + if (!curr->isWithDefault()) { + for (Index i = 0; i < fields.size(); i++) { contents.push_back( - builder.makeLocalSet(tempIndexes[i], allocation->operands[i])); - } - - // Copy them to the normal ones. - for (Index i = 0; i < tempIndexes.size(); i++) { - auto* value = builder.makeLocalGet(tempIndexes[i], fields[i].type); - contents.push_back(builder.makeLocalSet(localIndexes[i], value)); + builder.makeLocalSet(tempIndexes[i], curr->operands[i])); } + } + if (curr->descriptor) { + contents.push_back( + builder.makeLocalSet(tempIndexes[numTemps - 1], curr->descriptor)); + } - // TODO Check if the nondefault case does not increase code size in some - // cases. A heap allocation that implicitly sets the default values - // is smaller than multiple explicit settings of locals to - // defaults. - } else { - // Set the default values. - // - // Note that we must assign the defaults because we might be in a loop, - // that is, there might be a previous value. - for (Index i = 0; i < localIndexes.size(); i++) { - contents.push_back(builder.makeLocalSet( - localIndexes[i], - builder.makeConstantExpression(Literal::makeZero(fields[i].type)))); - } + // Store the values into the locals representing the fields. + for (Index i = 0; i < fields.size(); ++i) { + auto* val = + curr->isWithDefault() + ? builder.makeConstantExpression(Literal::makeZero(fields[i].type)) + : builder.makeLocalGet(tempIndexes[i], fields[i].type); + contents.push_back(builder.makeLocalSet(localIndexes[i], val)); + } + if (curr->descriptor) { + auto* val = + builder.makeLocalGet(tempIndexes[numTemps - 1], curr->descriptor->type); + contents.push_back( + builder.makeLocalSet(localIndexes[fields.size()], val)); } // Replace the allocation with a null reference. This changes the type @@ -838,25 +845,69 @@ struct Struct2Local : PostWalker { return; } - // We know this RefCast receives our allocation, so we can see whether it - // succeeds or fails. - if (Type::isSubType(allocation->type, curr->type)) { - // The cast succeeds, so it is a no-op, and we can skip it, since after we - // remove the allocation it will not even be needed for validation. - replaceCurrent(curr->ref); + if (curr->desc) { + // If we are doing a ref.cast_desc of the optimized allocation, but we + // know it does not have a descriptor, then we know the cast must fail. We + // also know the cast must fail if the optimized allocation flows in as + // the descriptor, since it cannot possibly have been used in the + // allocation of the cast value without having been considered to escape. + if (!allocation->descriptor || analyzer.getInteraction(curr->desc) == + ParentChildInteraction::Flows) { + // The allocation does not have a descriptor, so there is no way for the + // cast to succeed. + replaceCurrent(builder.blockify(builder.makeDrop(curr->ref), + builder.makeDrop(curr->desc), + builder.makeUnreachable())); + } else { + // The cast succeeds iff the optimized allocation's descriptor is the + // same as the given descriptor and traps otherwise. + auto type = allocation->descriptor->type; + replaceCurrent(builder.blockify( + builder.makeDrop(curr->ref), + builder.makeIf( + builder.makeRefEq( + curr->desc, + builder.makeLocalGet(localIndexes[fields.size()], type)), + builder.makeRefNull(allocation->type.getHeapType()), + builder.makeUnreachable()))); + } } else { - // The cast fails, so this must trap. - replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), - builder.makeUnreachable())); + // We know this RefCast receives our allocation, so we can see whether it + // succeeds or fails. + if (Type::isSubType(allocation->type, curr->type)) { + // The cast succeeds, so it is a no-op, and we can skip it, since after + // we remove the allocation it will not even be needed for validation. + replaceCurrent(curr->ref); + } else { + // The cast fails, so this must trap. + replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), + builder.makeUnreachable())); + } } - // Either way, we need to refinalize here (we either added an unreachable, + // In any case, we need to refinalize here (we either added an unreachable, // or we replaced a cast with the value being cast, which may have a less- // refined type - it will not be used after we remove the allocation, but we // must still fix that up for validation). refinalize = true; } + void visitRefGetDesc(RefGetDesc* curr) { + if (analyzer.getInteraction(curr) == ParentChildInteraction::None) { + return; + } + + auto type = allocation->descriptor->type; + if (type != curr->type) { + // We know exactly the allocation that flows into this expression, so we + // know the exact type of the descriptor. This type may be more precise + // than the static type of this expression. + refinalize = true; + } + auto* value = builder.makeLocalGet(localIndexes[fields.size()], type); + replaceCurrent(builder.blockify(builder.makeDrop(curr->ref), value)); + } + void visitStructSet(StructSet* curr) { if (analyzer.getInteraction(curr) == ParentChildInteraction::None) { return; diff --git a/test/lit/passes/heap2local-desc.wast b/test/lit/passes/heap2local-desc.wast new file mode 100644 index 00000000000..3fb72c65142 --- /dev/null +++ b/test/lit/passes/heap2local-desc.wast @@ -0,0 +1,464 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; (remove-unused-names allows the pass to see that blocks flow values) +;; RUN: wasm-opt %s -all --remove-unused-names --heap2local -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described (descriptor $descriptor (struct (field i32)))) + (type $described (descriptor $descriptor (struct (field i32)))) + ;; CHECK: (type $descriptor (describes $described (struct (field i64)))) + (type $descriptor (describes $described (struct (field i64)))) + + ;; CHECK: (type $super (sub (descriptor $super.desc (struct)))) + (type $super (sub (descriptor $super.desc (struct)))) + ;; CHECK: (type $super.desc (sub (describes $super (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + ;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub (sub $super (descriptor $sub.desc (struct)))) + ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + + ;; CHECK: (type $no-desc (struct)) + (type $no-desc (struct)) + + ;; CHECK: (type $chain-described (descriptor $chain-middle (struct))) + (type $chain-described (descriptor $chain-middle (struct))) + ;; CHECK: (type $chain-middle (describes $chain-described (descriptor $chain-descriptor (struct)))) + (type $chain-middle (describes $chain-described (descriptor $chain-descriptor (struct)))) + ;; CHECK: (type $chain-descriptor (describes $chain-middle (struct))) + (type $chain-descriptor (describes $chain-middle (struct))) + ) + + ;; CHECK: (type $10 (func)) + + ;; CHECK: (type $11 (func (param (ref null (exact $super.desc))))) + + ;; CHECK: (type $12 (func (result (ref null $super.desc)))) + + ;; CHECK: (type $13 (func (param (ref null (exact $super))))) + + ;; CHECK: (type $14 (func (param (ref null (exact $chain-descriptor))))) + + ;; CHECK: (global $desc (ref null (exact $descriptor)) (ref.null none)) + (global $desc (ref null (exact $descriptor)) (ref.null none)) + + ;; CHECK: (func $dropped (type $10) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 (ref null (exact $descriptor))) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 (ref null (exact $descriptor))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $dropped + (drop + (struct.new $described + (i32.const 1) + (global.get $desc) + ) + ) + ) + + ;; CHECK: (func $dropped-default (type $10) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 (ref null (exact $descriptor))) + ;; CHECK-NEXT: (local $2 (ref null (exact $descriptor))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $dropped-default + (drop + (struct.new_default $described + (global.get $desc) + ) + ) + ) + + ;; CHECK: (func $dropped-alloc-desc (type $10) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 (ref (exact $descriptor))) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 (ref (exact $descriptor))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.new $descriptor + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $dropped-alloc-desc + (drop + (struct.new $described + (i32.const 1) + (struct.new $descriptor + (i64.const 2) + ) + ) + ) + ) + + ;; CHECK: (func $dropped-default-alloc-desc (type $10) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 (ref (exact $descriptor))) + ;; CHECK-NEXT: (local $2 (ref (exact $descriptor))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.new $descriptor + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $dropped-default-alloc-desc + (drop + (struct.new_default $described + (struct.new $descriptor + (i64.const 2) + ) + ) + ) + ) + + ;; CHECK: (func $get-desc (type $12) (result (ref null $super.desc)) + ;; CHECK-NEXT: (local $0 (ref (exact $super.desc))) + ;; CHECK-NEXT: (local $1 (ref (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.new_default $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $get-desc (result (ref null $super.desc)) + (ref.get_desc $super + (struct.new $super + (struct.new $super.desc) + ) + ) + ) + + ;; CHECK: (func $get-desc-refinalize (type $12) (result (ref null $super.desc)) + ;; CHECK-NEXT: (local $0 (ref (exact $sub.desc))) + ;; CHECK-NEXT: (local $1 (ref (exact $sub.desc))) + ;; CHECK-NEXT: (block (result (ref (exact $sub.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.new_default $sub.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-desc-refinalize (result (ref null $super.desc)) + ;; This block should be refinalized. + (block (result (ref null $super.desc)) + (ref.get_desc $super + (block (result (ref null $super)) + (struct.new $sub + (struct.new $sub.desc) + ) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-success (type $10) + ;; CHECK-NEXT: (local $desc (ref null (exact $super.desc))) + ;; CHECK-NEXT: (local $1 (ref null (exact $super.desc))) + ;; CHECK-NEXT: (local $2 (ref null (exact $super.desc))) + ;; CHECK-NEXT: (local.set $desc + ;; CHECK-NEXT: (struct.new_default $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result nullref) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-success + (local $desc (ref null (exact $super.desc))) + (local.set $desc + (struct.new $super.desc) + ) + (drop + (ref.cast_desc (ref (exact $super)) + (struct.new $super + (local.get $desc) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-fail (type $11) (param $desc (ref null (exact $super.desc))) + ;; CHECK-NEXT: (local $1 (ref (exact $super.desc))) + ;; CHECK-NEXT: (local $2 (ref (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.new_default $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result nullref) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-fail (param $desc (ref null (exact $super.desc))) + (drop + (ref.cast_desc (ref (exact $super)) + (struct.new $super + (struct.new $super.desc) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-fail-reverse (type $11) (param $desc (ref null (exact $super.desc))) + ;; CHECK-NEXT: (local $1 (ref null (exact $super.desc))) + ;; CHECK-NEXT: (local $2 (ref null (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result nullref) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-fail-reverse (param $desc (ref null (exact $super.desc))) + ;; Same as above, but change where the parameter is used. + (drop + (ref.cast_desc (ref (exact $super)) + (struct.new $super + (local.get $desc) + ) + (struct.new $super.desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-fail-param (type $13) (param $ref (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-fail-param (param $ref (ref null (exact $super))) + ;; Now cast the parameter. We know it can't have the locally allocated + ;; descriptor, so the cast fails. + (drop + (ref.cast_desc (ref (exact $super)) + (local.get $ref) + (struct.new $super.desc) + ) + ) + ) + + ;; CHECK: (func $cast-no-desc (type $11) (param $desc (ref null (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-no-desc (param $desc (ref null (exact $super.desc))) + ;; The allocation does not have a descriptor, so we know the cast must fail. + (drop + (ref.cast_desc (ref (exact $super)) + (struct.new $no-desc) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-and-ref (type $14) (param $desc (ref null (exact $chain-descriptor))) + ;; CHECK-NEXT: (local $middle (ref null (exact $chain-middle))) + ;; CHECK-NEXT: (local $2 (ref null (exact $chain-descriptor))) + ;; CHECK-NEXT: (local $3 (ref null (exact $chain-descriptor))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-and-ref (param $desc (ref null (exact $chain-descriptor))) + ;; The same allocation flows into both the descriptor and the reference. The + ;; cast must fail because a value cannot be its own descriptor. We make sure + ;; the descriptor itself has a descriptor so it is not handled by the same + ;; logic as the previous test. + (local $middle (ref null (exact $chain-middle))) + (local.set $middle + (struct.new $chain-middle + (local.get $desc) + ) + ) + (drop + (ref.cast_desc (ref (exact $chain-described)) + (local.get $middle) + (local.get $middle) + ) + ) + ) +) From 8470f1b1f157201d1bc62f3202e474e7043df0ba Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sat, 21 Jun 2025 02:09:56 +0200 Subject: [PATCH 555/622] [Custom Descriptors] Update MinimizeRecGroups (#7671) Take descriptor and described types into account when comparing rec group structures. Also take the constraint that described types must appear before their descriptors when generating permutations of rec groups. --- scripts/test/fuzzing.py | 1 + src/passes/MinimizeRecGroups.cpp | 47 ++-- src/wasm/wasm-type-shape.cpp | 44 ++-- test/lit/passes/minimize-rec-groups-desc.wast | 203 ++++++++++++++++++ 4 files changed, 262 insertions(+), 33 deletions(-) create mode 100644 test/lit/passes/minimize-rec-groups-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 5857367ecfd..0a0d39ffe10 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -122,6 +122,7 @@ 'unsubtyping-desc.wast', 'type-merging-desc.wast', 'heap2local-desc.wast', + 'minimize-rec-groups-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/MinimizeRecGroups.cpp b/src/passes/MinimizeRecGroups.cpp index e00051de1ba..c10f517db42 100644 --- a/src/passes/MinimizeRecGroups.cpp +++ b/src/passes/MinimizeRecGroups.cpp @@ -33,7 +33,7 @@ // permutations, fall back to keeping the types distinct by adding distinct // brand types to the recursion groups to ensure they have different shapes. // -// There are several possible algorithmic design for detecting when to generate +// There are several possible algorithmic designs for detecting when to generate // permutations and determining what permutations to generate. They trade off // the amount of "wasted" work spent generating shapes that have already been // used with the amount of work spent trying to avoid the wasted work and with @@ -161,23 +161,30 @@ struct BrandTypeIterator { } }; -// Create an adjacency list with edges from supertype to subtypes. +// Create an adjacency list with edges from supertype to subtype and from +// described type to descriptor. std::vector> -createSubtypeGraph(const std::vector& types) { +createTypeOrderGraph(const std::vector& types) { std::unordered_map indices; for (auto type : types) { indices.insert({type, indices.size()}); } - std::vector> subtypeGraph(types.size()); + std::vector> typeOrderGraph(types.size()); for (Index i = 0; i < types.size(); ++i) { if (auto super = types[i].getDeclaredSuperType()) { if (auto it = indices.find(*super); it != indices.end()) { - subtypeGraph[it->second].push_back(i); + typeOrderGraph[it->second].push_back(i); } } + if (auto desc = types[i].getDescribedType()) { + // Described types must be in the same SCC / rec group. + auto it = indices.find(*desc); + assert(it != indices.end()); + typeOrderGraph[it->second].push_back(i); + } } - return subtypeGraph; + return typeOrderGraph; } struct RecGroupInfo; @@ -199,13 +206,13 @@ struct GroupClassInfo { // group, offset by 1 iff there is a brand type. Used to ensure that we only // find emit permutations that respect the constraint that supertypes must be // ordered before subtypes. - std::vector> subtypeGraph; + std::vector> typeOrderGraph; // A generator of valid permutations of the components in this class. TopologicalOrders orders; - // Initialize `subtypeGraph` and `orders` based on the canonical ordering + // Initialize `typeOrderGraph` and `orders` based on the canonical ordering // encoded by the group and permutation in `info`. - static std::vector> initSubtypeGraph(RecGroupInfo& info); + static std::vector> initTypeOrderGraph(RecGroupInfo& info); GroupClassInfo(RecGroupInfo& info); void advance(FeatureSet features) { @@ -222,10 +229,10 @@ struct GroupClassInfo { brand.emplace(); // Make room in the subtype graph for the brand type, which goes at the // beginning of the canonical order. - subtypeGraph.insert(subtypeGraph.begin(), {{}}); + typeOrderGraph.insert(typeOrderGraph.begin(), {{}}); // Adjust indices. - for (Index i = 1; i < subtypeGraph.size(); ++i) { - for (auto& edge : subtypeGraph[i]) { + for (Index i = 1; i < typeOrderGraph.size(); ++i) { + for (auto& edge : typeOrderGraph[i]) { ++edge; } } @@ -237,7 +244,7 @@ struct GroupClassInfo { } // Start back at the initial permutation with the new brand. orders.~TopologicalOrders(); - new (&orders) TopologicalOrders(subtypeGraph); + new (&orders) TopologicalOrders(typeOrderGraph); } // Permute the types in the given group to match the current configuration in @@ -266,7 +273,7 @@ struct RecGroupInfo { }; std::vector> -GroupClassInfo::initSubtypeGraph(RecGroupInfo& info) { +GroupClassInfo::initTypeOrderGraph(RecGroupInfo& info) { assert(!info.classInfo); assert(info.permutation.size() == info.group.size()); @@ -275,19 +282,19 @@ GroupClassInfo::initSubtypeGraph(RecGroupInfo& info) { canonical[info.permutation[i]] = info.group[i]; } - return createSubtypeGraph(canonical); + return createTypeOrderGraph(canonical); } GroupClassInfo::GroupClassInfo(RecGroupInfo& info) : singletonType(info.group.size() == 1 ? std::optional(info.group[0]) : std::nullopt), - brand(std::nullopt), subtypeGraph(initSubtypeGraph(info)), - orders(subtypeGraph) {} + brand(std::nullopt), typeOrderGraph(initTypeOrderGraph(info)), + orders(typeOrderGraph) {} void GroupClassInfo::permute(RecGroupInfo& info) { assert(info.group.size() == info.permutation.size()); - bool insertingBrand = info.group.size() < subtypeGraph.size(); + bool insertingBrand = info.group.size() < typeOrderGraph.size(); // First, un-permute the group to get back to the canonical order, offset by 1 // if we are newly inserting a brand. std::vector canonical(info.group.size() + insertingBrand); @@ -422,9 +429,9 @@ struct MinimizeRecGroups : Pass { groups.emplace_back(); // The SCC is not necessarily topologically sorted to have the supertypes - // come first. Fix that. + // and described types come first. Fix that. std::vector sccTypes(scc.begin(), scc.end()); - auto deps = createSubtypeGraph(sccTypes); + auto deps = createTypeOrderGraph(sccTypes); auto permutation = *TopologicalOrders(deps).begin(); groups.back().group.resize(sccTypes.size()); for (Index i = 0; i < sccTypes.size(); ++i) { diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index 522ee45bb3e..5541d8b72a1 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -64,15 +64,17 @@ template struct RecGroupComparator { if (a.isOpen() != b.isOpen()) { return a.isOpen() < b.isOpen() ? LT : GT; } - auto aSuper = a.getDeclaredSuperType(); - auto bSuper = b.getDeclaredSuperType(); - if (aSuper.has_value() != bSuper.has_value()) { - return aSuper.has_value() < bSuper.has_value() ? LT : GT; + if (auto cmp = compare(a.getDeclaredSuperType(), b.getDeclaredSuperType()); + cmp != EQ) { + return cmp; } - if (aSuper) { - if (auto cmp = compare(*aSuper, *bSuper); cmp != EQ) { - return cmp; - } + if (auto cmp = compare(a.getDescriptorType(), b.getDescriptorType()); + cmp != EQ) { + return cmp; + } + if (auto cmp = compare(a.getDescribedType(), b.getDescribedType()); + cmp != EQ) { + return cmp; } auto aKind = a.getKind(); auto bKind = b.getKind(); @@ -202,6 +204,16 @@ template struct RecGroupComparator { // comparator. return compareTypes(a, b); } + + Comparison compare(std::optional a, std::optional b) { + if (a.has_value() != b.has_value()) { + return a.has_value() < b.has_value() ? LT : GT; + } + if (a) { + return compare(*a, *b); + } + return EQ; + } }; // Deduction guide to satisfy -Wctad-maybe-unsupported. @@ -227,11 +239,9 @@ struct RecGroupHasher { size_t hashDefinition(HeapType type) { size_t digest = wasm::hash(type.isShared()); wasm::rehash(digest, type.isOpen()); - auto super = type.getDeclaredSuperType(); - wasm::rehash(digest, super.has_value()); - if (super) { - hash_combine(digest, hash(*super)); - } + hash_combine(digest, hash(type.getDeclaredSuperType())); + hash_combine(digest, hash(type.getDescriptorType())); + hash_combine(digest, hash(type.getDescribedType())); auto kind = type.getKind(); // Mix in very random numbers to differentiate the kinds. switch (kind) { @@ -326,6 +336,14 @@ struct RecGroupHasher { wasm::rehash(digest, type.getID()); return digest; } + + size_t hash(std::optional type) { + size_t digest = wasm::hash(type.has_value()); + if (type) { + hash_combine(digest, hash(*type)); + } + return digest; + } }; Comparison compareComparable(const ComparableRecGroupShape& a, diff --git a/test/lit/passes/minimize-rec-groups-desc.wast b/test/lit/passes/minimize-rec-groups-desc.wast new file mode 100644 index 00000000000..8d62460e172 --- /dev/null +++ b/test/lit/passes/minimize-rec-groups-desc.wast @@ -0,0 +1,203 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --minimize-rec-groups -S -o - | filecheck %s + +;; Independent SCCs of described/descriptor types are separated. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $a (descriptor $a.desc (struct))) + (type $a (descriptor $a.desc (struct))) + ;; CHECK: (type $a.desc (describes $a (struct))) + (type $a.desc (describes $a (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $b (descriptor $b.desc (struct (field i32)))) + (type $b (descriptor $b.desc (struct (field i32)))) + ;; CHECK: (type $b.desc (describes $b (struct))) + (type $b.desc (describes $b (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $c (descriptor $c.desc (struct (field i64)))) + (type $c (descriptor $c.desc (struct (field i64)))) + ;; CHECK: (type $c.desc (describes $c (struct))) + (type $c.desc (describes $c (struct))) + ) + ;; CHECK: (global $a (ref null $a) (ref.null none)) + (global $a (ref null $a) (ref.null none)) + ;; CHECK: (global $b (ref null $b) (ref.null none)) + (global $b (ref null $b) (ref.null none)) + ;; CHECK: (global $c (ref null $c) (ref.null none)) + (global $c (ref null $c) (ref.null none)) +) + +;; Same as above, but there are three types in each chain. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $a.1 (descriptor $a.2 (struct))) + (type $a.1 (descriptor $a.2 (struct))) + ;; CHECK: (type $a.2 (describes $a.1 (descriptor $a.3 (struct)))) + (type $a.2 (describes $a.1 (descriptor $a.3 (struct)))) + ;; CHECK: (type $a.3 (describes $a.2 (struct))) + (type $a.3 (describes $a.2 (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $b.1 (descriptor $b.2 (struct (field i32)))) + (type $b.1 (descriptor $b.2 (struct (field i32)))) + ;; CHECK: (type $b.2 (describes $b.1 (descriptor $b.3 (struct)))) + (type $b.2 (describes $b.1 (descriptor $b.3 (struct)))) + ;; CHECK: (type $b.3 (describes $b.2 (struct))) + (type $b.3 (describes $b.2 (struct))) + ) + + ;; CHECK: (global $a (ref null $a.1) (ref.null none)) + (global $a (ref null $a.1) (ref.null none)) + ;; CHECK: (global $b (ref null $b.1) (ref.null none)) + (global $b (ref null $b.1) (ref.null none)) +) + +;; Now the SCCs all have the same shape. The types cannot be reordered because +;; described types must precede their descriptors, so we must use brands. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $a (descriptor $a.desc (struct))) + (type $a (descriptor $a.desc (struct))) + ;; CHECK: (type $a.desc (describes $a (struct))) + (type $a.desc (describes $a (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $2 (struct)) + + ;; CHECK: (type $b (descriptor $b.desc (struct))) + (type $b (descriptor $b.desc (struct))) + ;; CHECK: (type $b.desc (describes $b (struct))) + (type $b.desc (describes $b (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $c (descriptor $c.desc (struct))) + (type $c (descriptor $c.desc (struct))) + ;; CHECK: (type $6 (struct)) + + ;; CHECK: (type $c.desc (describes $c (struct))) + (type $c.desc (describes $c (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $d (descriptor $d.desc (struct))) + (type $d (descriptor $d.desc (struct))) + ;; CHECK: (type $d.desc (describes $d (struct))) + (type $d.desc (describes $d (struct))) + ;; CHECK: (type $10 (struct)) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $11 (array (mut i8))) + + ;; CHECK: (type $e (descriptor $e.desc (struct))) + (type $e (descriptor $e.desc (struct))) + ;; CHECK: (type $e.desc (describes $e (struct))) + (type $e.desc (describes $e (struct))) + ) + + ;; CHECK: (global $a (ref null $a) (ref.null none)) + (global $a (ref null $a) (ref.null none)) + ;; CHECK: (global $b (ref null $b) (ref.null none)) + (global $b (ref null $b) (ref.null none)) + ;; CHECK: (global $c (ref null $c) (ref.null none)) + (global $c (ref null $c) (ref.null none)) + ;; CHECK: (global $d (ref null $d) (ref.null none)) + (global $d (ref null $d) (ref.null none)) + ;; CHECK: (global $e (ref null $e) (ref.null none)) + (global $e (ref null $e) (ref.null none)) +) + +;; The SCCs here contain an additional type that can be reordered around the +;; described and descriptor types to differentiate the rec groups. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $a.other (struct (field (ref $a.desc)))) + + ;; CHECK: (type $a (descriptor $a.desc (struct (field (ref $a.other))))) + (type $a (descriptor $a.desc (struct (ref $a.other)))) + ;; CHECK: (type $a.desc (describes $a (struct))) + (type $a.desc (describes $a (struct))) + (type $a.other (struct (ref $a.desc))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $b (descriptor $b.desc (struct (field (ref $b.other))))) + (type $b (descriptor $b.desc (struct (ref $b.other)))) + ;; CHECK: (type $b.other (struct (field (ref $b.desc)))) + + ;; CHECK: (type $b.desc (describes $b (struct))) + (type $b.desc (describes $b (struct))) + (type $b.other (struct (ref $b.desc))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $c (descriptor $c.desc (struct (field (ref $c.other))))) + (type $c (descriptor $c.desc (struct (ref $c.other)))) + ;; CHECK: (type $c.desc (describes $c (struct))) + (type $c.desc (describes $c (struct))) + ;; CHECK: (type $c.other (struct (field (ref $c.desc)))) + (type $c.other (struct (ref $c.desc))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $9 (struct)) + + ;; CHECK: (type $d.other (struct (field (ref $d.desc)))) + + ;; CHECK: (type $d (descriptor $d.desc (struct (field (ref $d.other))))) + (type $d (descriptor $d.desc (struct (ref $d.other)))) + ;; CHECK: (type $d.desc (describes $d (struct))) + (type $d.desc (describes $d (struct))) + (type $d.other (struct (ref $d.desc))) + ) + + ;; CHECK: (global $a (ref null $a) (ref.null none)) + (global $a (ref null $a) (ref.null none)) + ;; CHECK: (global $b (ref null $b) (ref.null none)) + (global $b (ref null $b) (ref.null none)) + ;; CHECK: (global $c (ref null $c) (ref.null none)) + (global $c (ref null $c) (ref.null none)) + ;; CHECK: (global $d (ref null $d) (ref.null none)) + (global $d (ref null $d) (ref.null none)) +) + +;; Here we have two chains in the same SCC. The types can be reordered around +;; types in the other chain to differentiate the SCCs. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $a.2 (descriptor $a.2.desc (struct (field (ref $a.1))))) + + ;; CHECK: (type $a.1 (descriptor $a.1.desc (struct (field (ref $a.2))))) + (type $a.1 (descriptor $a.1.desc (struct (field (ref $a.2))))) + ;; CHECK: (type $a.2.desc (describes $a.2 (struct))) + + ;; CHECK: (type $a.1.desc (describes $a.1 (struct))) + (type $a.1.desc (describes $a.1 (struct))) + (type $a.2 (descriptor $a.2.desc (struct (field (ref $a.1))))) + (type $a.2.desc (describes $a.2 (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $b.2 (descriptor $b.2.desc (struct (field (ref $b.1))))) + + ;; CHECK: (type $b.1 (descriptor $b.1.desc (struct (field (ref $b.2))))) + (type $b.1 (descriptor $b.1.desc (struct (field (ref $b.2))))) + ;; CHECK: (type $b.1.desc (describes $b.1 (struct))) + (type $b.1.desc (describes $b.1 (struct))) + (type $b.2 (descriptor $b.2.desc (struct (field (ref $b.1))))) + ;; CHECK: (type $b.2.desc (describes $b.2 (struct))) + (type $b.2.desc (describes $b.2 (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $c.2 (descriptor $c.2.desc (struct (field (ref $c.1))))) + + ;; CHECK: (type $c.2.desc (describes $c.2 (struct))) + + ;; CHECK: (type $c.1 (descriptor $c.1.desc (struct (field (ref $c.2))))) + (type $c.1 (descriptor $c.1.desc (struct (field (ref $c.2))))) + ;; CHECK: (type $c.1.desc (describes $c.1 (struct))) + (type $c.1.desc (describes $c.1 (struct))) + (type $c.2 (descriptor $c.2.desc (struct (field (ref $c.1))))) + (type $c.2.desc (describes $c.2 (struct))) + ) + + ;; CHECK: (global $a (ref null $a.1) (ref.null none)) + (global $a (ref null $a.1) (ref.null none)) + ;; CHECK: (global $b (ref null $b.1) (ref.null none)) + (global $b (ref null $b.1) (ref.null none)) + ;; CHECK: (global $c (ref null $c.1) (ref.null none)) + (global $c (ref null $c.1) (ref.null none)) +) From db7e9fc0eab33ce10a438761ff4463ff205b4493 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 23 Jun 2025 18:19:08 +0200 Subject: [PATCH 556/622] Add CMakeUserPresets.json to .gitignore (#7673) This is a standard CMake file used to configure builds in IDEs. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 28caf910140..3d54a5ae5bd 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ CMakeFiles /emcc-build compile_commands.json test/lit/lit.site.cfg.py +CMakeUserPresets.json # files related to bulding in-tree on windows /.vs/ From 1898ec3bd78365980a6b808d0d4b9917ea677768 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 23 Jun 2025 23:46:55 +0200 Subject: [PATCH 557/622] Add Testing/ and options-pinned.h to gitignore (#7674) Testing/ is a directory generated by the vscode cmake extension and options-pined.h is generated as part of the abseil build when fuzztest is enabled. --- .gitignore | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3d54a5ae5bd..1399b6622d4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ tags # autogenerated during the build /src/passes/WasmIntrinsics.cpp +# generated by Abseil when building fuzztest +options-pinned.h + # File generated by build-js.sh /out/ @@ -45,10 +48,13 @@ CMakeUserPresets.json # macOS .DS_Store -# files related to VS Code +# files related to VSCode /.history /.vscode +# Generated by VSCode CMake extension +/Testing + # files related to Emsdk installation .emsdk_version From c1a484e95d6bd9b504e4b17e37f1d89d2dbd4c42 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 24 Jun 2025 01:23:31 +0200 Subject: [PATCH 558/622] [Custom Descriptors] Interpret ref.get_desc (#7675) Add a descriptor to the GCData stored in a reference value. The descriptor itself is another literal representing either a null if there is no descriptor or otherwise a reference to the descriptor. Update the interpretation of struct.new to set the descriptor when allocating types with descriptors and then retrieve the descriptor when interpreting ref.get_desc. --- src/literal.h | 9 +++++++-- src/wasm-interpreter.h | 31 +++++++++++++++++++++++++++---- src/wasm/wasm.cpp | 3 +++ test/spec/ref.get_desc.wast | 23 +++++++++++++++++++++++ 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/literal.h b/src/literal.h index 357becab596..10f2158a403 100644 --- a/src/literal.h +++ b/src/literal.h @@ -775,8 +775,13 @@ struct GCData { // The element or field values. Literals values; - GCData(HeapType type, Literals&& values) - : type(type), values(std::move(values)) {} + // The descriptor, if it exists, or null. + Literal desc; + + GCData(HeapType type, + Literals&& values, + const Literal& desc = Literal::makeNull(HeapType::none)) + : type(type), values(std::move(values)), desc(desc) {} }; // The data of a (ref exn) literal. diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 29334d90130..a2bd995e243 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -199,9 +199,11 @@ class ExpressionRunner : public OverriddenVisitor { // this function in LSan. // // This consumes the input |data| entirely. - Literal makeGCData(Literals&& data, Type type) { + Literal makeGCData(Literals&& data, + Type type, + Literal desc = Literal::makeNull(HeapType::none)) { auto allocation = - std::make_shared(type.getHeapType(), std::move(data)); + std::make_shared(type.getHeapType(), std::move(data), desc); #if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer) // GC data with cycles will leak, since shared_ptrs do not handle cycles. // Binaryen is generally not used in long-running programs so we just ignore @@ -1673,7 +1675,15 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitRefGetDesc(RefGetDesc* curr) { NOTE_ENTER("RefGetDesc"); - WASM_UNREACHABLE("unimplemented"); + Flow ref = self()->visit(curr->ref); + if (ref.breaking()) { + return ref; + } + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + return data->desc; } Flow visitBrOn(BrOn* curr) { NOTE_ENTER("BrOn"); @@ -1742,6 +1752,12 @@ class ExpressionRunner : public OverriddenVisitor { return value; } } + if (curr->descriptor) { + auto value = self()->visit(curr->descriptor); + if (value.breaking()) { + return value; + } + } WASM_UNREACHABLE("unreachable but no unreachable child"); } auto heapType = curr->type.getHeapType(); @@ -1759,7 +1775,14 @@ class ExpressionRunner : public OverriddenVisitor { data[i] = truncateForPacking(value.getSingleValue(), field); } } - return makeGCData(std::move(data), curr->type); + if (!curr->descriptor) { + return makeGCData(std::move(data), curr->type); + } + auto desc = self()->visit(curr->descriptor); + if (desc.breaking()) { + return desc; + } + return makeGCData(std::move(data), curr->type, desc.getSingleValue()); } Flow visitStructGet(StructGet* curr) { NOTE_ENTER("StructGet"); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index c95ee8a296f..d2301ac2bf8 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1216,6 +1216,9 @@ void StructNew::finalize() { if (handleUnreachableOperands(this)) { return; } + if (descriptor && descriptor->type == Type::unreachable) { + type = Type::unreachable; + } } void StructGet::finalize() { diff --git a/test/spec/ref.get_desc.wast b/test/spec/ref.get_desc.wast index 4c01209ad3e..b5d21855fc6 100644 --- a/test/spec/ref.get_desc.wast +++ b/test/spec/ref.get_desc.wast @@ -3,6 +3,12 @@ (type $struct (descriptor $desc (struct))) (type $desc (describes $struct (struct))) ) + + (global $desc1 (ref (exact $desc)) (struct.new $desc)) + (global $desc2 (ref (exact $desc)) (struct.new $desc)) + (global $struct1 (ref $struct) (struct.new $struct (global.get $desc1))) + (global $struct2 (ref $struct) (struct.new $struct (global.get $desc2))) + (func $unreachable (param $struct (ref null $struct)) (result (ref $desc)) (ref.get_desc $struct (unreachable) @@ -23,8 +29,25 @@ (local.get $struct) ) ) + + (func (export "check-descs") (result i32) + (i32.and + (i32.and + (ref.eq (ref.get_desc $struct (global.get $struct1)) (global.get $desc1)) + (ref.eq (ref.get_desc $struct (global.get $struct2)) (global.get $desc2)) + ) + (i32.eqz (ref.eq (global.get $desc1) (global.get $desc2))) + ) + ) + + (func (export "desc-trap") (result (ref $struct)) + (struct.new $struct (unreachable)) + ) ) +(assert_return (invoke "check-descs") (i32.const 1)) +(assert_trap (invoke "desc-trap") "unreachable") + (assert_invalid (module (rec From da6c937ca7fc64f45b5c716a38e4b83e1c225d1f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 24 Jun 2025 03:17:30 +0200 Subject: [PATCH 559/622] Fix UB in OptimizeInstructions (#7676) When optimizing `add` operations, OptimizeInstructions calculates the max bits of the operands and shifts that value to produce a bit mask. When an operand is unreachable, it is arbitrarily considered to have a max bits value of 64, even for 32-bit operations. This leads to UB in the shift. Fix the problem by returning before the shift in this case. --- src/passes/OptimizeInstructions.cpp | 5 +- test/lit/passes/optimize-instructions-gc.wast | 48 ++++++++++++++----- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 0b1280a45d1..05938713413 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -3604,11 +3604,14 @@ struct OptimizeInstructions // Check left's max bits and right is constant. auto leftMaxBits = Bits::getMaxBits(left, this); uint64_t maskLeft; - if (!left->type.isNumber() || leftMaxBits == left->type.getByteSize() * 8) { + // leftMaxBits may be greater than size if the lhs is unreachable but has + // not been refinalized yet. + if (!left->type.isNumber() || leftMaxBits >= left->type.getByteSize() * 8) { // If we know nothing useful about the bits on the left, // we cannot optimize. return nullptr; } else { + assert(leftMaxBits < 64); maskLeft = (1ULL << leftMaxBits) - 1; } if (auto* c = right->dynCast()) { diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index a571dfa6138..d9010a2c445 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -51,7 +51,7 @@ ;; CHECK: (type $struct_i64 (func (param structref) (result i64))) (type $struct_i64 (func (param (ref null struct)) (result i64))) - ;; CHECK: (import "env" "get-i32" (func $get-i32 (type $9) (result i32))) + ;; CHECK: (import "env" "get-i32" (func $get-i32 (type $8) (result i32))) (import "env" "get-i32" (func $get-i32 (result i32))) ;; These functions test if an `if` with subtyped arms is correctly folded @@ -1418,7 +1418,7 @@ ) ) - ;; CHECK: (func $incompatible-test-heap-types-nonnullable (type $8) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-test-heap-types-nonnullable (type $9) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) @@ -1460,7 +1460,7 @@ ) ) - ;; CHECK: (func $incompatible-test-heap-types-nullable (type $8) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-test-heap-types-nullable (type $9) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) @@ -1501,7 +1501,7 @@ ) ) - ;; CHECK: (func $incompatible-test-heap-types-unreachable (type $8) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-test-heap-types-unreachable (type $9) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) @@ -2641,7 +2641,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-heap-types-nonnullable (type $8) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-cast-heap-types-nonnullable (type $9) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result (ref any)) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -2677,7 +2677,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-heap-types-nullable (type $8) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-cast-heap-types-nullable (type $9) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (block (result nullref) @@ -2709,7 +2709,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-heap-types-unreachable (type $8) (param $anyref anyref) (result anyref) + ;; CHECK: (func $incompatible-cast-heap-types-unreachable (type $9) (param $anyref anyref) (result anyref) ;; CHECK-NEXT: (block $outer (result anyref) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -2966,7 +2966,7 @@ ) ) - ;; CHECK: (func $non-null-bottom-ref-test (type $9) (result i32) + ;; CHECK: (func $non-null-bottom-ref-test (type $8) (result i32) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $0 @@ -2989,7 +2989,7 @@ ) ) - ;; CHECK: (func $non-null-bottom-ref-test-notee (type $9) (result i32) + ;; CHECK: (func $non-null-bottom-ref-test-notee (type $8) (result i32) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (loop (result (ref nofunc)) @@ -3009,7 +3009,7 @@ ) ) - ;; CHECK: (func $non-null-bottom-test (type $9) (result i32) + ;; CHECK: (func $non-null-bottom-test (type $8) (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $non-null-bottom-cast) ;; CHECK-NEXT: ) @@ -3079,7 +3079,7 @@ ) ) - ;; CHECK: (func $ref.test-then-optimizeAddedConstants (type $9) (result i32) + ;; CHECK: (func $ref.test-then-optimizeAddedConstants (type $8) (result i32) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -3691,4 +3691,30 @@ ) ) ) + + ;; Regression test for UB when analyzing bits. + ;; CHECK: (func $unreachable-bits (type $8) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $unreachable-bits (result i32) + ;; When this is optimized, the unreachable left hand side is arbitrarily + ;; considered to have 64 bits. This should not lead to UB. + (i32.and + ;; This will be optimized to an unreachable block. + (ref.test (ref none) + (ref.as_non_null + (ref.null none) + ) + ) + (i32.const 0) + ) + ) ) From 9bd8871876ad39fa371c90d0f25261eae3e12a97 Mon Sep 17 00:00:00 2001 From: Ruiyang Xu <91814026+xuruiyang2002@users.noreply.github.com> Date: Wed, 25 Jun 2025 00:19:58 +0800 Subject: [PATCH 560/622] RemoveUnusedBrs: Optimize breaks with constant fallthroughs (#7639) Fixes: #7637 --- src/passes/RemoveUnusedBrs.cpp | 32 +++++ test/lit/passes/remove-unused-brs.wast | 114 +++++++++++++++++- .../remove-unused-brs_all-features.wast | 6 +- .../remove-unused-brs_enable-multivalue.txt | 72 ++++++----- .../remove-unused-brs_enable-multivalue.wast | 72 ++++++----- .../remove-unused-brs_shrink-level=1.txt | 35 +++--- .../remove-unused-brs_shrink-level=1.wast | 36 +++--- ...s_shrink-level=1_ignore-implicit-traps.txt | 29 ++--- ..._shrink-level=1_ignore-implicit-traps.wast | 29 ++--- 9 files changed, 292 insertions(+), 133 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 6f20cfd63af..64c68f2354e 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -24,6 +24,8 @@ #include "ir/effects.h" #include "ir/gc-type-utils.h" #include "ir/literal-utils.h" +#include "ir/localize.h" +#include "ir/properties.h" #include "ir/utils.h" #include "parsing.h" #include "pass.h" @@ -1881,6 +1883,36 @@ struct RemoveUnusedBrs : public WalkerPass> { start = end; } } + + void visitBreak(Break* curr) { + if (!curr->condition) { + return; + } + auto* value = Properties::getFallthrough( + curr->condition, passOptions, *getModule()); + // Optimize if condition's fallthrough is a constant. + if (auto* c = value->dynCast()) { + ChildLocalizer localizer( + curr, getFunction(), *getModule(), passOptions); + auto* block = localizer.getChildrenReplacement(); + if (c->value.geti32()) { + // the branch is always taken, make it unconditional + curr->condition = nullptr; + curr->type = Type::unreachable; + block->list.push_back(curr); + block->finalize(); + // The type changed, so refinalize. + refinalize = true; + } else { + // the branch is never taken, allow control flow to fall through + if (curr->value) { + block->list.push_back(curr->value); + block->finalize(); + } + } + replaceCurrent(block); + } + } }; FinalOptimizer finalOptimizer(getPassOptions()); finalOptimizer.setModule(getModule()); diff --git a/test/lit/passes/remove-unused-brs.wast b/test/lit/passes/remove-unused-brs.wast index ed7fd168148..da8c61cc941 100644 --- a/test/lit/passes/remove-unused-brs.wast +++ b/test/lit/passes/remove-unused-brs.wast @@ -5,7 +5,7 @@ (module ;; Regression test in which we need to calculate a proper LUB. - ;; CHECK: (func $selectify-fresh-lub (type $4) (param $x i32) (result anyref) + ;; CHECK: (func $selectify-fresh-lub (type $5) (param $x i32) (result anyref) ;; CHECK-NEXT: (select (result i31ref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.i31 @@ -340,6 +340,106 @@ ) ) + ;; CHECK: (func $restructure-br_if-constant-branch-1 (type $3) (param $x i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $x (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $x0 (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-br_if-constant-branch-1 (param $x i32) + ;; We can see a nonzero constant falls through the tee, so this br always happens. + (drop + (block $x (result i32) + (drop + (br_if $x + (i32.const 10) + (local.tee $x + (i32.const 42) + ) + ) + ) + (i32.const 20) + ) + ) + ;; We can see the zero condition, so just let the control flow fall through. + (drop + (block $x (result i32) + (drop + (br_if $x + (i32.const 10) + (i32.const 0) + ) + ) + (i32.const 20) + ) + ) + ) + ;; CHECK: (func $restructure-br_if-constant-branch-2 (type $3) (param $x i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (block $x + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $x0 + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-br_if-constant-branch-2 (param $x i32) + ;; as before, but now there is no value. + (block $x + (br_if $x + (local.tee $x + (i32.const 1) + ) + ) + ) + (block $x + (br_if $x + (local.tee $x + (i32.const 0) + ) + ) + ) + ) + + ;; CHECK: (func $restructure-br_if-value-redundant-in-block-tail-1 (type $2) (result i32) ;; CHECK-NEXT: (block $parent (result i32) ;; CHECK-NEXT: (call $nothing) @@ -473,7 +573,7 @@ ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: (local.tee $temp - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -489,7 +589,7 @@ (br_if $block (local.get $temp) (local.tee $temp - (i32.const 1) + (local.get $temp) ) ) ) @@ -498,8 +598,9 @@ ) ;; CHECK: (func $restructure-select-no-multivalue (type $1) + ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (tuple.drop 2 - ;; CHECK-NEXT: (block $block (type $3) (result i32 i32) + ;; CHECK-NEXT: (block $block (type $4) (result i32 i32) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (tuple.make 2 @@ -508,7 +609,7 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (tuple.make 2 @@ -519,6 +620,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $restructure-select-no-multivalue + (local $x i32) (tuple.drop 2 (block $block (result i32 i32) (tuple.drop 2 @@ -533,7 +635,7 @@ (i32.const 2) ) ) - (i32.const 3) + (local.get $x) ) ) (tuple.make 2 diff --git a/test/lit/passes/remove-unused-brs_all-features.wast b/test/lit/passes/remove-unused-brs_all-features.wast index e2d89a5ead5..87c1b2ae197 100644 --- a/test/lit/passes/remove-unused-brs_all-features.wast +++ b/test/lit/passes/remove-unused-brs_all-features.wast @@ -64,9 +64,10 @@ ) ;; CHECK: (func $test-prefinalize (type $4) (result f64) + ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop (result f64) ;; CHECK-NEXT: (if (result f64) - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (f64.const 0) ;; CHECK-NEXT: ) @@ -85,12 +86,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test-prefinalize (result f64) + (local $x i32) (loop $loop (result f64) (block $block (result f64) (drop (br_if $block (f64.const 0) - (i32.const 1) + (local.get $x) ) ) (if diff --git a/test/passes/remove-unused-brs_enable-multivalue.txt b/test/passes/remove-unused-brs_enable-multivalue.txt index a20f2905bf0..321b061e524 100644 --- a/test/passes/remove-unused-brs_enable-multivalue.txt +++ b/test/passes/remove-unused-brs_enable-multivalue.txt @@ -63,7 +63,7 @@ (func $b6 (param $i1 i32) (block $topmost (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -74,7 +74,7 @@ (i32.const 0) ) (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -83,7 +83,7 @@ (block $topmost (block $inner (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -92,7 +92,7 @@ (block $topmost (block $inner (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -105,7 +105,7 @@ (i32.const 0) ) (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -119,7 +119,7 @@ (i32.const 0) ) (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -157,6 +157,7 @@ ) ) (func $b13 (result i32) + (local $x i32) (block $topmost (result i32) (if (i32.const 1) @@ -167,8 +168,8 @@ ) (drop (br_if $topmost - (i32.const 1) - (i32.const 1) + (local.get $x) + (local.get $x) ) ) ) @@ -224,9 +225,10 @@ ) ) (func $b15 + (local $x i32) (block $topmost (br_if $topmost - (i32.const 17) + (local.get $x) ) ) ) @@ -502,6 +504,7 @@ ) ) (func $loops + (local $x i32) (loop $in (block $out (br_if $in @@ -517,14 +520,14 @@ (loop (block $out0 (br_if $out0 - (i32.const 0) + (local.get $x) ) ) ) (loop $in1 (block $out1 (br_if $out1 - (i32.const 0) + (local.get $x) ) ) ) @@ -537,13 +540,13 @@ (loop $in4 (if (i32.eqz - (i32.const 0) + (local.get $x) ) (then (block $out3 (nop) (br_if $in4 - (i32.const 1) + (local.get $x) ) ) ) @@ -552,7 +555,7 @@ (loop $in5 (block $out4 (br_if $in5 - (i32.const 0) + (local.get $x) ) ) ) @@ -764,7 +767,7 @@ (loop $in19 (block $out18 (drop - (i32.const 0) + (local.get $x) ) (br $in19) ) @@ -772,10 +775,10 @@ (loop $in-not (block $out-not (br_if $out-not - (i32.const -1) + (local.get $x) ) (br_if $out-not - (i32.const 0) + (local.get $x) ) (call $loops) (br $in-not) @@ -833,16 +836,17 @@ ) ) (func $threading + (local $x i32) (drop (block $value-out (result i32) (block $value-in (result i32) (block $out (block $in (br_if $out - (i32.const 1) + (local.get $x) ) (br_if $out - (i32.const 2) + (local.get $x) ) (br $value-in (i32.const 3) @@ -858,7 +862,7 @@ (block $stack3 (block $stack4 (br_if $stack1 - (i32.const 1) + (local.get $x) ) (unreachable) ) @@ -884,7 +888,7 @@ ) (else (br_if $leave - (i32.const 1) + (local.get $x) ) ) ) @@ -905,7 +909,7 @@ (local.get $x) (then (br_if $leave - (i32.const 1) + (local.get $x) ) ) (else @@ -993,9 +997,10 @@ ) ) (func $iffify + (local $x i32) (if (i32.eqz - (i32.const 0) + (local.get $x) ) (then (block $yes @@ -1011,7 +1016,7 @@ ) (block $no (br_if $no - (i32.const 0) + (local.get $x) ) (drop (i32.const 1) @@ -1023,7 +1028,7 @@ ) (block $no2 (br_if $no2 - (i32.const 0) + (local.get $x) ) ) (block $no3 @@ -1038,7 +1043,7 @@ (block $no5 (block $no4 (br_if $no5 - (i32.const 0) + (local.get $x) ) (drop (i32.const 1) @@ -1095,7 +1100,7 @@ (block $label$0 (result i32) (block $label$1 (br_if $label$1 - (i32.const 607395945) + (local.get $1) ) (br_if $label$1 (i32.load offset=3 align=1 @@ -1109,7 +1114,7 @@ (i32.const 1628075109) ) ) - (i32.const 1764950569) + (local.get $1) ) (f32.const 1.1910939690100655e-32) (i32.const 1628057906) @@ -1924,9 +1929,10 @@ ) ) (func $same-target-br_if-and-br + (local $x i32) (block $x (drop - (i32.const 0) + (local.get $x) ) (br $x) (unreachable) @@ -2312,9 +2318,10 @@ ) ) (func $if-block-br + (local $x i32) (block $label (br_if $label - (i32.const 1) + (local.get $x) ) ) ) @@ -2531,6 +2538,7 @@ ) ) (func $refinalize-need-br-value (result i32) + (local $x i32) (loop $label$3 (result i32) (block $label$6 (result i32) (block $label$10 @@ -2539,7 +2547,7 @@ (br_if $label$3 (block $label$530 (result i32) (br_if $label$503 - (i32.const 0) + (local.get $x) ) (i32.const 0) ) @@ -2898,7 +2906,7 @@ (then (br_if $label$1 (local.tee $0 - (i32.const -7) + (local.get $0) ) ) ) diff --git a/test/passes/remove-unused-brs_enable-multivalue.wast b/test/passes/remove-unused-brs_enable-multivalue.wast index 0deff0db881..acbd3781ab9 100644 --- a/test/passes/remove-unused-brs_enable-multivalue.wast +++ b/test/passes/remove-unused-brs_enable-multivalue.wast @@ -59,7 +59,7 @@ (func $b6 (type $0) (param $i1 i32) (block $topmost (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -70,7 +70,7 @@ (i32.const 0) ) (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -79,7 +79,7 @@ (block $topmost (block $inner (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -88,7 +88,7 @@ (block $topmost (block $inner (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -101,7 +101,7 @@ (i32.const 0) ) (br_if $topmost - (i32.const 1) + (local.get $i1) ) ) ) @@ -115,7 +115,7 @@ (i32.const 0) ) (br_if $inner - (i32.const 1) + (local.get $i1) ) ) ) @@ -155,6 +155,7 @@ ) ) (func $b13 (type $2) (result i32) + (local $x i32) (block $topmost (result i32) (if (i32.const 1) @@ -165,8 +166,8 @@ ) (drop (br_if $topmost - (i32.const 1) - (i32.const 1) + (local.get $x) + (local.get $x) ) ) ) @@ -226,9 +227,10 @@ ) ) (func $b15 (type $1) + (local $x i32) (block $topmost (if - (i32.const 17) + (local.get $x) (then (br $topmost) ) @@ -538,6 +540,7 @@ ) ) (func $loops + (local $x i32) (loop $in (block $out (if (i32.const 0) (then (br $out))) @@ -549,13 +552,13 @@ ) (loop (block $out - (if (i32.const 0) (then (br $out))) + (if (local.get $x) (then (br $out))) (br $out) ) ) (loop $in (block $out - (if (i32.const 0) (then (br $out))) + (if (local.get $x) (then (br $out))) (br $out) ) ) @@ -565,13 +568,13 @@ ) (loop $in (block $out - (if (i32.const 0) (then (br $out))) - (br_if $in (i32.const 1)) + (if (local.get $x) (then (br $out))) + (br_if $in (local.get $x)) ) ) (loop $in (block $out - (if (i32.const 0) (then (br $in))) + (if (local.get $x) (then (br $in))) (br $out) ) ) @@ -755,14 +758,14 @@ ) (loop $in (block $out - (br_if $in (i32.const 0)) + (br_if $in (local.get $x)) (br $in) ) ) (loop $in-not ;; do NOT if-ify, the block can't be removed (block $out-not - (br_if $out-not (i32.const -1)) - (br_if $out-not (i32.const 0)) + (br_if $out-not (local.get $x)) + (br_if $out-not (local.get $x)) (call $loops) (br $in-not) ) @@ -790,17 +793,18 @@ ) ) (func $threading + (local $x i32) (drop (block $value-out (result i32) (block $value-in (result i32) (block $out (block $in - (if (i32.const 1) + (if (local.get $x) (then (br $in) ) ) - (br_if $in (i32.const 2)) + (br_if $in (local.get $x)) (br $value-in (i32.const 3)) ) (br $out) @@ -813,7 +817,7 @@ (block $stack2 (block $stack3 (block $stack4 - (if (i32.const 1) + (if (local.get $x) (then (br $stack4) ) @@ -842,7 +846,7 @@ ) ) (else - (br_if $leave (i32.const 1)) + (br_if $leave (local.get $x)) ) ) (unreachable) @@ -858,7 +862,7 @@ (if (local.get $x) (then - (br_if $leave (i32.const 1)) + (br_if $leave (local.get $x)) ) (else (br $out @@ -935,16 +939,17 @@ ) ) (func $iffify + (local $x i32) (block $yes (br_if $yes - (i32.const 0) + (local.get $x) ) (drop (i32.const 1)) (drop (i32.const 2)) ) (block $no (br_if $no - (i32.const 0) + (local.get $x) ) (drop (i32.const 1)) (br $no) @@ -952,7 +957,7 @@ ) (block $no2 (br_if $no2 - (i32.const 0) + (local.get $x) ) ) (block $no3 @@ -963,7 +968,7 @@ (block $no5 (block $no4 (br_if $no5 - (i32.const 0) + (local.get $x) ) (drop (i32.const 1)) (drop (i32.const 2)) @@ -1011,7 +1016,7 @@ (block $label$0 (result i32) (block $label$1 ;; this block has no taken brs, but we can't remove it without removing them first (br_if $label$1 - (i32.const 607395945) + (local.get $1) ) (br_if $label$1 (i32.load16_s offset=3 align=1 @@ -1025,7 +1030,7 @@ (i32.const 1628075109) ) ) - (i32.const 1764950569) + (local.get $1) ) (f32.const 1.1910939690100655e-32) (i32.const 1628057906) @@ -1615,9 +1620,10 @@ ) ) (func $same-target-br_if-and-br + (local $x i32) (block $x (br_if $x - (i32.const 0) + (local.get $x) ) (br $x) (unreachable) @@ -1943,9 +1949,10 @@ ) ) (func $if-block-br + (local $x i32) (block $label (if - (i32.const 1) + (local.get $x) (then (br $label) ) @@ -2160,6 +2167,7 @@ ) ) (func $refinalize-need-br-value (result i32) + (local $x i32) (loop $label$3 (result i32) (block $label$6 (result i32) (block $label$10 @@ -2168,7 +2176,7 @@ (br_if $label$3 (block $label$530 (result i32) (br_if $label$503 ;; while this br does not send a value - (i32.const 0) + (local.get $x) ) (i32.const 0) ) @@ -2569,7 +2577,7 @@ (then (br_if $label$1 (local.tee $0 ;; but here it is *not* ok - (i32.const -7) + (local.get $0) ) ) ) diff --git a/test/passes/remove-unused-brs_shrink-level=1.txt b/test/passes/remove-unused-brs_shrink-level=1.txt index 24696cb8355..127f30508d7 100644 --- a/test/passes/remove-unused-brs_shrink-level=1.txt +++ b/test/passes/remove-unused-brs_shrink-level=1.txt @@ -53,28 +53,29 @@ (i32.const 0) ) (func $join-br_ifs + (local $x i32) (block $out (br_if $out (i32.or - (i32.const 1) - (i32.const 2) + (local.get $x) + (local.get $x) ) ) (nop) (br_if $out - (i32.const 3) + (local.get $x) ) ) (block $out2 (block $out3 (br_if $out2 - (i32.const 1) + (local.get $x) ) (br_if $out3 - (i32.const 2) + (local.get $x) ) (br_if $out2 - (i32.const 3) + (local.get $x) ) ) (unreachable) @@ -82,12 +83,12 @@ (block $out4 (block $out5 (br_if $out4 - (i32.const 1) + (local.get $x) ) (br_if $out5 (i32.or - (i32.const 2) - (i32.const 3) + (local.get $x) + (local.get $x) ) ) (nop) @@ -98,13 +99,13 @@ (block $out7 (br_if $out6 (i32.or - (i32.const 1) - (i32.const 2) + (local.get $x) + (local.get $x) ) ) (nop) (br_if $out7 - (i32.const 3) + (local.get $x) ) ) (unreachable) @@ -113,7 +114,7 @@ (i32.eqz (i32.or (call $b14) - (i32.const 0) + (local.get $x) ) ) (then @@ -125,7 +126,7 @@ ) (block $out80 (br_if $out80 - (i32.const 1) + (local.get $x) ) (br_if $out80 (call $b14) @@ -149,19 +150,21 @@ ) ) (func $br-if-unreachable-pair + (local $x i32) (block $label$14 (br_if $label$14 (unreachable) ) (br_if $label$14 - (i32.const 0) + (local.get $x) ) ) ) (func $br-if-unreachable-pair2 + (local $x i32) (block $label$14 (br_if $label$14 - (i32.const 0) + (local.get $x) ) (br_if $label$14 (unreachable) diff --git a/test/passes/remove-unused-brs_shrink-level=1.wast b/test/passes/remove-unused-brs_shrink-level=1.wast index ce141c43003..00d29376b06 100644 --- a/test/passes/remove-unused-brs_shrink-level=1.wast +++ b/test/passes/remove-unused-brs_shrink-level=1.wast @@ -55,41 +55,42 @@ (i32.const 0) ) (func $join-br_ifs + (local $x i32) (block $out - (br_if $out (i32.const 1)) - (br_if $out (i32.const 2)) - (br_if $out (i32.const 3)) + (br_if $out (local.get $x)) + (br_if $out (local.get $x)) + (br_if $out (local.get $x)) ) (block $out2 (block $out3 - (br_if $out2 (i32.const 1)) - (br_if $out3 (i32.const 2)) - (br_if $out2 (i32.const 3)) + (br_if $out2 (local.get $x)) + (br_if $out3 (local.get $x)) + (br_if $out2 (local.get $x)) ) (unreachable) ) (block $out4 (block $out5 - (br_if $out4 (i32.const 1)) - (br_if $out5 (i32.const 2)) - (br_if $out5 (i32.const 3)) + (br_if $out4 (local.get $x)) + (br_if $out5 (local.get $x)) + (br_if $out5 (local.get $x)) ) (unreachable) ) (block $out6 (block $out7 - (br_if $out6 (i32.const 1)) - (br_if $out6 (i32.const 2)) - (br_if $out7 (i32.const 3)) + (br_if $out6 (local.get $x)) + (br_if $out6 (local.get $x)) + (br_if $out7 (local.get $x)) ) (unreachable) ) (block $out8 (br_if $out8 (call $b14)) ;; side effect - (br_if $out8 (i32.const 0)) + (br_if $out8 (local.get $x)) ) (block $out8 - (br_if $out8 (i32.const 1)) + (br_if $out8 (local.get $x)) (br_if $out8 (call $b14)) ;; side effect ) ) @@ -110,19 +111,21 @@ ) ) (func $br-if-unreachable-pair + (local $x i32) (block $label$14 (br_if $label$14 (unreachable) ) (br_if $label$14 - (i32.const 0) + (local.get $x) ) ) ) (func $br-if-unreachable-pair2 + (local $x i32) (block $label$14 (br_if $label$14 - (i32.const 0) + (local.get $x) ) (br_if $label$14 (unreachable) @@ -190,4 +193,3 @@ (return (i32.const 3)) ) ) - diff --git a/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.txt b/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.txt index 82f76d359c7..461f5bc194b 100644 --- a/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.txt +++ b/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.txt @@ -45,28 +45,29 @@ (i32.const 0) ) (func $join-br_ifs + (local $x i32) (block $out (br_if $out (i32.or - (i32.const 1) - (i32.const 2) + (local.get $x) + (local.get $x) ) ) (nop) (br_if $out - (i32.const 3) + (local.get $x) ) ) (block $out2 (block $out3 (br_if $out2 - (i32.const 1) + (local.get $x) ) (br_if $out3 - (i32.const 2) + (local.get $x) ) (br_if $out2 - (i32.const 3) + (local.get $x) ) ) (unreachable) @@ -74,12 +75,12 @@ (block $out4 (block $out5 (br_if $out4 - (i32.const 1) + (local.get $x) ) (br_if $out5 (i32.or - (i32.const 2) - (i32.const 3) + (local.get $x) + (local.get $x) ) ) (nop) @@ -90,13 +91,13 @@ (block $out7 (br_if $out6 (i32.or - (i32.const 1) - (i32.const 2) + (local.get $x) + (local.get $x) ) ) (nop) (br_if $out7 - (i32.const 3) + (local.get $x) ) ) (unreachable) @@ -105,7 +106,7 @@ (i32.eqz (i32.or (call $b14) - (i32.const 0) + (local.get $x) ) ) (then @@ -117,7 +118,7 @@ ) (block $out80 (br_if $out80 - (i32.const 1) + (local.get $x) ) (br_if $out80 (call $b14) diff --git a/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.wast b/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.wast index 4f15dc9c020..b93cd89925e 100644 --- a/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.wast +++ b/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.wast @@ -55,41 +55,42 @@ (i32.const 0) ) (func $join-br_ifs + (local $x i32) (block $out - (br_if $out (i32.const 1)) - (br_if $out (i32.const 2)) - (br_if $out (i32.const 3)) + (br_if $out (local.get $x)) + (br_if $out (local.get $x)) + (br_if $out (local.get $x)) ) (block $out2 (block $out3 - (br_if $out2 (i32.const 1)) - (br_if $out3 (i32.const 2)) - (br_if $out2 (i32.const 3)) + (br_if $out2 (local.get $x)) + (br_if $out3 (local.get $x)) + (br_if $out2 (local.get $x)) ) (unreachable) ) (block $out4 (block $out5 - (br_if $out4 (i32.const 1)) - (br_if $out5 (i32.const 2)) - (br_if $out5 (i32.const 3)) + (br_if $out4 (local.get $x)) + (br_if $out5 (local.get $x)) + (br_if $out5 (local.get $x)) ) (unreachable) ) (block $out6 (block $out7 - (br_if $out6 (i32.const 1)) - (br_if $out6 (i32.const 2)) - (br_if $out7 (i32.const 3)) + (br_if $out6 (local.get $x)) + (br_if $out6 (local.get $x)) + (br_if $out7 (local.get $x)) ) (unreachable) ) (block $out8 (br_if $out8 (call $b14)) ;; side effect - (br_if $out8 (i32.const 0)) + (br_if $out8 (local.get $x)) ) (block $out8 - (br_if $out8 (i32.const 1)) + (br_if $out8 (local.get $x)) (br_if $out8 (call $b14)) ;; side effect ) ) From 7dbb6704d808c6b07d54bcb5885bacea51a823ab Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 24 Jun 2025 18:32:50 +0200 Subject: [PATCH 561/622] [Custom Descriptors] Interpret descriptor casts (#7677) Descriptor casts must check that the expected descriptor is not null, check whether the cast value is null, and if so, whether the cast allows null values through, and finally must check whether the descriptor on a non-null cast value matches the expected descriptor. This is fairly different than the implementation of a normal cast, but the possible results are the same. Add a new `doDescCast` helper mirroring the existing `doCast` helper and use it to implement the various descriptor casts. --- src/wasm-interpreter.h | 45 +++++++-- test/spec/br_on_cast_desc.wast | 164 +++++++++++++++++++++++++++++++++ test/spec/ref.cast_desc.wast | 98 ++++++++++++++++++++ 3 files changed, 299 insertions(+), 8 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index a2bd995e243..0ae93c4bd71 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1651,6 +1651,36 @@ class ExpressionRunner : public OverriddenVisitor { return typename Cast::Failure{val}; } } + template Cast doDescCast(T* curr) { + Flow ref = self()->visit(curr->ref); + if (ref.breaking()) { + return typename Cast::Breaking{ref}; + } + Flow desc = self()->visit(curr->desc); + if (desc.breaking()) { + return typename Cast::Breaking{ref}; + } + auto expected = desc.getSingleValue().getGCData(); + if (!expected) { + trap("null descriptor"); + } + Literal val = ref.getSingleValue(); + auto data = val.getGCData(); + if (!data) { + // Check whether null is allowed. + if (curr->getCastType().isNullable()) { + return typename Cast::Success{val}; + } else { + return typename Cast::Failure{val}; + } + } + // The cast succeeds if we have the expected descriptor. + if (data->desc.getGCData() == expected) { + return typename Cast::Success{val}; + } else { + return typename Cast::Failure{val}; + } + } Flow visitRefTest(RefTest* curr) { NOTE_ENTER("RefTest"); @@ -1663,7 +1693,7 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitRefCast(RefCast* curr) { NOTE_ENTER("RefCast"); - auto cast = doCast(curr); + auto cast = curr->desc ? doDescCast(curr) : doCast(curr); if (auto* breaking = cast.getBreaking()) { return *breaking; } else if (auto* result = cast.getSuccess()) { @@ -1690,12 +1720,14 @@ class ExpressionRunner : public OverriddenVisitor { // BrOnCast* uses the casting infrastructure, so handle them first. switch (curr->op) { case BrOnCast: - case BrOnCastFail: { - auto cast = doCast(curr); + case BrOnCastFail: + case BrOnCastDesc: + case BrOnCastDescFail: { + auto cast = curr->desc ? doDescCast(curr) : doCast(curr); if (auto* breaking = cast.getBreaking()) { return *breaking; } else if (auto* original = cast.getFailure()) { - if (curr->op == BrOnCast) { + if (curr->op == BrOnCast || curr->op == BrOnCastDesc) { return *original; } else { return Flow(curr->name, *original); @@ -1703,16 +1735,13 @@ class ExpressionRunner : public OverriddenVisitor { } else { auto* result = cast.getSuccess(); assert(result); - if (curr->op == BrOnCast) { + if (curr->op == BrOnCast || curr->op == BrOnCastDesc) { return Flow(curr->name, *result); } else { return *result; } } } - case BrOnCastDesc: - case BrOnCastDescFail: - WASM_UNREACHABLE("TODO"); case BrOnNull: case BrOnNonNull: { // Otherwise we are just checking for null. diff --git a/test/spec/br_on_cast_desc.wast b/test/spec/br_on_cast_desc.wast index 834ddcb1f9a..beddf74950e 100644 --- a/test/spec/br_on_cast_desc.wast +++ b/test/spec/br_on_cast_desc.wast @@ -7,6 +7,13 @@ (type $sub.desc (sub $super.desc (describes $sub (struct)))) ) + (global $super.desc1 (ref (exact $super.desc)) (struct.new $super.desc)) + (global $super.desc2 (ref (exact $super.desc)) (struct.new $super.desc)) + (global $super1 (ref $super) (struct.new $super (global.get $super.desc1))) + + (global $sub.desc (ref (exact $sub.desc)) (struct.new $sub.desc)) + (global $sub (ref $sub) (struct.new $sub (global.get $sub.desc))) + ;; br_on_cast_desc (func $br_on_cast_desc-unreachable (result anyref) @@ -33,6 +40,78 @@ ) (unreachable) ) + (func (export "cast-success") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref null $super) + (global.get $super1) + (global.get $super.desc1) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "cast-success-supertype") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref null $super) + (global.get $sub) + (global.get $sub.desc) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "cast-success-null") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref null $super) + (ref.null none) + (global.get $super.desc1) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "cast-fail-null") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref $super) + (ref.null none) + (global.get $super.desc1) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "cast-fail-wrong-desc") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref null $super) + (global.get $super1) + (global.get $super.desc2) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "cast-fail-null-desc") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref null $super) + (global.get $super1) + (ref.null none) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) ;; br_on_cast_desc_fail @@ -61,8 +140,93 @@ ) ) ) + (func (export "fail-cast-success") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc_fail $l anyref (ref null $super) + (global.get $super1) + (global.get $super.desc1) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "fail-cast-success-supertype") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc_fail $l anyref (ref null $super) + (global.get $sub) + (global.get $sub.desc) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "fail-cast-success-null") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc_fail $l anyref (ref null $super) + (ref.null none) + (global.get $super.desc1) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "fail-cast-fail-null") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc_fail $l anyref (ref $super) + (ref.null none) + (global.get $super.desc1) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "fail-cast-fail-wrong-desc") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc_fail $l anyref (ref null $super) + (global.get $super1) + (global.get $super.desc2) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + (func (export "fail-cast-fail-null-desc") (result i32) + (drop + (block $l (result anyref) + (br_on_cast_desc_fail $l anyref (ref null $super) + (global.get $super1) + (ref.null none) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) ) +(assert_return (invoke "cast-success") (i32.const 1)) +(assert_return (invoke "cast-success-supertype") (i32.const 1)) +(assert_return (invoke "cast-success-null") (i32.const 1)) +(assert_return (invoke "cast-fail-null") (i32.const 0)) +(assert_return (invoke "cast-fail-wrong-desc") (i32.const 0)) +(assert_trap (invoke "cast-fail-null-desc") "null descriptor") +(assert_return (invoke "fail-cast-success") (i32.const 0)) +(assert_return (invoke "fail-cast-success-supertype") (i32.const 0)) +(assert_return (invoke "fail-cast-success-null") (i32.const 0)) +(assert_return (invoke "fail-cast-fail-null") (i32.const 1)) +(assert_return (invoke "fail-cast-fail-wrong-desc") (i32.const 1)) +(assert_trap (invoke "fail-cast-fail-null-desc") "null descriptor") + (assert_malformed ;; Input type must be a reference. (module quote "(module (rec (type $struct (descriptor $desc (struct))) (type $desc (describes $struct (struct)))) (func (result anyref) (unreachable) (br_on_cast_desc 0 i32 (ref null $struct))))") diff --git a/test/spec/ref.cast_desc.wast b/test/spec/ref.cast_desc.wast index 58e72777b23..c4e72e35814 100644 --- a/test/spec/ref.cast_desc.wast +++ b/test/spec/ref.cast_desc.wast @@ -7,6 +7,13 @@ (type $sub.desc (sub $super.desc (describes $sub (struct)))) ) + (global $super.desc1 (ref (exact $super.desc)) (struct.new $super.desc)) + (global $super.desc2 (ref (exact $super.desc)) (struct.new $super.desc)) + (global $super1 (ref $super) (struct.new $super (global.get $super.desc1))) + + (global $sub.desc (ref (exact $sub.desc)) (struct.new $sub.desc)) + (global $sub (ref $sub) (struct.new $sub (global.get $sub.desc))) + ;; ref.cast_desc (ref null ht) (func $ref.cast_desc-null-unreachable (result anyref) @@ -32,6 +39,46 @@ (local.get $super.desc) ) ) + (func (export "cast-success") + (drop + (ref.cast_desc (ref null $super) + (global.get $super1) + (global.get $super.desc1) + ) + ) + ) + (func (export "cast-success-supertype") + (drop + (ref.cast_desc (ref null $super) + (global.get $sub) + (global.get $sub.desc) + ) + ) + ) + (func (export "cast-success-null") + (drop + (ref.cast_desc (ref null $super) + (ref.null none) + (global.get $super.desc1) + ) + ) + ) + (func (export "cast-fail-wrong-desc") + (drop + (ref.cast_desc (ref null $super) + (global.get $super1) + (global.get $super.desc2) + ) + ) + ) + (func (export "cast-fail-null-desc") + (drop + (ref.cast_desc (ref null $super) + (global.get $super1) + (ref.null none) + ) + ) + ) ;; ref.cast_desc (ref ht) @@ -58,8 +105,59 @@ (local.get $super.desc) ) ) + (func (export "cast-nn-success") + (drop + (ref.cast_desc (ref $super) + (global.get $super1) + (global.get $super.desc1) + ) + ) + ) + (func (export "cast-nn-success-supertype") + (drop + (ref.cast_desc (ref $super) + (global.get $sub) + (global.get $sub.desc) + ) + ) + ) + (func (export "cast-nn-fail-null") + (drop + (ref.cast_desc (ref $super) + (ref.null none) + (global.get $super.desc1) + ) + ) + ) + (func (export "cast-nn-fail-wrong-desc") + (drop + (ref.cast_desc (ref $super) + (global.get $super1) + (global.get $super.desc2) + ) + ) + ) + (func (export "cast-nn-fail-null-desc") + (drop + (ref.cast_desc (ref $super) + (global.get $super1) + (ref.null none) + ) + ) + ) ) +(assert_return (invoke "cast-success")) +(assert_return (invoke "cast-success-supertype")) +(assert_return (invoke "cast-success-null")) +(assert_trap (invoke "cast-fail-wrong-desc") "cast error") +(assert_trap (invoke "cast-fail-null-desc") "null descriptor") +(assert_return (invoke "cast-nn-success")) +(assert_return (invoke "cast-nn-success-supertype")) +(assert_trap (invoke "cast-nn-fail-null") "cast error") +(assert_trap (invoke "cast-nn-fail-wrong-desc") "cast error") +(assert_trap (invoke "cast-nn-fail-null-desc") "null descriptor") + (assert_malformed ;; Cast type must be a reference. (module quote "(module (func (unreachable) (ref.cast_desc i32) (unreachable)))") From 482a54855a76edc0df748d0904cb9b7f64d90514 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 25 Jun 2025 00:54:32 +0200 Subject: [PATCH 562/622] [Custom Descriptors] Support in Precompute (#7678) Now that we support interpretation of ref.get_desc and the descriptor casts, test that Precompute handles them properly. --- scripts/test/fuzzing.py | 1 + src/passes/Precompute.cpp | 6 -- test/lit/passes/precompute-desc.wast | 85 ++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 test/lit/passes/precompute-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 0a0d39ffe10..c4b6ae23865 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -123,6 +123,7 @@ 'type-merging-desc.wast', 'heap2local-desc.wast', 'minimize-rec-groups-desc.wast', + 'precompute-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 6b307730344..f101cc525b6 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -240,12 +240,6 @@ class PrecomputingExpressionRunner // string.encode_wtf16_array anyhow.) return Flow(NONCONSTANT_FLOW); } - - Flow visitRefGetDesc(RefGetDesc* curr) { - // TODO: Implement this. For now, return nonconstant so that we skip it and - // do not error. - return Flow(NONCONSTANT_FLOW); - } }; struct Precompute diff --git a/test/lit/passes/precompute-desc.wast b/test/lit/passes/precompute-desc.wast new file mode 100644 index 00000000000..e0b261cca04 --- /dev/null +++ b/test/lit/passes/precompute-desc.wast @@ -0,0 +1,85 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s --remove-unused-names --precompute-propagate --fuzz-exec -all -S -o - \ +;; RUN: | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) + (type $struct (descriptor $desc (struct))) + ;; CHECK: (type $desc (describes $struct (struct))) + (type $desc (describes $struct (struct))) + ) + + ;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc)) + (global $desc (ref (exact $desc)) (struct.new $desc)) + ;; CHECK: (global $struct (ref $struct) (struct.new_default $struct + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: )) + (global $struct (ref $struct) (struct.new $struct (global.get $desc))) + + ;; CHECK: (func $eq-descs (type $0) (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $eq-descs (result i32) + (ref.eq + (ref.get_desc $struct + (struct.new $struct + (global.get $desc) + ) + ) + (global.get $desc) + ) + ) + + ;; CHECK: (func $different-descs (type $0) (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $different-descs (result i32) + (ref.eq + (ref.get_desc $struct + (struct.new $struct + (struct.new $desc) + ) + ) + (global.get $desc) + ) + ) + + ;; CHECK: (func $br-on-cast-desc (type $0) (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $br-on-cast-desc (result i32) + (ref.eq + (block $l (result eqref) + (drop + (br_on_cast_desc $l eqref (ref $struct) + (global.get $struct) + (global.get $desc) + ) + ) + (ref.null none) + ) + (global.get $struct) + ) + ) + + ;; CHECK: (func $br-on-cast-desc-fail (type $0) (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $br-on-cast-desc-fail (result i32) + (ref.eq + (block $l (result eqref) + (drop + (br_on_cast_desc_fail $l eqref (ref $struct) + (global.get $struct) + (global.get $desc) + ) + ) + (ref.null none) + ) + (global.get $struct) + ) + ) +) From 872a3bf348983b4a5a02ac059cc55c0bfe5fcd9a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 25 Jun 2025 00:54:50 +0200 Subject: [PATCH 563/622] [NFC] Rename StructNew::descriptor to desc (#7679) For consistency with the corresponding members of RefCast and BrOn. The shorter name also matches the usage in the standards-track instruction mnemonics. --- src/ir/child-typer.h | 2 +- src/ir/cost.h | 2 +- src/passes/Heap2Local.cpp | 27 +++++++++++------------ src/passes/RemoveUnusedModuleElements.cpp | 4 ++-- src/wasm-builder.h | 6 ++--- src/wasm-delegations-fields.def | 2 +- src/wasm-interpreter.h | 8 +++---- src/wasm.h | 2 +- src/wasm/wasm-ir-builder.cpp | 4 ++-- src/wasm/wasm-validator.cpp | 4 ++-- src/wasm/wasm.cpp | 2 +- 11 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index f802266e33a..459fb2b3b42 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -948,7 +948,7 @@ template struct ChildTyper : OverriddenVisitor { } auto desc = curr->type.getHeapType().getDescriptorType(); if (desc) { - note(&curr->descriptor, Type(*desc, NonNullable, Exact)); + note(&curr->desc, Type(*desc, NonNullable, Exact)); } } diff --git a/src/ir/cost.h b/src/ir/cost.h index 3827dfb2460..69aff667e05 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -694,7 +694,7 @@ struct CostAnalyzer : public OverriddenVisitor { for (auto* child : curr->operands) { ret += visit(child); } - ret += maybeVisit(curr->descriptor); + ret += maybeVisit(curr->desc); return ret; } CostType visitStructGet(StructGet* curr) { diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 9dc76f04950..1e6744ea8fe 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -612,9 +612,8 @@ struct Struct2Local : PostWalker { for (auto field : fields) { localIndexes.push_back(builder.addVar(func, field.type)); } - if (allocation->descriptor) { - localIndexes.push_back( - builder.addVar(func, allocation->descriptor->type)); + if (allocation->desc) { + localIndexes.push_back(builder.addVar(func, allocation->desc->type)); } // Replace the things we need to using the visit* methods. @@ -732,7 +731,7 @@ struct Struct2Local : PostWalker { // computed do we copy them into the locals representing the fields. std::vector tempIndexes; Index numTemps = - (curr->isWithDefault() ? 0 : fields.size()) + bool(curr->descriptor); + (curr->isWithDefault() ? 0 : fields.size()) + bool(curr->desc); tempIndexes.reserve(numTemps); // Create the temp variables. @@ -741,8 +740,8 @@ struct Struct2Local : PostWalker { tempIndexes.push_back(builder.addVar(func, field.type)); } } - if (curr->descriptor) { - tempIndexes.push_back(builder.addVar(func, curr->descriptor->type)); + if (curr->desc) { + tempIndexes.push_back(builder.addVar(func, curr->desc->type)); } // Store the initial values into the temp locals. @@ -752,9 +751,9 @@ struct Struct2Local : PostWalker { builder.makeLocalSet(tempIndexes[i], curr->operands[i])); } } - if (curr->descriptor) { + if (curr->desc) { contents.push_back( - builder.makeLocalSet(tempIndexes[numTemps - 1], curr->descriptor)); + builder.makeLocalSet(tempIndexes[numTemps - 1], curr->desc)); } // Store the values into the locals representing the fields. @@ -765,9 +764,9 @@ struct Struct2Local : PostWalker { : builder.makeLocalGet(tempIndexes[i], fields[i].type); contents.push_back(builder.makeLocalSet(localIndexes[i], val)); } - if (curr->descriptor) { + if (curr->desc) { auto* val = - builder.makeLocalGet(tempIndexes[numTemps - 1], curr->descriptor->type); + builder.makeLocalGet(tempIndexes[numTemps - 1], curr->desc->type); contents.push_back( builder.makeLocalSet(localIndexes[fields.size()], val)); } @@ -851,8 +850,8 @@ struct Struct2Local : PostWalker { // also know the cast must fail if the optimized allocation flows in as // the descriptor, since it cannot possibly have been used in the // allocation of the cast value without having been considered to escape. - if (!allocation->descriptor || analyzer.getInteraction(curr->desc) == - ParentChildInteraction::Flows) { + if (!allocation->desc || analyzer.getInteraction(curr->desc) == + ParentChildInteraction::Flows) { // The allocation does not have a descriptor, so there is no way for the // cast to succeed. replaceCurrent(builder.blockify(builder.makeDrop(curr->ref), @@ -861,7 +860,7 @@ struct Struct2Local : PostWalker { } else { // The cast succeeds iff the optimized allocation's descriptor is the // same as the given descriptor and traps otherwise. - auto type = allocation->descriptor->type; + auto type = allocation->desc->type; replaceCurrent(builder.blockify( builder.makeDrop(curr->ref), builder.makeIf( @@ -897,7 +896,7 @@ struct Struct2Local : PostWalker { return; } - auto type = allocation->descriptor->type; + auto type = allocation->desc->type; if (type != curr->type) { // We know exactly the allocation that flows into this expression, so we // know the exact type of the descriptor. This type may be more precise diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index cbb1180231e..a84a0b0b539 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -485,8 +485,8 @@ struct Analyzer { // Use the descriptor right now, normally. (We only have special // optimization for struct.new operands, below.) - if (new_->descriptor) { - use(new_->descriptor); + if (new_->desc) { + use(new_->desc); } auto type = new_->type.getHeapType(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 7bee64c2407..e31d905548e 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -931,7 +931,7 @@ class Builder { Expression* descriptor = nullptr) { auto* ret = wasm.allocator.alloc(); ret->operands.set(args); - ret->descriptor = descriptor; + ret->desc = descriptor; ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; @@ -941,7 +941,7 @@ class Builder { Expression* descriptor = nullptr) { auto* ret = wasm.allocator.alloc(); ret->operands = std::move(args); - ret->descriptor = descriptor; + ret->desc = descriptor; ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; @@ -952,7 +952,7 @@ class Builder { Expression* descriptor = nullptr) { auto* ret = wasm.allocator.alloc(); ret->operands.set(args); - ret->descriptor = descriptor; + ret->desc = descriptor; ret->type = Type(type, NonNullable, Exact); ret->finalize(); return ret; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index e2627e72fc1..b7e9d935580 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -671,7 +671,7 @@ DELEGATE_FIELD_CHILD(BrOn, ref) DELEGATE_FIELD_CASE_END(BrOn) DELEGATE_FIELD_CASE_START(StructNew) -DELEGATE_FIELD_OPTIONAL_CHILD(StructNew, descriptor) +DELEGATE_FIELD_OPTIONAL_CHILD(StructNew, desc) DELEGATE_FIELD_CHILD_VECTOR(StructNew, operands) DELEGATE_FIELD_CASE_END(StructNew) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 0ae93c4bd71..b4f56e7dfcb 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1781,8 +1781,8 @@ class ExpressionRunner : public OverriddenVisitor { return value; } } - if (curr->descriptor) { - auto value = self()->visit(curr->descriptor); + if (curr->desc) { + auto value = self()->visit(curr->desc); if (value.breaking()) { return value; } @@ -1804,10 +1804,10 @@ class ExpressionRunner : public OverriddenVisitor { data[i] = truncateForPacking(value.getSingleValue(), field); } } - if (!curr->descriptor) { + if (!curr->desc) { return makeGCData(std::move(data), curr->type); } - auto desc = self()->visit(curr->descriptor); + auto desc = self()->visit(curr->desc); if (desc.breaking()) { return desc; } diff --git a/src/wasm.h b/src/wasm.h index e3ca5f47997..55e7fa458da 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1683,7 +1683,7 @@ class StructNew : public SpecificExpression { // case, and binaryen doesn't guarantee roundtripping binaries anyhow. ExpressionList operands; - Expression* descriptor = nullptr; + Expression* desc = nullptr; bool isWithDefault() { return operands.empty(); } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 2c282684974..b56e32812a7 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2230,7 +2230,7 @@ Result<> IRBuilder::makeStructNew(HeapType type) { curr.type = Type(type, NonNullable, Exact); curr.operands.resize(type.getStruct().fields.size()); CHECK_ERR(visitStructNew(&curr)); - push(builder.makeStructNew(type, std::move(curr.operands), curr.descriptor)); + push(builder.makeStructNew(type, std::move(curr.operands), curr.desc)); return Ok{}; } @@ -2238,7 +2238,7 @@ Result<> IRBuilder::makeStructNewDefault(HeapType type) { StructNew curr(wasm.allocator); curr.type = Type(type, NonNullable, Exact); CHECK_ERR(visitStructNew(&curr)); - push(builder.makeStructNew(type, {}, curr.descriptor)); + push(builder.makeStructNew(type, {}, curr.desc)); return Ok{}; } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index d36ca80f38c..2fe4ea7d4ef 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3159,11 +3159,11 @@ void FunctionValidator::visitStructNew(StructNew* curr) { auto descType = curr->type.getHeapType().getDescriptorType(); if (!descType) { - shouldBeFalse(curr->descriptor, + shouldBeFalse(curr->desc, curr, "struct.new of type without descriptor should lack one"); } else { - shouldBeSubType(curr->descriptor->type, + shouldBeSubType(curr->desc->type, Type(*descType, Nullable, Exact), curr, "struct.new descriptor operand should have proper type"); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index d2301ac2bf8..dac47857f7d 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1216,7 +1216,7 @@ void StructNew::finalize() { if (handleUnreachableOperands(this)) { return; } - if (descriptor && descriptor->type == Type::unreachable) { + if (desc && desc->type == Type::unreachable) { type = Type::unreachable; } } From 47076caed855a2cdabf7caa487fcedf5a65d0bbc Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 25 Jun 2025 18:57:45 +0200 Subject: [PATCH 564/622] [NFC] Removed unused member from HeapTypeGenerator (#7680) --- src/tools/fuzzing/heap-types.cpp | 4 +--- src/tools/fuzzing/heap-types.h | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 4fc7ed91500..9b5e9789efc 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -57,9 +57,7 @@ struct HeapTypeGeneratorImpl { FuzzParams params; HeapTypeGeneratorImpl(Random& rand, FeatureSet features, size_t n) - : result{TypeBuilder(n), - std::vector>(n), - std::vector>(n)}, + : result{TypeBuilder(n), std::vector>(n)}, builder(result.builder), subtypeIndices(result.subtypeIndices), supertypeIndices(n), rand(rand), features(features) { // Set up the subtype relationships. Start with some number of root types, diff --git a/src/tools/fuzzing/heap-types.h b/src/tools/fuzzing/heap-types.h index bd30178f2b4..4e1e27048ed 100644 --- a/src/tools/fuzzing/heap-types.h +++ b/src/tools/fuzzing/heap-types.h @@ -32,9 +32,6 @@ struct HeapTypeGenerator { // The intended subtypes of each built type. std::vector> subtypeIndices; - // The intended supertype of each built type, if any. - std::vector> supertypeIndices; - // Create a populated `HeapTypeGenerator` with `n` random HeapTypes with // interesting subtyping. static HeapTypeGenerator create(Random& rand, FeatureSet features, size_t n); From 6b72393bfea2acd80c5f121f2564490f798b5a4f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 25 Jun 2025 19:50:10 +0200 Subject: [PATCH 565/622] [NFC] Refactor heap type fuzzer generator (#7681) Refactor the top-level driver in the heap type generator to plan recursion groups before planning the types they contain. This will make it easier to add support for generating described and descriptor types in a follow-on PR because these types can only be added if there is sufficient room left in the current recursion group. --- src/tools/fuzzing/heap-types.cpp | 94 ++++++++++++++++++-------------- test/lit/fuzz-types.test | 84 ++++++++++++++-------------- 2 files changed, 96 insertions(+), 82 deletions(-) diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 9b5e9789efc..c1d99ee5c48 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -66,55 +66,69 @@ struct HeapTypeGeneratorImpl { // appropriately use types we haven't constructed yet. typeKinds.reserve(builder.size()); supertypeIndices.reserve(builder.size()); - Index numRoots = 1 + rand.upTo(builder.size()); - for (Index i = 0; i < builder.size(); ++i) { - typeIndices.insert({builder[i], i}); - // Everything is a subtype of itself. - subtypeIndices[i].push_back(i); - if (i < numRoots || rand.oneIn(2)) { - // This is a root type with no supertype. Choose a kind for this type. - typeKinds.emplace_back(generateHeapTypeKind()); - builder[i].setShared( - !features.hasSharedEverything() || rand.oneIn(2) ? Unshared : Shared); - } else { - // This is a subtype. Choose one of the previous types to be the - // supertype. - Index super = rand.upTo(i); - builder[i].subTypeOf(builder[super]); - builder[i].setShared(HeapType(builder[super]).getShared()); - supertypeIndices[i] = super; - subtypeIndices[super].push_back(i); - typeKinds.push_back(typeKinds[super]); + recGroupEnds.reserve(builder.size()); + + // The number of root types to generate before we start adding subtypes. + size_t numRoots = 1 + rand.upTo(builder.size()); + + // The mean expected size of the recursion groups. + size_t expectedGroupSize = 1 + rand.upTo(builder.size()); + + size_t i = 0; + while (i < builder.size()) { + i += planGroup(i, numRoots, expectedGroupSize); + } + assert(recGroupEnds.size() == builder.size()); + + populateTypes(); + } + + size_t planGroup(size_t start, size_t numRoots, size_t expectedGroupSize) { + size_t maxSize = builder.size() - start; + size_t size = 1; + // Generate the group size according to a geometric distribution. + for (; size < maxSize; ++size) { + if (rand.oneIn(expectedGroupSize)) { + break; } } + assert(start + size <= builder.size()); + builder.createRecGroup(start, size); - // Types without nontrivial subtypes may be marked final. - for (Index i = 0; i < builder.size(); ++i) { - builder[i].setOpen(subtypeIndices[i].size() > 1 || rand.oneIn(2)); + size_t end = start + size; + for (size_t i = start; i < end; ++i) { + recGroupEnds.push_back(end); + planType(i, numRoots); } + return size; + } - // Initialize the recursion groups. - recGroupEnds.reserve(builder.size()); - // Create isorecursive recursion groups. Choose an expected group size - // uniformly at random, then create groups with random sizes on a geometric - // distribution based on that expected size. - size_t expectedSize = 1 + rand.upTo(builder.size()); - Index groupStart = 0; - for (Index i = 0; i < builder.size(); ++i) { - if (i == builder.size() - 1 || rand.oneIn(expectedSize)) { - // End the old group and create a new group. - Index newGroupStart = i + 1; - builder.createRecGroup(groupStart, newGroupStart - groupStart); - for (Index j = groupStart; j < newGroupStart; ++j) { - recGroupEnds.push_back(newGroupStart); - } - groupStart = newGroupStart; - } + void planType(size_t i, size_t numRoots) { + typeIndices.insert({builder[i], i}); + // Everything is a subtype of itself. + subtypeIndices[i].push_back(i); + if (i < numRoots || rand.oneIn(2)) { + // This is a root type with no supertype. Choose a kind for this type. + typeKinds.emplace_back(generateHeapTypeKind()); + builder[i].setShared( + !features.hasSharedEverything() || rand.oneIn(2) ? Unshared : Shared); + } else { + // This is a subtype. Choose one of the previous types to be the + // supertype. + Index super = rand.upTo(i); + builder[i].subTypeOf(builder[super]); + builder[i].setShared(HeapType(builder[super]).getShared()); + supertypeIndices[i] = super; + subtypeIndices[super].push_back(i); + typeKinds.push_back(typeKinds[super]); } - assert(recGroupEnds.size() == builder.size()); + } + void populateTypes() { // Create the heap types. for (; index < builder.size(); ++index) { + // Types without nontrivial subtypes may be marked final. + builder[index].setOpen(subtypeIndices[index].size() > 1 || rand.oneIn(2)); auto kind = typeKinds[index]; auto share = HeapType(builder[index]).getShared(); if (!supertypeIndices[index]) { diff --git a/test/lit/fuzz-types.test b/test/lit/fuzz-types.test index b0cedc0b54f..878f875b907 100644 --- a/test/lit/fuzz-types.test +++ b/test/lit/fuzz-types.test @@ -1,60 +1,60 @@ -;; RUN: wasm-fuzz-types -v --seed=1 | filecheck %s +;; RUN: wasm-fuzz-types -v --seed=0 | filecheck %s -;; CHECK: Running with seed 1 +;; CHECK: Running with seed 0 ;; CHECK-NEXT: Built 20 types: ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $0 (sub (struct (field (mut i16)) (field (mut (ref $2))) (field (mut (ref null $2)))))) -;; CHECK-NEXT: (type $1 (sub (func (param (ref $1)) (result f64 (ref $0) f32 structref)))) -;; CHECK-NEXT: (type $2 (sub (shared (struct (field (mut (ref null (shared extern)))) (field (mut (ref null $2))))))) -;; CHECK-NEXT: (type $3 (sub (shared (struct)))) +;; CHECK-NEXT: (type $0 (sub (func (param (ref (shared func)) f32 i64) (result (ref $7))))) +;; CHECK-NEXT: (type $1 (sub (array (mut i16)))) +;; CHECK-NEXT: (type $2 (sub (shared (func (param f64 (ref null $5) (ref $9)) (result (ref null $6) (ref $3) (ref null $8)))))) +;; CHECK-NEXT: (type $3 (sub final $2 (shared (func (param f64 (ref null $1) (ref $0)) (result (ref $6) (ref $3) (ref $8)))))) +;; CHECK-NEXT: (type $4 (sub $1 (array (mut i16)))) +;; CHECK-NEXT: (type $5 (sub final $4 (array (mut i16)))) +;; CHECK-NEXT: (type $6 (sub $2 (shared (func (param f64 arrayref funcref) (result (ref $6) (ref $3) (ref $8)))))) +;; CHECK-NEXT: (type $7 (sub $0 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) +;; CHECK-NEXT: (type $8 (sub final $0 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) +;; CHECK-NEXT: (type $9 (sub $7 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) +;; CHECK-NEXT: (type $10 (sub $2 (shared (func (param f64 (ref null $5) (ref func)) (result (ref $6) (ref $3) (ref null $8)))))) +;; CHECK-NEXT: (type $11 (sub final $2 (shared (func (param f64 arrayref (ref $0)) (result (ref $6) (ref $3) (ref $8)))))) +;; CHECK-NEXT: (type $12 (sub $10 (shared (func (param f64 (ref null $1) funcref) (result (ref $6) (ref (shared nofunc)) (ref $8)))))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $4 (sub (array i32))) -;; CHECK-NEXT: (type $5 (sub $4 (array i32))) -;; CHECK-NEXT: (type $6 (shared (func (param (ref null $3)) (result i32)))) -;; CHECK-NEXT: (type $7 (sub $2 (shared (struct (field (mut (ref null (shared extern)))) (field (mut (ref null $2))) (field (mut (ref null $3))) (field (mut i16)) (field (mut (ref null $7))) (field (mut (ref null $7))))))) -;; CHECK-NEXT: (type $8 (sub $0 (struct (field (mut i16)) (field (mut (ref $2))) (field (mut (ref null $2)))))) +;; CHECK-NEXT: (type $13 (sub (func (param f64 f32)))) +;; CHECK-NEXT: (type $14 (func (param i64) (result v128 (ref null $2) (ref $12) f32 v128))) +;; CHECK-NEXT: (type $15 (sub final $12 (shared (func (param f64 anyref funcref) (result (ref $6) (ref (shared nofunc)) (ref $8)))))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $9 (shared (array i32))) -;; CHECK-NEXT: (type $10 (sub $5 (array i32))) -;; CHECK-NEXT: (type $11 (func (result i32))) -;; CHECK-NEXT: (type $12 (sub (shared (array (ref $3))))) -;; CHECK-NEXT: (type $13 (sub (shared (func (param (ref null $19) v128) (result (ref null $12)))))) -;; CHECK-NEXT: (type $14 (sub final $12 (shared (array (ref $3))))) -;; CHECK-NEXT: (type $15 (sub (shared (func (param i31ref (ref $5)) (result i32))))) -;; CHECK-NEXT: (type $16 (sub $5 (array i32))) -;; CHECK-NEXT: (type $17 (sub (func (result (ref $7))))) -;; CHECK-NEXT: (type $18 (sub (array (mut i8)))) -;; CHECK-NEXT: (type $19 (shared (array v128))) +;; CHECK-NEXT: (type $16 (sub final $7 (func (param (ref (shared func)) f32 i64) (result (ref nofunc))))) +;; CHECK-NEXT: (type $17 (sub (shared (func (param (ref $11) i64 (ref (shared extern))))))) +;; CHECK-NEXT: (type $18 (sub (struct))) +;; CHECK-NEXT: (type $19 (sub $7 (func (param (ref null (shared func)) f32 i64) (result (ref $9))))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ;; CHECK-NEXT: Inhabitable types: ;; CHECK-NEXT: ;; CHECK-NEXT: Built 20 types: ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $0 (sub (struct (field (mut i16)) (field (mut (ref $2))) (field (mut (ref null $2)))))) -;; CHECK-NEXT: (type $1 (sub (func (param (ref $1)) (result f64 (ref $0) f32 structref)))) -;; CHECK-NEXT: (type $2 (sub (shared (struct (field (mut (ref null (shared extern)))) (field (mut (ref null $2))))))) -;; CHECK-NEXT: (type $3 (sub (shared (struct)))) +;; CHECK-NEXT: (type $0 (sub (func (param (ref (shared func)) f32 i64) (result (ref $7))))) +;; CHECK-NEXT: (type $1 (sub (array (mut i16)))) +;; CHECK-NEXT: (type $2 (sub (shared (func (param f64 (ref null $5) (ref $9)) (result (ref null $6) (ref $3) (ref null $8)))))) +;; CHECK-NEXT: (type $3 (sub final $2 (shared (func (param f64 (ref null $1) (ref $0)) (result (ref $6) (ref $3) (ref $8)))))) +;; CHECK-NEXT: (type $4 (sub $1 (array (mut i16)))) +;; CHECK-NEXT: (type $5 (sub final $4 (array (mut i16)))) +;; CHECK-NEXT: (type $6 (sub $2 (shared (func (param f64 arrayref funcref) (result (ref $6) (ref $3) (ref $8)))))) +;; CHECK-NEXT: (type $7 (sub $0 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) +;; CHECK-NEXT: (type $8 (sub final $0 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) +;; CHECK-NEXT: (type $9 (sub $7 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) +;; CHECK-NEXT: (type $10 (sub $2 (shared (func (param f64 (ref null $5) (ref func)) (result (ref $6) (ref $3) (ref null $8)))))) +;; CHECK-NEXT: (type $11 (sub final $2 (shared (func (param f64 arrayref (ref $0)) (result (ref $6) (ref $3) (ref $8)))))) +;; CHECK-NEXT: (type $12 (sub $10 (shared (func (param f64 (ref null $1) funcref) (result (ref $6) (ref (shared nofunc)) (ref $8)))))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $4 (sub (array i32))) -;; CHECK-NEXT: (type $5 (sub $4 (array i32))) -;; CHECK-NEXT: (type $6 (shared (func (param (ref null $3)) (result i32)))) -;; CHECK-NEXT: (type $7 (sub $2 (shared (struct (field (mut (ref null (shared extern)))) (field (mut (ref null $2))) (field (mut (ref null $3))) (field (mut i16)) (field (mut (ref null $7))) (field (mut (ref null $7))))))) -;; CHECK-NEXT: (type $8 (sub $0 (struct (field (mut i16)) (field (mut (ref $2))) (field (mut (ref null $2)))))) +;; CHECK-NEXT: (type $13 (sub (func (param f64 f32)))) +;; CHECK-NEXT: (type $14 (func (param i64) (result v128 (ref null $2) (ref $12) f32 v128))) +;; CHECK-NEXT: (type $15 (sub final $12 (shared (func (param f64 anyref funcref) (result (ref $6) (ref (shared nofunc)) (ref $8)))))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $9 (shared (array i32))) -;; CHECK-NEXT: (type $10 (sub $5 (array i32))) -;; CHECK-NEXT: (type $11 (func (result i32))) -;; CHECK-NEXT: (type $12 (sub (shared (array (ref $3))))) -;; CHECK-NEXT: (type $13 (sub (shared (func (param (ref null $19) v128) (result (ref null $12)))))) -;; CHECK-NEXT: (type $14 (sub final $12 (shared (array (ref $3))))) -;; CHECK-NEXT: (type $15 (sub (shared (func (param i31ref (ref $5)) (result i32))))) -;; CHECK-NEXT: (type $16 (sub $5 (array i32))) -;; CHECK-NEXT: (type $17 (sub (func (result (ref $7))))) -;; CHECK-NEXT: (type $18 (sub (array (mut i8)))) -;; CHECK-NEXT: (type $19 (shared (array v128))) +;; CHECK-NEXT: (type $16 (sub final $7 (func (param (ref (shared func)) f32 i64) (result (ref nofunc))))) +;; CHECK-NEXT: (type $17 (sub (shared (func (param (ref $11) i64 (ref (shared extern))))))) +;; CHECK-NEXT: (type $18 (sub (struct))) +;; CHECK-NEXT: (type $19 (sub $7 (func (param (ref null (shared func)) f32 i64) (result (ref $9))))) ;; CHECK-NEXT: ) From dcf18a7abd50154a65bdea2dbcdee8cfbf4ffcac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Wed, 25 Jun 2025 22:13:19 +0200 Subject: [PATCH 566/622] [Testing] Port test/passes/duplicate-function-elimination-* tests to lit (#7682) This just runs `./scripts/port_passes_tests_to_lit.py test/passes/duplicate-function-elimination_*.wast`. The script just copies the `.wast` files to `test/lit/passes` and runs `update_lit_checks.py` on the copied files. --- ...ate-function-elimination_all-features.wast | 79 + ...function-elimination_optimize-level=1.wast | 2226 +++++++++++++++++ ...function-elimination_optimize-level=2.wast | 2223 ++++++++++++++++ ...cate-function-elimination_all-features.txt | 33 - ...ate-function-elimination_all-features.wast | 41 - ...-function-elimination_optimize-level=1.txt | 1058 -------- ...function-elimination_optimize-level=1.wast | 1221 --------- ...-function-elimination_optimize-level=2.txt | 1055 -------- ...function-elimination_optimize-level=2.wast | 1221 --------- 9 files changed, 4528 insertions(+), 4629 deletions(-) create mode 100644 test/lit/passes/duplicate-function-elimination_all-features.wast create mode 100644 test/lit/passes/duplicate-function-elimination_optimize-level=1.wast create mode 100644 test/lit/passes/duplicate-function-elimination_optimize-level=2.wast delete mode 100644 test/passes/duplicate-function-elimination_all-features.txt delete mode 100644 test/passes/duplicate-function-elimination_all-features.wast delete mode 100644 test/passes/duplicate-function-elimination_optimize-level=1.txt delete mode 100644 test/passes/duplicate-function-elimination_optimize-level=1.wast delete mode 100644 test/passes/duplicate-function-elimination_optimize-level=2.txt delete mode 100644 test/passes/duplicate-function-elimination_optimize-level=2.wast diff --git a/test/lit/passes/duplicate-function-elimination_all-features.wast b/test/lit/passes/duplicate-function-elimination_all-features.wast new file mode 100644 index 00000000000..bbd23675eab --- /dev/null +++ b/test/lit/passes/duplicate-function-elimination_all-features.wast @@ -0,0 +1,79 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. + +;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --all-features -S -o - | filecheck %s + +;; --duplicate-function-elimination should not remove functions used in ref.func. +(module + ;; CHECK: (type $0 (func (result i32))) + + ;; CHECK: (type $1 (func (result funcref))) + + ;; CHECK: (elem declare func $0) + + ;; CHECK: (func $0 (type $0) (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $0 (result i32) + (i32.const 0) + ) + (func $1 (result i32) + (i32.const 0) + ) + ;; CHECK: (func $test (type $1) (result funcref) + ;; CHECK-NEXT: (ref.func $0) + ;; CHECK-NEXT: ) + (func $test (result funcref) + (ref.func $1) + ) +) +;; renaming after deduplication must only affect functions +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (global $bar i32 (i32.const 0)) + + ;; CHECK: (memory $foo 16 16) + (memory $foo 16 16) + (global $bar i32 (i32.const 0)) + ;; CHECK: (export "memory" (memory $foo)) + (export "memory" (memory $foo)) + ;; CHECK: (export "global" (global $bar)) + (export "global" (global $bar)) + ;; CHECK: (func $bar (type $0) + ;; CHECK-NEXT: ) + (func $bar ;; happens to share a name with the global + ) + (func $foo ;; happens to share a name with the memory + ) +) +;; renaming after deduplication must update ref.funcs in globals +(module + ;; CHECK: (type $func (func (result i32))) + (type $func (func (result i32))) + ;; CHECK: (global $global$0 (ref $func) (ref.func $foo)) + (global $global$0 (ref $func) (ref.func $bar)) + ;; These two identical functions can be merged. The ref.func in the global must + ;; be updated accordingly. + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $foo (type $func) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $foo (result i32) + (unreachable) + ) + (func $bar (result i32) + (unreachable) + ) + ;; CHECK: (func $export (type $func) (result i32) + ;; CHECK-NEXT: (call_ref $func + ;; CHECK-NEXT: (global.get $global$0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $export (export "export") (result i32) + (call_ref $func + (global.get $global$0) + ) + ) +) diff --git a/test/lit/passes/duplicate-function-elimination_optimize-level=1.wast b/test/lit/passes/duplicate-function-elimination_optimize-level=1.wast new file mode 100644 index 00000000000..6dd71c7969d --- /dev/null +++ b/test/lit/passes/duplicate-function-elimination_optimize-level=1.wast @@ -0,0 +1,2226 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. + +;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --optimize-level=1 -S -o - | filecheck %s + +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (nop) + ) + (func $other (type $0) + (nop) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $other (type $0) + (nop) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (i32.const 0) + ) + ) + (func $other (type $0) + (drop + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.const 1) + ) + ) +) +(module + (memory 0) + (start $other) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (table $0 3 3 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $keep2 $keep2 $caller) + + ;; CHECK: (export "keep2" (func $keep2)) + (export "keep2" (func $keep2)) + (export "other" (func $other)) + (table 3 3 funcref) + (elem (i32.const 0) $keep2 $other $caller) + ;; CHECK: (export "other" (func $keep2)) + + ;; CHECK: (start $keep2) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (nop) + ) + (func $other (type $0) + (nop) + ) + ;; CHECK: (func $caller + ;; CHECK-NEXT: (call $keep2) + ;; CHECK-NEXT: (call $keep2) + ;; CHECK-NEXT: ) + (func $caller (type $0) + (call $keep2) + (call $other) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2-after-two-passes + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keep2-after-two-passes (type $0) + (nop) + ) + (func $other (type $0) + (nop) + ) + ;; CHECK: (func $keep-caller + ;; CHECK-NEXT: (call $keep2-after-two-passes) + ;; CHECK-NEXT: ) + (func $keep-caller (type $0) + (call $keep2-after-two-passes) + ) + ;; CHECK: (func $other-caller + ;; CHECK-NEXT: (call $keep2-after-two-passes) + ;; CHECK-NEXT: ) + (func $other-caller (type $0) + (call $other) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep-4 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keep-4 (type $0) + (nop) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $other (type $0) + (unreachable) + ) + ;; CHECK: (func $keep-caller + ;; CHECK-NEXT: (call $keep-4) + ;; CHECK-NEXT: ) + (func $keep-caller (type $0) + (call $keep-4) + ) + ;; CHECK: (func $other-caller + ;; CHECK-NEXT: (call $other) + ;; CHECK-NEXT: ) + (func $other-caller (type $0) + (call $other) + ) +) +(module + (memory 0) + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param i32))) + + ;; CHECK: (type $T (func (result i32))) + (type $T (func (result i32))) + (type $S (func (result i32))) + (type $2 (func)) + (type $3 (func (param i32))) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep4-similar-but-func-sig-differs + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep4-similar-but-func-sig-differs (type $2) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other1 (param $i i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other1 (type $3) (param $i i32) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other2 (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $other2 (type $T) (result i32) + (i32.const 0) + ) + (func $other3 (type $S) (result i32) + (i32.const 0) + ) +) +(module + (memory 0) + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (type $S (func (result i32))) + (type $S (func (result i32))) + (type $1 (func (param i32))) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2-similar-but-func-sig-differs (param $i i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2-similar-but-func-sig-differs (type $1) (param $i i32) + (drop + (i32.const 0) + ) + ) + (func $other1 (type $1) (param $i i32) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other2 (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $other2 (type $S) (result i32) + (i32.const 0) + ) + (func $other3 (type $S) (result i32) + (i32.const 0) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (nop) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $other (type $0) + (nop) + (nop) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (block $block0 + ) + ) + (func $other (type $0) + (block $block0 + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $block0 + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $block0 + (nop) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (block $block0 + (nop) + ) + ) + (func $other (type $0) + (block $block0 + (nop) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $block0 + (nop) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $block0 + (nop) + (unreachable) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $block0 + (nop) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $block0 + (unreachable) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase-since-block-names-do-not-matter + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase-since-block-names-do-not-matter (type $0) + (block $foo + ) + ) + (func $other (type $0) + (block $bar + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase-since-block-names-do-not-matter + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br $foo) + ;; CHECK-NEXT: (br_table $foo $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase-since-block-names-do-not-matter (type $0) + (block $foo + (br $foo) + (br_table $foo $foo + (i32.const 0) + ) + ) + ) + (func $other (type $0) + (block $bar + (br $bar) + (br_table $bar $bar + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $foo + (block + (drop + (i32.const 0) + ) + (br $foo) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $bar) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $bar + (block + (drop + (i32.const 1) + ) + (br $bar) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br_if $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $foo + (br_if $foo + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (br_if $bar + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $bar + (br_if $bar + (i32.const 1) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br_if $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (block $foo + (br_if $foo + (i32.const 0) + ) + ) + ) + (func $other (type $0) + (block $bar + (br_if $bar + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br_table $foo $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $foo + (br_table $foo $foo + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (br_table $bar $bar + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $bar + (br_table $bar $bar + (i32.const 1) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (loop $bar + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (loop $bar + (nop) + ) + ) + (func $other (type $0) + (loop $sjc + (nop) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $foo (result i32) + ;; CHECK-NEXT: (br_table $foo $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (block $foo (result i32) + (br_table $foo $foo + (i32.const 0) + (i32.const 0) + ) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $bar (result i32) + ;; CHECK-NEXT: (br_table $bar $bar + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (block $bar (result i32) + (br_table $bar $bar + (i32.const 0) + (i32.const 1) + ) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (br_table $foo $bar + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $foo + (block $bar + (br_table $foo $bar + (i32.const 0) + ) + ) + ) + ) + (func $other (type $0) + (block $bar + (block $foo + (br_table $bar $foo + (i32.const 0) + ) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (br_table $foo $bar + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (block $foo + (block $bar + (br_table $foo $bar + (i32.const 0) + ) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br_table $foo $bar + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $bar + (block $foo + (br_table $foo $bar + (i32.const 0) + ) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (call $erase) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (call $erase) + ) + (func $other (type $0) + (call $erase) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2-but-in-theory-we-could-erase + ;; CHECK-NEXT: (call $keep2-but-in-theory-we-could-erase) + ;; CHECK-NEXT: ) + (func $keep2-but-in-theory-we-could-erase (type $0) + (call $keep2-but-in-theory-we-could-erase) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (call $other) + ;; CHECK-NEXT: ) + (func $other (type $0) + (call $other) + ) +) +(module + ;; CHECK: (type $FUNCSIG$v (func)) + + ;; CHECK: (import "env" "i" (func $i)) + (import "env" "i" (func $i)) + ;; CHECK: (import "env" "j" (func $j)) + (import "env" "j" (func $j)) + (memory 0) + (type $FUNCSIG$v (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (call $i) + ;; CHECK-NEXT: ) + (func $erase (type $FUNCSIG$v) + (call $i) + ) + (func $other (type $FUNCSIG$v) + (call $i) + ) +) +(module + ;; CHECK: (type $FUNCSIG$v (func)) + + ;; CHECK: (import "env" "i" (func $i)) + (import "env" "i" (func $i)) + ;; CHECK: (import "env" "j" (func $j)) + (import "env" "j" (func $j)) + (memory 0) + (type $FUNCSIG$v (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (call $i) + ;; CHECK-NEXT: ) + (func $keep2 (type $FUNCSIG$v) + (call $i) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (call $j) + ;; CHECK-NEXT: ) + (func $other (type $FUNCSIG$v) + (call $j) + ) +) +(module + (memory 0) + ;; CHECK: (type $T (func)) + (type $T (func)) + (table 2 2 funcref) + (elem (i32.const 0) $erase $other) + ;; CHECK: (memory $0 0) + + ;; CHECK: (table $0 2 2 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $erase $erase) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $T) + (call_indirect (type $T) + (i32.const 0) + ) + ) + (func $other (type $T) + (call_indirect (type $T) + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $T (func)) + (type $T (func)) + (table 2 2 funcref) + (elem (i32.const 0) $keep2 $other) + ;; CHECK: (memory $0 0) + + ;; CHECK: (table $0 2 2 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $keep2 $other) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $T) + (call_indirect (type $T) + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $T) + (call_indirect (type $T) + (i32.const 1) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $T (func)) + (type $T (func)) + (type $S (func)) + (table 2 2 funcref) + (elem (i32.const 0) $keep2 $other) + ;; CHECK: (memory $0 0) + + ;; CHECK: (table $0 2 2 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $keep2 $keep2) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $T) + (call_indirect (type $T) + (i32.const 0) + ) + ) + (func $other (type $T) + (call_indirect (type $S) + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase-even-locals-with-different-names + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase-even-locals-with-different-names (type $0) + (local $i i32) + (drop + (local.get $i) + ) + ) + (func $other (type $0) + (local $j i32) + (drop + (local.get $j) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (local $i i32) + (drop + (local.get $i) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (local $j i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (local $j i64) + (drop + (local.get $j) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase-even-locals-with-different-names + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase-even-locals-with-different-names (type $0) + (local $i i32) + (local.set $i + (i32.const 0) + ) + ) + (func $other (type $0) + (local $j i32) + (local.set $j + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (local $i i32) + (local.set $i + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (local $j i64) + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (local $j i64) + (local.set $j + (i64.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (local $i i32) + (local.set $i + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (local $j i32) + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (local $j i32) + (local.set $j + (i32.const 1) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (i32.load + (i32.const 0) + ) + ) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) + (func $other (type $0) + (drop + (i32.load + (i32.const 0) + ) + ) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load offset=3 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load16_s offset=3 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 align=1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=1 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load16_s align=2 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 1) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_u offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load16_u offset=3 align=2 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (i32.store + (i32.const 0) + (i32.const 100) + ) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) + (func $other (type $0) + (i32.store + (i32.const 0) + (i32.const 100) + ) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store offset=3 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store16 offset=3 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 align=1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=1 + (i32.const 0) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store16 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store16 align=2 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=2 + (i32.const 1) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 101) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 101) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i64.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f64.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i64.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i64.const 1) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const 0.10000000149011612) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.const 0.10000000149011612) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const -0.10000000149011612) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.const -0.10000000149011612) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.const 0.1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f64.const 0.1) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.const 0.2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f64.const 0.2) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (f32.abs + (f32.const 0) + ) + ) + ) + (func $other (type $0) + (drop + (f32.abs + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.abs + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.abs + (f32.const 1) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.abs + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.neg + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.neg + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) + (func $other (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 1) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 1) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.add + (f32.const 1) + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.sub + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.sub + (f32.const 0) + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) + (func $other (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (select + (i32.const 1) + (i32.const 0) + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (select + (i32.const 0) + (i32.const 2) + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 3) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (return) + ) + (func $other (type $0) + (return) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func (result i32))) + (type $0 (func (result i32))) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase (result i32) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) (result i32) + (return + (i32.const 0) + ) + ) + (func $other (type $0) (result i32) + (return + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func (result i32))) + (type $0 (func (result i32))) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep (result i32) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) (result i32) + (return + (i32.const 0) + ) + ) + ;; CHECK: (func $other (result i32) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) (result i32) + (return + (i32.const 1) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (memory.size) + ) + ) + (func $other (type $0) + (drop + (memory.size) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (memory.grow + (i32.const 10) + ) + ) + ) + (func $other (type $0) + (drop + (memory.grow + (i32.const 10) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (memory.grow + (i32.const 10) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (memory.grow + (i32.const 11) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (memory.size) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (memory.grow + (i32.const 10) + ) + ) + ) +) diff --git a/test/lit/passes/duplicate-function-elimination_optimize-level=2.wast b/test/lit/passes/duplicate-function-elimination_optimize-level=2.wast new file mode 100644 index 00000000000..7d6e3a50ebd --- /dev/null +++ b/test/lit/passes/duplicate-function-elimination_optimize-level=2.wast @@ -0,0 +1,2223 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. + +;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --optimize-level=2 -S -o - | filecheck %s + +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (nop) + ) + (func $other (type $0) + (nop) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $other (type $0) + (nop) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (i32.const 0) + ) + ) + (func $other (type $0) + (drop + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.const 1) + ) + ) +) +(module + (memory 0) + (start $other) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (table $0 3 3 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $keep2 $keep2 $caller) + + ;; CHECK: (export "keep2" (func $keep2)) + (export "keep2" (func $keep2)) + (export "other" (func $other)) + (table 3 3 funcref) + (elem (i32.const 0) $keep2 $other $caller) + ;; CHECK: (export "other" (func $keep2)) + + ;; CHECK: (start $keep2) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (nop) + ) + (func $other (type $0) + (nop) + ) + ;; CHECK: (func $caller + ;; CHECK-NEXT: (call $keep2) + ;; CHECK-NEXT: (call $keep2) + ;; CHECK-NEXT: ) + (func $caller (type $0) + (call $keep2) + (call $other) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2-after-two-passes + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keep2-after-two-passes (type $0) + (nop) + ) + (func $other (type $0) + (nop) + ) + ;; CHECK: (func $keep-caller + ;; CHECK-NEXT: (call $keep2-after-two-passes) + ;; CHECK-NEXT: ) + (func $keep-caller (type $0) + (call $keep2-after-two-passes) + ) + (func $other-caller (type $0) + (call $other) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep-4 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keep-4 (type $0) + (nop) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $other (type $0) + (unreachable) + ) + ;; CHECK: (func $keep-caller + ;; CHECK-NEXT: (call $keep-4) + ;; CHECK-NEXT: ) + (func $keep-caller (type $0) + (call $keep-4) + ) + ;; CHECK: (func $other-caller + ;; CHECK-NEXT: (call $other) + ;; CHECK-NEXT: ) + (func $other-caller (type $0) + (call $other) + ) +) +(module + (memory 0) + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param i32))) + + ;; CHECK: (type $T (func (result i32))) + (type $T (func (result i32))) + (type $S (func (result i32))) + (type $2 (func)) + (type $3 (func (param i32))) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep4-similar-but-func-sig-differs + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep4-similar-but-func-sig-differs (type $2) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other1 (param $i i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other1 (type $3) (param $i i32) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other2 (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $other2 (type $T) (result i32) + (i32.const 0) + ) + (func $other3 (type $S) (result i32) + (i32.const 0) + ) +) +(module + (memory 0) + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (type $S (func (result i32))) + (type $S (func (result i32))) + (type $1 (func (param i32))) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2-similar-but-func-sig-differs (param $i i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2-similar-but-func-sig-differs (type $1) (param $i i32) + (drop + (i32.const 0) + ) + ) + (func $other1 (type $1) (param $i i32) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other2 (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $other2 (type $S) (result i32) + (i32.const 0) + ) + (func $other3 (type $S) (result i32) + (i32.const 0) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (nop) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $other (type $0) + (nop) + (nop) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (block $block0 + ) + ) + (func $other (type $0) + (block $block0 + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $block0 + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $block0 + (nop) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (block $block0 + (nop) + ) + ) + (func $other (type $0) + (block $block0 + (nop) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $block0 + (nop) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $block0 + (nop) + (unreachable) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $block0 + (nop) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $block0 + (unreachable) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase-since-block-names-do-not-matter + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase-since-block-names-do-not-matter (type $0) + (block $foo + ) + ) + (func $other (type $0) + (block $bar + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase-since-block-names-do-not-matter + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br $foo) + ;; CHECK-NEXT: (br_table $foo $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase-since-block-names-do-not-matter (type $0) + (block $foo + (br $foo) + (br_table $foo $foo + (i32.const 0) + ) + ) + ) + (func $other (type $0) + (block $bar + (br $bar) + (br_table $bar $bar + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $foo + (block + (drop + (i32.const 0) + ) + (br $foo) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $bar) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $bar + (block + (drop + (i32.const 1) + ) + (br $bar) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br_if $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $foo + (br_if $foo + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (br_if $bar + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $bar + (br_if $bar + (i32.const 1) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br_if $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (block $foo + (br_if $foo + (i32.const 0) + ) + ) + ) + (func $other (type $0) + (block $bar + (br_if $bar + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br_table $foo $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $foo + (br_table $foo $foo + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (br_table $bar $bar + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $bar + (br_table $bar $bar + (i32.const 1) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (loop $bar + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (loop $bar + (nop) + ) + ) + (func $other (type $0) + (loop $sjc + (nop) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $foo (result i32) + ;; CHECK-NEXT: (br_table $foo $foo + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (block $foo (result i32) + (br_table $foo $foo + (i32.const 0) + (i32.const 0) + ) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $bar (result i32) + ;; CHECK-NEXT: (br_table $bar $bar + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (block $bar (result i32) + (br_table $bar $bar + (i32.const 0) + (i32.const 1) + ) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (br_table $foo $bar + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (block $foo + (block $bar + (br_table $foo $bar + (i32.const 0) + ) + ) + ) + ) + (func $other (type $0) + (block $bar + (block $foo + (br_table $bar $foo + (i32.const 0) + ) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (br_table $foo $bar + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (block $foo + (block $bar + (br_table $foo $bar + (i32.const 0) + ) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (block $bar + ;; CHECK-NEXT: (block $foo + ;; CHECK-NEXT: (br_table $foo $bar + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (block $bar + (block $foo + (br_table $foo $bar + (i32.const 0) + ) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (call $erase) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (call $erase) + ) + (func $other (type $0) + (call $erase) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2-but-in-theory-we-could-erase + ;; CHECK-NEXT: (call $keep2-but-in-theory-we-could-erase) + ;; CHECK-NEXT: ) + (func $keep2-but-in-theory-we-could-erase (type $0) + (call $keep2-but-in-theory-we-could-erase) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (call $other) + ;; CHECK-NEXT: ) + (func $other (type $0) + (call $other) + ) +) +(module + ;; CHECK: (type $FUNCSIG$v (func)) + + ;; CHECK: (import "env" "i" (func $i)) + (import "env" "i" (func $i)) + ;; CHECK: (import "env" "j" (func $j)) + (import "env" "j" (func $j)) + (memory 0) + (type $FUNCSIG$v (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (call $i) + ;; CHECK-NEXT: ) + (func $erase (type $FUNCSIG$v) + (call $i) + ) + (func $other (type $FUNCSIG$v) + (call $i) + ) +) +(module + ;; CHECK: (type $FUNCSIG$v (func)) + + ;; CHECK: (import "env" "i" (func $i)) + (import "env" "i" (func $i)) + ;; CHECK: (import "env" "j" (func $j)) + (import "env" "j" (func $j)) + (memory 0) + (type $FUNCSIG$v (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (call $i) + ;; CHECK-NEXT: ) + (func $keep2 (type $FUNCSIG$v) + (call $i) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (call $j) + ;; CHECK-NEXT: ) + (func $other (type $FUNCSIG$v) + (call $j) + ) +) +(module + (memory 0) + ;; CHECK: (type $T (func)) + (type $T (func)) + (table 2 2 funcref) + (elem (i32.const 0) $erase $other) + ;; CHECK: (memory $0 0) + + ;; CHECK: (table $0 2 2 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $erase $erase) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $T) + (call_indirect (type $T) + (i32.const 0) + ) + ) + (func $other (type $T) + (call_indirect (type $T) + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $T (func)) + (type $T (func)) + (table 2 2 funcref) + (elem (i32.const 0) $keep2 $other) + ;; CHECK: (memory $0 0) + + ;; CHECK: (table $0 2 2 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $keep2 $other) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $T) + (call_indirect (type $T) + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $T) + (call_indirect (type $T) + (i32.const 1) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $T (func)) + (type $T (func)) + (type $S (func)) + (table 2 2 funcref) + (elem (i32.const 0) $keep2 $other) + ;; CHECK: (memory $0 0) + + ;; CHECK: (table $0 2 2 funcref) + + ;; CHECK: (elem $0 (i32.const 0) $keep2 $keep2) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (call_indirect (type $T) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $T) + (call_indirect (type $T) + (i32.const 0) + ) + ) + (func $other (type $T) + (call_indirect (type $S) + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase-even-locals-with-different-names + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase-even-locals-with-different-names (type $0) + (local $i i32) + (drop + (local.get $i) + ) + ) + (func $other (type $0) + (local $j i32) + (drop + (local.get $j) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (local $i i32) + (drop + (local.get $i) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (local $j i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (local $j i64) + (drop + (local.get $j) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase-even-locals-with-different-names + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase-even-locals-with-different-names (type $0) + (local $i i32) + (local.set $i + (i32.const 0) + ) + ) + (func $other (type $0) + (local $j i32) + (local.set $j + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (local $i i32) + (local.set $i + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (local $j i64) + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (local $j i64) + (local.set $j + (i64.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (local $i i32) + (local.set $i + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (local $j i32) + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (local $j i32) + (local.set $j + (i32.const 1) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (i32.load + (i32.const 0) + ) + ) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) + (func $other (type $0) + (drop + (i32.load + (i32.const 0) + ) + ) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load offset=3 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load16_s offset=3 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 align=1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=1 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load16_s align=2 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 1) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_u offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.load16_u offset=3 align=2 + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i32.load16_s offset=3 align=2 + (i32.const 0) + ) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (i32.store + (i32.const 0) + (i32.const 100) + ) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) + (func $other (type $0) + (i32.store + (i32.const 0) + (i32.const 100) + ) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store offset=3 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store16 offset=3 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 align=1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=1 + (i32.const 0) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store16 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store16 align=2 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=2 + (i32.const 1) + (i32.const 100) + ) + ) +) +(module + (memory 10) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 10) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 100) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (i32.store16 offset=3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 101) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (i32.store16 offset=3 align=2 + (i32.const 0) + (i32.const 101) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i64.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i32.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f64.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (i64.const 0) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (i64.const 1) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const 0.10000000149011612) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.const 0.10000000149011612) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const -0.10000000149011612) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.const -0.10000000149011612) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.const 0.1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f64.const 0.1) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.const 0.2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f64.const 0.2) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (f32.abs + (f32.const 0) + ) + ) + ) + (func $other (type $0) + (drop + (f32.abs + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.abs + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.abs + (f32.const 1) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.abs + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.neg + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.neg + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) + (func $other (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 1) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 1) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.add + (f32.const 1) + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.add + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep2 (type $0) + (drop + (f32.add + (f32.const 0) + (f32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.sub + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (f32.sub + (f32.const 0) + (f32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) + (func $other (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (select + (i32.const 1) + (i32.const 0) + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (select + (i32.const 0) + (i32.const 2) + (i32.const 0) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (select + (i32.const 0) + (i32.const 0) + (i32.const 3) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (return) + ) + (func $other (type $0) + (return) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func (result i32))) + (type $0 (func (result i32))) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase (result i32) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) (result i32) + (return + (i32.const 0) + ) + ) + (func $other (type $0) (result i32) + (return + (i32.const 0) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func (result i32))) + (type $0 (func (result i32))) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep (result i32) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) (result i32) + (return + (i32.const 0) + ) + ) + ;; CHECK: (func $other (result i32) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) (result i32) + (return + (i32.const 1) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (memory.size) + ) + ) + (func $other (type $0) + (drop + (memory.size) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $erase + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $erase (type $0) + (drop + (memory.grow + (i32.const 10) + ) + ) + ) + (func $other (type $0) + (drop + (memory.grow + (i32.const 10) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (memory.grow + (i32.const 10) + ) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (memory.grow + (i32.const 11) + ) + ) + ) +) +(module + (memory 0) + ;; CHECK: (type $0 (func)) + (type $0 (func)) + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $keep + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keep (type $0) + (drop + (memory.size) + ) + ) + ;; CHECK: (func $other + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other (type $0) + (drop + (memory.grow + (i32.const 10) + ) + ) + ) +) diff --git a/test/passes/duplicate-function-elimination_all-features.txt b/test/passes/duplicate-function-elimination_all-features.txt deleted file mode 100644 index d46f4ed416f..00000000000 --- a/test/passes/duplicate-function-elimination_all-features.txt +++ /dev/null @@ -1,33 +0,0 @@ -(module - (type $0 (func (result i32))) - (type $1 (func (result funcref))) - (elem declare func $0) - (func $0 (type $0) (result i32) - (i32.const 0) - ) - (func $test (type $1) (result funcref) - (ref.func $0) - ) -) -(module - (type $0 (func)) - (global $bar i32 (i32.const 0)) - (memory $foo 16 16) - (export "memory" (memory $foo)) - (export "global" (global $bar)) - (func $bar (type $0) - ) -) -(module - (type $func (func (result i32))) - (global $global$0 (ref $func) (ref.func $foo)) - (export "export" (func $export)) - (func $foo (type $func) (result i32) - (unreachable) - ) - (func $export (type $func) (result i32) - (call_ref $func - (global.get $global$0) - ) - ) -) diff --git a/test/passes/duplicate-function-elimination_all-features.wast b/test/passes/duplicate-function-elimination_all-features.wast deleted file mode 100644 index 2dccd051f58..00000000000 --- a/test/passes/duplicate-function-elimination_all-features.wast +++ /dev/null @@ -1,41 +0,0 @@ -;; --duplicate-function-elimination should not remove functions used in ref.func. -(module - (func $0 (result i32) - (i32.const 0) - ) - (func $1 (result i32) - (i32.const 0) - ) - (func $test (result funcref) - (ref.func $1) - ) -) -;; renaming after deduplication must only affect functions -(module - (memory $foo 16 16) - (global $bar i32 (i32.const 0)) - (export "memory" (memory $foo)) - (export "global" (global $bar)) - (func $bar ;; happens to share a name with the global - ) - (func $foo ;; happens to share a name with the memory - ) -) -;; renaming after deduplication must update ref.funcs in globals -(module - (type $func (func (result i32))) - (global $global$0 (ref $func) (ref.func $bar)) - ;; These two identical functions can be merged. The ref.func in the global must - ;; be updated accordingly. - (func $foo (result i32) - (unreachable) - ) - (func $bar (result i32) - (unreachable) - ) - (func $export (export "export") (result i32) - (call_ref $func - (global.get $global$0) - ) - ) -) diff --git a/test/passes/duplicate-function-elimination_optimize-level=1.txt b/test/passes/duplicate-function-elimination_optimize-level=1.txt deleted file mode 100644 index a61abe7224f..00000000000 --- a/test/passes/duplicate-function-elimination_optimize-level=1.txt +++ /dev/null @@ -1,1058 +0,0 @@ -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (nop) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (nop) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (i32.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (drop - (i32.const 1) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (table $0 3 3 funcref) - (elem $0 (i32.const 0) $keep2 $keep2 $caller) - (export "keep2" (func $keep2)) - (export "other" (func $keep2)) - (start $keep2) - (func $keep2 - (nop) - ) - (func $caller - (call $keep2) - (call $keep2) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2-after-two-passes - (nop) - ) - (func $keep-caller - (call $keep2-after-two-passes) - ) - (func $other-caller - (call $keep2-after-two-passes) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep-4 - (nop) - ) - (func $other - (unreachable) - ) - (func $keep-caller - (call $keep-4) - ) - (func $other-caller - (call $other) - ) -) -(module - (type $2 (func)) - (type $3 (func (param i32))) - (type $T (func (result i32))) - (memory $0 0) - (func $keep4-similar-but-func-sig-differs - (drop - (i32.const 0) - ) - ) - (func $other1 (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other2 (result i32) - (i32.const 0) - ) -) -(module - (type $1 (func (param i32))) - (type $S (func (result i32))) - (memory $0 0) - (func $keep2-similar-but-func-sig-differs (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other2 (result i32) - (i32.const 0) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (nop) - ) - (func $other - (nop) - (nop) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (block $block0 - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $block0 - ) - ) - (func $other - (block $block0 - (nop) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (block $block0 - (nop) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $block0 - (nop) - ) - ) - (func $other - (block $block0 - (nop) - (unreachable) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $block0 - (nop) - ) - ) - (func $other - (block $block0 - (unreachable) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase-since-block-names-do-not-matter - (block $foo - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase-since-block-names-do-not-matter - (block $foo - (br $foo) - (br_table $foo $foo - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $foo - (block - (drop - (i32.const 0) - ) - (br $foo) - ) - ) - ) - (func $other - (block $bar - (block - (drop - (i32.const 1) - ) - (br $bar) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $foo - (br_if $foo - (i32.const 0) - ) - ) - ) - (func $other - (block $bar - (br_if $bar - (i32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (block $foo - (br_if $foo - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $foo - (br_table $foo $foo - (i32.const 0) - ) - ) - ) - (func $other - (block $bar - (br_table $bar $bar - (i32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (loop $bar - (nop) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (block $foo (result i32) - (br_table $foo $foo - (i32.const 0) - (i32.const 0) - ) - ) - ) - ) - (func $other - (drop - (block $bar (result i32) - (br_table $bar $bar - (i32.const 0) - (i32.const 1) - ) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $foo - (block $bar - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (block $foo - (block $bar - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) - (func $other - (block $bar - (block $foo - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (call $erase) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2-but-in-theory-we-could-erase - (call $keep2-but-in-theory-we-could-erase) - ) - (func $other - (call $other) - ) -) -(module - (type $FUNCSIG$v (func)) - (import "env" "i" (func $i)) - (import "env" "j" (func $j)) - (memory $0 0) - (func $erase - (call $i) - ) -) -(module - (type $FUNCSIG$v (func)) - (import "env" "i" (func $i)) - (import "env" "j" (func $j)) - (memory $0 0) - (func $keep2 - (call $i) - ) - (func $other - (call $j) - ) -) -(module - (type $T (func)) - (memory $0 0) - (table $0 2 2 funcref) - (elem $0 (i32.const 0) $erase $erase) - (func $erase - (call_indirect (type $T) - (i32.const 0) - ) - ) -) -(module - (type $T (func)) - (memory $0 0) - (table $0 2 2 funcref) - (elem $0 (i32.const 0) $keep2 $other) - (func $keep2 - (call_indirect (type $T) - (i32.const 0) - ) - ) - (func $other - (call_indirect (type $T) - (i32.const 1) - ) - ) -) -(module - (type $T (func)) - (memory $0 0) - (table $0 2 2 funcref) - (elem $0 (i32.const 0) $keep2 $keep2) - (func $keep2 - (call_indirect (type $T) - (i32.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase-even-locals-with-different-names - (local $i i32) - (drop - (local.get $i) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (local $i i32) - (drop - (local.get $i) - ) - ) - (func $other - (local $j i64) - (drop - (local.get $j) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase-even-locals-with-different-names - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other - (local $j i64) - (local.set $j - (i64.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other - (local $j i32) - (local.set $j - (i32.const 1) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $erase - (drop - (i32.load - (i32.const 0) - ) - ) - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load offset=3 - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 align=1 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load16_s - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 - (i32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load16_u offset=3 - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $erase - (i32.store - (i32.const 0) - (i32.const 100) - ) - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 align=1 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store16 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 - (i32.const 1) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 - (i32.const 0) - (i32.const 101) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (drop - (i64.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (drop - (f32.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (drop - (f64.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i64.const 0) - ) - ) - (func $other - (drop - (i64.const 1) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.const 0.10000000149011612) - ) - ) - (func $other - (drop - (f32.const -0.10000000149011612) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f64.const 0.1) - ) - ) - (func $other - (drop - (f64.const 0.2) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (f32.abs - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.abs - (f32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.neg - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.add - (f32.const 0) - (f32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.add - (f32.const 1) - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.sub - (f32.const 0) - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other - (drop - (select - (i32.const 1) - (i32.const 0) - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other - (drop - (select - (i32.const 0) - (i32.const 2) - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 3) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (return) - ) -) -(module - (type $0 (func (result i32))) - (memory $0 0) - (func $erase (result i32) - (return - (i32.const 0) - ) - ) -) -(module - (type $0 (func (result i32))) - (memory $0 0) - (func $keep (result i32) - (return - (i32.const 0) - ) - ) - (func $other (result i32) - (return - (i32.const 1) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (memory.size) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (memory.grow - (i32.const 10) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (memory.grow - (i32.const 10) - ) - ) - ) - (func $other - (drop - (memory.grow - (i32.const 11) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (memory.size) - ) - ) - (func $other - (drop - (memory.grow - (i32.const 10) - ) - ) - ) -) diff --git a/test/passes/duplicate-function-elimination_optimize-level=1.wast b/test/passes/duplicate-function-elimination_optimize-level=1.wast deleted file mode 100644 index c66cfba5dd0..00000000000 --- a/test/passes/duplicate-function-elimination_optimize-level=1.wast +++ /dev/null @@ -1,1221 +0,0 @@ -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (nop) - ) - (func $other (type $0) - (nop) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (nop) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (i32.const 1) - ) - ) -) -(module - (memory 0) - (start $other) - (type $0 (func)) - (export "keep2" (func $keep2)) - (export "other" (func $other)) - (table 3 3 funcref) - (elem (i32.const 0) $keep2 $other $caller) - (func $keep2 (type $0) - (nop) - ) - (func $other (type $0) - (nop) - ) - (func $caller (type $0) - (call $keep2) - (call $other) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2-after-two-passes (type $0) - (nop) - ) - (func $other (type $0) - (nop) - ) - (func $keep-caller (type $0) - (call $keep2-after-two-passes) - ) - (func $other-caller (type $0) - (call $other) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep-4 (type $0) - (nop) - ) - (func $other (type $0) - (unreachable) - ) - (func $keep-caller (type $0) - (call $keep-4) - ) - (func $other-caller (type $0) - (call $other) - ) -) -(module - (memory 0) - (type $T (func (result i32))) - (type $S (func (result i32))) - (type $2 (func)) - (type $3 (func (param i32))) - (func $keep4-similar-but-func-sig-differs (type $2) - (drop - (i32.const 0) - ) - ) - (func $other1 (type $3) (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other2 (type $T) (result i32) - (i32.const 0) - ) - (func $other3 (type $S) (result i32) - (i32.const 0) - ) -) -(module - (memory 0) - (type $S (func (result i32))) - (type $1 (func (param i32))) - (func $keep2-similar-but-func-sig-differs (type $1) (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other1 (type $1) (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other2 (type $S) (result i32) - (i32.const 0) - ) - (func $other3 (type $S) (result i32) - (i32.const 0) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (nop) - ) - (func $other (type $0) - (nop) - (nop) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (block $block0 - ) - ) - (func $other (type $0) - (block $block0 - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $block0 - ) - ) - (func $other (type $0) - (block $block0 - (nop) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (block $block0 - (nop) - ) - ) - (func $other (type $0) - (block $block0 - (nop) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $block0 - (nop) - ) - ) - (func $other (type $0) - (block $block0 - (nop) - (unreachable) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $block0 - (nop) - ) - ) - (func $other (type $0) - (block $block0 - (unreachable) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase-since-block-names-do-not-matter (type $0) - (block $foo - ) - ) - (func $other (type $0) - (block $bar - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase-since-block-names-do-not-matter (type $0) - (block $foo - (br $foo) - (br_table $foo $foo - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (block $bar - (br $bar) - (br_table $bar $bar - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $foo - (block - (drop - (i32.const 0) - ) - (br $foo) - ) - ) - ) - (func $other (type $0) - (block $bar - (block - (drop - (i32.const 1) - ) - (br $bar) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $foo - (br_if $foo - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (block $bar - (br_if $bar - (i32.const 1) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (block $foo - (br_if $foo - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (block $bar - (br_if $bar - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $foo - (br_table $foo $foo - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (block $bar - (br_table $bar $bar - (i32.const 1) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (loop $bar - (nop) - ) - ) - (func $other (type $0) - (loop $sjc - (nop) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (block $foo (result i32) - (br_table $foo $foo - (i32.const 0) - (i32.const 0) - ) - ) - ) - ) - (func $other (type $0) - (drop - (block $bar (result i32) - (br_table $bar $bar - (i32.const 0) - (i32.const 1) - ) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $foo - (block $bar - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) - (func $other (type $0) - (block $bar - (block $foo - (br_table $bar $foo - (i32.const 0) - ) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (block $foo - (block $bar - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) - (func $other (type $0) - (block $bar - (block $foo - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (call $erase) - ) - (func $other (type $0) - (call $erase) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2-but-in-theory-we-could-erase (type $0) - (call $keep2-but-in-theory-we-could-erase) - ) - (func $other (type $0) - (call $other) - ) -) -(module - (import "env" "i" (func $i)) - (import "env" "j" (func $j)) - (memory 0) - (type $FUNCSIG$v (func)) - (func $erase (type $FUNCSIG$v) - (call $i) - ) - (func $other (type $FUNCSIG$v) - (call $i) - ) -) -(module - (import "env" "i" (func $i)) - (import "env" "j" (func $j)) - (memory 0) - (type $FUNCSIG$v (func)) - (func $keep2 (type $FUNCSIG$v) - (call $i) - ) - (func $other (type $FUNCSIG$v) - (call $j) - ) -) -(module - (memory 0) - (type $T (func)) - (table 2 2 funcref) - (elem (i32.const 0) $erase $other) - (func $erase (type $T) - (call_indirect (type $T) - (i32.const 0) - ) - ) - (func $other (type $T) - (call_indirect (type $T) - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $T (func)) - (table 2 2 funcref) - (elem (i32.const 0) $keep2 $other) - (func $keep2 (type $T) - (call_indirect (type $T) - (i32.const 0) - ) - ) - (func $other (type $T) - (call_indirect (type $T) - (i32.const 1) - ) - ) -) -(module - (memory 0) - (type $T (func)) - (type $S (func)) - (table 2 2 funcref) - (elem (i32.const 0) $keep2 $other) - (func $keep2 (type $T) - (call_indirect (type $T) - (i32.const 0) - ) - ) - (func $other (type $T) - (call_indirect (type $S) - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase-even-locals-with-different-names (type $0) - (local $i i32) - (drop - (local.get $i) - ) - ) - (func $other (type $0) - (local $j i32) - (drop - (local.get $j) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (local $i i32) - (drop - (local.get $i) - ) - ) - (func $other (type $0) - (local $j i64) - (drop - (local.get $j) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase-even-locals-with-different-names (type $0) - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other (type $0) - (local $j i32) - (local.set $j - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other (type $0) - (local $j i64) - (local.set $j - (i64.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other (type $0) - (local $j i32) - (local.set $j - (i32.const 1) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $erase (type $0) - (drop - (i32.load - (i32.const 0) - ) - ) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load - (i32.const 0) - ) - ) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load offset=3 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=1 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load16_s align=2 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 1) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load16_u offset=3 align=2 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $erase (type $0) - (i32.store - (i32.const 0) - (i32.const 100) - ) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store - (i32.const 0) - (i32.const 100) - ) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=1 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store16 align=2 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=2 - (i32.const 1) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 101) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (i64.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (f32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (f64.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i64.const 0) - ) - ) - (func $other (type $0) - (drop - (i64.const 1) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.const 0.10000000149011612) - ) - ) - (func $other (type $0) - (drop - (f32.const -0.10000000149011612) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f64.const 0.1) - ) - ) - (func $other (type $0) - (drop - (f64.const 0.2) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.abs - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.abs - (f32.const 1) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.neg - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 1) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.add - (f32.const 1) - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.sub - (f32.const 0) - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (select - (i32.const 1) - (i32.const 0) - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (select - (i32.const 0) - (i32.const 2) - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 3) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (return) - ) - (func $other (type $0) - (return) - ) -) -(module - (memory 0) - (type $0 (func (result i32))) - (func $erase (type $0) (result i32) - (return - (i32.const 0) - ) - ) - (func $other (type $0) (result i32) - (return - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func (result i32))) - (func $keep (type $0) (result i32) - (return - (i32.const 0) - ) - ) - (func $other (type $0) (result i32) - (return - (i32.const 1) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (memory.size) - ) - ) - (func $other (type $0) - (drop - (memory.size) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (memory.grow - (i32.const 10) - ) - ) - ) - (func $other (type $0) - (drop - (memory.grow - (i32.const 10) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (memory.grow - (i32.const 10) - ) - ) - ) - (func $other (type $0) - (drop - (memory.grow - (i32.const 11) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (memory.size) - ) - ) - (func $other (type $0) - (drop - (memory.grow - (i32.const 10) - ) - ) - ) -) diff --git a/test/passes/duplicate-function-elimination_optimize-level=2.txt b/test/passes/duplicate-function-elimination_optimize-level=2.txt deleted file mode 100644 index 27ea0bffbbe..00000000000 --- a/test/passes/duplicate-function-elimination_optimize-level=2.txt +++ /dev/null @@ -1,1055 +0,0 @@ -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (nop) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (nop) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (i32.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (drop - (i32.const 1) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (table $0 3 3 funcref) - (elem $0 (i32.const 0) $keep2 $keep2 $caller) - (export "keep2" (func $keep2)) - (export "other" (func $keep2)) - (start $keep2) - (func $keep2 - (nop) - ) - (func $caller - (call $keep2) - (call $keep2) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2-after-two-passes - (nop) - ) - (func $keep-caller - (call $keep2-after-two-passes) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep-4 - (nop) - ) - (func $other - (unreachable) - ) - (func $keep-caller - (call $keep-4) - ) - (func $other-caller - (call $other) - ) -) -(module - (type $2 (func)) - (type $3 (func (param i32))) - (type $T (func (result i32))) - (memory $0 0) - (func $keep4-similar-but-func-sig-differs - (drop - (i32.const 0) - ) - ) - (func $other1 (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other2 (result i32) - (i32.const 0) - ) -) -(module - (type $1 (func (param i32))) - (type $S (func (result i32))) - (memory $0 0) - (func $keep2-similar-but-func-sig-differs (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other2 (result i32) - (i32.const 0) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (nop) - ) - (func $other - (nop) - (nop) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (block $block0 - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $block0 - ) - ) - (func $other - (block $block0 - (nop) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (block $block0 - (nop) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $block0 - (nop) - ) - ) - (func $other - (block $block0 - (nop) - (unreachable) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $block0 - (nop) - ) - ) - (func $other - (block $block0 - (unreachable) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase-since-block-names-do-not-matter - (block $foo - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase-since-block-names-do-not-matter - (block $foo - (br $foo) - (br_table $foo $foo - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $foo - (block - (drop - (i32.const 0) - ) - (br $foo) - ) - ) - ) - (func $other - (block $bar - (block - (drop - (i32.const 1) - ) - (br $bar) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $foo - (br_if $foo - (i32.const 0) - ) - ) - ) - (func $other - (block $bar - (br_if $bar - (i32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (block $foo - (br_if $foo - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $foo - (br_table $foo $foo - (i32.const 0) - ) - ) - ) - (func $other - (block $bar - (br_table $bar $bar - (i32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (loop $bar - (nop) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (block $foo (result i32) - (br_table $foo $foo - (i32.const 0) - (i32.const 0) - ) - ) - ) - ) - (func $other - (drop - (block $bar (result i32) - (br_table $bar $bar - (i32.const 0) - (i32.const 1) - ) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (block $foo - (block $bar - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (block $foo - (block $bar - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) - (func $other - (block $bar - (block $foo - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (call $erase) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2-but-in-theory-we-could-erase - (call $keep2-but-in-theory-we-could-erase) - ) - (func $other - (call $other) - ) -) -(module - (type $FUNCSIG$v (func)) - (import "env" "i" (func $i)) - (import "env" "j" (func $j)) - (memory $0 0) - (func $erase - (call $i) - ) -) -(module - (type $FUNCSIG$v (func)) - (import "env" "i" (func $i)) - (import "env" "j" (func $j)) - (memory $0 0) - (func $keep2 - (call $i) - ) - (func $other - (call $j) - ) -) -(module - (type $T (func)) - (memory $0 0) - (table $0 2 2 funcref) - (elem $0 (i32.const 0) $erase $erase) - (func $erase - (call_indirect (type $T) - (i32.const 0) - ) - ) -) -(module - (type $T (func)) - (memory $0 0) - (table $0 2 2 funcref) - (elem $0 (i32.const 0) $keep2 $other) - (func $keep2 - (call_indirect (type $T) - (i32.const 0) - ) - ) - (func $other - (call_indirect (type $T) - (i32.const 1) - ) - ) -) -(module - (type $T (func)) - (memory $0 0) - (table $0 2 2 funcref) - (elem $0 (i32.const 0) $keep2 $keep2) - (func $keep2 - (call_indirect (type $T) - (i32.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase-even-locals-with-different-names - (local $i i32) - (drop - (local.get $i) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (local $i i32) - (drop - (local.get $i) - ) - ) - (func $other - (local $j i64) - (drop - (local.get $j) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase-even-locals-with-different-names - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other - (local $j i64) - (local.set $j - (i64.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other - (local $j i32) - (local.set $j - (i32.const 1) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $erase - (drop - (i32.load - (i32.const 0) - ) - ) - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load offset=3 - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 align=1 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load16_s - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 - (i32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (drop - (i32.load16_u offset=3 - (i32.const 0) - ) - ) - ) - (func $other - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $erase - (i32.store - (i32.const 0) - (i32.const 100) - ) - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 align=1 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store16 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 - (i32.const 1) - (i32.const 100) - ) - ) -) -(module - (type $0 (func)) - (memory $0 10) - (func $keep2 - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other - (i32.store16 offset=3 - (i32.const 0) - (i32.const 101) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (drop - (i64.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (drop - (f32.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i32.const 0) - ) - ) - (func $other - (drop - (f64.const 0) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (i64.const 0) - ) - ) - (func $other - (drop - (i64.const 1) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.const 0.10000000149011612) - ) - ) - (func $other - (drop - (f32.const -0.10000000149011612) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f64.const 0.1) - ) - ) - (func $other - (drop - (f64.const 0.2) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (f32.abs - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.abs - (f32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.neg - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.add - (f32.const 0) - (f32.const 1) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.add - (f32.const 1) - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep2 - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other - (drop - (f32.sub - (f32.const 0) - (f32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other - (drop - (select - (i32.const 1) - (i32.const 0) - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other - (drop - (select - (i32.const 0) - (i32.const 2) - (i32.const 0) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 3) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (return) - ) -) -(module - (type $0 (func (result i32))) - (memory $0 0) - (func $erase (result i32) - (return - (i32.const 0) - ) - ) -) -(module - (type $0 (func (result i32))) - (memory $0 0) - (func $keep (result i32) - (return - (i32.const 0) - ) - ) - (func $other (result i32) - (return - (i32.const 1) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (memory.size) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $erase - (drop - (memory.grow - (i32.const 10) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (memory.grow - (i32.const 10) - ) - ) - ) - (func $other - (drop - (memory.grow - (i32.const 11) - ) - ) - ) -) -(module - (type $0 (func)) - (memory $0 0) - (func $keep - (drop - (memory.size) - ) - ) - (func $other - (drop - (memory.grow - (i32.const 10) - ) - ) - ) -) diff --git a/test/passes/duplicate-function-elimination_optimize-level=2.wast b/test/passes/duplicate-function-elimination_optimize-level=2.wast deleted file mode 100644 index c66cfba5dd0..00000000000 --- a/test/passes/duplicate-function-elimination_optimize-level=2.wast +++ /dev/null @@ -1,1221 +0,0 @@ -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (nop) - ) - (func $other (type $0) - (nop) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (nop) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (i32.const 1) - ) - ) -) -(module - (memory 0) - (start $other) - (type $0 (func)) - (export "keep2" (func $keep2)) - (export "other" (func $other)) - (table 3 3 funcref) - (elem (i32.const 0) $keep2 $other $caller) - (func $keep2 (type $0) - (nop) - ) - (func $other (type $0) - (nop) - ) - (func $caller (type $0) - (call $keep2) - (call $other) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2-after-two-passes (type $0) - (nop) - ) - (func $other (type $0) - (nop) - ) - (func $keep-caller (type $0) - (call $keep2-after-two-passes) - ) - (func $other-caller (type $0) - (call $other) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep-4 (type $0) - (nop) - ) - (func $other (type $0) - (unreachable) - ) - (func $keep-caller (type $0) - (call $keep-4) - ) - (func $other-caller (type $0) - (call $other) - ) -) -(module - (memory 0) - (type $T (func (result i32))) - (type $S (func (result i32))) - (type $2 (func)) - (type $3 (func (param i32))) - (func $keep4-similar-but-func-sig-differs (type $2) - (drop - (i32.const 0) - ) - ) - (func $other1 (type $3) (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other2 (type $T) (result i32) - (i32.const 0) - ) - (func $other3 (type $S) (result i32) - (i32.const 0) - ) -) -(module - (memory 0) - (type $S (func (result i32))) - (type $1 (func (param i32))) - (func $keep2-similar-but-func-sig-differs (type $1) (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other1 (type $1) (param $i i32) - (drop - (i32.const 0) - ) - ) - (func $other2 (type $S) (result i32) - (i32.const 0) - ) - (func $other3 (type $S) (result i32) - (i32.const 0) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (nop) - ) - (func $other (type $0) - (nop) - (nop) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (block $block0 - ) - ) - (func $other (type $0) - (block $block0 - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $block0 - ) - ) - (func $other (type $0) - (block $block0 - (nop) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (block $block0 - (nop) - ) - ) - (func $other (type $0) - (block $block0 - (nop) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $block0 - (nop) - ) - ) - (func $other (type $0) - (block $block0 - (nop) - (unreachable) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $block0 - (nop) - ) - ) - (func $other (type $0) - (block $block0 - (unreachable) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase-since-block-names-do-not-matter (type $0) - (block $foo - ) - ) - (func $other (type $0) - (block $bar - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase-since-block-names-do-not-matter (type $0) - (block $foo - (br $foo) - (br_table $foo $foo - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (block $bar - (br $bar) - (br_table $bar $bar - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $foo - (block - (drop - (i32.const 0) - ) - (br $foo) - ) - ) - ) - (func $other (type $0) - (block $bar - (block - (drop - (i32.const 1) - ) - (br $bar) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $foo - (br_if $foo - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (block $bar - (br_if $bar - (i32.const 1) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (block $foo - (br_if $foo - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (block $bar - (br_if $bar - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $foo - (br_table $foo $foo - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (block $bar - (br_table $bar $bar - (i32.const 1) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (loop $bar - (nop) - ) - ) - (func $other (type $0) - (loop $sjc - (nop) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (block $foo (result i32) - (br_table $foo $foo - (i32.const 0) - (i32.const 0) - ) - ) - ) - ) - (func $other (type $0) - (drop - (block $bar (result i32) - (br_table $bar $bar - (i32.const 0) - (i32.const 1) - ) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (block $foo - (block $bar - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) - (func $other (type $0) - (block $bar - (block $foo - (br_table $bar $foo - (i32.const 0) - ) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (block $foo - (block $bar - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) - (func $other (type $0) - (block $bar - (block $foo - (br_table $foo $bar - (i32.const 0) - ) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (call $erase) - ) - (func $other (type $0) - (call $erase) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2-but-in-theory-we-could-erase (type $0) - (call $keep2-but-in-theory-we-could-erase) - ) - (func $other (type $0) - (call $other) - ) -) -(module - (import "env" "i" (func $i)) - (import "env" "j" (func $j)) - (memory 0) - (type $FUNCSIG$v (func)) - (func $erase (type $FUNCSIG$v) - (call $i) - ) - (func $other (type $FUNCSIG$v) - (call $i) - ) -) -(module - (import "env" "i" (func $i)) - (import "env" "j" (func $j)) - (memory 0) - (type $FUNCSIG$v (func)) - (func $keep2 (type $FUNCSIG$v) - (call $i) - ) - (func $other (type $FUNCSIG$v) - (call $j) - ) -) -(module - (memory 0) - (type $T (func)) - (table 2 2 funcref) - (elem (i32.const 0) $erase $other) - (func $erase (type $T) - (call_indirect (type $T) - (i32.const 0) - ) - ) - (func $other (type $T) - (call_indirect (type $T) - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $T (func)) - (table 2 2 funcref) - (elem (i32.const 0) $keep2 $other) - (func $keep2 (type $T) - (call_indirect (type $T) - (i32.const 0) - ) - ) - (func $other (type $T) - (call_indirect (type $T) - (i32.const 1) - ) - ) -) -(module - (memory 0) - (type $T (func)) - (type $S (func)) - (table 2 2 funcref) - (elem (i32.const 0) $keep2 $other) - (func $keep2 (type $T) - (call_indirect (type $T) - (i32.const 0) - ) - ) - (func $other (type $T) - (call_indirect (type $S) - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase-even-locals-with-different-names (type $0) - (local $i i32) - (drop - (local.get $i) - ) - ) - (func $other (type $0) - (local $j i32) - (drop - (local.get $j) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (local $i i32) - (drop - (local.get $i) - ) - ) - (func $other (type $0) - (local $j i64) - (drop - (local.get $j) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase-even-locals-with-different-names (type $0) - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other (type $0) - (local $j i32) - (local.set $j - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other (type $0) - (local $j i64) - (local.set $j - (i64.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (local $i i32) - (local.set $i - (i32.const 0) - ) - ) - (func $other (type $0) - (local $j i32) - (local.set $j - (i32.const 1) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $erase (type $0) - (drop - (i32.load - (i32.const 0) - ) - ) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load - (i32.const 0) - ) - ) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load offset=3 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load16_s offset=3 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=1 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load16_s align=2 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 1) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.load16_u offset=3 align=2 - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (i32.load16_s offset=3 align=2 - (i32.const 0) - ) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $erase (type $0) - (i32.store - (i32.const 0) - (i32.const 100) - ) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store - (i32.const 0) - (i32.const 100) - ) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store16 offset=3 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=1 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store16 align=2 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=2 - (i32.const 1) - (i32.const 100) - ) - ) -) -(module - (memory 10) - (type $0 (func)) - (func $keep2 (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 100) - ) - ) - (func $other (type $0) - (i32.store16 offset=3 align=2 - (i32.const 0) - (i32.const 101) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (i64.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (f32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i32.const 0) - ) - ) - (func $other (type $0) - (drop - (f64.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (i64.const 0) - ) - ) - (func $other (type $0) - (drop - (i64.const 1) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.const 0.10000000149011612) - ) - ) - (func $other (type $0) - (drop - (f32.const -0.10000000149011612) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f64.const 0.1) - ) - ) - (func $other (type $0) - (drop - (f64.const 0.2) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.abs - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.abs - (f32.const 1) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.abs - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.neg - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 1) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.add - (f32.const 1) - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep2 (type $0) - (drop - (f32.add - (f32.const 0) - (f32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (f32.sub - (f32.const 0) - (f32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (select - (i32.const 1) - (i32.const 0) - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (select - (i32.const 0) - (i32.const 2) - (i32.const 0) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - ) - (func $other (type $0) - (drop - (select - (i32.const 0) - (i32.const 0) - (i32.const 3) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (return) - ) - (func $other (type $0) - (return) - ) -) -(module - (memory 0) - (type $0 (func (result i32))) - (func $erase (type $0) (result i32) - (return - (i32.const 0) - ) - ) - (func $other (type $0) (result i32) - (return - (i32.const 0) - ) - ) -) -(module - (memory 0) - (type $0 (func (result i32))) - (func $keep (type $0) (result i32) - (return - (i32.const 0) - ) - ) - (func $other (type $0) (result i32) - (return - (i32.const 1) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (memory.size) - ) - ) - (func $other (type $0) - (drop - (memory.size) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $erase (type $0) - (drop - (memory.grow - (i32.const 10) - ) - ) - ) - (func $other (type $0) - (drop - (memory.grow - (i32.const 10) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (memory.grow - (i32.const 10) - ) - ) - ) - (func $other (type $0) - (drop - (memory.grow - (i32.const 11) - ) - ) - ) -) -(module - (memory 0) - (type $0 (func)) - (func $keep (type $0) - (drop - (memory.size) - ) - ) - (func $other (type $0) - (drop - (memory.grow - (i32.const 10) - ) - ) - ) -) From e2aefc76c8b34fd6c09aca7a09dbada075259be2 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 27 Jun 2025 17:46:32 +0200 Subject: [PATCH 567/622] [NFC] Generalize DFS machinery in makeInhabitable (#7685) The heap type fuzzer provides a utility for transforming a type definition graph to ensure that all the defined types are inhabitable. This requires finding and breaking cycles of non-nullable references. We use a simple DFS to find the cycles, but this DFS previously assumed that each type would be visited only once at a time because it would always make the first backedge found nullable. A follow-on PR will update this DFS to handle descriptor types, which are like non-nullable references that cannot be made nullable. When the DFS finds a backedge correspnding to a descriptor, it will not be able to make that edge nullable. Instead, it will have to continue searching for the next backedge. To make that follow-on PR simpler, refactor the data structures used in the DFS to make it easier to adapt to visiting a type more than once at a time. --- src/tools/fuzzing/heap-types.cpp | 95 +++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index c1d99ee5c48..7917f0f3745 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -811,55 +811,87 @@ void Inhabitator::markExternRefsNullable() { } } -// Use a depth-first search to find cycles, marking the last found reference in -// the cycle to be made non-nullable. +// Break cycles of non-nullable references. Doing this optimally (i.e. by +// changing the fewest possible references) is NP-complete[1], so use a simple +// depth-first search rather than anything fancy. When we find a back edge +// forming a cycle, mark the reference forming the edge as nullable. +// +// [1]: https://en.wikipedia.org/wiki/Feedback_arc_set void Inhabitator::breakNonNullableCycles() { + // The types reachable from each heap type. + // TODO: Include descriptors. + std::unordered_map> children; + + auto getChildren = [&children](HeapType type) { + auto [it, inserted] = children.insert({type, {}}); + if (inserted) { + // TODO: Add descriptors. + it->second = type.getTypeChildren(); + } + return it->second; + }; + + // The sequence of visited types and edge indices comprising the current DFS + // search path. + std::vector> path; + + // Track how many times each heap type appears on the current path. + std::unordered_map visiting; + // Types we've finished visiting. We don't need to visit them again. std::unordered_set visited; - // The path of types we are currently visiting. If one of them comes back up, - // we've found a cycle. Map the types to the other types they reference and - // our current index into that list so we can track where we are in each level - // of the search. - InsertOrderedMap, Index>> visiting; + auto visitType = [&](HeapType type) { + path.push_back({type, 0}); + ++visiting[type]; + }; + + auto finishType = [&]() { + auto type = path.back().first; + path.pop_back(); + auto it = visiting.find(type); + assert(it != visiting.end()); + if (--it->second == 0) { + visiting.erase(it); + } + visited.insert(type); + }; for (auto root : types) { if (visited.count(root)) { continue; } assert(visiting.size() == 0); - visiting.insert({root, {root.getTypeChildren(), 0}}); + visitType(root); - while (visiting.size()) { - auto& [curr, state] = *std::prev(visiting.end()); - auto& [children, idx] = state; + while (path.size()) { + auto [curr, index] = path.back(); + const auto& children = getChildren(curr); - while (idx < children.size()) { + while (index < children.size()) { // Skip non-reference children because they cannot refer to other types. - if (!children[idx].isRef()) { - ++idx; + if (!children[index].isRef()) { + ++index; continue; } // Skip nullable references because they don't cause uninhabitable // cycles. - if (children[idx].isNullable()) { - ++idx; + if (children[index].isNullable()) { + ++index; continue; } // Skip references that we have already marked nullable to satisfy - // subtyping constraints. TODO: We could take such nullable references - // into account when detecting cycles by tracking where in the current - // search path we have made references nullable. - if (nullables.count({curr, idx})) { - ++idx; + // subtyping constraints. + if (nullables.count({curr, index})) { + ++index; continue; } // Skip references to types that we have finished visiting. We have // visited the full graph reachable from such references, so we know // they cannot cycle back to anything we are currently visiting. - auto heapType = children[idx].getHeapType(); + auto heapType = children[index].getHeapType(); if (visited.count(heapType)) { - ++idx; + ++index; continue; } // Skip references to function types. Functions types can always be @@ -867,14 +899,14 @@ void Inhabitator::breakNonNullableCycles() { // params or results. Function references therefore break cycles that // would otherwise produce uninhabitability. if (heapType.isSignature()) { - ++idx; + ++index; continue; } // If this ref forms a cycle, break the cycle by marking it nullable and // continue. if (auto it = visiting.find(heapType); it != visiting.end()) { - markNullable({curr, idx}); - ++idx; + markNullable({curr, index}); + ++index; continue; } break; @@ -882,16 +914,15 @@ void Inhabitator::breakNonNullableCycles() { // If we've finished the DFS on the current type, pop it off the search // path and continue searching the previous type. - if (idx == children.size()) { - visited.insert(curr); - visiting.erase(std::prev(visiting.end())); + if (index == children.size()) { + finishType(); continue; } // Otherwise we have a non-nullable reference we need to search. - assert(children[idx].isRef() && children[idx].isNonNullable()); - auto next = children[idx++].getHeapType(); - visiting.insert({next, {next.getTypeChildren(), 0}}); + assert(children[index].isRef() && children[index].isNonNullable()); + auto next = children[index++].getHeapType(); + visitType(next); } } } From dc50ee59be58aca15497b120f6b6df682868cf7b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 27 Jun 2025 18:49:22 +0200 Subject: [PATCH 568/622] [Custom Descriptors] Descriptors in type fuzzer (#7686) Generate custom descriptor types in the heap type fuzzer. When generating an eligible new type, choose a length for its descriptor chain. The length is limited by the space remaining in the current recursion group. Save the described type in a list of other such described types, and optionally pick a type to describe from that list when generating future types. Take descriptor chains into account to ensure validity when choosing supertypes as well. Also update other parts of the type fuzzer to acccount for custom descriptors. For example, update the utility for making types inhabitable to consider custom descriptors as another kind of non-nullable reference. Disable custom descriptors in the main fuzzer because it cannot yet handle generated custom descriptor types. --- src/tools/fuzzing/fuzzing.cpp | 5 +- src/tools/fuzzing/heap-types.cpp | 232 +++++++++++++++++++++++++++---- src/tools/fuzzing/heap-types.h | 3 + src/tools/wasm-fuzz-types.cpp | 13 ++ test/lit/fuzz-types.test | 80 +++++------ 5 files changed, 263 insertions(+), 70 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b79d9264ab9..5d474aaa728 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -446,8 +446,11 @@ void TranslateToFuzzReader::setupHeapTypes() { // For GC, also generate random types. if (wasm.features.hasGC()) { + // TODO: Support custom descriptors. + auto features = wasm.features; + features.setCustomDescriptors(false); auto generator = HeapTypeGenerator::create( - random, wasm.features, upTo(fuzzParams->MAX_NEW_GC_TYPES)); + random, features, upTo(fuzzParams->MAX_NEW_GC_TYPES)); auto result = generator.builder.build(); if (auto* err = result.getError()) { Fatal() << "Failed to build heap types: " << err->reason << " at index " diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 7917f0f3745..ee9cb1d2080 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include "ir/gc-type-utils.h" @@ -31,6 +32,9 @@ struct HeapTypeGeneratorImpl { TypeBuilder& builder; std::vector>& subtypeIndices; std::vector> supertypeIndices; + std::vector>& descriptorIndices; + std::vector> describedIndices; + std::vector descriptorChainLengths; Random& rand; FeatureSet features; @@ -57,9 +61,13 @@ struct HeapTypeGeneratorImpl { FuzzParams params; HeapTypeGeneratorImpl(Random& rand, FeatureSet features, size_t n) - : result{TypeBuilder(n), std::vector>(n)}, + : result{TypeBuilder(n), + std::vector>(n), + std::vector>(n)}, builder(result.builder), subtypeIndices(result.subtypeIndices), - supertypeIndices(n), rand(rand), features(features) { + supertypeIndices(n), descriptorIndices(result.descriptorIndices), + describedIndices(n), descriptorChainLengths(n), rand(rand), + features(features) { // Set up the subtype relationships. Start with some number of root types, // then after that start creating subtypes of existing types. Determine the // top-level kind and shareability of each type in advance so that we can @@ -95,32 +103,170 @@ struct HeapTypeGeneratorImpl { assert(start + size <= builder.size()); builder.createRecGroup(start, size); + // The indices of types that need descriptors and the total number of + // remaining descriptors we have committed to create in this group. + std::vector describees; + size_t numPlannedDescriptors = 0; + size_t end = start + size; for (size_t i = start; i < end; ++i) { recGroupEnds.push_back(end); - planType(i, numRoots); + planType(i, numRoots, end - i, describees, numPlannedDescriptors); } return size; } - void planType(size_t i, size_t numRoots) { + void planType(size_t i, + size_t numRoots, + size_t remaining, + std::vector& describees, + size_t& numPlannedDescriptors) { + assert(remaining >= numPlannedDescriptors); typeIndices.insert({builder[i], i}); // Everything is a subtype of itself. subtypeIndices[i].push_back(i); - if (i < numRoots || rand.oneIn(2)) { + + // We may pick a supertype. If we have a described type that itself has a + // supertype, then we must choose that supertype's descriptor as our + // supertype. + std::optional super; + + // Pick a type to describe, or choose not to describe a type by + // picking the one-past-the-end index. If all of the remaining types must be + // descriptors, then we must choose a describee. + Index describee = + rand.upTo(describees.size() + (remaining != numPlannedDescriptors)); + + bool isDescriptor = false; + if (describee != describees.size()) { + isDescriptor = true; + --numPlannedDescriptors; + + // If the intended described type has a supertype with a descriptor, then + // that descriptor must be the supertype of the type we intend to + // generate. However, we may not have generated that descriptor yet, + // meaning it is unavailable to be the supertype of the current type. + // Detect that situation and plan to generate the missing supertype + // instead. + Index described; + while (true) { + assert(describee < describees.size()); + described = describees[describee]; + auto describedSuper = supertypeIndices[described]; + if (!describedSuper) { + // The described type has no supertype, so there is no problem. + break; + } + if (descriptorChainLengths[*describedSuper] == 0) { + // The supertype of the described type will not have a descriptor, + // so there is no problem. + break; + } + if ((super = descriptorIndices[*describedSuper])) { + // The descriptor of the described type's supertype, which must become + // the current type's supertype, has already been generated. There is + // no problem. + break; + } + // The necessary supertype does not yet exist. Find its described type + // so we can try to generate the missing supertype instead. + for (describee = 0; describee < describees.size(); ++describee) { + if (describees[describee] == *describedSuper) { + break; + } + } + // Go back and check whether the new intended type can be generated. + continue; + } + + // We have locked in the type we will describe. + std::swap(describees[describee], describees.back()); + describees.pop_back(); + descriptorIndices[described] = i; + describedIndices[i] = described; + builder[described].descriptor(builder[i]); + builder[i].describes(builder[described]); + + // The length of the descriptor chain from this type is determined by the + // planned length of the chain from its described type. + descriptorChainLengths[i] = descriptorChainLengths[described] - 1; + } + + --remaining; + assert(remaining >= numPlannedDescriptors); + size_t remainingUncommitted = remaining - numPlannedDescriptors; + + if (!super && i >= numRoots && rand.oneIn(2)) { + // Try to pick a supertype. The supertype must be a descriptor type if and + // only if we are currently generating a descriptor type. Furthermore, we + // must have space left in the current chain if it exists, or else in the + // rec group, to mirror the supertype's descriptor chain, if it has one. + // Finally, if this is a descriptor, the sharedness of the described type + // and supertype must match. + size_t maxChain = + isDescriptor ? descriptorChainLengths[i] : remainingUncommitted; + std::vector candidates; + candidates.reserve(i); + for (Index candidate = 0; candidate < i; ++candidate) { + bool descMatch = bool(describedIndices[candidate]) == isDescriptor; + bool chainMatch = descriptorChainLengths[candidate] <= maxChain; + bool shareMatch = !isDescriptor || + HeapType(builder[candidate]).getShared() == + HeapType(builder[*describedIndices[i]]).getShared(); + if (descMatch && chainMatch && shareMatch) { + candidates.push_back(candidate); + } + } + if (!candidates.empty()) { + super = rand.pick(candidates); + } + } + + // Set up the builder entry and type kind for this type. + if (super) { + typeKinds.push_back(typeKinds[*super]); + builder[i].subTypeOf(builder[*super]); + builder[i].setShared(HeapType(builder[*super]).getShared()); + supertypeIndices[i] = *super; + subtypeIndices[*super].push_back(i); + } else if (isDescriptor) { + // Descriptor types must be structs and their sharedness must match their + // described types. + typeKinds.push_back(StructKind{}); + builder[i].setShared(HeapType(builder[*describedIndices[i]]).getShared()); + } else { // This is a root type with no supertype. Choose a kind for this type. typeKinds.emplace_back(generateHeapTypeKind()); builder[i].setShared( !features.hasSharedEverything() || rand.oneIn(2) ? Unshared : Shared); - } else { - // This is a subtype. Choose one of the previous types to be the - // supertype. - Index super = rand.upTo(i); - builder[i].subTypeOf(builder[super]); - builder[i].setShared(HeapType(builder[super]).getShared()); - supertypeIndices[i] = super; - subtypeIndices[super].push_back(i); - typeKinds.push_back(typeKinds[super]); + } + + // Plan this descriptor chain for this type if it is not already determined + // by a described type. Only structs may have descriptor chains. + if (!isDescriptor && std::get_if(&typeKinds.back()) && + remainingUncommitted && features.hasCustomDescriptors()) { + if (super) { + // If we have a supertype, our descriptor chain must be at least as + // long as the supertype's descriptor chain. + size_t length = descriptorChainLengths[*super]; + if (rand.oneIn(2)) { + length += rand.upToSquared(remainingUncommitted - length); + } + descriptorChainLengths[i] = length; + numPlannedDescriptors += length; + } else { + // We can choose to start a brand new chain at this type. + if (rand.oneIn(2)) { + size_t length = rand.upToSquared(remainingUncommitted); + descriptorChainLengths[i] = length; + numPlannedDescriptors += length; + } + } + } + // If this type has a descriptor chain, then we need to be able to + // choose to generate the next type in the chain in the future. + if (descriptorChainLengths[i]) { + describees.push_back(i); } } @@ -557,9 +703,9 @@ struct HeapTypeGeneratorImpl { // from JS). There are also no subtypes to consider, so just return. return super; } - auto nullability = super.nullability == NonNullable - ? NonNullable - : rand.oneIn(2) ? Nullable : NonNullable; + auto nullability = super.nullability == NonNullable ? NonNullable + : rand.oneIn(2) ? Nullable + : NonNullable; return {pickSubHeapType(super.type), nullability}; } @@ -736,9 +882,13 @@ void Inhabitator::markNullable(FieldPos field) { switch (getVariance(field)) { case Covariant: // Mark the field null in all supertypes. If the supertype field is - // already nullable or does not exist, that's ok and this will have no - // effect. + // already nullable, that's ok and this will have no effect. while (auto super = curr.getDeclaredSuperType()) { + if (super->isStruct() && idx >= super->getStruct().fields.size()) { + // Do not mark fields that don't exist as nullable; this index may be + // used by a descriptor. + break; + } nullables.insert({*super, idx}); curr = *super; } @@ -818,15 +968,17 @@ void Inhabitator::markExternRefsNullable() { // // [1]: https://en.wikipedia.org/wiki/Feedback_arc_set void Inhabitator::breakNonNullableCycles() { - // The types reachable from each heap type. - // TODO: Include descriptors. + // The types reachable from each heap type. Descriptors are modeled as + // additional non-nullable reference types appended to the other children. std::unordered_map> children; auto getChildren = [&children](HeapType type) { auto [it, inserted] = children.insert({type, {}}); if (inserted) { - // TODO: Add descriptors. it->second = type.getTypeChildren(); + if (auto desc = type.getDescriptorType()) { + it->second.push_back(Type(*desc, NonNullable, Exact)); + } } return it->second; }; @@ -865,7 +1017,14 @@ void Inhabitator::breakNonNullableCycles() { visitType(root); while (path.size()) { - auto [curr, index] = path.back(); + auto& [curr, index] = path.back(); + // We may have visited this type again after searching through a + // descriptor backedge. If we've already finished visiting this type on + // that later visit, we don't need to continue this earlier visit. + if (visited.count(curr)) { + finishType(); + continue; + } const auto& children = getChildren(curr); while (index < children.size()) { @@ -903,11 +1062,15 @@ void Inhabitator::breakNonNullableCycles() { continue; } // If this ref forms a cycle, break the cycle by marking it nullable and - // continue. - if (auto it = visiting.find(heapType); it != visiting.end()) { - markNullable({curr, index}); - ++index; - continue; + // continue. We can't do this for descriptors, though. For those we will + // continue searching as if for any other non-nullable reference and + // eventually find a non-descriptor backedge. + if (!curr.getDescriptorType() || index != children.size() - 1) { + if (auto it = visiting.find(heapType); it != visiting.end()) { + markNullable({curr, index}); + ++index; + continue; + } } break; } @@ -1002,7 +1165,7 @@ std::vector Inhabitator::build() { start += size; } - // Establish supertypes and finality. + // Establish supertypes, descriptors, and finality. for (size_t i = 0; i < types.size(); ++i) { if (auto super = types[i].getDeclaredSuperType()) { if (auto it = typeIndices.find(*super); it != typeIndices.end()) { @@ -1011,6 +1174,12 @@ std::vector Inhabitator::build() { builder[i].subTypeOf(*super); } } + if (auto desc = types[i].getDescriptorType()) { + auto it = typeIndices.find(*desc); + assert(it != typeIndices.end()); + builder[i].descriptor(builder[it->second]); + builder[it->second].describes(builder[i]); + } builder[i].setOpen(types[i].isOpen()); builder[i].setShared(types[i].getShared()); } @@ -1094,6 +1263,11 @@ bool isUninhabitable(HeapType type, if (!inserted) { return true; } + if (auto desc = type.getDescriptorType()) { + if (isUninhabitable(Type(*desc, NonNullable, Exact), visited, visiting)) { + return true; + } + } switch (type.getKind()) { case HeapTypeKind::Struct: for (auto& field : type.getStruct().fields) { diff --git a/src/tools/fuzzing/heap-types.h b/src/tools/fuzzing/heap-types.h index 4e1e27048ed..6b0e4f7206a 100644 --- a/src/tools/fuzzing/heap-types.h +++ b/src/tools/fuzzing/heap-types.h @@ -32,6 +32,9 @@ struct HeapTypeGenerator { // The intended subtypes of each built type. std::vector> subtypeIndices; + // The intended descriptor of each built type. + std::vector> descriptorIndices; + // Create a populated `HeapTypeGenerator` with `n` random HeapTypes with // interesting subtyping. static HeapTypeGenerator create(Random& rand, FeatureSet features, size_t n); diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index 0f5e8bff2a6..dc04ae96733 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -265,6 +266,18 @@ void Fuzzer::checkCanonicalization() { } } + // Set descriptors. + for (size_t i = 0; i < types.size(); ++i) { + if (auto desc = types[i].getDescriptorType()) { + // The correct descriptor index must be the next one greater than i. + auto& descriptors = typeIndices[*desc]; + auto it = std::lower_bound(descriptors.begin(), descriptors.end(), i); + assert(it != descriptors.end()); + builder[i].descriptor(builder[*it]); + builder[*it].describes(builder[i]); + } + } + // Set finality and shareability for (size_t i = 0; i < types.size(); ++i) { builder[i].setOpen(types[i].isOpen()); diff --git a/test/lit/fuzz-types.test b/test/lit/fuzz-types.test index 878f875b907..5ccaf51c4f3 100644 --- a/test/lit/fuzz-types.test +++ b/test/lit/fuzz-types.test @@ -3,58 +3,58 @@ ;; CHECK: Running with seed 0 ;; CHECK-NEXT: Built 20 types: ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $0 (sub (func (param (ref (shared func)) f32 i64) (result (ref $7))))) -;; CHECK-NEXT: (type $1 (sub (array (mut i16)))) -;; CHECK-NEXT: (type $2 (sub (shared (func (param f64 (ref null $5) (ref $9)) (result (ref null $6) (ref $3) (ref null $8)))))) -;; CHECK-NEXT: (type $3 (sub final $2 (shared (func (param f64 (ref null $1) (ref $0)) (result (ref $6) (ref $3) (ref $8)))))) -;; CHECK-NEXT: (type $4 (sub $1 (array (mut i16)))) -;; CHECK-NEXT: (type $5 (sub final $4 (array (mut i16)))) -;; CHECK-NEXT: (type $6 (sub $2 (shared (func (param f64 arrayref funcref) (result (ref $6) (ref $3) (ref $8)))))) -;; CHECK-NEXT: (type $7 (sub $0 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) -;; CHECK-NEXT: (type $8 (sub final $0 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) -;; CHECK-NEXT: (type $9 (sub $7 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) -;; CHECK-NEXT: (type $10 (sub $2 (shared (func (param f64 (ref null $5) (ref func)) (result (ref $6) (ref $3) (ref null $8)))))) -;; CHECK-NEXT: (type $11 (sub final $2 (shared (func (param f64 arrayref (ref $0)) (result (ref $6) (ref $3) (ref $8)))))) -;; CHECK-NEXT: (type $12 (sub $10 (shared (func (param f64 (ref null $1) funcref) (result (ref $6) (ref (shared nofunc)) (ref $8)))))) +;; CHECK-NEXT: (type $0 (sub (shared (array (mut i32))))) +;; CHECK-NEXT: (type $1 (sub (shared (array (ref null $7))))) +;; CHECK-NEXT: (type $2 (sub (shared (func (result (ref $0)))))) +;; CHECK-NEXT: (type $3 (sub $0 (shared (array (mut i32))))) +;; CHECK-NEXT: (type $4 (sub (array f32))) +;; CHECK-NEXT: (type $5 (sub $0 (shared (array (mut i32))))) +;; CHECK-NEXT: (type $6 (sub $2 (shared (func (result (ref $3)))))) +;; CHECK-NEXT: (type $7 (sub (shared (descriptor $9 (struct (field (mut f32)) (field f64) (field (mut i32))))))) +;; CHECK-NEXT: (type $8 (sub $2 (shared (func (result (ref $0)))))) +;; CHECK-NEXT: (type $9 (shared (describes $7 (descriptor $12 (struct (field (ref $5)) (field i64) (field (mut f32)) (field (mut (ref null $0))) (field (mut f64)) (field i64)))))) +;; CHECK-NEXT: (type $10 (sub (struct (field i8) (field f32)))) +;; CHECK-NEXT: (type $11 (sub (shared (struct (field i64) (field v128) (field (mut i16)) (field i32) (field (mut (ref (shared array)))))))) +;; CHECK-NEXT: (type $12 (shared (describes $9 (struct)))) ;; CHECK-NEXT: ) +;; CHECK-NEXT: (type $13 (array (ref $11))) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $13 (sub (func (param f64 f32)))) -;; CHECK-NEXT: (type $14 (func (param i64) (result v128 (ref null $2) (ref $12) f32 v128))) -;; CHECK-NEXT: (type $15 (sub final $12 (shared (func (param f64 anyref funcref) (result (ref $6) (ref (shared nofunc)) (ref $8)))))) +;; CHECK-NEXT: (type $14 (sub final $10 (struct (field i8) (field f32) (field (ref null (shared any)))))) +;; CHECK-NEXT: (type $15 (sub (shared (struct (field i64) (field (ref (shared array))) (field (ref $0)) (field (mut i32)) (field i64))))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $16 (sub final $7 (func (param (ref (shared func)) f32 i64) (result (ref nofunc))))) -;; CHECK-NEXT: (type $17 (sub (shared (func (param (ref $11) i64 (ref (shared extern))))))) -;; CHECK-NEXT: (type $18 (sub (struct))) -;; CHECK-NEXT: (type $19 (sub $7 (func (param (ref null (shared func)) f32 i64) (result (ref $9))))) +;; CHECK-NEXT: (type $16 (sub (shared (struct (field (ref $1)))))) +;; CHECK-NEXT: (type $17 (sub $5 (shared (array (mut i32))))) +;; CHECK-NEXT: (type $18 (sub final $3 (shared (array (mut i32))))) ;; CHECK-NEXT: ) +;; CHECK-NEXT: (type $19 (sub final $16 (shared (struct (field (ref $1)) (field (mut v128)) (field (mut (ref $17))) (field v128))))) ;; CHECK-NEXT: ;; CHECK-NEXT: Inhabitable types: ;; CHECK-NEXT: ;; CHECK-NEXT: Built 20 types: ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $0 (sub (func (param (ref (shared func)) f32 i64) (result (ref $7))))) -;; CHECK-NEXT: (type $1 (sub (array (mut i16)))) -;; CHECK-NEXT: (type $2 (sub (shared (func (param f64 (ref null $5) (ref $9)) (result (ref null $6) (ref $3) (ref null $8)))))) -;; CHECK-NEXT: (type $3 (sub final $2 (shared (func (param f64 (ref null $1) (ref $0)) (result (ref $6) (ref $3) (ref $8)))))) -;; CHECK-NEXT: (type $4 (sub $1 (array (mut i16)))) -;; CHECK-NEXT: (type $5 (sub final $4 (array (mut i16)))) -;; CHECK-NEXT: (type $6 (sub $2 (shared (func (param f64 arrayref funcref) (result (ref $6) (ref $3) (ref $8)))))) -;; CHECK-NEXT: (type $7 (sub $0 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) -;; CHECK-NEXT: (type $8 (sub final $0 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) -;; CHECK-NEXT: (type $9 (sub $7 (func (param (ref (shared func)) f32 i64) (result (ref $9))))) -;; CHECK-NEXT: (type $10 (sub $2 (shared (func (param f64 (ref null $5) (ref func)) (result (ref $6) (ref $3) (ref null $8)))))) -;; CHECK-NEXT: (type $11 (sub final $2 (shared (func (param f64 arrayref (ref $0)) (result (ref $6) (ref $3) (ref $8)))))) -;; CHECK-NEXT: (type $12 (sub $10 (shared (func (param f64 (ref null $1) funcref) (result (ref $6) (ref (shared nofunc)) (ref $8)))))) +;; CHECK-NEXT: (type $0 (sub (shared (array (mut i32))))) +;; CHECK-NEXT: (type $1 (sub (shared (array (ref null $7))))) +;; CHECK-NEXT: (type $2 (sub (shared (func (result (ref $0)))))) +;; CHECK-NEXT: (type $3 (sub $0 (shared (array (mut i32))))) +;; CHECK-NEXT: (type $4 (sub (array f32))) +;; CHECK-NEXT: (type $5 (sub $0 (shared (array (mut i32))))) +;; CHECK-NEXT: (type $6 (sub $2 (shared (func (result (ref $3)))))) +;; CHECK-NEXT: (type $7 (sub (shared (descriptor $9 (struct (field (mut f32)) (field f64) (field (mut i32))))))) +;; CHECK-NEXT: (type $8 (sub $2 (shared (func (result (ref $0)))))) +;; CHECK-NEXT: (type $9 (shared (describes $7 (descriptor $12 (struct (field (ref $5)) (field i64) (field (mut f32)) (field (mut (ref null $0))) (field (mut f64)) (field i64)))))) +;; CHECK-NEXT: (type $10 (sub (struct (field i8) (field f32)))) +;; CHECK-NEXT: (type $11 (sub (shared (struct (field i64) (field v128) (field (mut i16)) (field i32) (field (mut (ref (shared array)))))))) +;; CHECK-NEXT: (type $12 (shared (describes $9 (struct)))) ;; CHECK-NEXT: ) +;; CHECK-NEXT: (type $13 (array (ref $11))) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $13 (sub (func (param f64 f32)))) -;; CHECK-NEXT: (type $14 (func (param i64) (result v128 (ref null $2) (ref $12) f32 v128))) -;; CHECK-NEXT: (type $15 (sub final $12 (shared (func (param f64 anyref funcref) (result (ref $6) (ref (shared nofunc)) (ref $8)))))) +;; CHECK-NEXT: (type $14 (sub final $10 (struct (field i8) (field f32) (field (ref null (shared any)))))) +;; CHECK-NEXT: (type $15 (sub (shared (struct (field i64) (field (ref (shared array))) (field (ref $0)) (field (mut i32)) (field i64))))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $16 (sub final $7 (func (param (ref (shared func)) f32 i64) (result (ref nofunc))))) -;; CHECK-NEXT: (type $17 (sub (shared (func (param (ref $11) i64 (ref (shared extern))))))) -;; CHECK-NEXT: (type $18 (sub (struct))) -;; CHECK-NEXT: (type $19 (sub $7 (func (param (ref null (shared func)) f32 i64) (result (ref $9))))) +;; CHECK-NEXT: (type $16 (sub (shared (struct (field (ref $1)))))) +;; CHECK-NEXT: (type $17 (sub $5 (shared (array (mut i32))))) +;; CHECK-NEXT: (type $18 (sub final $3 (shared (array (mut i32))))) ;; CHECK-NEXT: ) +;; CHECK-NEXT: (type $19 (sub final $16 (shared (struct (field (ref $1)) (field (mut v128)) (field (mut (ref $17))) (field v128))))) From 6f4484c93b1b793d96257f5e63f6a0eaf55ebfb4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 27 Jun 2025 22:32:42 +0200 Subject: [PATCH 569/622] [Custom Descriptors] Test struct.new execution (#7687) And fix a missing check for null descriptors. Passing a null descriptor to struct.new should trap. --- src/wasm-interpreter.h | 3 +++ test/spec/struct.new-desc.wast | 37 ++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index b4f56e7dfcb..b73a69b7905 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1811,6 +1811,9 @@ class ExpressionRunner : public OverriddenVisitor { if (desc.breaking()) { return desc; } + if (desc.getSingleValue().isNull()) { + trap("null descriptor"); + } return makeGCData(std::move(data), curr->type, desc.getSingleValue()); } Flow visitStructGet(StructGet* curr) { diff --git a/test/spec/struct.new-desc.wast b/test/spec/struct.new-desc.wast index 653a6404cbe..00698c66359 100644 --- a/test/spec/struct.new-desc.wast +++ b/test/spec/struct.new-desc.wast @@ -3,22 +3,47 @@ (type $pair (descriptor $pair.desc (struct (field i32 i64)))) (type $pair.desc (describes $pair (struct))) ) - (func $struct.new (result (ref (exact $pair))) - (local $desc (ref null (exact $pair.desc))) + (func $struct.new (param $desc (ref null (exact $pair.desc))) (result (ref (exact $pair))) (struct.new $pair - (i32.const 0) - (i64.const 1) + (i32.const 1) + (i64.const 2) (local.get $desc) ) ) - (func $struct.new_default (result (ref (exact $pair))) - (local $desc (ref null (exact $pair.desc))) + (func (export "check-new") (result i32) + (local $pair (ref null $pair)) + (local.set $pair (call $struct.new (struct.new $pair.desc))) + (i32.and + (i32.eq (struct.get $pair 0 (local.get $pair)) (i32.const 1)) + (i64.eq (struct.get $pair 1 (local.get $pair)) (i64.const 2)) + ) + ) + (func (export "new-null-desc") + (drop (call $struct.new (ref.null none))) + ) + (func $struct.new_default (param $desc (ref null (exact $pair.desc))) (result (ref (exact $pair))) (struct.new_default $pair (local.get $desc) ) ) + (func (export "check-new-default") (result i32) + (local $pair (ref null $pair)) + (local.set $pair (call $struct.new_default (struct.new $pair.desc))) + (i32.and + (i32.eq (struct.get $pair 0 (local.get $pair)) (i32.const 0)) + (i64.eq (struct.get $pair 1 (local.get $pair)) (i64.const 0)) + ) + ) + (func (export "new-default-null-desc") + (drop (call $struct.new_default (ref.null none))) + ) ) +(assert_return (invoke "check-new") (i32.const 1)) +(assert_return (invoke "check-new-default") (i32.const 1)) +(assert_trap (invoke "new-null-desc") "null descriptor") +(assert_trap (invoke "new-default-null-desc") "null descriptor") + (assert_invalid (module (rec From b52f669b3ae347167146eaf0f605aa0879901133 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 27 Jun 2025 22:32:52 +0200 Subject: [PATCH 570/622] Fix null pointer dereference validating struct.new (#7688) We would previously dereference a null descriptor operand to struct.new when the allocated type has a descriptor. This only happens in invalid IR, but the validator needs to handle that gracefully. --- src/wasm/wasm-validator.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 2fe4ea7d4ef..06ce9c9d330 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3163,6 +3163,12 @@ void FunctionValidator::visitStructNew(StructNew* curr) { curr, "struct.new of type without descriptor should lack one"); } else { + if (!shouldBeTrue( + curr->desc, + curr, + "struct.new of type with descriptor requires descriptor operand")) { + return; + } shouldBeSubType(curr->desc->type, Type(*descType, Nullable, Exact), curr, From 8c286178a8a3ebb039eb189574b36fe517fb2f01 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 27 Jun 2025 22:33:08 +0200 Subject: [PATCH 571/622] [Custom Descriptors] Update wasm-ctor-eval (#7689) Update wasm-ctor-eval to serialize descriptor operands to struct.new for those allocations that need them. --- scripts/test/fuzzing.py | 1 + src/tools/wasm-ctor-eval.cpp | 9 ++++++-- test/lit/ctor-eval/gc-desc.wast | 38 +++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 test/lit/ctor-eval/gc-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index c4b6ae23865..5b33b3fe5b1 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -124,6 +124,7 @@ 'heap2local-desc.wast', 'minimize-rec-groups-desc.wast', 'precompute-desc.wast', + 'gc-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 0df95aa8b9e..c7ed64cc1f5 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -850,7 +850,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { } else { // This is the first usage of this data. Generate a struct.new / // array.new for it. - auto& values = value.getGCData()->values; + auto& values = data->values; std::vector args; // The initial values for this allocation may themselves be GC @@ -876,10 +876,15 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { args.push_back(getSerialization(value)); } + Expression* desc = nullptr; + if (data->desc.getGCData()) { + desc = getSerialization(data->desc); + } + Expression* init; auto heapType = type.getHeapType(); if (heapType.isStruct()) { - init = builder.makeStructNew(heapType, args); + init = builder.makeStructNew(heapType, args, desc); } else if (heapType.isArray()) { // TODO: for repeated identical values, can use ArrayNew init = builder.makeArrayNewFixed(heapType, args); diff --git a/test/lit/ctor-eval/gc-desc.wast b/test/lit/ctor-eval/gc-desc.wast new file mode 100644 index 00000000000..ff35e70e071 --- /dev/null +++ b/test/lit/ctor-eval/gc-desc.wast @@ -0,0 +1,38 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-ctor-eval %s --ctors=ctor --kept-exports=ctor --quiet -all -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct)))) + (type $struct (sub (descriptor $desc (struct)))) + ;; CHECK: (type $desc (describes $struct (struct))) + (type $desc (describes $struct (struct))) + ) + ;; CHECK: (type $2 (func)) + + ;; CHECK: (global $ctor-eval$global_2 (ref (exact $desc)) (struct.new_default $desc)) + + ;; CHECK: (global $ctor-eval$global (ref (exact $struct)) (struct.new_default $struct + ;; CHECK-NEXT: (global.get $ctor-eval$global_2) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $global (ref $struct) (global.get $ctor-eval$global)) + (global $global (export "g") (ref $struct) + (struct.new $struct + (struct.new $desc) + ) + ) + + ;; Export some arbitrary ctor so we do anything at all. + (func $ctor (export "ctor") + (nop) + ) +) +;; CHECK: (export "g" (global $global)) + +;; CHECK: (export "ctor" (func $ctor_1)) + +;; CHECK: (func $ctor_1 (type $2) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) From b12a949064b183ccb934d1740e63dcf90f7a7074 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 27 Jun 2025 23:31:28 +0200 Subject: [PATCH 572/622] Add a utility for principal types (#7642) WebAssembly has the property that every valid sequence of instructions has a principal type, i.e. a unique most-specific type. However, due to various kinds of polymorphism, these principal types may contain type variables. Specifically, the syntaxes for value types, heap types, nullability, sharedness, and exactness are all augmented with type variables. Add a PrincipalType utility for representing and composing these principal types. This can be seen as a more powerful version of the existing StackSignature utility. Follow-up PRs will update ChildTyper to use principal types to represent type constraints and use the principal types of outlined instruction sequences to detect polymorphism and determine the types of outlined functions. There are also further potential applications in simplifying validation, simplifying expression typing, and performing spec-compliant validation. --- src/ir/CMakeLists.txt | 1 + src/ir/principal-type.cpp | 1061 +++++++++++++++++++++++++++++++++ src/ir/principal-type.h | 136 +++++ src/support/disjoint_sets.h | 7 +- test/gtest/CMakeLists.txt | 1 + test/gtest/principal-type.cpp | 612 +++++++++++++++++++ 6 files changed, 1816 insertions(+), 2 deletions(-) create mode 100644 src/ir/principal-type.cpp create mode 100644 src/ir/principal-type.h create mode 100644 test/gtest/principal-type.cpp diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index 264828e0e8d..a74c06897f8 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -16,6 +16,7 @@ set(ir_SOURCES properties.cpp LocalGraph.cpp LocalStructuralDominance.cpp + principal-type.cpp public-type-validator.cpp ReFinalize.cpp return-utils.cpp diff --git a/src/ir/principal-type.cpp b/src/ir/principal-type.cpp new file mode 100644 index 00000000000..ec8ceb81685 --- /dev/null +++ b/src/ir/principal-type.cpp @@ -0,0 +1,1061 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "principal-type.h" +#include "support/disjoint_sets.h" + +namespace wasm { + +namespace { + +#ifndef NDEBUG + +bool valid(const VarAbsHeapType& type) { + if (!type.ht.isBasic()) { + return false; + } + // Sharedness is represented in `share`, not `ht`. + if (type.ht.isShared()) { + return false; + } + return true; +} + +bool valid(const VarDefHeapType& type) { return !type.ht.isBasic(); } + +bool valid(const VarHeapType& type) { + if (auto* t = std::get_if(&type)) { + return valid(*t); + } + if (auto* t = std::get_if(&type)) { + return valid(*t); + } + return true; +} + +bool valid(const VarRef& type) { return valid(type.ht); } + +bool valid(const VarType& type) { + if (auto* ref = std::get_if(&type)) { + return valid(*ref); + } + if (auto* t = std::get_if(&type)) { + return t->isSingle() || *t == Type::unreachable; + } + return true; +} + +#endif // NDEBUG + +// Convert a VarRef that does not use the extended type syntax into a normal +// Type. +VarType canonicalizeRef(const VarRef& ref) { + const auto* null = std::get_if(&ref.null); + if (!null) { + return ref; + } + if (auto* t = std::get_if(&ref.ht)) { + if (auto* share = std::get_if(&t->share)) { + assert(t->ht.isBasic()); + return Type(t->ht.getBasic(*share), *null); + } + return ref; + } + if (auto* t = std::get_if(&ref.ht)) { + if (auto* exact = std::get_if(&t->exact)) { + assert(!t->ht.isBasic()); + return Type(t->ht, *null, *exact); + } + return ref; + } + return ref; +} + +// Convert a given reference type into a VarRef. The result will not use the +// extended type syntax. +VarRef asVarRef(Type type) { + assert(type.isRef()); + auto null = type.getNullability(); + auto ht = type.getHeapType(); + if (ht.isBasic()) { + return VarRef{null, VarAbsHeapType{ht.getShared(), ht.getBasic(Unshared)}}; + } + return VarRef{null, VarDefHeapType{type.getExactness(), ht}}; +} + +// join: Update `value` to the least upper bound of `value` and `other`. Returns +// `true` iff the least upper bound exists. + +bool join(VarNullability& value, const VarNullability& other) { + assert(!std::get_if(&value) && !std::get_if(&other) && + "joining variables not yet supported"); + if (value == other) { + return true; + } + value = Nullable; + return true; +} + +bool join(VarExactness& value, const VarExactness& other) { + assert(!std::get_if(&value) && !std::get_if(&other) && + "joining variables not yet supported"); + if (value == other) { + return true; + } + value = Inexact; + return true; +} + +bool join(VarSharedness& value, const VarSharedness& other) { + assert(!std::get_if(&value) && !std::get_if(&other) && + "joining variables not yet supported"); + if (value == other || std::get_if(&other)) { + return true; + } + if (std::get_if(&value)) { + value = other; + return true; + } + return false; +} + +bool join(HeapType& value, const HeapType& other) { + if (auto lub = HeapType::getLeastUpperBound(value, other)) { + value = *lub; + return true; + } + return false; +} + +bool join(VarAbsHeapType& value, const VarAbsHeapType& other) { + return join(value.share, other.share) && join(value.ht, other.ht); +} + +bool join(VarDefHeapType& value, const VarDefHeapType& other) { + if (value.ht != other.ht) { + VarExactness inexact = Inexact; + return join(value.ht, other.ht) && join(value.exact, inexact) && + join(inexact, other.exact); + } + return join(value.exact, other.exact); +} + +bool join(VarAbsHeapType& value, const VarDefHeapType& other) { + WASM_UNREACHABLE("TODO"); +} + +bool join(VarDefHeapType& value, const VarAbsHeapType& other) { + WASM_UNREACHABLE("TODO"); +} + +bool join(VarHeapType& value, const VarHeapType& other) { + assert(!std::get_if(&value) && !std::get_if(&other)); + if (value == other || std::get_if(&other)) { + return true; + } + if (std::get_if(&value)) { + value = other; + return true; + } + auto* abs = std::get_if(&value); + auto* otherAbs = std::get_if(&other); + auto* def = std::get_if(&value); + auto* otherDef = std::get_if(&other); + if (abs && otherAbs) { + return join(*abs, *otherAbs); + } + if (abs && otherDef) { + return join(*abs, *otherDef); + } + if (def && otherAbs) { + return join(*def, *otherAbs); + } + if (def && otherDef) { + return join(*def, *otherDef); + } + WASM_UNREACHABLE("unexpected variant"); +} + +bool join(VarRef& value, const VarRef& other) { + return join(value.null, other.null) && join(value.ht, other.ht); +} + +bool join(Type& value, const Type& other) { + value = Type::getLeastUpperBound(value, other); + return value != Type::none; +} + +bool join(VarRef& value, const Type& other) { WASM_UNREACHABLE("TODO"); } + +bool join(VarType& value, const VarType& other) { + assert(!std::get_if(&value) && !std::get_if(&other)); + if (value == other) { + return true; + } + auto* ref = std::get_if(&value); + auto* otherRef = std::get_if(&other); + auto* type = std::get_if(&value); + auto* otherType = std::get_if(&other); + if (ref && otherRef) { + if (!join(*ref, *otherRef)) { + return false; + } + value = canonicalizeRef(*ref); + return true; + } + if (ref && otherType) { + if (!join(*ref, *otherType)) { + return false; + } + value = canonicalizeRef(*ref); + return true; + } + if (type && otherRef) { + if (!type->isRef()) { + return false; + } + auto r = asVarRef(*type); + if (!join(r, *otherRef)) { + return false; + } + value = canonicalizeRef(r); + return true; + } + if (type && otherType) { + return join(*type, *otherType); + } + WASM_UNREACHABLE("unexpected variant"); +} + +// Find the least upper bound where T is lifted so that nullopt is the bottom +// value. +template +bool join(std::optional& value, const std::optional& other) { + if (value && other) { + return join(*value, *other); + } else if (other && !value) { + value = other; + } + return true; +} + +// countVars: Count the number of distinct variables for each kind of variable. + +struct VarCounts { + Index nulls = 0; + Index exacts = 0; + Index shares = 0; + Index heapTypes = 0; + Index types = 0; +}; + +void countVar(Index* countp, Index i) { + // Variables must be introduced sequentially starting at 0 so their count is + // one greater than their max value. + if (i >= *countp) { + assert(*countp == i); + ++*countp; + } +} + +void countVars(VarCounts& counts, const VarAbsHeapType& type) { + if (auto* i = std::get_if(&type.share)) { + countVar(&counts.shares, *i); + } +} + +void countVars(VarCounts& counts, const VarDefHeapType& type) { + if (auto* i = std::get_if(&type.exact)) { + countVar(&counts.exacts, *i); + } +} + +void countVars(VarCounts& counts, const VarHeapType& type) { + if (auto* i = std::get_if(&type)) { + countVar(&counts.heapTypes, *i); + } else if (auto* t = std::get_if(&type)) { + countVars(counts, *t); + } else if (auto* t = std::get_if(&type)) { + countVars(counts, *t); + } +} + +void countVars(VarCounts& counts, const VarRef& ref) { + if (auto* i = std::get_if(&ref.null)) { + countVar(&counts.nulls, *i); + } + countVars(counts, ref.ht); +} + +void countVars(VarCounts& counts, const VarType& type) { + if (auto* i = std::get_if(&type)) { + countVar(&counts.types, *i); + } else if (auto* t = std::get_if(&type)) { + countVars(counts, *t); + } +} + +void countVars(VarCounts& counts, const std::vector& types) { + for (const auto& type : types) { + countVars(counts, type); + } +} + +void countVars(VarCounts& counts, const PrincipalType& type) { + countVars(counts, type.rparams); + countVars(counts, type.results); +} + +// renumber: Replace variables with new variables given in `renumbering` or +// otherwise indexed sequentially starting at `base`. + +// Sorted vector mapping original to renumbered variables. +using Renumbering = std::vector>; + +struct Renumberings { + Renumbering nulls; + Renumbering exacts; + Renumbering shares; + Renumbering heapTypes; + Renumbering types; +}; + +void renumber(Index base, Renumbering& renumbering, Index& i) { + auto it = std::lower_bound( + renumbering.begin(), + renumbering.end(), + std::pair{i, i}, + [&](const auto& a, const auto& b) { return a.first < b.first; }); + if (it != renumbering.end() && it->first == i) { + i = it->second; + return; + } + // We have not already renumbered this index. Insert a new mapping. + Index fresh = base + renumbering.size(); + renumbering.insert(it, {i, fresh}); + i = fresh; +} + +void renumber(const VarCounts& bases, + Renumberings& renumberings, + VarNullability& null) { + if (auto* i = std::get_if(&null)) { + renumber(bases.nulls, renumberings.nulls, *i); + } +} + +void renumber(const VarCounts& bases, + Renumberings& renumberings, + VarExactness& exact) { + if (auto* i = std::get_if(&exact)) { + renumber(bases.exacts, renumberings.exacts, *i); + } +} + +void renumber(const VarCounts& bases, + Renumberings& renumberings, + VarSharedness& share) { + if (auto* i = std::get_if(&share)) { + renumber(bases.shares, renumberings.shares, *i); + } +} + +void renumber(const VarCounts& bases, + Renumberings& renumberings, + VarAbsHeapType& type) { + renumber(bases, renumberings, type.share); +} + +void renumber(const VarCounts& bases, + Renumberings& renumberings, + VarDefHeapType& type) { + renumber(bases, renumberings, type.exact); +} + +void renumber(const VarCounts& bases, + Renumberings& renumberings, + VarHeapType& type) { + if (auto* i = std::get_if(&type)) { + renumber(bases.heapTypes, renumberings.heapTypes, *i); + } else if (auto* t = std::get_if(&type)) { + renumber(bases, renumberings, *t); + } else if (auto* t = std::get_if(&type)) { + renumber(bases, renumberings, *t); + } +} + +void renumber(const VarCounts& bases, + Renumberings& renumberings, + VarRef& type) { + renumber(bases, renumberings, type.null); + renumber(bases, renumberings, type.ht); +} + +void renumber(const VarCounts& bases, + Renumberings& renumberings, + VarType& type) { + if (auto* i = std::get_if(&type)) { + renumber(bases.types, renumberings.types, *i); + } else if (auto* t = std::get_if(&type)) { + renumber(bases, renumberings, *t); + } +} + +void renumberVars(const VarCounts& bases, PrincipalType& type) { + Renumberings renumberings; + for (auto& t : type.rparams) { + renumber(bases, renumberings, t); + } + for (auto& t : type.results) { + renumber(bases, renumberings, t); + } +} + +// Renumber the variables starting at zero for each index space. +void canonicalizeVars(PrincipalType& type) { + VarCounts zeros; + renumberVars(zeros, type); +} + +// Variables can be assigned to values and also to other variables. The set of +// variables and values that are known to be equivalent to each other form an +// equivalence class, which we track here using a disjoint set data structure. +// The disjoint set indices are the same as variable indices. +template struct AssignmentClasses { + // Map each variable in an equivalence class to the representative variable. + DisjointSets classes; + // Map representative variables to the values of their classes. + std::vector> values; + + bool assign(Index i, T value) { + auto* other = std::get_if(&value); + if (other && *other == i) { + // Setting a variable equal to itself is a no-op. + return true; + } + // Make sure we have a class for i. + classes.reserve(i + 1); + values.reserve(i + 1); + while (values.size() <= i) { + [[maybe_unused]] size_t set = classes.addSet(); + assert(set == values.size()); + values.push_back(std::nullopt); + } + size_t root = classes.getRoot(i); + assert(root < values.size()); + // If the assigned value is not another variable, then merge it into the + // current assigned value. If this is a forward assignment, meaning it's a + // provided value being assigned to a required variable, then this merge is + // a join (i.e. the least upper bound) and if it is a backward assignment, + // meaning it's a required value being assigned to a provided variable, this + // merge should be meet (i.e. the greatest lower bound). However, + // WebAssembly does not currently have principal types that have more than + // one instance of a variable in the produced types, so we can get away + // without properly differentiating between forward and backward + // assignments. + if (!other) { + return join(values[root], {value}); + } + // Otherwise we have to join the two classes. + size_t otherRoot = classes.getRoot(*other); + assert(otherRoot < values.size()); + if (otherRoot == root) { + // Already the same class. + return true; + } + size_t newRoot = classes.getUnion(i, *other); + assert(newRoot < values.size()); + // Join the values from the merged classes. + return join(values[newRoot], values[newRoot == root ? otherRoot : root]); + } + + T get(Index i) const { + if (i >= values.size()) { + return i; + } + Index root = classes.getRoot(i); + assert(root < values.size()); + if (values[root]) { + return *values[root]; + } else { + // No assigned value, so return the representative variable. + return root; + } + } + + bool operator==(const AssignmentClasses& other) const { + // The maximum assigned variable should be the same. + if (values.size() != other.values.size()) { + return false; + } + // The equivalence classes and assigned values in this set of assignments + // needs to match those in the other set of assignments. Two equivalent + // equivalence classes might have different representative indices, but we + // can check that both assignment sets agree that each variable and the + // other set's representative element for that variable's equivalence class + // are in the same class. If the classes are different in any way, there + // will be an element for which this is not true. + for (Index i = 0; i < values.size(); ++i) { + auto root = classes.getRoot(i); + assert(root < values.size()); + auto otherRoot = other.classes.getRoot(i); + assert(otherRoot < other.values.size()); + // We only need to compare the values for each group once. + if (i == root && values[root] != other.values[otherRoot]) { + return false; + } + if (root == otherRoot) { + continue; + } + if (root != classes.getRoot(otherRoot)) { + return false; + } + if (otherRoot != other.classes.getRoot(root)) { + return false; + } + } + return true; + } +}; + +// Assignments for all the kinds of variables that can appear in a principal +// type. +struct VarAssignments { + AssignmentClasses nulls; + AssignmentClasses exacts; + AssignmentClasses shares; + AssignmentClasses heapTypes; + AssignmentClasses types; + + bool operator==(const VarAssignments& other) const { + return nulls == other.nulls && exacts == other.exacts && + shares == other.shares && heapTypes == other.heapTypes && + types == other.types; + } + + bool operator!=(const VarAssignments& other) const { + return !(*this == other); + } +}; + +// apply: replace variables with their assigned values according to +// `assignments`. + +void apply(const VarAssignments& assignments, VarNullability& null) { + if (auto* i = std::get_if(&null)) { + null = assignments.nulls.get(*i); + } +} + +void apply(const VarAssignments& assignments, VarExactness& exact) { + if (auto* i = std::get_if(&exact)) { + exact = assignments.exacts.get(*i); + } +} + +void apply(const VarAssignments& assignments, VarSharedness& share) { + if (auto* i = std::get_if(&share)) { + share = assignments.shares.get(*i); + } +} + +void apply(const VarAssignments& assignments, VarAbsHeapType& type) { + apply(assignments, type.share); +} + +void apply(const VarAssignments& assignments, VarDefHeapType& type) { + apply(assignments, type.exact); +} + +void apply(const VarAssignments& assignments, VarHeapType& type) { + if (auto* i = std::get_if(&type)) { + type = assignments.heapTypes.get(*i); + } + if (auto* t = std::get_if(&type)) { + apply(assignments, *t); + } else if (auto* t = std::get_if(&type)) { + apply(assignments, *t); + } +} + +void apply(const VarAssignments& assignments, VarRef& ref) { + apply(assignments, ref.null); + apply(assignments, ref.ht); +} + +void apply(const VarAssignments& assignments, VarType& type) { + if (auto* i = std::get_if(&type)) { + type = assignments.types.get(*i); + } + if (auto* t = std::get_if(&type)) { + apply(assignments, *t); + type = canonicalizeRef(*t); + } +} + +void apply(const VarAssignments& assignments, PrincipalType& type) { + for (auto& t : type.rparams) { + apply(assignments, t); + } + for (auto& t : type.results) { + apply(assignments, t); + } +} + +// matchBottom: join the bottom value into the assignment in `assignments` for +// any variables appearing in the second parameter. Returns `true` if this +// succeeds. TODO: This currently always succeeds, but if we supported repeated +// variables in outputs properly, we would need to avoid mixing left assignment +// and right assignment, so this would be able to fail. + +bool matchBottom(VarAssignments& assignments, const VarNullability& null) { + if (auto* i = std::get_if(&null)) { + return assignments.nulls.assign(*i, NonNullable); + } + return true; +} + +bool matchBottom(VarAssignments& assignments, const VarSharedness& share) { + if (auto* i = std::get_if(&share)) { + return assignments.shares.assign(*i, BottomShare{}); + } + return true; +} + +bool matchBottom(VarAssignments& assignments, const VarExactness& exact) { + if (auto* i = std::get_if(&exact)) { + return assignments.exacts.assign(*i, Exact); + } + return true; +} + +bool matchBottom(VarAssignments& assignments, const VarAbsHeapType& type) { + return matchBottom(assignments, type.share); +} + +bool matchBottom(VarAssignments& assignments, const VarDefHeapType& type) { + return matchBottom(assignments, type.exact); +} + +bool matchBottom(VarAssignments& assignments, const VarHeapType& type) { + if (auto* i = std::get_if(&type)) { + return assignments.heapTypes.assign(*i, BottomHeapType{}); + } else if (auto* t = std::get_if(&type)) { + return matchBottom(assignments, *t); + } else if (auto* t = std::get_if(&type)) { + return matchBottom(assignments, *t); + } else { + assert(std::get_if(&type)); + return true; + } +} + +bool matchBottom(VarAssignments& assignments, const VarRef& ref) { + return matchBottom(assignments, ref.null) && matchBottom(assignments, ref.ht); +} + +// match: Record the variable assignments necessary to make `a` match `b` (i.e. +// to ensure `a` <: `b`). + +bool match(VarAssignments& assignments, + const VarNullability& a, + const VarNullability& b) { + if (auto* i = std::get_if(&a)) { + return assignments.nulls.assign(*i, b); + } + if (auto* i = std::get_if(&b)) { + return assignments.nulls.assign(*i, a); + } + return std::get(a) == NonNullable || + std::get(b) == Nullable; +} + +bool match(VarAssignments& assignments, + const VarSharedness& a, + const VarSharedness& b) { + if (auto* i = std::get_if(&a)) { + return assignments.shares.assign(*i, b); + } + if (auto* i = std::get_if(&b)) { + return assignments.shares.assign(*i, a); + } + if (std::get_if(&a)) { + return true; + } + if (std::get_if(&b)) { + return false; + } + return std::get(a) == std::get(b); +} + +bool match(VarAssignments& assignments, + const VarAbsHeapType& a, + const VarAbsHeapType& b) { + return HeapType::isSubType(a.ht, b.ht) && + match(assignments, a.share, b.share); +} + +bool match(VarAssignments& assignments, + const VarDefHeapType& a, + const VarDefHeapType& b) { + if (!HeapType::isSubType(a.ht, b.ht)) { + return false; + } + bool sameType = a.ht == b.ht; + auto* exactA = std::get_if(&a.exact); + auto* exactB = std::get_if(&b.exact); + if (exactB && *exactB == Exact && !sameType) { + return false; + } + auto* indexA = std::get_if(&a.exact); + auto* indexB = std::get_if(&b.exact); + if (indexA || indexB) { + if (indexA && + !assignments.exacts.assign(*indexA, sameType ? b.exact : Inexact)) { + return false; + } + if (indexB && + !assignments.exacts.assign(*indexB, sameType ? a.exact : Inexact)) { + return false; + } + return true; + } + assert(exactA && exactB); + return *exactA == Exact || *exactB == Inexact; +} + +bool match(VarAssignments& assignments, + const VarAbsHeapType& a, + const VarDefHeapType& b) { + auto* shareA = std::get_if(&a.share); + HeapType htA = a.ht.getBasic(shareA ? *shareA : b.ht.getShared()); + if (!HeapType::isSubType(htA, b.ht)) { + return false; + } + if (auto* i = std::get_if(&a.share)) { + if (!assignments.shares.assign(*i, b.ht.getShared())) { + return false; + } + } + if (auto* i = std::get_if(&b.exact)) { + if (!assignments.exacts.assign(*i, Exact)) { + return false; + } + } + return true; +} + +bool match(VarAssignments& assignments, + const VarDefHeapType& a, + const VarAbsHeapType& b) { + if (std::get_if(&b.share)) { + return false; + } + auto* shareB = std::get_if(&b.share); + HeapType htB = b.ht.getBasic(shareB ? *shareB : a.ht.getShared()); + if (!HeapType::isSubType(a.ht, htB)) { + return false; + } + if (auto* i = std::get_if(&a.exact)) { + if (!assignments.exacts.assign(*i, Inexact)) { + return false; + } + } + if (auto* i = std::get_if(&b.share)) { + if (!assignments.shares.assign(*i, a.ht.getShared())) { + return false; + } + } + return true; +} + +bool match(VarAssignments& assignments, + const VarHeapType& a, + const VarHeapType& b) { + if (auto* i = std::get_if(&a)) { + return assignments.heapTypes.assign(*i, b); + } + if (auto* i = std::get_if(&b)) { + return assignments.heapTypes.assign(*i, a); + } + if (std::get_if(&a)) { + return matchBottom(assignments, b); + } + if (std::get_if(&b)) { + return false; + } + auto* absA = std::get_if(&a); + auto* absB = std::get_if(&b); + auto* defA = std::get_if(&a); + auto* defB = std::get_if(&b); + if (absA && absB) { + return match(assignments, *absA, *absB); + } + if (absA && defB) { + return match(assignments, *absA, *defB); + } + if (defA && absB) { + return match(assignments, *defA, *absB); + } + if (defA && defB) { + return match(assignments, *defA, *defB); + } + WASM_UNREACHABLE("unexpected variants"); +} + +bool match(VarAssignments& assignments, const VarRef& a, const VarRef& b) { + return match(assignments, a.null, b.null) && match(assignments, a.ht, b.ht); +} + +// Update the assignments as necessary to make a <: b, returning true iff this +// is possible. Otherwise the assignments may be partially modified. +bool match(VarAssignments& assignments, const VarType& a, const VarType& b) { + assert(valid(a) && valid(b)); + if (auto* i = std::get_if(&a)) { + return assignments.types.assign(*i, b); + } + if (auto* i = std::get_if(&b)) { + return assignments.types.assign(*i, a); + } + + auto* refA = std::get_if(&a); + assert(!refA || a == canonicalizeRef(*refA)); + auto* refB = std::get_if(&b); + assert(!refB || b == canonicalizeRef(*refB)); + + if (refA && refB) { + return match(assignments, *refA, *refB); + } + + auto* typeA = std::get_if(&a); + auto* typeB = std::get_if(&b); + + if (typeA && typeB) { + return Type::isSubType(*typeA, *typeB); + } + + if (typeA && *typeA == Type::unreachable) { + assert(refB); + return matchBottom(assignments, *refB); + } + + if (typeA) { + assert(refB); + if (!typeA->isRef()) { + return false; + } + return match(assignments, asVarRef(*typeA), *refB); + } + + assert(refA && typeB); + if (!typeB->isRef()) { + return false; + } + return match(assignments, *refA, asVarRef(*typeB)); +} + +// print: print a principal type to `o`. Used for debugging and producing +// helpful error messages in tests. + +void print(std::ostream& o, const VarNullability& null) { + if (auto* i = std::get_if(&null)) { + o << "n" << *i; + } else if (std::get(null) == Nullable) { + o << "null "; + } +} + +void print(std::ostream& o, const VarAbsHeapType& type) { + bool hasParen = false; + if (auto* i = std::get_if(&type.share)) { + o << "(s" << *i << " "; + hasParen = true; + } else if (std::get_if(&type.share)) { + o << "(bot-share "; + hasParen = true; + } else if (std::get(type.share) == Shared) { + o << "(shared "; + hasParen = true; + } + o << type.ht; + if (hasParen) { + o << ")"; + } +} + +void print(std::ostream& o, const VarDefHeapType& type) { + bool hasParen = false; + if (auto* i = std::get_if(&type.exact)) { + o << "(e" << *i << " "; + hasParen = true; + } else if (std::get(type.exact) == Exact) { + o << "(exact "; + hasParen = true; + } + o << type.ht; + if (hasParen) { + o << ")"; + } +} + +void print(std::ostream& o, const VarHeapType& type) { + if (auto* i = std::get_if(&type)) { + o << "ht" << *i; + } else if (auto* t = std::get_if(&type)) { + print(o, *t); + } else if (auto* t = std::get_if(&type)) { + print(o, *t); + } else { + assert(std::get_if(&type)); + o << "bot"; + } +} + +void print(std::ostream& o, const VarRef& ref) { + o << "(ref "; + print(o, ref.null); + print(o, ref.ht); + o << ")"; +} + +void print(std::ostream& o, const VarType& type) { + if (auto* i = std::get_if(&type)) { + o << "t" << *i; + } else if (auto* t = std::get_if(&type)) { + print(o, *t); + } else { + o << std::get(type); + } +} + +void print(std::ostream& o, const PrincipalType& type) { + o << "["; + for (auto it = type.rparams.rbegin(); it != type.rparams.rend(); ++it) { + if (it != type.rparams.rbegin()) { + o << " "; + } + print(o, *it); + } + o << ']'; + if (type.unreachable) { + o << '*'; + } + o << "->["; + for (auto it = type.results.begin(); it != type.results.end(); ++it) { + if (it != type.results.begin()) { + o << " "; + } + print(o, *it); + } + o << "]"; +} + +} // anonymous namespace + +bool PrincipalType::compose(const PrincipalType& next) { + assert(&next != this); + + // Renumber the variables in this type to ensure they're distinct from the + // variables in `next`. `counts` will end up with the total counts across both + // types. + VarCounts counts; + countVars(counts, next); + renumberVars(counts, *this); + + // Match up the provided and required types, collecting values for and + // unifying variables. + Index numProvided = results.size(); + Index numRequired = next.rparams.size(); + Index numMatched = std::min(numProvided, numRequired); + VarAssignments assignments; + for (Index i = 0; i < numMatched; ++i) { + const auto& provided = results[numProvided - i - 1]; + const auto& required = next.rparams[i]; + if (!match(assignments, provided, required)) { + // Undo our previous renumbering of indices. + canonicalizeVars(*this); + return false; + } + } + + // The matched provided and required types are consistent, so from this point + // we know the composition will succeed and we can start mutating things. The + // matched provided types are annihilated by the matched required types. + results.resize(numProvided - numMatched); + + // Any extra required parameters are prepended to the current parameters (i.e. + // appended to the reversed list of parameters), unless they would be + // satisfied by popping from an unreachable stack instead. + if (numRequired > numMatched) { + if (unreachable) { + for (Index i = numMatched; i < numRequired; ++i) { + const auto& required = next.rparams[i]; + [[maybe_unused]] bool assigned = + match(assignments, Type(Type::unreachable), required); + assert(assigned); + } + } else { + rparams.reserve(rparams.size() + (numRequired - numMatched)); + for (Index i = numMatched; i < numRequired; ++i) { + rparams.push_back(next.rparams[i]); + } + } + } + + if (next.unreachable) { + // The existing results do not make it past the following unreachable. + results.clear(); + unreachable = true; + } + + // Append the new results. + for (const VarType& result : next.results) { + results.push_back(result); + } + + apply(assignments, *this); + // If a type variable was instantiated with bottom type (i.e. unreachable) due + // to popping from an unreachabile stack, the result type may end with some + // number of unreachables. This is nonsensical, since `unreachable` is not a + // concrete type. We could have alternatively left the variables + // uninstantiated, but it would have no corresponding introduction on the left + // hand side of the type. Even better is to canonicalize to empty results. + // Since the new principal type is unreachable, this has the same semantics as + // having an unconstrained result variable. + if (!results.empty()) { + auto& last = results[results.size() - 1]; + if (auto* t = std::get_if(&last); t && *t == Type::unreachable) { + results.clear(); + } + } + + canonicalizeVars(*this); + return true; +} + +std::optional PrincipalType::getSignature() const { + WASM_UNREACHABLE("TODO"); +} + +std::ostream& operator<<(std::ostream& o, const PrincipalType& type) { + print(o, type); + return o; +} + +} // namespace wasm diff --git a/src/ir/principal-type.h b/src/ir/principal-type.h new file mode 100644 index 00000000000..00986e55529 --- /dev/null +++ b/src/ir/principal-type.h @@ -0,0 +1,136 @@ +/* + * Copyright 2025 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_ir_principal_types_h +#define wasm_ir_principal_types_h + +#include +#include + +#include "wasm-type.h" + +namespace wasm { + +// Due to various kinds of polymorphism, describing principal types requires +// extensions to the normal syntax of types. These extensions include the +// introduction of type variables, which we represent as indices. + +// A sharedness that is a subtype of both `shared` and `unshared`. +struct BottomShare : std::monostate {}; + +// A heap type that is a subtype of all other heap types, no matter what +// hierarchy they belong to. +struct BottomHeapType : std::monostate {}; + +// Either a nullability or a nullability type variable. +using VarNullability = std::variant; + +// Bottom sharedness, a normal sharedness, or a sharedness type variable. +using VarSharedness = std::variant; + +// An abstract heap type with some given sharedness. +struct VarAbsHeapType { + VarSharedness share; + // Must be basic and unshared. + HeapType ht; + + bool operator==(const VarAbsHeapType& other) const { + return share == other.share && ht == other.ht; + } + bool operator!=(const VarAbsHeapType& other) const { + return !(*this == other); + } +}; + +// Either an exactness or an exactness type variable. +using VarExactness = std::variant; + +// A defined heap type with a given exactness. +struct VarDefHeapType { + VarExactness exact; + // Must be defined. + HeapType ht; + + bool operator==(const VarDefHeapType& other) const { + return exact == other.exact && ht == other.ht; + } + bool operator!=(const VarDefHeapType& other) const { + return !(*this == other); + } +}; + +// A bottom heap type, an abstract heap type, a defined heap type, or a heap +// type variable. +using VarHeapType = + std::variant; + +// A reference comprising a given nullability and heap type. +struct VarRef { + VarNullability null; + VarHeapType ht; + + bool operator==(const VarRef& other) const { + return null == other.null && ht == other.ht; + } + bool operator!=(const VarRef& other) const { return !(*this == other); } +}; + +// A normal type, a reference type using the extended syntax, or a type +// variable. If a reference type can be expressed in the normal syntax rather +// than the extended syntax, it should be represented here as a `Type`, not a +// `VarRef`. +using VarType = std::variant; + +// A principal type, including extended input and output types for an +// instruction sequence and whether the sequence is stack-polymorphic, i.e. +// contains an instruction that unconditionally transfers control flow out of +// the sequence, i.e. is unreachable. The parameters are stored in reverse order +// for efficiency. Variable indices for each kind of variable must be introduced +// contiguously in order starting at zero from the end to beginning of the +// params (beginning to end of the reversed params). +struct PrincipalType { + std::vector rparams; + std::vector results; + bool unreachable; + + PrincipalType(std::initializer_list params, + std::initializer_list results, + bool unreachable = false) + : rparams(params), results(results), unreachable(unreachable) { + std::reverse(rparams.begin(), rparams.end()); + } + + // Update this type to be the composition of this and `next`. + bool compose(const PrincipalType& next); + + // Get the signature represented by this type if it is closed, i.e. has no + // variables. + std::optional getSignature() const; + + bool operator==(const PrincipalType& other) const { + return rparams == other.rparams && results == other.results && + unreachable == other.unreachable; + } + bool operator!=(const PrincipalType& other) const { + return !(*this == other); + } +}; + +std::ostream& operator<<(std::ostream& o, const PrincipalType& type); + +} // namespace wasm + +#endif // wasm_ir_principal_types_h diff --git a/src/support/disjoint_sets.h b/src/support/disjoint_sets.h index 198e5cb67c2..52f1cf1ab59 100644 --- a/src/support/disjoint_sets.h +++ b/src/support/disjoint_sets.h @@ -33,7 +33,7 @@ struct DisjointSets { // An upper bound on the height of the tree rooted at this element. size_t rank; }; - std::vector info; + mutable std::vector info; // Add an element and return its index. size_t addSet() { @@ -43,7 +43,7 @@ struct DisjointSets { } // Get the representative element of the set to which `elem` belongs. - size_t getRoot(size_t elem) { + size_t getRoot(size_t elem) const { assert(elem < info.size()); size_t root = elem; // Follow parent pointers up to the root. @@ -80,6 +80,9 @@ struct DisjointSets { } return root1; } + + // Reserve space for `size` disjoint sets. + void reserve(size_t size) { info.reserve(size); } }; } // namespace wasm diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index d1ef8454a9a..5e2714df41d 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -17,6 +17,7 @@ set(unittest_SOURCES lattices.cpp local-graph.cpp possible-contents.cpp + principal-type.cpp printing.cpp public-type-validator.cpp scc.cpp diff --git a/test/gtest/principal-type.cpp b/test/gtest/principal-type.cpp new file mode 100644 index 00000000000..617c2b63ade --- /dev/null +++ b/test/gtest/principal-type.cpp @@ -0,0 +1,612 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ir/principal-type.h" +#include "wasm-type.h" + +#include "gtest/gtest.h" + +using namespace wasm; + +void expectCompose(const char* file, + int line, + PrincipalType a, + PrincipalType b, + PrincipalType c) { + std::stringstream ss; + ss << a << " + " << b << " = " << c; + testing::ScopedTrace trace(file, line, ss.str()); + EXPECT_TRUE(a.compose(b)); + EXPECT_EQ(a, c); +} + +#define COMPOSE(a, b, c) \ + expectCompose( \ + __FILE__, __LINE__, PrincipalType a, PrincipalType b, PrincipalType c) + +void expectNoCompose(const char* file, + int line, + PrincipalType a, + PrincipalType b) { + std::stringstream ss; + ss << a << " + " << b << " = X"; + testing::ScopedTrace trace(file, line, ss.str()); + auto original = a; + EXPECT_FALSE(a.compose(b)); + EXPECT_EQ(a, original); +} + +#define NO_COMPOSE(a, b) \ + expectNoCompose(__FILE__, __LINE__, PrincipalType a, PrincipalType b) + +TEST(PrincipalTypeTest, ComposeBasic) { + Type i32 = Type::i32; + Type i64 = Type::i64; + Type f32 = Type::f32; + Type f64 = Type::f64; + + // Empty types with all combinations of unreachability. + // []->[] + []->[] = []->[] + COMPOSE(({}, {}), ({}, {}), ({}, {})); + // []->[] + []*->[] = []*->[] + COMPOSE(({}, {}), ({}, {}, true), ({}, {}, true)); + // []*->[] + []->[] = []*->[] + COMPOSE(({}, {}, true), ({}, {}), ({}, {}, true)); + // []*->[] + []*->[] = []*->[] + COMPOSE(({}, {}, true), ({}, {}, true), ({}, {}, true)); + + // i32 in all positions with all combinations of unreachability. + // [i32]->[] + []->[] = [i32]->[] + COMPOSE(({i32}, {}), ({}, {}), ({i32}, {})); + // []->[i32] + []->[] = []->[i32] + COMPOSE(({}, {i32}), ({}, {}), ({}, {i32})); + // []->[] + [i32]->[] = [i32]->[] + COMPOSE(({}, {}), ({i32}, {}), ({i32}, {})); + // []->[] + []->[i32] = []->[i32] + COMPOSE(({}, {}), ({}, {i32}), ({}, {i32})); + + // [i32]*->[] + []->[] = [i32]*->[] + COMPOSE(({i32}, {}, true), ({}, {}), ({i32}, {}, true)); + // []*->[i32] + []->[] = []*->[i32] + COMPOSE(({}, {i32}, true), ({}, {}), ({}, {i32}, true)); + // []*->[] + [i32]->[] = []*->[] + COMPOSE(({}, {}, true), ({i32}, {}), ({}, {}, true)); + // []*->[] + []->[i32] = []*->[i32] + COMPOSE(({}, {}, true), ({}, {i32}), ({}, {i32}, true)); + + // [i32]->[] + []*->[] = [i32]*->[] + COMPOSE(({i32}, {}), ({}, {}, true), ({i32}, {}, true)); + // []->[i32] + []*->[] = []*->[] + COMPOSE(({}, {i32}), ({}, {}, true), ({}, {}, true)); + // []->[] + [i32]*->[] = [i32]*->[] + COMPOSE(({}, {}), ({i32}, {}, true), ({i32}, {}, true)); + // []->[] + []*->[i32] = []*->[i32] + COMPOSE(({}, {}), ({}, {i32}, true), ({}, {i32}, true)); + + // [i32]*->[] + []*->[] = [i32]*->[] + COMPOSE(({i32}, {}, true), ({}, {}, true), ({i32}, {}, true)); + // []*->[i32] + []*->[] = []*->[] + COMPOSE(({}, {i32}, true), ({}, {}, true), ({}, {}, true)); + // []*->[] + [i32]*->[] = []*->[] + COMPOSE(({}, {}, true), ({i32}, {}, true), ({}, {}, true)); + // []*->[] + []*->[i32] = []*->[i32] + COMPOSE(({}, {}, true), ({}, {i32}, true), ({}, {i32}, true)); + + // A second type (f64) in all possible positions. + // [i32]->[i64] + [i64]->[f32] = [i32]->[f32] + COMPOSE(({i32}, {i64}), ({i64}, {f32}), ({i32}, {f32})); + // [f64 i32]->[i64] + [i64]->[f32] = [f64 i32]->[f32] + COMPOSE(({f64, i32}, {i64}), ({i64}, {f32}), ({f64, i32}, {f32})); + // [i32 f64]->[i64] + [i64]->[f32] = [f64 i32]->[f32] + COMPOSE(({i32, f64}, {i64}), ({i64}, {f32}), ({i32, f64}, {f32})); + // [i32]->[f64 i64] + [i64]->[f32] = [i32]->[f64 f32] + COMPOSE(({i32}, {f64, i64}), ({i64}, {f32}), ({i32}, {f64, f32})); + // [i32]->[i64 f64] + [i64]->[f32] = X + NO_COMPOSE(({i32}, {i64, f64}), ({i64}, {f32})); + // [i32]->[i64] + [f64 i64]->[f32] = [f64 i32]->[f32] + COMPOSE(({i32}, {i64}), ({f64, i64}, {f32}), ({f64, i32}, {f32})); + // [i32]->[i64] + [i64 f64]->[f32] = X + NO_COMPOSE(({i32}, {i64}), ({i64, f64}, {f32})); + // [i32]->[i64] + [i64]->[f64 f32] = [i32]->[f64 f32] + COMPOSE(({i32}, {i64}), ({i64}, {f64, f32}), ({i32}, {f64, f32})); + // [i32]->[i64] + [i64]->[f32 f64] = [i32]->[f32 f64] + COMPOSE(({i32}, {i64}), ({i64}, {f32, f64}), ({i32}, {f32, f64})); +} + +TEST(PrincipalTypeTest, MatchSubtypes) { + Type anyref = Type(HeapType::any, Nullable); + Type eqref = Type(HeapType::eq, Nullable); + + // []->[eqref] + [anyref]->[] = [] + COMPOSE(({}, {eqref}), ({anyref}, {}), ({}, {})); + // []->[anyref] + [eqref]->[] = X + NO_COMPOSE(({}, {anyref}), ({eqref}, {})); +} + +TEST(PrincipalTypeTest, MatchTypeVariables) { + Type i32 = Type::i32; + Type i64 = Type::i64; + Type f32 = Type::f32; + VarType t0 = {0u}; + VarType t1 = {1u}; + VarType t2 = {2u}; + + // Match type variables with nothing or other type variables. + // []->[] + [t0]->[] = [t0]->[] + COMPOSE(({}, {}), ({t0}, {}), ({t0}, {})); + // []->[] + [t0]->[t0] = [t0]->[t0] + COMPOSE(({}, {}), ({t0}, {t0}), ({t0}, {t0})); + // [t0]->[] + []->[] = [t0]->[] + COMPOSE(({t0}, {}), ({}, {}), ({t0}, {})); + // [t0]->[] + [t0]->[] = [t1 t0]->[] + COMPOSE(({t0}, {}), ({t0}, {}), ({t1, t0}, {})); + // [t0]->[] + [t0]->[t0] = [t1 t0]->[t1] + COMPOSE(({t0}, {}), ({t0}, {t0}), ({t1, t0}, {t1})); + // [t0]->[t0] + []->[] = [t0]->[t0] + COMPOSE(({t0}, {t0}), ({}, {}), ({t0}, {t0})); + // [t0]->[t0] + [t0]->[] = [t0]->[] + COMPOSE(({t0}, {t0}), ({t0}, {}), ({t0}, {})); + // [t0]->[t0] + [t0]->[t0] = [t0]->[t0] + COMPOSE(({t0}, {t0}), ({t0}, {t0}), ({t0}, {t0})); + + // Match a concrete type. + // []->[i32] + [t0]->[] = []->[] + COMPOSE(({}, {i32}), ({t0}, {}), ({}, {})); + // []->[i32] + [t0]->[t0] = []->[i32] + COMPOSE(({}, {i32}), ({t0}, {t0}), ({}, {i32})); + // [t0]->[i32] + [t0']->[t0'] = [t0]->[i32] + COMPOSE(({t0}, {i32}), ({t0}, {t0}), ({t0}, {i32})); + // [i32]->[i32] + [t0]->[t0] = [i32]->[i32] + COMPOSE(({i32}, {i32}), ({t0}, {t0}), ({i32}, {i32})); + // [t0]->[] + [i32]->[] = [i32 t0]->[] + COMPOSE(({t0}, {}), ({i32}, {}), ({i32, t0}, {})); + // [t0]->[t0] + [i32]->[] = [i32]->[] + COMPOSE(({t0}, {t0}), ({i32}, {}), ({i32}, {})); + // [t0]->[t0] + [i32]->[i32] = [i32]->[i32] + COMPOSE(({t0}, {t0}), ({i32}, {i32}), ({i32}, {i32})); + // [t0]->[t0] + [t0']->[i32] = [t0]->[i32] + COMPOSE(({t0}, {t0}), ({t0}, {i32}), ({t0}, {i32})); + + // Match bottom. + // []*->[] + [t0]->[t0] = []*->[] + COMPOSE(({}, {}, true), ({t0}, {t0}), ({}, {}, true)); + // [t0]->[t0] + []*->[] = [t0]*->[] + COMPOSE(({t0}, {t0}), ({}, {}, true), ({t0}, {}, true)); + + // Multiple variables. + // []->[i32 i64 f32] + [t2 t1 t0]->[t0 t1 t2] = []->[f32 i64 i32] + COMPOSE( + ({}, {i32, i64, f32}), ({t2, t1, t0}, {t0, t1, t2}), ({}, {f32, i64, i32})); +} + +TEST(PrincipalTypeTest, MatchHeapTypeVariables) { + VarType refAny = Type(HeapType::any, NonNullable); + VarType refNullAny = Type(HeapType::any, Nullable); + VarType refT0 = VarRef{NonNullable, {0u}}; + VarType refNullT0 = VarRef{Nullable, {0u}}; + VarType refBot = VarRef{NonNullable, BottomHeapType{}}; + VarType refNullBot = VarRef{Nullable, BottomHeapType{}}; + + // Forward match a concrete type. + // []->[(ref any)] + [(ref t0)]->[(ref t0)] = []->[(ref any)] + COMPOSE(({}, {refAny}), ({refT0}, {refT0}), ({}, {refAny})); + // []->[(ref any)] + [(ref null t0)]->[(ref t0)] = []->[(ref any)] + COMPOSE(({}, {refAny}), ({refNullT0}, {refT0}), ({}, {refAny})); + // []->[(ref null any)] + [(ref t0)]->[(ref t0)] = X + NO_COMPOSE(({}, {refNullAny}), ({refT0}, {refT0})); + // []->[(ref null any)] + [(ref null t0)]->[(ref t0)] = []->[(ref any)] + COMPOSE(({}, {refNullAny}), ({refNullT0}, {refT0}), ({}, {refAny})); + // []->[(ref bot)] + [(ref t0)]->[(ref t0)] = []->[(ref bot)] + COMPOSE(({}, {refBot}), ({refT0}, {refT0}), ({}, {refBot})); + // []->[(ref bot)] + [(ref null t0)]->[(ref t0)] = []->[(ref bot)] + COMPOSE(({}, {refBot}), ({refNullT0}, {refT0}), ({}, {refBot})); + // []->[(ref null bot)] + [(ref t0)]->[(ref t0)] = X + NO_COMPOSE(({}, {refNullBot}), ({refT0}, {refT0})); + // []->[(ref null bot)] + [(ref null t0)]->[(ref t0)] = []->[(ref bot)] + COMPOSE(({}, {refNullBot}), ({refNullT0}, {refT0}), ({}, {refBot})); + + // Backward match a concrete type. + // [(ref t0)]->[(ref t0)] + [(ref any)]->[] = [(ref any)]->[] + COMPOSE(({refT0}, {refT0}), ({refAny}, {}), ({refAny}, {})); + // [(ref t0)]->[(ref null t0)] + [(ref any)]->[] = X + NO_COMPOSE(({refT0}, {refNullT0}), ({refAny}, {})); + // [(ref t0)]->[(ref t0)] + [(ref null any)]->[] = [(ref any)]->[] + COMPOSE(({refT0}, {refT0}), ({refNullAny}, {}), ({refAny}, {})); + // [(ref t0)]->[(ref null t0)] + [(ref null any)]->[] = [(ref any)]->[] + COMPOSE(({refT0}, {refNullT0}), ({refNullAny}, {}), ({refAny}, {})); + // [(ref t0)]->[(ref t0)] + [(ref bot)]->[] = [(ref bot)]->[] + COMPOSE(({refT0}, {refT0}), ({refBot}, {}), ({refBot}, {})); + // [(ref t0)]->[(ref null t0)] + [(ref bot)]->[] = X + NO_COMPOSE(({refT0}, {refNullT0}), ({refBot}, {})); + // [(ref t0)]->[(ref t0)] + [(ref null bot)]->[] = [(ref bot)]->[] + COMPOSE(({refT0}, {refT0}), ({refNullBot}, {}), ({refBot}, {})); + // [(ref t0)]->[(ref null t0)] + [(ref null bot)]->[] = [(ref bot)]->[] + COMPOSE(({refT0}, {refNullT0}), ({refNullBot}, {}), ({refBot}, {})); + + // Match another variable. + // [(ref null t0)]->[(ref t0)] + [(ref t0')]->[(ref t0')] + // = [(ref null t0)]->[(ref t0)] + COMPOSE(({refNullT0}, {refT0}), ({refT0}, {refT0}), ({refNullT0}, {refT0})); + + // Match bottom. + // []*->[] + [(ref t0)]->[(ref t0)] = []*->[(ref bot))] + COMPOSE(({}, {}, true), ({refT0}, {refT0}), ({}, {refBot}, true)); + // [(ref t0)]->[(ref t0)] + []*->[] = [(ref t0)]*->[] + COMPOSE(({refT0}, {refT0}), ({}, {}, true), ({refT0}, {}, true)); +} + +TEST(PrincipalTypeTest, MatchNullabilityVariables) { + VarType refAny = Type(HeapType::any, NonNullable); + VarType refNullAny = Type(HeapType::any, Nullable); + VarType refN0Any = VarRef{{0u}, VarAbsHeapType{Unshared, HeapType::any}}; + VarType refEq = Type(HeapType::eq, NonNullable); + VarType refNullEq = Type(HeapType::eq, Nullable); + VarType refN0Eq = VarRef{{0u}, VarAbsHeapType{Unshared, HeapType::eq}}; + + // Forward match a concrete nullability. + // []->[(ref any)] + [(ref n0 any)]->[(ref n0 eq)] = []->[(ref eq)] + COMPOSE(({}, {refAny}), ({refN0Any}, {refN0Eq}), ({}, {refEq})); + // []->[(ref null any)] + [(ref n0 any)]->[(ref n0 eq)] = []->[(ref null eq)] + COMPOSE(({}, {refNullAny}), ({refN0Any}, {refN0Eq}), ({}, {refNullEq})); + // []->[(ref eq)] + [(ref n0 any)]->[(ref n0 eq)] = []->[(ref eq)] + COMPOSE(({}, {refEq}), ({refN0Any}, {refN0Eq}), ({}, {refEq})); + // []->[(ref null eq)] + [(ref n0 any)]->[(ref n0 eq)] = []->[(ref null eq)] + COMPOSE(({}, {refNullEq}), ({refN0Any}, {refN0Eq}), ({}, {refNullEq})); + // []->[(ref any)] + [(ref n0 eq)]->[(ref n0 eq)] = X + NO_COMPOSE(({}, {refAny}), ({refN0Eq}, {refN0Eq})); + // []->[(ref null any)] + [(ref n0 eq)]->[(ref n0 eq)] = X + NO_COMPOSE(({}, {refNullAny}), ({refN0Eq}, {refN0Eq})); + + // Backward match a concrete nullability. + // [(ref n0 eq)]->[(ref n0 any)] + [(ref any)]->[] = [(ref eq)]->[] + COMPOSE(({refN0Eq}, {refN0Any}), ({refAny}, {}), ({refEq}, {})); + // [(ref n0 eq)]->[(ref n0 any)] + [(ref null any)]->[] = [(ref null eq)]->[] + COMPOSE(({refN0Eq}, {refN0Any}), ({refNullAny}, {}), ({refNullEq}, {})); + // [(ref n0 eq)]->[(ref n0 any)] + [(ref eq)]->[] = X + NO_COMPOSE(({refN0Eq}, {refN0Any}), ({refEq}, {})); + // [(ref n0 eq)]->[(ref n0 any)] + [(ref null eq)]->[] = X + NO_COMPOSE(({refN0Eq}, {refN0Any}), ({refNullEq}, {})); + // [(ref n0 eq)]->[(ref n0 eq)] + [(ref any)]->[] = [(ref eq)]->[] + COMPOSE(({refN0Eq}, {refN0Eq}), ({refAny}, {}), ({refEq}, {})); + // [(ref n0 eq)]->[(ref n0 eq)] + [(ref null any)]->[] = [(ref null eq)]->[] + COMPOSE(({refN0Eq}, {refN0Eq}), ({refNullAny}, {}), ({refNullEq}, {})); + + // Match another variable. + // [(ref n0 eq)]->[(ref n0 eq)] + [(ref n0' eq)]->[(ref n0' eq)] + // = [(ref n0 eq)]->[(ref n0 eq)] + COMPOSE( + ({refN0Eq}, {refN0Eq}), ({refN0Eq}, {refN0Eq}), ({refN0Eq}, {refN0Eq})); + + // Match bottom. + // []*->[] + [(ref n0 any)]->[(ref n0 any)] = []*->[(ref any))] + COMPOSE(({}, {}, true), ({refN0Any}, {refN0Any}), ({}, {refAny}, true)); + // [(ref n0 any)]->[(ref n0 any)] + []*->[] = [(ref n0 any)]*->[] + COMPOSE(({refN0Any}, {refN0Any}), ({}, {}, true), ({refN0Any}, {}, true)); +} + +TEST(PrincipalTypeTest, MatchExactnessVariables) { + TypeBuilder builder(2); + builder[0] = Struct{}; + builder[0].setOpen(); + builder[1] = Struct(); + builder[1].subTypeOf(builder[0]); + auto built = builder.build(); + HeapType foo = (*built)[0]; + VarType refFoo = Type(foo, NonNullable); + VarType refExactFoo = Type(foo, NonNullable, Exact); + VarType refE0Foo = VarRef{NonNullable, VarDefHeapType{{0u}, foo}}; + HeapType bar = (*built)[1]; + VarType refBar = Type(bar, NonNullable); + VarType refExactBar = Type(bar, NonNullable, Exact); + VarType refE0Bar = VarRef{NonNullable, VarDefHeapType{{0u}, bar}}; + VarType refBot = VarRef{NonNullable, BottomHeapType{}}; + + // Forward match a concrete exactness. + // []->[(ref foo)] + [(ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refFoo}), ({refE0Foo}, {refE0Foo}), ({}, {refFoo})); + // []->[(ref (exact foo))] + [(ref (e0 foo))]->[(ref (e0 foo))] + // = []->[(ref (exact foo))] + COMPOSE(({}, {refExactFoo}), ({refE0Foo}, {refE0Foo}), ({}, {refExactFoo})); + // []->[(ref bar)] + [(ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refBar}), ({refE0Foo}, {refE0Foo}), ({}, {refFoo})); + // []->[(ref (exact bar))] + [(ref (e0 foo))]->[(ref (e0 foo))] + // = []->[(ref foo)] + COMPOSE(({}, {refExactBar}), ({refE0Foo}, {refE0Foo}), ({}, {refFoo})); + // []->[(ref foo)] + [(ref (e0 bar))]->[(ref (e0 foo))] = X + NO_COMPOSE(({}, {refFoo}), ({refE0Bar}, {refE0Foo})); + // []->[(ref (exact foo))] + [(ref (e0 bar))]->[(ref (e0 foo))] = X + NO_COMPOSE(({}, {refExactFoo}), ({refE0Bar}, {refE0Foo})); + + // Backward match a concrete nullability. + // [(ref (e0 foo))]->[(ref (e0 foo))] + [(ref foo)]->[] = [(ref foo)]->[] + COMPOSE(({refE0Foo}, {refE0Foo}), ({refFoo}, {}), ({refFoo}, {})); + // [(ref (e0 foo))]->[(ref (e0 foo))] + [(ref (exact foo))]->[] + // = [(ref (exact foo))]->[] + COMPOSE(({refE0Foo}, {refE0Foo}), ({refExactFoo}, {}), ({refExactFoo}, {})); + // [(ref (e0 foo))]->[(ref (e0 foo))] + [(ref bar)]->[] = X + NO_COMPOSE(({refE0Foo}, {refE0Foo}), ({refBar}, {})); + // [(ref (e0 foo))]->[(ref (e0 foo))] + [(ref (exact bar))]->[] = X + NO_COMPOSE(({refE0Foo}, {refE0Foo}), ({refExactBar}, {})); + // [(ref (e0 foo))]->[(ref (e0 bar))] + [(ref foo)]->[] = [(ref foo)]->[] + COMPOSE(({refE0Foo}, {refE0Bar}), ({refFoo}, {}), ({refFoo}, {})); + // [(ref (e0 foo))]->[(ref (e0 bar))] + [(ref (exact foo))]->[] = X + NO_COMPOSE(({refE0Foo}, {refE0Bar}), ({refExactFoo}, {})); + + // Match another variable. + // [(ref (e0 foo))]->[(ref (e0 foo))] + [(ref (e0' foo))]->[(ref (e0' foo))] + // = [(ref (e0 foo))]->[(ref (e0 foo))] + COMPOSE(({refE0Foo}, {refE0Foo}), + ({refE0Foo}, {refE0Foo}), + ({refE0Foo}, {refE0Foo})); + // [(ref (e0 foo))]->[(ref (e0 bar))] + [(ref (e0' foo))]->[(ref (e0' foo))] + // = [(ref foo)]->[(ref foo)] + COMPOSE( + ({refE0Foo}, {refE0Bar}), ({refE0Foo}, {refE0Foo}), ({refFoo}, {refFoo})); + // [(ref (e0 foo))]->[(ref (e0 foo))] + [(ref (e0' bar))]->[(ref (e0' foo))] + // = X + NO_COMPOSE(({refE0Foo}, {refE0Foo}), ({refE0Bar}, {refE0Foo})); + + // Match bottom. + // []->[(ref bot)] + [(ref (e0 foo))]->[(ref (e0 foo))] + // = []->[(ref (exact foo))] + COMPOSE(({}, {refBot}), ({refE0Foo}, {refE0Foo}), ({}, {refExactFoo})); + // [(ref (e0 foo))]->[(ref (e0 foo))] + [(ref bot)]->[] = X + NO_COMPOSE(({refE0Foo}, {refE0Foo}), ({refBot}, {})); + // []*->[] + [(ref (e0 foo))]->[(ref (e0 foo))] = []*->[(ref (exact foo))] + COMPOSE(({}, {}, true), ({refE0Foo}, {refE0Foo}), ({}, {refExactFoo}, true)); + // [(ref (e0 foo))]->[(ref (e0 foo))] + []*->[] = [(ref (e0 foo))]*->[] + COMPOSE(({refE0Foo}, {refE0Foo}), ({}, {}, true), ({refE0Foo}, {}, true)); +} + +TEST(PrincipalTypeTest, MatchSharednessVariables) { + HeapType any = HeapType::any; + VarType refAny = Type(any, NonNullable); + VarType refSharedAny = Type(any.getBasic(Shared), NonNullable); + VarType refBotAny = VarRef{NonNullable, VarAbsHeapType{BottomShare{}, any}}; + VarType refS0Any = VarRef{NonNullable, VarAbsHeapType{{0u}, any}}; + HeapType eq = HeapType::eq; + VarType refEq = Type(eq, NonNullable); + VarType refSharedEq = Type(eq.getBasic(Shared), NonNullable); + VarType refBotEq = VarRef{NonNullable, VarAbsHeapType{BottomShare{}, eq}}; + VarType refS0Eq = VarRef{NonNullable, VarAbsHeapType{{0u}, eq}}; + VarType refBot = VarRef{NonNullable, BottomHeapType{}}; + + // Forward match a concrete sharedness. + // []->[(ref any)] + [(ref (s0 any))]->[(ref (s0 any))] = []->[(ref any)] + COMPOSE(({}, {refAny}), ({refS0Any}, {refS0Any}), ({}, {refAny})); + // []->[(ref (shared any))] + [(ref (s0 any))]->[(ref (s0 any))] + // = []->[(ref (shared any))] + COMPOSE(({}, {refSharedAny}), ({refS0Any}, {refS0Any}), ({}, {refSharedAny})); + // []->[(ref (bot-share any))] + [(ref (s0 any))]->[(ref (s0 any))] + // = []->[(ref (bot-share any))] + COMPOSE(({}, {refBotAny}), ({refS0Any}, {refS0Any}), ({}, {refBotAny})); + // []->[(ref eq)] + [(ref (s0 any))]->[(ref (s0 any))] = []->[(ref any)] + COMPOSE(({}, {refEq}), ({refS0Any}, {refS0Any}), ({}, {refAny})); + // []->[(ref (shared eq))] + [(ref (s0 any))]->[(ref (s0 any))] + // = []->[(ref (shared any))] + COMPOSE(({}, {refSharedEq}), ({refS0Any}, {refS0Any}), ({}, {refSharedAny})); + // []->[(ref (bot-share eq))] + [(ref (s0 any))]->[(ref (s0 any))] + // = []->[(ref (bot-share any))] + COMPOSE(({}, {refBotEq}), ({refS0Any}, {refS0Any}), ({}, {refBotAny})); + // []->[(ref any)] + [(ref (s0 eq))]->[(ref (s0 any))] = X + NO_COMPOSE(({}, {refAny}), ({refS0Eq}, {refS0Any})); + // []->[(ref (shared any))] + [(ref (s0 eq))]->[(ref (s0 any))] = X + NO_COMPOSE(({}, {refSharedAny}), ({refS0Eq}, {refS0Any})); + // []->[(ref (bot-share any))] + [(ref (s0 eq))]->[(ref (s0 any))] = X + NO_COMPOSE(({}, {refBotAny}), ({refS0Eq}, {refS0Any})); + + // Backward match a concrete sharedness. + // [(ref (s0 any))]->[(ref (s0 any))] + [(ref any)]->[] = [(ref any)]->[] + COMPOSE(({refS0Any}, {refS0Any}), ({refAny}, {}), ({refAny}, {})); + // [(ref (s0 any))]->[(ref (s0 any))] + [(ref (shared any))]->[] + // = [(ref (shared any))]->[] + COMPOSE(({refS0Any}, {refS0Any}), ({refSharedAny}, {}), ({refSharedAny}, {})); + // [(ref (s0 any))]->[(ref (s0 any))] + [(ref (bot-share any))]->[] + // = [(ref (bot-share any))]->[] + COMPOSE(({refS0Any}, {refS0Any}), ({refBotAny}, {}), ({refBotAny}, {})); + // [(ref (s0 any))]->[(ref (s0 eq))] + [(ref any)]->[] = [(ref any)]->[] + COMPOSE(({refS0Any}, {refS0Eq}), ({refAny}, {}), ({refAny}, {})); + // [(ref (s0 any))]->[(ref (s0 eq))] + [(ref (shared any))]->[] + // = [(ref (shared any))]->[] + COMPOSE(({refS0Any}, {refS0Eq}), ({refSharedAny}, {}), ({refSharedAny}, {})); + // [(ref (s0 any))]->[(ref (s0 eq))] + [(ref (bot-share any))]->[] + // = [(ref (bot-share any))]->[] + COMPOSE(({refS0Any}, {refS0Eq}), ({refBotAny}, {}), ({refBotAny}, {})); + // [(ref (s0 any))]->[(ref (s0 any))] + [(ref eq)]->[] = X + NO_COMPOSE(({refS0Any}, {refS0Any}), ({refEq}, {})); + // [(ref (s0 any))]->[(ref (s0 any))] + [(ref (shared eq))]->[] = X + NO_COMPOSE(({refS0Any}, {refS0Any}), ({refSharedEq}, {})); + // [(ref (s0 any))]->[(ref (s0 any))] + [(ref (bot-share eq))]->[] = X + NO_COMPOSE(({refS0Any}, {refS0Any}), ({refBotEq}, {})); + + // Match another variable. + // [(ref (s0 eq))]->[(ref (s0 eq))] + [(ref (s0' eq))]->[(ref (s0' eq))] + // = [(ref (s0 eq))]->[(ref (s0 eq))] + COMPOSE( + ({refS0Eq}, {refS0Eq}), ({refS0Eq}, {refS0Eq}), ({refS0Eq}, {refS0Eq})); + + // Match bottom. + // []->[(ref bot)] + [(ref (s0 any))]->[(ref (s0 any))] + // = []->[(ref (bot-share any))] + COMPOSE(({}, {refBot}), ({refS0Any}, {refS0Any}), ({}, {refBotAny})); + // [(ref (s0 any))]->[(ref (s0 any))] + [(ref bot)]->[] = X + NO_COMPOSE(({refS0Any}, {refS0Any}), ({refBot}, {})); + // []*->[] + [(ref (s0 any))]->[(ref (s0 any))] = []*->[(ref (bot-share any))] + COMPOSE(({}, {}, true), ({refS0Any}, {refS0Any}), ({}, {refBotAny}, true)); + // [(ref (s0 any))]->[(ref (s0 any))] + []*->[] = [(ref (s0 any))]*->[] + COMPOSE(({refS0Any}, {refS0Any}), ({}, {}, true), ({refS0Any}, {}, true)); +} + +TEST(PrincipalTypeTest, UnifyVariables) { + Type i32 = Type::i32; + HeapType any = HeapType::any; + Type anyref = Type(any, Nullable); + Type refAny = Type(any, NonNullable); + Type refSharedAny = Type(any.getBasic(Shared), NonNullable); + VarType refBotAny = VarRef{NonNullable, VarAbsHeapType{BottomShare{}, any}}; + VarType refS0Any = VarRef{NonNullable, VarAbsHeapType{{0u}, any}}; + HeapType eq = HeapType::eq; + Type eqref = Type(eq, Nullable); + Type refEq = Type(eq, NonNullable); + Type refSharedEq = Type(eq.getBasic(Shared), NonNullable); + VarType refBotEq = VarRef{NonNullable, VarAbsHeapType{BottomShare{}, eq}}; + VarType refS0Eq = VarRef{NonNullable, VarAbsHeapType{{0u}, eq}}; + VarType t0 = {0u}; + VarType t1 = {1u}; + VarType t2 = {2u}; + VarType refN0T0 = VarRef{{0u}, {0u}}; + + TypeBuilder builder(2); + builder[0] = Struct{}; + builder[0].setOpen(); + builder[1] = Struct(); + builder[1].subTypeOf(builder[0]); + auto built = builder.build(); + HeapType foo = (*built)[0]; + VarType refFoo = Type(foo, NonNullable); + VarType refExactFoo = Type(foo, NonNullable, Exact); + VarType refE0Foo = VarRef{NonNullable, VarDefHeapType{{0u}, foo}}; + HeapType bar = (*built)[1]; + VarType refBar = Type(bar, NonNullable); + VarType refExactBar = Type(bar, NonNullable, Exact); + + // Unify multiple variables. + // []->[t2 t2 t1 t1 t0] + [t2' t1' t1' t0' t0']->[t0' t1' t2'] + // = []->[t0 t0 t0] + COMPOSE(({}, {t2, t2, t1, t1, t0}), + ({t2, t1, t1, t0, t0}, {t0, t1, t2}), + ({}, {t0, t0, t0})); + // []->[t1 t1 t0 t0 i32] + [t2' t1' t1' t0' t0']->[t0' t1' t2'] + // = []->[i32 i32 i32] + COMPOSE(({}, {t1, t1, t0, t0, i32}), + ({t2, t1, t1, t0, t0}, {t0, t1, t2}), + ({}, {i32, i32, i32})); + + // Join multiple type variables. + // []->[eqref (ref any)] + [t0 t0]->[t0] = []->[anyref] + COMPOSE(({}, {eqref, refAny}), ({t0, t0}, {t0}), ({}, {anyref})); + // []->[eqref (ref any)] + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)] + // = []->[anyref] + COMPOSE( + ({}, {eqref, refAny}), ({refN0T0, refN0T0}, {refN0T0}), ({}, {anyref})); + // TODO: Join abstract and defined heap types. + + // Join sharedness assignments. + // []->[(ref any) (ref any)] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] = []->[(ref eq)] + COMPOSE( + ({}, {refAny, refAny}), ({refS0Any, refS0Any}, {refS0Eq}), ({}, {refEq})); + // []->[(ref any) (ref (shared any))] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] = X + NO_COMPOSE(({}, {refAny, refSharedAny}), ({refS0Any, refS0Any}, {refS0Eq})); + // []->[(ref any) (ref (bot-share any))] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] = []->[(ref eq)] + COMPOSE(({}, {refAny, refBotAny}), + ({refS0Any, refS0Any}, {refS0Eq}), + ({}, {refEq})); + // []->[(ref (shared any)) (ref any)] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] = X + NO_COMPOSE(({}, {refSharedAny, refAny}), ({refS0Any, refS0Any}, {refS0Any})); + // []->[(ref (shared any)) (ref (shared any))] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] + // = []->[(ref (shared eq))] + COMPOSE(({}, {refSharedAny, refSharedAny}), + ({refS0Any, refS0Any}, {refS0Eq}), + ({}, {refSharedEq})); + // []->[(ref (shared any)) (ref (bot-share any))] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] + // = []->[(ref (shared eq))] + COMPOSE(({}, {refSharedAny, refBotAny}), + ({refS0Any, refS0Any}, {refS0Eq}), + ({}, {refSharedEq})); + // []->[(ref (bot-share any)) (ref any)] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] = []->[(ref eq)] + COMPOSE(({}, {refBotAny, refAny}), + ({refS0Any, refS0Any}, {refS0Eq}), + ({}, {refEq})); + // []->[(ref (bot-share any)) (ref (shared any))] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] + // = []->[(ref (shared eq))) + COMPOSE(({}, {refBotAny, refSharedAny}), + ({refS0Any, refS0Any}, {refS0Eq}), + ({}, {refSharedEq})); + // []->[(ref (bot-share any)) (ref (bot-share any))] + // + [(ref (s0 any)) (ref (s0 any))]->[(ref (s0 eq))] + // = []->[(ref (bot-share eq))] + COMPOSE(({}, {refBotAny, refBotAny}), + ({refS0Any, refS0Any}, {refS0Eq}), + ({}, {refBotEq})); + + // Join exactness assignments. + // []->[(ref foo) (ref foo)] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE( + ({}, {refFoo, refFoo}), ({refE0Foo, refE0Foo}, {refE0Foo}), ({}, {refFoo})); + // []->[(ref (exact foo)) (ref (exact foo))] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] + // = []->[(ref (exact foo))] + COMPOSE(({}, {refExactFoo, refExactFoo}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refExactFoo})); + // []->[(ref (exact foo)) (ref foo)] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refExactFoo, refFoo}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refFoo})); + // []->[(ref foo) (ref (exact foo))] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refFoo, refExactFoo}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refFoo})); + // []->[(ref bar) (ref foo)] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE( + ({}, {refBar, refFoo}), ({refE0Foo, refE0Foo}, {refE0Foo}), ({}, {refFoo})); + // []->[(ref (exact bar)) (ref (exact foo))] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refExactBar, refExactFoo}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refFoo})); + // []->[(ref (exact bar)) (ref foo)] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refExactBar, refFoo}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refFoo})); + // []->[(ref bar) (ref (exact foo))] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refBar, refExactFoo}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refFoo})); + // []->[(ref foo) (ref bar)] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE( + ({}, {refFoo, refBar}), ({refE0Foo, refE0Foo}, {refE0Foo}), ({}, {refFoo})); + // []->[(ref (exact foo)) (ref (exact bar))] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refExactFoo, refExactBar}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refFoo})); + // []->[(ref (exact foo)) (ref bar)] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refExactFoo, refBar}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refFoo})); + // []->[(ref foo) (ref (exact bar))] + // + [(ref (e0 foo)) (ref (e0 foo))]->[(ref (e0 foo))] = []->[(ref foo)] + COMPOSE(({}, {refFoo, refExactBar}), + ({refE0Foo, refE0Foo}, {refE0Foo}), + ({}, {refFoo})); +} From 48d11fdd6ca5c98ca372776604ea74afd2c92f03 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sat, 28 Jun 2025 02:14:09 +0200 Subject: [PATCH 573/622] [Custom Descriptors] Add effects for struct.new (#7690) struct.new can now trap if provided a null descriptor. Update its effects accordingly and add a test that would incorrectly remove a trapping struct.new without this fix. --- scripts/test/fuzzing.py | 1 + src/ir/effects.h | 11 +++++++- test/lit/passes/simplify-locals-desc.wast | 31 +++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/simplify-locals-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 5b33b3fe5b1..c5dd37e326c 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -125,6 +125,7 @@ 'minimize-rec-groups-desc.wast', 'precompute-desc.wast', 'gc-desc.wast', + 'simplify-locals-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/ir/effects.h b/src/ir/effects.h index 504c1cc6488..a02a9627071 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -858,7 +858,16 @@ class EffectAnalyzer { parent.implicitTrap = true; } void visitBrOn(BrOn* curr) { parent.breakTargets.insert(curr->name); } - void visitStructNew(StructNew* curr) {} + void visitStructNew(StructNew* curr) { + if (curr->desc) { + // Traps when the descriptor is null. + if (curr->desc->type.isNull()) { + parent.trap = true; + } else if (curr->desc->type.isNullable()) { + parent.implicitTrap = true; + } + } + } void visitStructGet(StructGet* curr) { if (curr->ref->type == Type::unreachable) { return; diff --git a/test/lit/passes/simplify-locals-desc.wast b/test/lit/passes/simplify-locals-desc.wast new file mode 100644 index 00000000000..b4fa8a13f61 --- /dev/null +++ b/test/lit/passes/simplify-locals-desc.wast @@ -0,0 +1,31 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) + (type $struct (descriptor $desc (struct))) + ;; CHECK: (type $desc (sub (describes $struct (struct)))) + (type $desc (sub (describes $struct (struct)))) + ) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (local $l (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") + (local $l (ref null $struct)) + (local.set $l + ;; We should preserve this trap. + (struct.new_default $struct + (ref.null none) + ) + ) + ) +) From b89d65ac994e48ebcfe4d86e7f0d0af0ce8a5b06 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sat, 28 Jun 2025 03:03:02 +0200 Subject: [PATCH 574/622] [Custom Descriptors] Optimize struct.new (#7691) Optimize struct.new instructions that are known to trap due to having null descriptors. Also optimize out non-null casts of descriptors, since struct.new will implicitly perform that cast. --- scripts/test/fuzzing.py | 1 + src/passes/OptimizeInstructions.cpp | 11 +++- .../passes/optimize-instructions-desc.wast | 53 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/optimize-instructions-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index c5dd37e326c..4a610946935 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -126,6 +126,7 @@ 'precompute-desc.wast', 'gc-desc.wast', 'simplify-locals-desc.wast', + 'optimize-instructions-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 05938713413..f53987829ad 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1830,9 +1830,18 @@ struct OptimizeInstructions } void visitStructNew(StructNew* curr) { + if (curr->type == Type::unreachable) { + // Leave this for DCE. + return; + } + if (curr->desc) { + skipNonNullCast(curr->desc, curr); + trapOnNull(curr, curr->desc); + } + // If values are provided, but they are all the default, then we can remove // them (in reachable code). - if (curr->type == Type::unreachable || curr->isWithDefault()) { + if (curr->isWithDefault()) { return; } diff --git a/test/lit/passes/optimize-instructions-desc.wast b/test/lit/passes/optimize-instructions-desc.wast new file mode 100644 index 00000000000..0c00fcc1318 --- /dev/null +++ b/test/lit/passes/optimize-instructions-desc.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) + (type $struct (descriptor $desc (struct))) + ;; CHECK: (type $desc (describes $struct (struct))) + (type $desc (describes $struct (struct))) + ) + + ;; CHECK: (func $trap-null-desc (type $2) (result (ref (exact $struct))) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $trap-null-desc (result (ref (exact $struct))) + (struct.new $struct + (ref.null none) + ) + ) + + ;; CHECK: (func $trap-null-desc-fallthrough (type $2) (result (ref (exact $struct))) + ;; CHECK-NEXT: (local $desc (ref null (exact $desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $desc + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $trap-null-desc-fallthrough (result (ref (exact $struct))) + (local $desc (ref null (exact $desc))) + (struct.new $struct + (local.tee $desc + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $nonnull-cast-desc (type $3) (param $desc (ref null (exact $desc))) (result (ref (exact $struct))) + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nonnull-cast-desc (param $desc (ref null (exact $desc))) (result (ref (exact $struct))) + (struct.new $struct + (ref.as_non_null + (local.get $desc) + ) + ) + ) +) From 9d2bf83776e002a35c921678f6c0fe68872e8223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 30 Jun 2025 17:53:24 +0200 Subject: [PATCH 575/622] Inlining: Always inline trivial calls that always shrink (#7669) Currently a "trivial call" is a function that just calls another function. (func $foo ... (call $target ...)) Where the arguments to `$target` are all flat instructions like `local.get`, or consts. Currently we inline these functions always only when not optimizing for code size. When optimizing for code size, these functions can always be inlined when 1. The arguments to `$target` are all function argument locals. 2. Each local is used once. 3. In the order they appear in `$foo`'s signature. When these hold, inlining `$foo` never increases code size as it doesn't cause introducing more locals (or `drop`s etc.) at the call sites. `$foo` above when these hold looks like this: (func $foo (param $arg1 ...) (param $arg2 ...) (call $target (local.get $arg1) (local.get $arg2))) Update `FunctionInfo` type and `FunctionInfoScanner` to annotate functions with more detailed "trivial call" information that also contains whether inlining shrinks code size. If a function shrinks when inlined always inline it even with `-Os`. Otherwise inline it as before, i.e. when not optimizing for code size. --- src/passes/Inlining.cpp | 99 +++++++---- test/lit/passes/inlining-const-args.wat | 95 ++++++++++ test/lit/passes/inlining-trivial-calls-1.wast | 164 ++++++++++++++++++ test/lit/passes/inlining-trivial-calls-2.wast | 61 +++++++ test/lit/passes/inlining-trivial-calls-3.wast | 70 ++++++++ 5 files changed, 459 insertions(+), 30 deletions(-) create mode 100644 test/lit/passes/inlining-const-args.wat create mode 100644 test/lit/passes/inlining-trivial-calls-1.wast create mode 100644 test/lit/passes/inlining-trivial-calls-2.wast create mode 100644 test/lit/passes/inlining-trivial-calls-3.wast diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 876c186d471..1f93493f7bc 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -67,7 +67,32 @@ enum class InliningMode { SplitPatternB }; -// Useful into on a function, helping us decide if we can inline it +// Whether a function just calls another function in a way that always shrinks +// when the calling function is inlined. +enum class TrivialCall { + // Function does not just call another function, or it may not shrink when + // inlined. + NotTrivial, + + // Function just calls another function, with `local.get`s as arguments, and + // with each `local` is used exactly once, and in the order they appear in the + // argument list. + // + // In this case, inlining the function generates smaller code, and it is also + // good for runtime. + Shrinks, + + // Function just calls another function, but maybe with constant arguments, or + // maybe some locals are used more than once. In these cases code size does + // not always shrink: at the call sites, omitted locals can create `drop` + // instructions, a local used multiple times can create new locals, and + // encoding of constants may be larger than just a `local.get` with a small + // index. In these cases we still want to inline with `-O3`, but the code size + // may increase when inlined. + MayNotShrink, +}; + +// Useful info on a function, helping us decide if we can inline it. struct FunctionInfo { std::atomic refs; Index size; @@ -77,16 +102,7 @@ struct FunctionInfo { // Something is used globally if there is a reference to it in a table or // export etc. bool usedGlobally; - // We consider a function to be a trivial call if the body is just a call with - // trivial arguments, like this: - // - // (func $forward (param $x) (param $y) - // (call $target (local.get $x) (local.get $y)) - // ) - // - // Specifically the body must be a call, and the operands to the call must be - // of size 1 (generally, LocalGet or Const). - bool isTrivialCall; + TrivialCall trivialCall; InliningMode inliningMode; FunctionInfo() { clear(); } @@ -98,7 +114,7 @@ struct FunctionInfo { hasLoops = false; hasTryDelegate = false; usedGlobally = false; - isTrivialCall = false; + trivialCall = TrivialCall::NotTrivial; inliningMode = InliningMode::Unknown; } @@ -110,7 +126,7 @@ struct FunctionInfo { hasLoops = other.hasLoops; hasTryDelegate = other.hasTryDelegate; usedGlobally = other.usedGlobally; - isTrivialCall = other.isTrivialCall; + trivialCall = other.trivialCall; inliningMode = other.inliningMode; return *this; } @@ -132,6 +148,11 @@ struct FunctionInfo { size <= options.inlining.oneCallerInlineMaxSize) { return true; } + // If the function calls another one in a way that always shrinks when + // inlined, inline it in all optimization and shrink modes. + if (trivialCall == TrivialCall::Shrinks) { + return true; + } // If it's so big that we have no flexible options that could allow it, // do not inline. if (size > options.inlining.flexibleInlineMaxSize) { @@ -143,22 +164,15 @@ struct FunctionInfo { if (options.shrinkLevel > 0 || options.optimizeLevel < 3) { return false; } - if (hasCalls) { - // This has calls. If it is just a trivial call itself then inline, as we - // will save a call that way - basically we skip a trampoline in the - // middle - but if it is something more complex, leave it alone, as we may - // not help much (and with recursion we may end up with a wasteful - // increase in code size). - // - // Note that inlining trivial calls may increase code size, e.g. if they - // use a parameter more than once (forcing us after inlining to save that - // value to a local, etc.), but here we are optimizing for speed and not - // size, so we risk it. - return isTrivialCall; - } - // This doesn't have calls. Inline if loops do not prevent us (normally, a - // loop suggests a lot of work and so inlining is less useful). - return !hasLoops || options.inlining.allowFunctionsWithLoops; + // The function just calls another function, but the code size may increase + // when inlined. We only inline it fully with `-O3`. + if (trivialCall == TrivialCall::MayNotShrink) { + return true; + } + // Trivial calls are already handled. Inline if + // 1. The function doesn't have calls, and + // 2. The function doesn't have loops, or we allow inlining with loops. + return !hasCalls && (!hasLoops || options.inlining.allowFunctionsWithLoops); } }; @@ -227,10 +241,35 @@ struct FunctionInfoScanner info.size = Measurer::measure(curr->body); if (auto* call = curr->body->dynCast()) { + // If call arguments are function locals read in order, then the code size + // always shrinks when the call is inlined. Note that we don't allow + // skipping function arguments here, as that can create `drop` + // instructions at the call sites, increasing code size. + bool shrinks = true; + Index nextLocalGetIndex = 0; + for (auto* operand : call->operands) { + if (auto* localGet = operand->dynCast()) { + if (localGet->index == nextLocalGetIndex) { + nextLocalGetIndex++; + } else { + shrinks = false; + break; + } + } else { + shrinks = false; + break; + } + } + + if (shrinks) { + info.trivialCall = TrivialCall::Shrinks; + return; + } + if (info.size == call->operands.size() + 1) { // This function body is a call with some trivial (size 1) operands like // LocalGet or Const, so it is a trivial call. - info.isTrivialCall = true; + info.trivialCall = TrivialCall::MayNotShrink; } } } diff --git a/test/lit/passes/inlining-const-args.wat b/test/lit/passes/inlining-const-args.wat new file mode 100644 index 00000000000..73fedcbc853 --- /dev/null +++ b/test/lit/passes/inlining-const-args.wat @@ -0,0 +1,95 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; With `-O3`, we always inline calls to functions that just call other +;; functions with "trivial" arguments. +;; +;; A trivial argument for now is just an instruction with size 1. E.g. +;; `local.get`, constants. +;; +;; In this test we check inlining when the trivial call arguments are +;; constants. In tests inlining-trivial-calls-{1,2,3}.wast we check locals. + +;; RUN: foreach %s %t wasm-opt -all -O3 -S -o - | filecheck %s --check-prefix=O3 +;; RUN: foreach %s %t wasm-opt -all -O2 -S -o - | filecheck %s --check-prefix=O2 +;; RUN: foreach %s %t wasm-opt -all -Os -S -o - | filecheck %s --check-prefix=Os + +(module + ;; O3: (type $0 (func (param i32 i32 i32))) + ;; O2: (type $1 (func)) + + ;; O2: (type $0 (func (param i32 i32 i32))) + ;; Os: (type $1 (func)) + + ;; Os: (type $0 (func (param i32 i32 i32))) + (type $0 (func (param i32 i32 i32))) + + ;; O3: (type $1 (func)) + (type $1 (func)) + + (type $2 (func)) + + ;; O3: (import "env" "foo" (func $imported-foo (type $0) (param i32 i32 i32))) + ;; O2: (import "env" "foo" (func $imported-foo (type $0) (param i32 i32 i32))) + ;; Os: (import "env" "foo" (func $imported-foo (type $0) (param i32 i32 i32))) + (import "env" "foo" (func $imported-foo (type $0) (param i32 i32 i32))) + + ;; O3: (export "main" (func $main)) + ;; O2: (export "main" (func $main)) + ;; Os: (export "main" (func $main)) + (export "main" (func $main)) + + ;; O2: (func $call-foo (type $1) + ;; O2-NEXT: (call $imported-foo + ;; O2-NEXT: (i32.const 1) + ;; O2-NEXT: (i32.const 2) + ;; O2-NEXT: (i32.const 3) + ;; O2-NEXT: ) + ;; O2-NEXT: ) + ;; Os: (func $call-foo (type $1) + ;; Os-NEXT: (call $imported-foo + ;; Os-NEXT: (i32.const 1) + ;; Os-NEXT: (i32.const 2) + ;; Os-NEXT: (i32.const 3) + ;; Os-NEXT: ) + ;; Os-NEXT: ) + (func $call-foo (type $1) + (call $imported-foo + (i32.const 1) + (i32.const 2) + (i32.const 3))) + + ;; O3: (func $main (type $1) + ;; O3-NEXT: (call $imported-foo + ;; O3-NEXT: (i32.const 1) + ;; O3-NEXT: (i32.const 2) + ;; O3-NEXT: (i32.const 3) + ;; O3-NEXT: ) + ;; O3-NEXT: (call $imported-foo + ;; O3-NEXT: (i32.const 1) + ;; O3-NEXT: (i32.const 2) + ;; O3-NEXT: (i32.const 3) + ;; O3-NEXT: ) + ;; O3-NEXT: (call $imported-foo + ;; O3-NEXT: (i32.const 1) + ;; O3-NEXT: (i32.const 2) + ;; O3-NEXT: (i32.const 3) + ;; O3-NEXT: ) + ;; O3-NEXT: ) + ;; O2: (func $main (type $1) + ;; O2-NEXT: (call $call-foo) + ;; O2-NEXT: (call $call-foo) + ;; O2-NEXT: (call $call-foo) + ;; O2-NEXT: ) + ;; Os: (func $main (type $1) + ;; Os-NEXT: (call $call-foo) + ;; Os-NEXT: (call $call-foo) + ;; Os-NEXT: (call $call-foo) + ;; Os-NEXT: ) + (func $main (type $2) + ;; All calls below should be inlined in -O3, but not in -O2 or -Os. We call + ;; it multiple times to make sure it won't be inlined because there's only + ;; one call, instead it will be inlined based on optimization settings and + ;; whether the call is trivial. + (call $call-foo) + (call $call-foo) + (call $call-foo))) diff --git a/test/lit/passes/inlining-trivial-calls-1.wast b/test/lit/passes/inlining-trivial-calls-1.wast new file mode 100644 index 00000000000..c4962af20fc --- /dev/null +++ b/test/lit/passes/inlining-trivial-calls-1.wast @@ -0,0 +1,164 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Check that "trivial calls" are always inlined, even when optimizing for +;; size. +;; +;; A trivial call is a function that calls another, using its locals in +;; the order, without skipping any locals. +;; +;; These functions can always be inlined because they can't cause binary size +;; increase at the call sites. + +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=0 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=1 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=2 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=3 -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func (param i32 i32 i32 i32 i32 i32))) + (type $0 (func (param i32 i32 i32 i32 i32 i32))) + ;; CHECK: (type $1 (func)) + (type $1 (func)) + ;; CHECK: (import "env" "foo" (func $imported-foo (type $0) (param i32 i32 i32 i32 i32 i32))) + (import "env" "foo" (func $imported-foo (type $0) (param i32 i32 i32 i32 i32 i32))) + (func $call-foo (type $0) (param $p1 i32) (param $p2 i32) (param $p3 i32) (param $p4 i32) (param $p5 i32) (param $p6 i32) + (call $imported-foo + (local.get $p1) + (local.get $p2) + (local.get $p3) + (local.get $p4) + (local.get $p5) + (local.get $p6) + ) + ) + ;; CHECK: (func $main (type $1) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 i32) + ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 i32) + ;; CHECK-NEXT: (local $14 i32) + ;; CHECK-NEXT: (local $15 i32) + ;; CHECK-NEXT: (local $16 i32) + ;; CHECK-NEXT: (local $17 i32) + ;; CHECK-NEXT: (block $__inlined_func$call-foo + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $imported-foo + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$call-foo$1 + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (i32.const 9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $imported-foo + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$call-foo$2 + ;; CHECK-NEXT: (local.set $12 + ;; CHECK-NEXT: (i32.const 13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (i32.const 14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $14 + ;; CHECK-NEXT: (i32.const 15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $15 + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $16 + ;; CHECK-NEXT: (i32.const 17) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $17 + ;; CHECK-NEXT: (i32.const 18) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $imported-foo + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: (local.get $15) + ;; CHECK-NEXT: (local.get $16) + ;; CHECK-NEXT: (local.get $17) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $main (type $1) + (call $call-foo + (i32.const 1) + (i32.const 2) + (i32.const 3) + (i32.const 4) + (i32.const 5) + (i32.const 6) + ) + (call $call-foo + (i32.const 7) + (i32.const 8) + (i32.const 9) + (i32.const 10) + (i32.const 11) + (i32.const 12) + ) + (call $call-foo + (i32.const 13) + (i32.const 14) + (i32.const 15) + (i32.const 16) + (i32.const 17) + (i32.const 18) + ) + ) +) diff --git a/test/lit/passes/inlining-trivial-calls-2.wast b/test/lit/passes/inlining-trivial-calls-2.wast new file mode 100644 index 00000000000..dfd908c5557 --- /dev/null +++ b/test/lit/passes/inlining-trivial-calls-2.wast @@ -0,0 +1,61 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Same as inlining-trivial-calls-1, but arguments to the "trivial call" are +;; different than the caller's arguments. +;; +;; This can result in adding locals at the call sites and increase binary sizes. +;; So we don't inline these calls when optimizing for binary sizes. + +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=0 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=1 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=2 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=3 -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func (param i32 i32))) + (type $0 (func (param i32 i32))) + ;; CHECK: (type $1 (func)) + (type $1 (func)) + ;; CHECK: (import "env" "foo" (func $imported-foo (type $0) (param i32 i32))) + (import "env" "foo" (func $imported-foo (type $0) (param i32 i32))) + ;; CHECK: (func $call-foo (type $0) (param $p1 i32) (param $p2 i32) + ;; CHECK-NEXT: (call $imported-foo + ;; CHECK-NEXT: (local.get $p2) + ;; CHECK-NEXT: (local.get $p1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-foo (type $0) (param $p1 i32) (param $p2 i32) + (call $imported-foo + (local.get $p2) + (local.get $p1) + ) + ) + ;; CHECK: (func $main (type $1) + ;; CHECK-NEXT: (call $call-foo + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $call-foo + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $call-foo + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $main (type $1) + (call $call-foo + (i32.const 1) + (i32.const 2) + ) + (call $call-foo + (i32.const 3) + (i32.const 4) + ) + (call $call-foo + (i32.const 5) + (i32.const 6) + ) + ) +) diff --git a/test/lit/passes/inlining-trivial-calls-3.wast b/test/lit/passes/inlining-trivial-calls-3.wast new file mode 100644 index 00000000000..8628cf7c547 --- /dev/null +++ b/test/lit/passes/inlining-trivial-calls-3.wast @@ -0,0 +1,70 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Same as inlining-trivial-calls-2, but the "trivial call" omits an +;; argument from its own arguments. +;; +;; This can result in `drop` instructions at the call sites and increase binary +;; sizes. So we don't inline these calls when optimizing for binary sizes. + +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=0 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=1 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=2 -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --inlining --shrink-level=3 -S -o - | filecheck %s + +(module + ;; CHECK: (type $1 (func (param i32 i32))) + + ;; CHECK: (type $0 (func (param i32 i32 i32))) + (type $0 (func (param i32 i32 i32))) + (type $1 (func (param i32 i32))) + ;; CHECK: (type $2 (func)) + (type $2 (func)) + ;; CHECK: (import "env" "foo" (func $imported-foo (type $1) (param i32 i32))) + (import "env" "foo" (func $imported-foo (type $1) (param i32 i32))) + ;; CHECK: (func $call-foo (type $0) (param $p1 i32) (param $p2 i32) (param $p3 i32) + ;; CHECK-NEXT: (call $imported-foo + ;; CHECK-NEXT: (local.get $p1) + ;; CHECK-NEXT: (local.get $p3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-foo (type $0) (param $p1 i32) (param $p2 i32) (param $p3 i32) + (call $imported-foo + (local.get $p1) + (local.get $p3) + ) + ) + ;; CHECK: (func $main (type $2) + ;; CHECK-NEXT: (call $call-foo + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $call-foo + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $call-foo + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: (i32.const 9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $main (type $2) + (call $call-foo + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + (call $call-foo + (i32.const 4) + (i32.const 5) + (i32.const 6) + ) + (call $call-foo + (i32.const 7) + (i32.const 8) + (i32.const 9) + ) + ) +) From 5082d072e91f10eb68bb4dd46ad036d6a8b77498 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Mon, 30 Jun 2025 15:17:18 -0700 Subject: [PATCH 576/622] Update mimalloc to v2.2.4 (#7441) This brings in general updates, as well as a revision (4aae566191b9443d53995245b637ce28d617710a) needed to build on Windows. --- third_party/mimalloc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/mimalloc b/third_party/mimalloc index 891f9f4cf6a..fbd8b99c2b8 160000 --- a/third_party/mimalloc +++ b/third_party/mimalloc @@ -1 +1 @@ -Subproject commit 891f9f4cf6afbd213d7260b880795af523646c11 +Subproject commit fbd8b99c2b828428947d70fdc046bb55609be93e From 1f56a1578169726b8ed1e8fad7bd1e2398d3283a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 1 Jul 2025 02:15:24 +0200 Subject: [PATCH 577/622] [Custom Descriptors] Preserve trap in Heap2Local (#7692) Insert a ref.as_non_null when optimizing struct.new with a nullable descriptor to preserve the trap if the descriptor is null. --- src/passes/Heap2Local.cpp | 9 +++++++-- test/lit/passes/heap2local-desc.wast | 20 +++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 1e6744ea8fe..af94877080a 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -752,8 +752,13 @@ struct Struct2Local : PostWalker { } } if (curr->desc) { - contents.push_back( - builder.makeLocalSet(tempIndexes[numTemps - 1], curr->desc)); + // Preserve the trapping on null descriptors by inserting a + // ref.as_non_null. + Expression* desc = curr->desc; + if (curr->desc->type.isNullable()) { + desc = builder.makeRefAs(RefAsNonNull, desc); + } + contents.push_back(builder.makeLocalSet(tempIndexes[numTemps - 1], desc)); } // Store the values into the locals representing the fields. diff --git a/test/lit/passes/heap2local-desc.wast b/test/lit/passes/heap2local-desc.wast index 3fb72c65142..caad93b0680 100644 --- a/test/lit/passes/heap2local-desc.wast +++ b/test/lit/passes/heap2local-desc.wast @@ -56,7 +56,9 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $2) @@ -84,7 +86,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) @@ -240,7 +244,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) @@ -328,7 +334,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) @@ -423,7 +431,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $3) From ec1d0b68b317538cc43ab7c769704dfbebcba2c3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 1 Jul 2025 22:41:15 +0200 Subject: [PATCH 578/622] [Custom Descriptors] Keep trapping initializers (#7693) Update RemoveUnusedModuleElements to preserve traps in global and element segment initializers due to null or possibly-null descriptors passed to struct.new. --- src/passes/RemoveUnusedModuleElements.cpp | 46 +++++++ ...used-module-elements-refs-descriptors.wast | 112 ++++++++++++++++-- 2 files changed, 148 insertions(+), 10 deletions(-) diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index a84a0b0b539..27162278456 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -36,6 +36,7 @@ // #include +#include #include "ir/element-utils.h" #include "ir/find_all.h" @@ -45,8 +46,11 @@ #include "ir/subtypes.h" #include "ir/utils.h" #include "pass.h" +#include "support/insert_ordered.h" #include "support/stdckdint.h" +#include "support/utilities.h" #include "wasm-builder.h" +#include "wasm-traversal.h" #include "wasm.h" namespace wasm { @@ -712,6 +716,27 @@ struct RemoveUnusedModuleElements : public Pass { roots.emplace_back(ModuleElementKind::Function, name); }); + // Just as out-of-bound segments may cause observable traps at instantiation + // time, so can struct.new instructions with null descriptors cause traps in + // global or element segment initializers. + if (!getPassOptions().trapsNeverHappen) { + for (auto& segment : module->elementSegments) { + for (auto* init : segment->data) { + if (isMaybeTrappingInit(*module, init)) { + roots.emplace_back(ModuleElementKind::ElementSegment, + segment->name); + break; + } + } + } + for (auto& global : module->globals) { + if (auto* init = global->init; + init && isMaybeTrappingInit(*module, init)) { + roots.emplace_back(ModuleElementKind::Global, global->name); + } + } + } + // Analyze the module. auto& options = getPassOptions(); Analyzer analyzer(module, options, roots); @@ -832,6 +857,27 @@ struct RemoveUnusedModuleElements : public Pass { } } } + + bool isMaybeTrappingInit(Module& wasm, Expression* root) { + // Traverse the expression, looking for nullable descriptors passed to + // struct.new. The descriptors might be null, which is the only situations + // (beyond exceeded implementation limits, which we don't model) that can + // lead a constant expression to trap. We depend on other optimizations to + // make the descriptors non-nullable if we can determine that they are not + // null. + struct NullDescFinder : PostWalker { + bool mayTrap = false; + void visitStructNew(StructNew* curr) { + if (curr->desc && curr->desc->type.isNullable()) { + mayTrap = true; + } + } + }; + + NullDescFinder finder; + finder.walk(root); + return finder.mayTrap; + } }; Pass* createRemoveUnusedModuleElementsPass() { diff --git a/test/lit/passes/remove-unused-module-elements-refs-descriptors.wast b/test/lit/passes/remove-unused-module-elements-refs-descriptors.wast index b894f4ec2f4..c56d7e3f28c 100644 --- a/test/lit/passes/remove-unused-module-elements-refs-descriptors.wast +++ b/test/lit/passes/remove-unused-module-elements-refs-descriptors.wast @@ -6,32 +6,124 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $B (sub (descriptor $A (struct)))) - (type $B (sub (descriptor $A (struct)))) - ;; CHECK: (type $A (sub (describes $B (struct)))) - (type $A (sub (describes $B (struct)))) + ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) + (type $struct (descriptor $desc (struct))) + ;; CHECK: (type $desc (describes $struct (struct))) + (type $desc (describes $struct (struct))) ) ;; CHECK: (type $2 (func)) - ;; CHECK: (global $global (ref (exact $A)) (struct.new_default $A)) - (global $global (ref (exact $A)) (struct.new $A)) + ;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc)) + (global $desc (ref (exact $desc)) (struct.new $desc)) ;; CHECK: (export "export" (func $export)) ;; CHECK: (func $export (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $B - ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (global.get $desc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $export (export "export") (drop - (struct.new $B - (global.get $global) + (struct.new $struct + (global.get $desc) ) ) ) ) +;; We cannot optimize out globals whose initializers might trap due to a null +;; descriptor (or conservatively even just a nullable descriptor). +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) + (type $struct (descriptor $desc (struct))) + ;; CHECK: (type $desc (describes $struct (struct))) + (type $desc (describes $struct (struct))) + ;; CHECK: (type $list (struct (field (ref $struct)) (field (ref null $list)))) + (type $list (struct (field (ref $struct)) (field (ref null $list)))) + ) + + ;; CHECK: (global $null nullref (ref.null none)) + (global $null nullref (ref.null none)) + ;; CHECK: (global $nullable-desc (ref null (exact $desc)) (struct.new_default $desc)) + (global $nullable-desc (ref null (exact $desc)) (struct.new $desc)) + ;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc)) + (global $desc (ref (exact $desc)) (struct.new $desc)) + + ;; Trapping globals must be kept, but non-trapping globals can be removed. + ;; CHECK: (global $trap (ref $struct) (struct.new_default $struct + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $trap (ref $struct) (struct.new $struct (ref.null none))) + (global $no-trap (ref $struct) (struct.new $struct (struct.new $desc))) + + ;; CHECK: (global $trap-get-null (ref $struct) (struct.new_default $struct + ;; CHECK-NEXT: (global.get $null) + ;; CHECK-NEXT: )) + (global $trap-get-null (ref $struct) (struct.new $struct (global.get $null))) + ;; CHECK: (global $trap-get-nullable (ref $struct) (struct.new_default $struct + ;; CHECK-NEXT: (global.get $nullable-desc) + ;; CHECK-NEXT: )) + (global $trap-get-nullable (ref $struct) (struct.new $struct (global.get $nullable-desc))) + (global $no-trap-get (ref $struct) (struct.new $struct (global.get $desc))) + + ;; CHECK: (global $trap-nested (ref $list) (struct.new $list + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $list + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $list + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: )) + (global $trap-nested (ref $list) + (struct.new $list + (struct.new $struct (struct.new $desc)) + (struct.new $list + (struct.new $struct (global.get $desc)) + (struct.new $list + (struct.new $struct (ref.null none)) + (ref.null none) + ) + ) + ) + ) + + (global $no-trap-nested (ref $list) + (struct.new $list + (struct.new $struct (struct.new $desc)) + (struct.new $list + (struct.new $struct (global.get $desc)) + (ref.null none) + ) + ) + ) + + ;; CHECK: (elem $trap anyref (item (struct.new_default $struct + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ))) + (elem $trap anyref (item (struct.new $struct (ref.null none)))) + (elem $no-trap anyref (item (struct.new $struct (struct.new $desc)))) + ;; CHECK: (elem $trap-get-null anyref (item (struct.new_default $struct + ;; CHECK-NEXT: (global.get $null) + ;; CHECK-NEXT: ))) + (elem $trap-get-null anyref (item (struct.new $struct (global.get $null)))) + ;; CHECK: (elem $trap-get-nullable anyref (item (struct.new_default $struct + ;; CHECK-NEXT: (global.get $nullable-desc) + ;; CHECK-NEXT: ))) + (elem $trap-get-nullable anyref (item (struct.new $struct (global.get $nullable-desc)))) + (elem $no-trap-get anyref (item (struct.new $struct (global.get $desc)))) +) + From 22f87dc1cb9e8a520fa7db78740b9cde912542d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Wed, 2 Jul 2025 20:59:53 +0200 Subject: [PATCH 579/622] [Strings] Implement string.test (#7684) --- scripts/gen-s-parser.py | 1 + src/gen-s-parser.inc | 6 ++ src/interpreter/interpreter.cpp | 1 + src/ir/ReFinalize.cpp | 1 + src/ir/child-typer.h | 5 + src/ir/cost.h | 4 + src/ir/effects.h | 1 + src/ir/possible-contents.cpp | 8 +- src/ir/subtype-exprs.h | 1 + src/parser/contexts.h | 8 ++ src/parser/parsers.h | 9 ++ src/passes/Print.cpp | 1 + src/passes/StringLifting.cpp | 10 ++ src/passes/StringLowering.cpp | 9 ++ src/passes/TypeGeneralizing.cpp | 1 + src/wasm-binary.h | 1 + src/wasm-builder.h | 6 ++ src/wasm-delegations-fields.def | 4 + src/wasm-delegations.def | 1 + src/wasm-interpreter.h | 8 ++ src/wasm-ir-builder.h | 1 + src/wasm.h | 11 +++ src/wasm/wasm-binary.cpp | 2 + src/wasm/wasm-ir-builder.cpp | 7 ++ src/wasm/wasm-stack.cpp | 4 + src/wasm/wasm-validator.cpp | 23 +++-- src/wasm/wasm.cpp | 8 ++ src/wasm2js.h | 4 + test/binaryen.js/kitchen-sink.js.txt | 4 +- test/lit/passes/string-gathering.wast | 92 ++++++++++-------- test/lit/passes/string-lifting-section.wast | 22 +++-- test/lit/passes/string-lifting.wast | 33 +++++-- ...string-lowering-imports-custom-module.wast | 26 ++--- .../passes/string-lowering-instructions.wast | 97 +++++++++++-------- test/lit/passes/string-lowering_types.wast | 62 ++++++------ test/lit/strings.wast | 54 ++++++++--- 36 files changed, 364 insertions(+), 172 deletions(-) diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index df5121428e8..7370b3c69bf 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -678,6 +678,7 @@ ("string.concat", "makeStringConcat()"), ("string.eq", "makeStringEq(StringEqEqual)"), ("string.compare", "makeStringEq(StringEqCompare)"), + ("string.test", "makeStringTest()"), ("stringview_wtf16.get_codeunit", "makeStringWTF16Get()"), ("stringview_wtf16.slice", "makeStringSliceWTF()"), # Ignored in input diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 2dccad00271..de588f76118 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -5139,6 +5139,12 @@ switch (buf[0]) { default: goto parse_error; } } + case 't': + if (op == "string.test"sv) { + CHECK_ERR(makeStringTest(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; default: goto parse_error; } } diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index e9df1bab4d3..0e97cf4f39e 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -273,6 +273,7 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitStringEncode(StringEncode* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStringConcat(StringConcat* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStringEq(StringEq* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStringTest(StringTest* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); } Flow visitContNew(ContNew* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 05d45233e35..43dd53ab9d4 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -182,6 +182,7 @@ void ReFinalize::visitStringMeasure(StringMeasure* curr) { curr->finalize(); } void ReFinalize::visitStringEncode(StringEncode* curr) { curr->finalize(); } void ReFinalize::visitStringConcat(StringConcat* curr) { curr->finalize(); } void ReFinalize::visitStringEq(StringEq* curr) { curr->finalize(); } +void ReFinalize::visitStringTest(StringTest* curr) { curr->finalize(); } void ReFinalize::visitStringWTF16Get(StringWTF16Get* curr) { curr->finalize(); } void ReFinalize::visitStringSliceWTF(StringSliceWTF* curr) { curr->finalize(); } void ReFinalize::visitContNew(ContNew* curr) { curr->finalize(); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 459fb2b3b42..c3b34e7a256 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1226,6 +1226,11 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->right, stringref); } + void visitStringTest(StringTest* curr) { + auto stringref = Type(HeapType::ext, Nullable); + note(&curr->ref, stringref); + } + void visitStringWTF16Get(StringWTF16Get* curr) { note(&curr->ref, Type(HeapType::string, Nullable)); note(&curr->pos, Type::i32); diff --git a/src/ir/cost.h b/src/ir/cost.h index 69aff667e05..f68725d4bc8 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -788,6 +788,10 @@ struct CostAnalyzer : public OverriddenVisitor { // "3" is chosen since strings might or might not be interned in the engine. return 3 + visit(curr->left) + visit(curr->right); } + CostType visitStringTest(StringTest* curr) { + // "3" copied from `visitStringEq`. + return 3 + visit(curr->ref); + } CostType visitStringWTF16Get(StringWTF16Get* curr) { return 1 + visit(curr->ref) + visit(curr->pos); } diff --git a/src/ir/effects.h b/src/ir/effects.h index a02a9627071..9f08a3b922b 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1081,6 +1081,7 @@ class EffectAnalyzer { } } } + void visitStringTest(StringTest* curr) {} void visitStringWTF16Get(StringWTF16Get* curr) { // traps when ref is null. parent.implicitTrap = true; diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index d36d57c6172..86323801069 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -662,9 +662,7 @@ struct InfoCollector info.calledFromOutside.insert(curr->func); } } - void visitRefEq(RefEq* curr) { - addRoot(curr); - } + void visitRefEq(RefEq* curr) { addRoot(curr); } void visitTableGet(TableGet* curr) { // TODO: be more precise addRoot(curr); @@ -1143,6 +1141,10 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } + void visitStringTest(StringTest* curr) { + // TODO: optimize when possible + addRoot(curr); + } void visitStringWTF16Get(StringWTF16Get* curr) { // TODO: optimize when possible addRoot(curr); diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 0e26ca96ee8..78d3ef5232c 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -429,6 +429,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStringEncode(StringEncode* curr) {} void visitStringConcat(StringConcat* curr) {} void visitStringEq(StringEq* curr) {} + void visitStringTest(StringTest* curr) {} void visitStringWTF16Get(StringWTF16Get* curr) {} void visitStringSliceWTF(StringSliceWTF* curr) {} diff --git a/src/parser/contexts.h b/src/parser/contexts.h index f27a7128738..82f4d31ce63 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -890,6 +890,9 @@ struct NullInstrParserCtx { Result<> makeStringEq(Index, const std::vector&, StringEqOp) { return Ok{}; } + Result<> makeStringTest(Index, const std::vector&) { + return Ok{}; + } Result<> makeStringWTF8Advance(Index, const std::vector&) { return Ok{}; } @@ -2823,6 +2826,11 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeStringEq(op)); } + Result<> makeStringTest(Index pos, + const std::vector& annotations) { + return withLoc(pos, irBuilder.makeStringTest()); + } + Result<> makeStringWTF16Get(Index pos, const std::vector& annotations) { return withLoc(pos, irBuilder.makeStringWTF16Get()); diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 1d1974a9d27..7b9c28cef68 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -319,6 +319,8 @@ Result<> makeStringConcat(Ctx&, Index, const std::vector&); template Result<> makeStringEq(Ctx&, Index, const std::vector&, StringEqOp); template +Result<> makeStringTest(Ctx&, Index, const std::vector&); +template Result<> makeStringWTF16Get(Ctx&, Index, const std::vector&); template Result<> makeStringSliceWTF(Ctx&, Index, const std::vector&); @@ -2627,6 +2629,13 @@ Result<> makeStringEq(Ctx& ctx, return ctx.makeStringEq(pos, annotations, op); } +template +Result<> makeStringTest(Ctx& ctx, + Index pos, + const std::vector& annotations) { + return ctx.makeStringTest(pos, annotations); +} + template Result<> makeStringWTF16Get(Ctx& ctx, Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index f60642a23c8..45233f7728e 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2547,6 +2547,7 @@ struct PrintExpressionContents WASM_UNREACHABLE("invalid string.eq*"); } } + void visitStringTest(StringTest* curr) { printMedium(o, "string.test"); } void visitStringWTF16Get(StringWTF16Get* curr) { printMedium(o, "stringview_wtf16.get_codeunit"); } diff --git a/src/passes/StringLifting.cpp b/src/passes/StringLifting.cpp index 07b7fdd7bcd..09260e4a541 100644 --- a/src/passes/StringLifting.cpp +++ b/src/passes/StringLifting.cpp @@ -44,6 +44,7 @@ struct StringLifting : public Pass { Name fromCodePointImport; Name concatImport; Name equalsImport; + Name testImport; Name compareImport; Name lengthImport; Name charCodeAtImport; @@ -163,6 +164,12 @@ struct StringLifting : public Pass { } equalsImport = func->name; found = true; + } else if (func->base == "test") { + if (sig != Signature({externref}, i32)) { + Fatal() << "StringLifting: bad signature for test: " << sig; + } + testImport = func->name; + found = true; } else if (func->base == "compare") { if (sig != Signature({externref, externref}, i32)) { Fatal() << "StringLifting: bad signature for compare: " << sig; @@ -247,6 +254,9 @@ struct StringLifting : public Pass { .makeStringEq(StringEqEqual, curr->operands[0], curr->operands[1])); + } else if (curr->target == parent.testImport) { + replaceCurrent( + Builder(*getModule()).makeStringTest(curr->operands[0])); } else if (curr->target == parent.compareImport) { replaceCurrent(Builder(*getModule()) .makeStringEq(StringEqCompare, diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 9a74bebbeb8..5ee6c40cda8 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -362,6 +362,7 @@ struct StringLowering : public StringGathering { Name fromCodePointImport; Name concatImport; Name equalsImport; + Name testImport; Name compareImport; Name lengthImport; Name charCodeAtImport; @@ -397,6 +398,8 @@ struct StringLowering : public StringGathering { Type::i32); // string.equals: string, string -> i32 equalsImport = addImport(module, "equals", {nullExt, nullExt}, Type::i32); + // string.test: externref -> i32 + testImport = addImport(module, "test", {nullExt}, Type::i32); // string.compare: string, string -> i32 compareImport = addImport(module, "compare", {nullExt, nullExt}, Type::i32); // string.length: string -> i32 @@ -473,6 +476,12 @@ struct StringLowering : public StringGathering { } } + void visitStringTest(StringTest* curr) { + Builder builder(*getModule()); + replaceCurrent( + builder.makeCall(lowering.testImport, {curr->ref}, Type::i32)); + } + void visitStringMeasure(StringMeasure* curr) { Builder builder(*getModule()); replaceCurrent( diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 5ede9fa019b..89b9796416a 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -880,6 +880,7 @@ struct TransferFn : OverriddenVisitor { void visitStringEncode(StringEncode* curr) { WASM_UNREACHABLE("TODO"); } void visitStringConcat(StringConcat* curr) { WASM_UNREACHABLE("TODO"); } void visitStringEq(StringEq* curr) { WASM_UNREACHABLE("TODO"); } + void visitStringTest(StringTest* curr) { WASM_UNREACHABLE("TODO"); } void visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("TODO"); } void visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 857ebe0d540..21b53f34329 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1222,6 +1222,7 @@ enum ASTNodes { StringConcat = 0x88, StringEq = 0x89, StringIsUSV = 0x8a, + StringTest = 0x8b, StringAsWTF16 = 0x98, StringViewWTF16GetCodePoint = 0x9a, StringViewWTF16Slice = 0x9c, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index e31d905548e..00612a4020b 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1231,6 +1231,12 @@ class Builder { ret->finalize(); return ret; } + StringTest* makeStringTest(Expression* ref) { + auto* ret = wasm.allocator.alloc(); + ret->ref = ref; + ret->finalize(); + return ret; + } StringWTF16Get* makeStringWTF16Get(Expression* ref, Expression* pos) { auto* ret = wasm.allocator.alloc(); ret->ref = ref; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index b7e9d935580..28f008d5593 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -830,6 +830,10 @@ DELEGATE_FIELD_CHILD(StringEq, right) DELEGATE_FIELD_CHILD(StringEq, left) DELEGATE_FIELD_CASE_END(StringEq) +DELEGATE_FIELD_CASE_START(StringTest) +DELEGATE_FIELD_CHILD(StringTest, ref) +DELEGATE_FIELD_CASE_END(StringTest) + DELEGATE_FIELD_CASE_START(StringWTF16Get) DELEGATE_FIELD_CHILD(StringWTF16Get, pos) DELEGATE_FIELD_CHILD(StringWTF16Get, ref) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index 568b16883c5..ce50255b896 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -105,6 +105,7 @@ DELEGATE(StringMeasure); DELEGATE(StringEncode); DELEGATE(StringConcat); DELEGATE(StringEq); +DELEGATE(StringTest); DELEGATE(StringWTF16Get); DELEGATE(StringSliceWTF); DELEGATE(ContNew); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index b73a69b7905..2325fba340f 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2448,6 +2448,14 @@ class ExpressionRunner : public OverriddenVisitor { } return Literal(result); } + Flow visitStringTest(StringTest* curr) { + Flow flow = visit(curr->ref); + if (flow.breaking()) { + return flow; + } + auto value = flow.getSingleValue(); + return Literal((uint32_t)value.isString()); + } Flow visitStringWTF16Get(StringWTF16Get* curr) { NOTE_ENTER("StringWTF16Get"); Flow ref = visit(curr->ref); diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 540d75983c5..b1b2ff9226c 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -257,6 +257,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeStringEncode(StringEncodeOp op); Result<> makeStringConcat(); Result<> makeStringEq(StringEqOp op); + Result<> makeStringTest(); Result<> makeStringWTF8Advance(); Result<> makeStringWTF16Get(); Result<> makeStringIterNext(); diff --git a/src/wasm.h b/src/wasm.h index 55e7fa458da..e1bd262400b 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -748,6 +748,7 @@ class Expression { StringEncodeId, StringConcatId, StringEqId, + StringTestId, StringWTF16GetId, StringSliceWTFId, ContNewId, @@ -2006,6 +2007,16 @@ class StringEq : public SpecificExpression { void finalize(); }; +class StringTest : public SpecificExpression { +public: + StringTest() = default; + StringTest(MixedArena& allocator) {} + + Expression* ref; + + void finalize(); +}; + class StringWTF16Get : public SpecificExpression { public: StringWTF16Get() = default; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 08c6e19e298..39b5e7fc77f 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4663,6 +4663,8 @@ Result<> WasmBinaryReader::readInst() { return builder.makeStringConcat(); case BinaryConsts::StringEq: return builder.makeStringEq(StringEqEqual); + case BinaryConsts::StringTest: + return builder.makeStringTest(); case BinaryConsts::StringCompare: return builder.makeStringEq(StringEqCompare); case BinaryConsts::StringViewWTF16GetCodePoint: diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index b56e32812a7..8912610932a 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2505,6 +2505,13 @@ Result<> IRBuilder::makeStringEq(StringEqOp op) { return Ok{}; } +Result<> IRBuilder::makeStringTest() { + StringTest curr; + CHECK_ERR(visitStringTest(&curr)); + push(builder.makeStringTest(curr.ref)); + return Ok{}; +} + Result<> IRBuilder::makeStringWTF16Get() { StringWTF16Get curr; CHECK_ERR(visitStringWTF16Get(&curr)); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 406a75b600f..0991fc77eff 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2697,6 +2697,10 @@ void BinaryInstWriter::visitStringEq(StringEq* curr) { } } +void BinaryInstWriter::visitStringTest(StringTest* curr) { + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringTest); +} + void BinaryInstWriter::visitStringWTF16Get(StringWTF16Get* curr) { // We need to convert the ref operand to a stringview, but it is under the pos // operand. Put the i32 in a scratch local, emit the conversion, then get the diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 06ce9c9d330..a570b990271 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -524,6 +524,7 @@ struct FunctionValidator : public WalkerPass> { void visitStringEncode(StringEncode* curr); void visitStringConcat(StringConcat* curr); void visitStringEq(StringEq* curr); + void visitStringTest(StringTest* curr); void visitStringWTF16Get(StringWTF16Get* curr); void visitStringSliceWTF(StringSliceWTF* curr); void visitContNew(ContNew* curr); @@ -3830,7 +3831,7 @@ void FunctionValidator::visitArrayCmpxchg(ArrayCmpxchg* curr) { void FunctionValidator::visitStringNew(StringNew* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, - "string operations require reference-types [--enable-strings]"); + "string operations require strings [--enable-strings]"); switch (curr->op) { case StringNewLossyUTF8Array: @@ -3875,43 +3876,49 @@ void FunctionValidator::visitStringNew(StringNew* curr) { void FunctionValidator::visitStringConst(StringConst* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, - "string operations require reference-types [--enable-strings]"); + "string operations require strings [--enable-strings]"); } void FunctionValidator::visitStringMeasure(StringMeasure* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, - "string operations require reference-types [--enable-strings]"); + "string operations require strings [--enable-strings]"); } void FunctionValidator::visitStringEncode(StringEncode* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, - "string operations require reference-types [--enable-strings]"); + "string operations require strings [--enable-strings]"); } void FunctionValidator::visitStringConcat(StringConcat* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, - "string operations require reference-types [--enable-strings]"); + "string operations require strings [--enable-strings]"); } void FunctionValidator::visitStringEq(StringEq* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, - "string operations require reference-types [--enable-strings]"); + "string operations require strings [--enable-strings]"); +} + +void FunctionValidator::visitStringTest(StringTest* curr) { + shouldBeTrue(!getModule() || getModule()->features.hasStrings(), + curr, + "string operations require strings [--enable-strings]"); } void FunctionValidator::visitStringWTF16Get(StringWTF16Get* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, - "string operations require reference-types [--enable-strings]"); + "string operations require strings [--enable-strings]"); } void FunctionValidator::visitStringSliceWTF(StringSliceWTF* curr) { shouldBeTrue(!getModule() || getModule()->features.hasStrings(), curr, - "string operations require reference-types [--enable-strings]"); + "string operations require strings [--enable-strings]"); } void FunctionValidator::visitContNew(ContNew* curr) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index dac47857f7d..ee28c14f97d 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1472,6 +1472,14 @@ void StringEq::finalize() { } } +void StringTest::finalize() { + if (ref->type == Type::unreachable) { + type = Type::unreachable; + } else { + type = Type::i32; + } +} + void StringWTF16Get::finalize() { if (ref->type == Type::unreachable || pos->type == Type::unreachable) { type = Type::unreachable; diff --git a/src/wasm2js.h b/src/wasm2js.h index ddc839e6f60..abfcdd99a46 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2412,6 +2412,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitStringTest(StringTest* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitStringWTF16Get(StringWTF16Get* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 03714168237..47977a46e03 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -109,8 +109,8 @@ StringMeasure: 88 StringEncode: 89 StringConcat: 90 StringEq: 91 -StringWTF16Get: 92 -StringSliceWTF: 93 +StringWTF16Get: 93 +StringSliceWTF: 94 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/lit/passes/string-gathering.wast b/test/lit/passes/string-gathering.wast index cf29c787f6d..c8724ab27d2 100644 --- a/test/lit/passes/string-gathering.wast +++ b/test/lit/passes/string-gathering.wast @@ -31,15 +31,15 @@ ;; LOWER: (type $2 (func (param externref externref) (result i32))) - ;; LOWER: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (type $3 (func (param externref) (result i32))) - ;; LOWER: (type $4 (func (param i32) (result (ref extern)))) + ;; LOWER: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (type $5 (func (param externref externref) (result (ref extern)))) + ;; LOWER: (type $5 (func (param i32) (result (ref extern)))) - ;; LOWER: (type $6 (func (param externref (ref null $0) i32) (result i32))) + ;; LOWER: (type $6 (func (param externref externref) (result (ref extern)))) - ;; LOWER: (type $7 (func (param externref) (result i32))) + ;; LOWER: (type $7 (func (param externref (ref null $0) i32) (result i32))) ;; LOWER: (type $8 (func (param externref i32) (result i32))) @@ -51,19 +51,21 @@ ;; LOWER: (import "string.const" "2" (global $"string.const_\"other\"" (ref extern))) - ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) @@ -151,15 +153,15 @@ ;; LOWER: (type $1 (func (param externref externref) (result i32))) - ;; LOWER: (type $2 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (type $2 (func (param externref) (result i32))) - ;; LOWER: (type $3 (func (param i32) (result (ref extern)))) + ;; LOWER: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (type $4 (func (param externref externref) (result (ref extern)))) + ;; LOWER: (type $4 (func (param i32) (result (ref extern)))) - ;; LOWER: (type $5 (func (param externref (ref null $0) i32) (result i32))) + ;; LOWER: (type $5 (func (param externref externref) (result (ref extern)))) - ;; LOWER: (type $6 (func (param externref) (result i32))) + ;; LOWER: (type $6 (func (param externref (ref null $0) i32) (result i32))) ;; LOWER: (type $7 (func (param externref i32) (result i32))) @@ -178,19 +180,21 @@ ;; CHECK: (global $global4 (ref string) (string.const "bar")) ;; CHECK: (global $global2 (ref string) (global.get $global1)) - ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $2) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $3) (param i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $4) (param externref externref) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $5) (param externref (ref null $0) i32) (result i32))) + ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "length" (func $length (type $6) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $7) (param externref i32) (result i32))) @@ -225,17 +229,17 @@ ;; LOWER: (type $1 (func (param externref externref) (result i32))) - ;; LOWER: (type $2 (func)) + ;; LOWER: (type $2 (func (param externref) (result i32))) - ;; LOWER: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (type $3 (func)) - ;; LOWER: (type $4 (func (param i32) (result (ref extern)))) + ;; LOWER: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (type $5 (func (param externref externref) (result (ref extern)))) + ;; LOWER: (type $5 (func (param i32) (result (ref extern)))) - ;; LOWER: (type $6 (func (param externref (ref null $0) i32) (result i32))) + ;; LOWER: (type $6 (func (param externref externref) (result (ref extern)))) - ;; LOWER: (type $7 (func (param externref) (result i32))) + ;; LOWER: (type $7 (func (param externref (ref null $0) i32) (result i32))) ;; LOWER: (type $8 (func (param externref i32) (result i32))) @@ -243,19 +247,21 @@ ;; LOWER: (import "string.const" "0" (global $"string.const_\"foo\"" (ref extern))) - ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) @@ -269,7 +275,7 @@ ;; CHECK-NEXT: (global.get $"string.const_\"foo\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOWER: (func $a (type $2) + ;; LOWER: (func $a (type $3) ;; LOWER-NEXT: (drop ;; LOWER-NEXT: (global.get $"string.const_\"foo\"") ;; LOWER-NEXT: ) @@ -300,15 +306,15 @@ ;; CHECK-NEXT: )) ;; LOWER: (type $2 (func (param externref externref) (result i32))) - ;; LOWER: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (type $3 (func (param externref) (result i32))) - ;; LOWER: (type $4 (func (param i32) (result (ref extern)))) + ;; LOWER: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (type $5 (func (param externref externref) (result (ref extern)))) + ;; LOWER: (type $5 (func (param i32) (result (ref extern)))) - ;; LOWER: (type $6 (func (param externref (ref null $0) i32) (result i32))) + ;; LOWER: (type $6 (func (param externref externref) (result (ref extern)))) - ;; LOWER: (type $7 (func (param externref) (result i32))) + ;; LOWER: (type $7 (func (param externref (ref null $0) i32) (result i32))) ;; LOWER: (type $8 (func (param externref i32) (result i32))) @@ -316,19 +322,21 @@ ;; LOWER: (import "string.const" "0" (global $string (ref extern))) - ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) diff --git a/test/lit/passes/string-lifting-section.wast b/test/lit/passes/string-lifting-section.wast index 815af9fc646..5406494432a 100644 --- a/test/lit/passes/string-lifting-section.wast +++ b/test/lit/passes/string-lifting-section.wast @@ -11,15 +11,15 @@ ;; CHECK: (type $2 (func (param externref externref) (result i32))) - ;; CHECK: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $3 (func (param externref) (result i32))) - ;; CHECK: (type $4 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (type $5 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $5 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $6 (func (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (type $6 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $7 (func (param externref) (result i32))) + ;; CHECK: (type $7 (func (param externref (ref null $0) i32) (result i32))) ;; CHECK: (type $8 (func (param externref i32) (result i32))) @@ -39,19 +39,21 @@ ;; CHECK: (import "string.const" "6" (global $"string.const_\"z\\\\\"" (ref extern))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) diff --git a/test/lit/passes/string-lifting.wast b/test/lit/passes/string-lifting.wast index 78da0c040a8..da537c59e98 100644 --- a/test/lit/passes/string-lifting.wast +++ b/test/lit/passes/string-lifting.wast @@ -3,12 +3,12 @@ ;; RUN: foreach %s %t wasm-opt -all --string-lifting -S -o - | filecheck %s (module + ;; CHECK: (type $0 (func (param externref) (result i32))) + ;; CHECK: (type $array16 (array (mut i16))) (type $array16 (array (mut i16))) - ;; CHECK: (type $1 (func (param externref externref) (result i32))) - - ;; CHECK: (type $2 (func (param externref) (result i32))) + ;; CHECK: (type $2 (func (param externref externref) (result i32))) ;; CHECK: (type $3 (func (param externref i32 i32) (result (ref extern)))) @@ -47,11 +47,13 @@ (import "wasm:js-string" "concat" (func $concat (param externref externref) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $array16) i32) (result i32))) (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (param externref (ref null $array16) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) (import "wasm:js-string" "equals" (func $equals (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) (import "wasm:js-string" "compare" (func $compare (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $0) (param externref) (result i32))) + (import "wasm:js-string" "test" (func $test (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $0) (param externref) (result i32))) (import "wasm:js-string" "length" (func $length (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) (import "wasm:js-string" "charCodeAt" (func $charCodeAt (param externref i32) (result i32))) @@ -150,7 +152,7 @@ ) ) - ;; CHECK: (func $string.eq (type $1) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.eq (type $2) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (string.eq ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -163,7 +165,7 @@ ) ) - ;; CHECK: (func $string.compare (type $1) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.compare (type $2) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (string.compare ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -176,7 +178,18 @@ ) ) - ;; CHECK: (func $string.length (type $2) (param $ref externref) (result i32) + ;; CHECK: (func $string.test (type $0) (param $a externref) (result i32) + ;; CHECK-NEXT: (string.test + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.test (param $a externref) (result i32) + (call $test + (local.get $a) + ) + ) + + ;; CHECK: (func $string.length (type $0) (param $ref externref) (result i32) ;; CHECK-NEXT: (string.measure_wtf16 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) @@ -187,7 +200,7 @@ ) ) - ;; CHECK: (func $string.get_codeunit (type $2) (param $ref externref) (result i32) + ;; CHECK: (func $string.get_codeunit (type $0) (param $ref externref) (result i32) ;; CHECK-NEXT: (stringview_wtf16.get_codeunit ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 2) diff --git a/test/lit/passes/string-lowering-imports-custom-module.wast b/test/lit/passes/string-lowering-imports-custom-module.wast index 5884e98a267..2d82bfb74d8 100644 --- a/test/lit/passes/string-lowering-imports-custom-module.wast +++ b/test/lit/passes/string-lowering-imports-custom-module.wast @@ -11,17 +11,17 @@ ;; CHECK: (type $1 (func (param externref externref) (result i32))) - ;; CHECK: (type $2 (func)) + ;; CHECK: (type $2 (func (param externref) (result i32))) - ;; CHECK: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $3 (func)) - ;; CHECK: (type $4 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (type $5 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $5 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $6 (func (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (type $6 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $7 (func (param externref) (result i32))) + ;; CHECK: (type $7 (func (param externref (ref null $0) i32) (result i32))) ;; CHECK: (type $8 (func (param externref i32) (result i32))) @@ -29,19 +29,21 @@ ;; CHECK: (import "strings" "foo" (global $"string.const_\"foo\"" (ref extern))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) @@ -49,7 +51,7 @@ ;; CHECK: (export "const" (func $const)) - ;; CHECK: (func $const (type $2) + ;; CHECK: (func $const (type $3) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $"string.const_\"foo\"") ;; CHECK-NEXT: ) diff --git a/test/lit/passes/string-lowering-instructions.wast b/test/lit/passes/string-lowering-instructions.wast index 1a27ac8adb4..a3a59df0636 100644 --- a/test/lit/passes/string-lowering-instructions.wast +++ b/test/lit/passes/string-lowering-instructions.wast @@ -6,13 +6,15 @@ (rec ;; CHECK: (type $0 (func)) - ;; CHECK: (type $1 (array (mut i16))) + ;; CHECK: (type $1 (func (param externref) (result i32))) + + ;; CHECK: (type $2 (array (mut i16))) ;; CHECK: (rec ;; CHECK-NEXT: (type $struct-of-string (struct (field externref) (field i32) (field anyref))) (type $struct-of-string (struct (field stringref) (field i32) (field anyref))) - ;; CHECK: (type $struct-of-array (struct (field (ref $1)))) + ;; CHECK: (type $struct-of-array (struct (field (ref $2)))) (type $struct-of-array (struct (field (ref $array16)))) ;; CHECK: (type $array16-imm (array i32)) @@ -31,35 +33,33 @@ (type $array16 (array (mut i16))) ) - ;; CHECK: (type $9 (func (param (ref $1)))) - - ;; CHECK: (type $10 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $10 (func (param (ref $2)))) - ;; CHECK: (type $11 (func (param externref (ref $1)) (result i32))) + ;; CHECK: (type $11 (func (param externref) (result i32))) - ;; CHECK: (type $12 (func (param externref externref) (result i32))) + ;; CHECK: (type $12 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $13 (func (param externref) (result i32))) + ;; CHECK: (type $13 (func (param externref (ref $2)) (result i32))) - ;; CHECK: (type $14 (func (param externref) (result externref))) + ;; CHECK: (type $14 (func (param externref externref) (result i32))) - ;; CHECK: (type $15 (func (param externref))) + ;; CHECK: (type $15 (func (param externref) (result externref))) - ;; CHECK: (type $16 (func (result externref))) + ;; CHECK: (type $16 (func (param externref))) - ;; CHECK: (type $17 (func (param externref externref) (result i32))) + ;; CHECK: (type $17 (func (result externref))) - ;; CHECK: (type $18 (func (param externref i32 externref))) + ;; CHECK: (type $18 (func (param externref externref) (result i32))) - ;; CHECK: (type $19 (func (param (ref null $1) i32 i32) (result (ref extern)))) + ;; CHECK: (type $19 (func (param externref i32 externref))) - ;; CHECK: (type $20 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $20 (func (param (ref null $2) i32 i32) (result (ref extern)))) - ;; CHECK: (type $21 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $21 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $22 (func (param externref (ref null $1) i32) (result i32))) + ;; CHECK: (type $22 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $23 (func (param externref) (result i32))) + ;; CHECK: (type $23 (func (param externref (ref null $2) i32) (result i32))) ;; CHECK: (type $24 (func (param externref i32) (result i32))) @@ -73,19 +73,21 @@ (import "colliding" "name" (func $fromCodePoint)) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $19) (param (ref null $1) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $20) (param (ref null $2) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_18 (type $20) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_19 (type $21) (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $21) (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $22) (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $22) (param externref (ref null $1) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $23) (param externref (ref null $2) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $17) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $18) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $17) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $1) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $23) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $18) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $1) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $24) (param externref i32) (result i32))) @@ -98,7 +100,7 @@ ;; CHECK: (export "export.2" (func $exported-string-receiver)) - ;; CHECK: (func $string.new.gc (type $9) (param $array16 (ref $1)) + ;; CHECK: (func $string.new.gc (type $10) (param $array16 (ref $2)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $fromCharCodeArray ;; CHECK-NEXT: (local.get $array16) @@ -117,8 +119,8 @@ ) ) - ;; CHECK: (func $string.from_code_point (type $16) (result externref) - ;; CHECK-NEXT: (call $fromCodePoint_18 + ;; CHECK: (func $string.from_code_point (type $17) (result externref) + ;; CHECK-NEXT: (call $fromCodePoint_19 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -128,7 +130,7 @@ ) ) - ;; CHECK: (func $string.concat (type $10) (param $0 externref) (param $1 externref) (result (ref extern)) + ;; CHECK: (func $string.concat (type $12) (param $0 externref) (param $1 externref) (result (ref extern)) ;; CHECK-NEXT: (call $concat ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -141,7 +143,7 @@ ) ) - ;; CHECK: (func $string.encode (type $11) (param $ref externref) (param $array16 (ref $1)) (result i32) + ;; CHECK: (func $string.encode (type $13) (param $ref externref) (param $array16 (ref $2)) (result i32) ;; CHECK-NEXT: (call $intoCharCodeArray ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $array16) @@ -156,7 +158,7 @@ ) ) - ;; CHECK: (func $string.eq (type $12) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.eq (type $14) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (call $equals ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -169,7 +171,7 @@ ) ) - ;; CHECK: (func $string.compare (type $12) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.compare (type $14) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (call $compare ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -182,7 +184,18 @@ ) ) - ;; CHECK: (func $string.length (type $13) (param $ref externref) (result i32) + ;; CHECK: (func $string-test (type $11) (param $str externref) (result i32) + ;; CHECK-NEXT: (call $test + ;; CHECK-NEXT: (local.get $str) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-test (param $str externref) (result i32) + (string.test + (local.get $str) + ) + ) + + ;; CHECK: (func $string.length (type $11) (param $ref externref) (result i32) ;; CHECK-NEXT: (call $length ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) @@ -193,7 +206,7 @@ ) ) - ;; CHECK: (func $string.get_codeunit (type $13) (param $ref externref) (result i32) + ;; CHECK: (func $string.get_codeunit (type $11) (param $ref externref) (result i32) ;; CHECK-NEXT: (call $charCodeAt ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 2) @@ -206,7 +219,7 @@ ) ) - ;; CHECK: (func $string.slice (type $14) (param $ref externref) (result externref) + ;; CHECK: (func $string.slice (type $15) (param $ref externref) (result externref) ;; CHECK-NEXT: (call $substring ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 2) @@ -221,7 +234,7 @@ ) ) - ;; CHECK: (func $if.string (type $14) (param $ref externref) (result externref) + ;; CHECK: (func $if.string (type $15) (param $ref externref) (result externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -245,7 +258,7 @@ ) ) - ;; CHECK: (func $if.string.flip (type $14) (param $ref externref) (result externref) + ;; CHECK: (func $if.string.flip (type $15) (param $ref externref) (result externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -269,7 +282,7 @@ ) ) - ;; CHECK: (func $exported-string-returner (type $16) (result externref) + ;; CHECK: (func $exported-string-returner (type $17) (result externref) ;; CHECK-NEXT: (global.get $"string.const_\"exported\"") ;; CHECK-NEXT: ) (func $exported-string-returner (export "export.1") (result stringref) @@ -278,7 +291,7 @@ (string.const "exported") ) - ;; CHECK: (func $exported-string-receiver (type $18) (param $x externref) (param $y i32) (param $z externref) + ;; CHECK: (func $exported-string-receiver (type $19) (param $x externref) (param $y i32) (param $z externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -304,7 +317,7 @@ ) ;; CHECK: (func $use-struct-of-array (type $0) - ;; CHECK-NEXT: (local $array16 (ref $1)) + ;; CHECK-NEXT: (local $array16 (ref $2)) ;; CHECK-NEXT: (local $open (ref $array16-open)) ;; CHECK-NEXT: (local $child (ref $array16-child)) ;; CHECK-NEXT: (local $32 (ref $array32)) @@ -313,7 +326,7 @@ ;; CHECK-NEXT: (call $fromCharCodeArray ;; CHECK-NEXT: (struct.get $struct-of-array 0 ;; CHECK-NEXT: (struct.new $struct-of-array - ;; CHECK-NEXT: (array.new_fixed $1 2 + ;; CHECK-NEXT: (array.new_fixed $2 2 ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) @@ -402,7 +415,7 @@ ) ) - ;; CHECK: (func $call-param-null (type $15) (param $str externref) + ;; CHECK: (func $call-param-null (type $16) (param $str externref) ;; CHECK-NEXT: (call $call-param-null ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/string-lowering_types.wast b/test/lit/passes/string-lowering_types.wast index cdbb2dc64ed..36c92ba6fb5 100644 --- a/test/lit/passes/string-lowering_types.wast +++ b/test/lit/passes/string-lowering_types.wast @@ -12,50 +12,52 @@ ;; CHECK: (type $1 (func (param externref externref) (result i32))) - ;; CHECK: (type $2 (func (param externref))) + ;; CHECK: (type $2 (func (param externref) (result i32))) - ;; CHECK: (type $3 (func (param (ref extern)))) + ;; CHECK: (type $3 (func (param externref))) - ;; CHECK: (type $4 (func)) + ;; CHECK: (type $4 (func (param (ref extern)))) + + ;; CHECK: (type $5 (func)) ;; CHECK: (type $private (struct (field externref))) (type $private (struct (field stringref))) ) (type $public (func (param stringref))) - ;; CHECK: (type $6 (func (param (ref null $0) i32 i32) (result (ref extern)))) - - ;; CHECK: (type $7 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $7 (func (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (type $8 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $8 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $9 (func (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (type $9 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $10 (func (param externref) (result i32))) + ;; CHECK: (type $10 (func (param externref (ref null $0) i32) (result i32))) ;; CHECK: (type $11 (func (param externref i32) (result i32))) ;; CHECK: (type $12 (func (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (import "a" "b" (func $import (type $2) (param externref))) + ;; CHECK: (import "a" "b" (func $import (type $3) (param externref))) (import "a" "b" (func $import (type $public) (param stringref))) - ;; CHECK: (import "a" "b" (func $import-implicit (type $3) (param (ref extern)))) + ;; CHECK: (import "a" "b" (func $import-implicit (type $4) (param (ref extern)))) (import "a" "b" (func $import-implicit (param (ref string)))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $6) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $7) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $7) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $8) (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $8) (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $9) (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $9) (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $10) (param externref (ref null $0) i32) (result i32))) ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $10) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $11) (param externref i32) (result i32))) @@ -63,7 +65,7 @@ ;; CHECK: (export "export" (func $export)) - ;; CHECK: (func $export (type $4) + ;; CHECK: (func $export (type $5) ;; CHECK-NEXT: (local $0 (ref $private)) ;; CHECK-NEXT: ) (func $export (export "export") @@ -79,35 +81,37 @@ ;; CHECK: (type $1 (func (param externref externref) (result i32))) - ;; CHECK: (type $2 (func (result (ref extern)))) + ;; CHECK: (type $2 (func (param externref) (result i32))) - ;; CHECK: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $3 (func (result (ref extern)))) - ;; CHECK: (type $4 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (type $5 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $5 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $6 (func (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (type $6 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $7 (func (param externref) (result i32))) + ;; CHECK: (type $7 (func (param externref (ref null $0) i32) (result i32))) ;; CHECK: (type $8 (func (param externref i32) (result i32))) ;; CHECK: (type $9 (func (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $7) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) @@ -119,7 +123,7 @@ ;; CHECK: (elem $elem (i32.const 0) $func) (elem $elem (i32.const 0) $func) - ;; CHECK: (func $func (type $2) (result (ref extern)) + ;; CHECK: (func $func (type $3) (result (ref extern)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) diff --git a/test/lit/strings.wast b/test/lit/strings.wast index 8c72ea7cc06..1f392aa2bc8 100644 --- a/test/lit/strings.wast +++ b/test/lit/strings.wast @@ -12,31 +12,38 @@ ;; RUN: wasm-opt %s -all -S -o - | wasm-opt -all --precompute -S -o - | filecheck %s (module - (memory 10 10) - ;; CHECK: (type $0 (func (param stringref stringref))) ;; CHECK: (type $array (sub (array (mut i8)))) - (type $array (sub (array (mut i8)))) + ;; CHECK: (type $array16 (sub (array (mut i16)))) - (type $array16 (sub (array (mut i16)))) - ;; CHECK: (type $3 (func (param (ref string)))) + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (result externref))) - ;; CHECK: (type $4 (func (param stringref))) + ;; CHECK: (type $5 (func (param (ref string)))) - ;; CHECK: (type $5 (func (param (ref $array) (ref $array16)))) + ;; CHECK: (type $6 (func (param stringref))) - ;; CHECK: (type $6 (func (param stringref (ref $array) (ref $array16)))) + ;; CHECK: (type $7 (func (param (ref $array) (ref $array16)))) - ;; CHECK: (type $7 (func)) + ;; CHECK: (type $8 (func (param stringref (ref $array) (ref $array16)))) + + ;; CHECK: (import "env" "get-string-ref" (func $get-string-ref (type $4) (result externref))) + (import "env" "get-string-ref" (func $get-string-ref (result externref))) + + (memory 10 10) + + (type $array (sub (array (mut i8)))) + (type $array16 (sub (array (mut i16)))) ;; CHECK: (global $string-const stringref (string.const "string in a global \c2\a3_\e2\82\ac_\f0\90\8d\88 \01\00\t\t\n\n\r\r\"\"\'\'\\\\ ")) (global $string-const stringref (string.const "string in a global \C2\A3_\E2\82\AC_\F0\90\8D\88 \01\00\t\t\n\n\r\r\"\"\'\'\\\\ ")) ;; CHECK: (memory $0 10 10) - ;; CHECK: (func $string.const (type $3) (param $param (ref string)) + ;; CHECK: (func $string.const (type $5) (param $param (ref string)) ;; CHECK-NEXT: (call $string.const ;; CHECK-NEXT: (string.const "foo") ;; CHECK-NEXT: ) @@ -60,7 +67,7 @@ ) ) - ;; CHECK: (func $string.measure (type $4) (param $ref stringref) + ;; CHECK: (func $string.measure (type $6) (param $ref stringref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (string.measure_wtf16 @@ -148,7 +155,7 @@ ) ) - ;; CHECK: (func $string.new.gc (type $5) (param $array (ref $array)) (param $array16 (ref $array16)) + ;; CHECK: (func $string.new.gc (type $7) (param $array (ref $array)) (param $array16 (ref $array16)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (string.new_wtf16_array ;; CHECK-NEXT: (local.get $array16) @@ -181,7 +188,7 @@ ) ) - ;; CHECK: (func $string.encode.gc (type $6) (param $ref stringref) (param $array (ref $array)) (param $array16 (ref $array16)) + ;; CHECK: (func $string.encode.gc (type $8) (param $ref stringref) (param $array (ref $array)) (param $array16 (ref $array16)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (string.encode_wtf16_array @@ -218,7 +225,7 @@ ) ) - ;; CHECK: (func $string.from_code_point (type $7) + ;; CHECK: (func $string.from_code_point (type $3) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (string.from_code_point ;; CHECK-NEXT: (i32.const 1) @@ -232,4 +239,23 @@ ) ) ) + + ;; CHECK: (func $string.test (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (string.test + ;; CHECK-NEXT: (call $get-string-ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.test + (drop + (i32.eqz + (string.test + (call $get-string-ref) + ) + ) + ) + ) ) From 99cf922698951afe6bfd55609f23e1e034b0432c Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Wed, 2 Jul 2025 18:13:06 -0700 Subject: [PATCH 580/622] Don't emit the 'no output file specified' warning when using a pass that emits to stdout (#7696) --- src/tools/wasm-opt.cpp | 10 +++++++++- test/unit/test_warnings.py | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index d94b30ef4c9..5edd2a15b83 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -436,7 +436,15 @@ For more on how to optimize effectively, see if (options.extra.count("output") == 0) { if (!options.quiet) { - std::cerr << "warning: no output file specified, not emitting output\n"; + bool printsToStdout = std::any_of( + options.passes.begin(), + options.passes.end(), + [](const OptimizationOptions::PassInfo& info) { + return info.name == "print" || info.name == "print-function-map"; + }); + if (!printsToStdout) { + std::cerr << "warning: no output file specified, not emitting output\n"; + } } return 0; } diff --git a/test/unit/test_warnings.py b/test/unit/test_warnings.py index 05fc110995c..cb912f42c16 100644 --- a/test/unit/test_warnings.py +++ b/test/unit/test_warnings.py @@ -16,3 +16,11 @@ def test_warn_on_no_output(self): def test_quiet_suppresses_warnings(self): err = shared.run_process(shared.WASM_OPT + [self.input_path('asyncify-pure.wat'), '-q'], stderr=subprocess.PIPE).stderr self.assertNotIn('warning', err) + + def test_no_warn_on_print(self): + err = shared.run_process(shared.WASM_OPT + [self.input_path('asyncify-pure.wat'), '--print'], stderr=subprocess.PIPE).stderr + self.assertNotIn('warning: no output file specified, not emitting output', err) + + def test_no_warn_on_print_function_map(self): + err = shared.run_process(shared.WASM_OPT + [self.input_path('asyncify-pure.wat'), '--print-function-map'], stderr=subprocess.PIPE).stderr + self.assertNotIn('warning: no output file specified, not emitting output', err) From 8844b1f261cbc304a1a6aec97fb848b18cd9a41f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 7 Jul 2025 09:03:29 -0700 Subject: [PATCH 581/622] [Custom Descriptors] Handle global effects in GTO (#7698) GTO uses ChildLocalizer to ensure that removing children from a struct.new does not drop any side effects. But that does not work outside function contexts. This was previously not a problem because there were no side effects possible outside function contexts, but now with custom descriptors it is possible for struct.news in constant expressions to trap due to being given null descriptors. To preserve side effects during instantiation, put expressions that might trap and that have been removed outside function contexts into new globals. This ensures that their side effects, if any, are still executed during instantiation. --- scripts/test/fuzzing.py | 1 + src/passes/GlobalTypeOptimization.cpp | 39 ++++++-- test/lit/passes/gto-desc.wast | 130 ++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 test/lit/passes/gto-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 4a610946935..4e8582b78d8 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -127,6 +127,7 @@ 'gc-desc.wast', 'simplify-locals-desc.wast', 'optimize-instructions-desc.wast', + 'gto-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index bf7d0ae0724..6c1872d2bb4 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -25,6 +25,7 @@ #include "ir/eh-utils.h" #include "ir/localize.h" #include "ir/module-utils.h" +#include "ir/names.h" #include "ir/ordering.h" #include "ir/struct-utils.h" #include "ir/subtypes.h" @@ -461,6 +462,11 @@ struct GlobalTypeOptimization : public Pass { bool needEHFixups = false; + // Expressions that might trap that have been removed from module-level + // initializers. These need to be placed in new globals to preserve any + // instantiation-time traps. + std::vector removedTrappingInits; + void visitStructNew(StructNew* curr) { if (curr->type == Type::unreachable) { return; @@ -481,22 +487,31 @@ struct GlobalTypeOptimization : public Pass { // Ensure any children with non-trivial effects are replaced with // local.gets, so that we can remove/reorder to our hearts' content. - ChildLocalizer localizer( - curr, getFunction(), *getModule(), getPassOptions()); - replaceCurrent(localizer.getReplacement()); - // Adding a block here requires EH fixups. - needEHFixups = true; + // We can only do this inside functions. Outside of functions, we + // preserve traps during instantiation by creating new globals to hold + // removed and potentially-trapping operands instead. + auto* func = getFunction(); + if (func) { + ChildLocalizer localizer(curr, func, *getModule(), getPassOptions()); + replaceCurrent(localizer.getReplacement()); + // Adding a block here requires EH fixups. + needEHFixups = true; + } // Remove and reorder operands. Index removed = 0; std::vector old(operands.begin(), operands.end()); - for (Index i = 0; i < operands.size(); i++) { + for (Index i = 0; i < operands.size(); ++i) { auto newIndex = indexesAfterRemoval[i]; if (newIndex != RemovedField) { assert(newIndex < operands.size()); operands[newIndex] = old[i]; } else { - removed++; + ++removed; + if (!func && + EffectAnalyzer(getPassOptions(), *getModule(), old[i]).trap) { + removedTrappingInits.push_back(old[i]); + } } } if (removed) { @@ -573,6 +588,16 @@ struct GlobalTypeOptimization : public Pass { FieldRemover remover(*this); remover.run(getPassRunner(), &wasm); remover.runOnModuleCode(getPassRunner(), &wasm); + + // Insert globals necessary to preserve instantiation-time trapping of + // removed expressions. + for (Index i = 0; i < remover.removedTrappingInits.size(); ++i) { + auto* curr = remover.removedTrappingInits[i]; + auto name = Names::getValidGlobalName( + wasm, std::string("gto-removed-") + std::to_string(i)); + wasm.addGlobal( + Builder::makeGlobal(name, curr->type, curr, Builder::Immutable)); + } } }; diff --git a/test/lit/passes/gto-desc.wast b/test/lit/passes/gto-desc.wast new file mode 100644 index 00000000000..d7762626e35 --- /dev/null +++ b/test/lit/passes/gto-desc.wast @@ -0,0 +1,130 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all --closed-world --gto --preserve-type-order -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc (struct (field i32)))) + (type $struct (descriptor $desc (struct (field i32)))) + ;; CHECK: (type $desc (describes $struct (struct))) + (type $desc (describes $struct (struct))) + ;; CHECK: (type $pair (struct)) + (type $pair (struct (field (ref $struct)) (field (ref $struct)))) + ;; CHECK: (type $used-pair (struct (field (ref $struct)) (field (ref $struct)))) + (type $used-pair (struct (field (ref $struct)) (field (ref $struct)))) + ) + + ;; CHECK: (type $4 (func (param (ref $struct) (ref $used-pair)))) + + ;; CHECK: (global $nullable-desc (ref null (exact $desc)) (struct.new_default $desc)) + (global $nullable-desc (ref null (exact $desc)) (struct.new $desc)) + + ;; Check that we generate fresh names for added globals. + ;; CHECK: (global $gto-removed-1 nullref (ref.null none)) + (global $gto-removed-1 nullref (ref.null none)) + + ;; CHECK: (global $second-traps (ref $pair) (struct.new_default $pair)) + (global $second-traps (ref $pair) + ;; Both fields will be removed, but only the second can trap, so only the + ;; second will be moved to a new global. + (struct.new $pair + (struct.new $struct + (i32.const 0) + (struct.new $desc) + ) + (struct.new $struct + (i32.const 1) + (ref.null none) + ) + ) + ) + + ;; CHECK: (global $first-traps (ref $pair) (struct.new_default $pair)) + (global $first-traps (ref $pair) + ;; Same as above, but now the first traps (or at least we assume it can + ;; based on its type). + (struct.new $pair + (struct.new $struct + (i32.const 2) + (global.get $nullable-desc) + ) + (struct.new $struct + (i32.const 3) + (struct.new $desc) + ) + ) + ) + + ;; CHECK: (global $used-traps (ref $used-pair) (struct.new $used-pair + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: )) + (global $used-traps (ref $used-pair) + ;; Now both trap, but they are also used, so they will not be removed and no + ;; globals will be created. + (struct.new $used-pair + (struct.new $struct + (i32.const 4) + (ref.null none) + ) + (struct.new $struct + (i32.const 5) + (ref.null none) + ) + ) + ) + + ;; CHECK: (global $gto-removed-0 (ref (exact $struct)) (struct.new $struct + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $gto-removed-1_6 (ref (exact $struct)) (struct.new $struct + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (global.get $nullable-desc) + ;; CHECK-NEXT: )) + + ;; CHECK: (func $use-struct-fields (type $4) (param $0 (ref $struct)) (param $1 (ref $used-pair)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $used-pair 0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $used-pair 1 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-struct-fields (param (ref $struct)) (param (ref $used-pair)) + ;; Prevent the i32s in the initializers from being removed. + (drop + (struct.get $struct 0 + (local.get 0) + ) + ) + ;; Prevent the fields in used-pair from being removed. + (drop + (struct.get $used-pair 0 + (local.get 1) + ) + ) + (drop + (struct.get $used-pair 1 + (local.get 1) + ) + ) + ) +) From 03c63e4f3c4dfae88eb988a4d515f4191cd03858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 7 Jul 2025 21:47:57 +0200 Subject: [PATCH 582/622] Generalize "trivial call" flag to other simple instructions (#7670) A "trivial call" function is a function that just calls another with small arguments. Currently, these functions are always inlined when they arguments are just local.gets with the right order, and inlined with -O3 when the arguments have size 1. This PR generalizes "trivial calls" to "trivial instructions", to cover instructions other than just calls. All instructions other than control flow instructions are now covered. With this PR wasm-opt now inlines trivial functions in dart2wasm outputs like: (func $IntWasmInstructions|ltU (;40;) (param $var0 i64) (param $var1 i64) (result i32) local.get $var0 local.get $var1 i64.lt_u ) (func $... implicit setter (param $var0 (ref exact $...)) (param $var1 (ref exact $WasmListBase)) local.get $var0 local.get $var1 struct.set $... $field6 ) Note: dart2wasm doesn't directly generate these functions, these become "trivial" after wasm-opt passes. Impact of this change in a Wasm module optimized with flags: --closed-world --traps-never-happen --type-unfinalizing -Os \ --type-ssa --gufa -Os --type-merging -Os --type-finalizing --minimize-rec-groups Before optimizations: 38,035,036 bytes Optimized with main branch: 21,147,177 bytes Optimized with this branch: 20,991,223 bytes Diff: -155,954 bytes, -0.73% --- src/passes/Inlining.cpp | 71 ++-- .../passes/inlining-trivial-instructions.wast | 401 ++++++++++++++++++ 2 files changed, 444 insertions(+), 28 deletions(-) create mode 100644 test/lit/passes/inlining-trivial-instructions.wast diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 1f93493f7bc..bc8dfb950aa 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -40,6 +40,7 @@ #include "ir/localize.h" #include "ir/module-utils.h" #include "ir/names.h" +#include "ir/properties.h" #include "ir/type-updating.h" #include "ir/utils.h" #include "parsing.h" @@ -67,22 +68,25 @@ enum class InliningMode { SplitPatternB }; -// Whether a function just calls another function in a way that always shrinks -// when the calling function is inlined. -enum class TrivialCall { - // Function does not just call another function, or it may not shrink when - // inlined. +// Whether a function is just one instruction that always shrinks when inlined. +enum class TrivialInstruction { + // Function is not a single instruction, or it may not shrink when inlined. NotTrivial, - // Function just calls another function, with `local.get`s as arguments, and - // with each `local` is used exactly once, and in the order they appear in the + // Function is just one instruction, with `local.get`s as arguments, and with + // each `local` is used exactly once, and in the order they appear in the // argument list. // // In this case, inlining the function generates smaller code, and it is also - // good for runtime. + // good for runtime. (Note that in theory inlining an instruction might grow + // the size, if before we had a call - one byte+LEB - and after we have + // something like a prefixed instruction - two bytes - with some other LEB + // like a type index. Figuring out when the LEBs will cause growth here is + // hard, and probably not worth it, since doing a call to run a single + // instruction is almost always going to be larger and slower.) Shrinks, - // Function just calls another function, but maybe with constant arguments, or + // Function is a single instruction, but maybe with constant arguments, or // maybe some locals are used more than once. In these cases code size does // not always shrink: at the call sites, omitted locals can create `drop` // instructions, a local used multiple times can create new locals, and @@ -102,7 +106,7 @@ struct FunctionInfo { // Something is used globally if there is a reference to it in a table or // export etc. bool usedGlobally; - TrivialCall trivialCall; + TrivialInstruction trivialInstruction; InliningMode inliningMode; FunctionInfo() { clear(); } @@ -114,7 +118,7 @@ struct FunctionInfo { hasLoops = false; hasTryDelegate = false; usedGlobally = false; - trivialCall = TrivialCall::NotTrivial; + trivialInstruction = TrivialInstruction::NotTrivial; inliningMode = InliningMode::Unknown; } @@ -126,7 +130,7 @@ struct FunctionInfo { hasLoops = other.hasLoops; hasTryDelegate = other.hasTryDelegate; usedGlobally = other.usedGlobally; - trivialCall = other.trivialCall; + trivialInstruction = other.trivialInstruction; inliningMode = other.inliningMode; return *this; } @@ -150,7 +154,7 @@ struct FunctionInfo { } // If the function calls another one in a way that always shrinks when // inlined, inline it in all optimization and shrink modes. - if (trivialCall == TrivialCall::Shrinks) { + if (trivialInstruction == TrivialInstruction::Shrinks) { return true; } // If it's so big that we have no flexible options that could allow it, @@ -164,12 +168,12 @@ struct FunctionInfo { if (options.shrinkLevel > 0 || options.optimizeLevel < 3) { return false; } - // The function just calls another function, but the code size may increase - // when inlined. We only inline it fully with `-O3`. - if (trivialCall == TrivialCall::MayNotShrink) { + // The function is just one instruction, but the code size may increase when + // inlined. We only inline it fully with `-O3`. + if (trivialInstruction == TrivialInstruction::MayNotShrink) { return true; } - // Trivial calls are already handled. Inline if + // Trivial instructions are already handled. Inline if // 1. The function doesn't have calls, and // 2. The function doesn't have loops, or we allow inlining with loops. return !hasCalls && (!hasLoops || options.inlining.allowFunctionsWithLoops); @@ -240,14 +244,23 @@ struct FunctionInfoScanner info.size = Measurer::measure(curr->body); - if (auto* call = curr->body->dynCast()) { - // If call arguments are function locals read in order, then the code size - // always shrinks when the call is inlined. Note that we don't allow - // skipping function arguments here, as that can create `drop` - // instructions at the call sites, increasing code size. + // If the body is a simple instruction with roughly the same encoded size as + // a `call` instruction, and arguments are function locals read in order, + // then the code size always shrinks when the call is inlined. + // + // Note that skipping arguments can create `drop` instructions, and using + // arguments multiple times can create new locals, at the call sites. So we + // don't consider the function as "always shrinks" in these cases. + // TODO: Consider allowing drops, as at least in traps-never-happen mode + // they can usually be removed. + auto* body = curr->body; + // Skip control flow as those can be substantially larger (middle and end + // bytes in an If), or no situation exists where we can optimize them (a + // Block with only LocalGets would have been removed by other passes). + if (!Properties::isControlFlowStructure(body)) { bool shrinks = true; Index nextLocalGetIndex = 0; - for (auto* operand : call->operands) { + for (auto* operand : ChildIterator(body)) { if (auto* localGet = operand->dynCast()) { if (localGet->index == nextLocalGetIndex) { nextLocalGetIndex++; @@ -262,14 +275,16 @@ struct FunctionInfoScanner } if (shrinks) { - info.trivialCall = TrivialCall::Shrinks; + info.trivialInstruction = TrivialInstruction::Shrinks; return; } - if (info.size == call->operands.size() + 1) { - // This function body is a call with some trivial (size 1) operands like - // LocalGet or Const, so it is a trivial call. - info.trivialCall = TrivialCall::MayNotShrink; + // If the operands are trivial (size 1) like LocalGet or Const, we still + // consider this as trivial instruction, but the size may not shrink when + // inlined. + uint32_t numOperands = ChildIterator(body).children.size(); + if (info.size == numOperands + 1) { + info.trivialInstruction = TrivialInstruction::MayNotShrink; } } } diff --git a/test/lit/passes/inlining-trivial-instructions.wast b/test/lit/passes/inlining-trivial-instructions.wast new file mode 100644 index 00000000000..339bede9219 --- /dev/null +++ b/test/lit/passes/inlining-trivial-instructions.wast @@ -0,0 +1,401 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt -all --inlining %s -S -o - | filecheck %s --check-prefix=shrink0 +;; RUN: wasm-opt -all --inlining --shrink-level=1 %s -S -o - | filecheck %s --check-prefix=shrink1 +;; RUN: wasm-opt -all --inlining --optimize-level=3 %s -S -o - | filecheck %s --check-prefix=optimize3 + +(module + ;; shrink0: (type $2 (func (param i32))) + + ;; shrink0: (type $1 (func (param i32) (result i32))) + + ;; shrink0: (type $0 (func (param i32 i32) (result i32))) + ;; shrink1: (type $2 (func (param i32))) + + ;; shrink1: (type $1 (func (param i32) (result i32))) + + ;; shrink1: (type $0 (func (param i32 i32) (result i32))) + (type $0 (func (param i32 i32) (result i32))) + (type $1 (func (param i32) (result i32))) + ;; optimize3: (type $2 (func (param i32))) + (type $2 (func (param i32))) + ;; shrink0: (type $3 (func)) + ;; shrink1: (type $3 (func)) + ;; optimize3: (type $3 (func)) + (type $3 (func)) + ;; shrink0: (import "env" "foo" (func $drop-import (type $2) (param i32))) + ;; shrink1: (import "env" "foo" (func $drop-import (type $2) (param i32))) + ;; optimize3: (import "env" "foo" (func $drop-import (type $2) (param i32))) + (import "env" "foo" (func $drop-import (type $2) (param i32))) + ;; shrink0: (export "main" (func $main)) + ;; shrink1: (export "main" (func $main)) + ;; optimize3: (export "main" (func $main)) + (export "main" (func $main)) + ;; This will be inlined in all shrink and optimization modes. + (func $trivial-binary-instruction-small (type $0) (param $p1 i32) (param $p2 i32) (result i32) + (i32.add + (local.get $p1) + (local.get $p2) + ) + ) + ;; This will always be inlined when not shrinking and optimize level is 3. + ;; shrink0: (func $trivial-binary-instruction-large (type $1) (param $p1 i32) (result i32) + ;; shrink0-NEXT: (i32.add + ;; shrink0-NEXT: (local.get $p1) + ;; shrink0-NEXT: (i32.const 2147483647) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink1: (func $trivial-binary-instruction-large (type $1) (param $p1 i32) (result i32) + ;; shrink1-NEXT: (i32.add + ;; shrink1-NEXT: (local.get $p1) + ;; shrink1-NEXT: (i32.const 2147483647) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + (func $trivial-binary-instruction-large (type $1) (param $p1 i32) (result i32) + (i32.add + (local.get $p1) + (i32.const 2147483647) + ) + ) + ;; This will always be inlined when not shrinking and optimize level is 3. + ;; shrink0: (func $non-trivial-binary-instruction (type $0) (param $p1 i32) (param $p2 i32) (result i32) + ;; shrink0-NEXT: (i32.add + ;; shrink0-NEXT: (local.get $p2) + ;; shrink0-NEXT: (local.get $p1) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink1: (func $non-trivial-binary-instruction (type $0) (param $p1 i32) (param $p2 i32) (result i32) + ;; shrink1-NEXT: (i32.add + ;; shrink1-NEXT: (local.get $p2) + ;; shrink1-NEXT: (local.get $p1) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + (func $non-trivial-binary-instruction (type $0) (param $p1 i32) (param $p2 i32) (result i32) + (i32.add + (local.get $p2) + (local.get $p1) + ) + ) + ;; This will be inlined in all shrink and optimization modes. + (func $trivial-unary-instruction (type $1) (param $p1 i32) (result i32) + (i32.eqz + (local.get $p1) + ) + ) + ;; Note: we need more than one caller for each function to test inlining based + ;; on trivial-ness of the function bodies. With one call we always inline. + ;; shrink0: (func $main (type $3) + ;; shrink0-NEXT: (local $0 i32) + ;; shrink0-NEXT: (local $1 i32) + ;; shrink0-NEXT: (local $2 i32) + ;; shrink0-NEXT: (local $3 i32) + ;; shrink0-NEXT: (local $4 i32) + ;; shrink0-NEXT: (local $5 i32) + ;; shrink0-NEXT: (call $drop-import + ;; shrink0-NEXT: (block $__inlined_func$trivial-binary-instruction-small (result i32) + ;; shrink0-NEXT: (local.set $0 + ;; shrink0-NEXT: (i32.const 1) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (local.set $1 + ;; shrink0-NEXT: (i32.const 2) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (i32.add + ;; shrink0-NEXT: (local.get $0) + ;; shrink0-NEXT: (local.get $1) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (call $drop-import + ;; shrink0-NEXT: (block $__inlined_func$trivial-binary-instruction-small$1 (result i32) + ;; shrink0-NEXT: (local.set $2 + ;; shrink0-NEXT: (i32.const 3) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (local.set $3 + ;; shrink0-NEXT: (i32.const 4) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (i32.add + ;; shrink0-NEXT: (local.get $2) + ;; shrink0-NEXT: (local.get $3) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (call $drop-import + ;; shrink0-NEXT: (call $trivial-binary-instruction-large + ;; shrink0-NEXT: (i32.const 5) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (call $drop-import + ;; shrink0-NEXT: (call $trivial-binary-instruction-large + ;; shrink0-NEXT: (i32.const 6) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (call $drop-import + ;; shrink0-NEXT: (call $non-trivial-binary-instruction + ;; shrink0-NEXT: (i32.const 7) + ;; shrink0-NEXT: (i32.const 8) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (call $drop-import + ;; shrink0-NEXT: (call $non-trivial-binary-instruction + ;; shrink0-NEXT: (i32.const 9) + ;; shrink0-NEXT: (i32.const 10) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (call $drop-import + ;; shrink0-NEXT: (block $__inlined_func$trivial-unary-instruction$2 (result i32) + ;; shrink0-NEXT: (local.set $4 + ;; shrink0-NEXT: (i32.const 11) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (i32.eqz + ;; shrink0-NEXT: (local.get $4) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (call $drop-import + ;; shrink0-NEXT: (block $__inlined_func$trivial-unary-instruction$3 (result i32) + ;; shrink0-NEXT: (local.set $5 + ;; shrink0-NEXT: (i32.const 12) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: (i32.eqz + ;; shrink0-NEXT: (local.get $5) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink0-NEXT: ) + ;; shrink1: (func $main (type $3) + ;; shrink1-NEXT: (local $0 i32) + ;; shrink1-NEXT: (local $1 i32) + ;; shrink1-NEXT: (local $2 i32) + ;; shrink1-NEXT: (local $3 i32) + ;; shrink1-NEXT: (local $4 i32) + ;; shrink1-NEXT: (local $5 i32) + ;; shrink1-NEXT: (call $drop-import + ;; shrink1-NEXT: (block $__inlined_func$trivial-binary-instruction-small (result i32) + ;; shrink1-NEXT: (local.set $0 + ;; shrink1-NEXT: (i32.const 1) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (local.set $1 + ;; shrink1-NEXT: (i32.const 2) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (i32.add + ;; shrink1-NEXT: (local.get $0) + ;; shrink1-NEXT: (local.get $1) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (call $drop-import + ;; shrink1-NEXT: (block $__inlined_func$trivial-binary-instruction-small$1 (result i32) + ;; shrink1-NEXT: (local.set $2 + ;; shrink1-NEXT: (i32.const 3) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (local.set $3 + ;; shrink1-NEXT: (i32.const 4) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (i32.add + ;; shrink1-NEXT: (local.get $2) + ;; shrink1-NEXT: (local.get $3) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (call $drop-import + ;; shrink1-NEXT: (call $trivial-binary-instruction-large + ;; shrink1-NEXT: (i32.const 5) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (call $drop-import + ;; shrink1-NEXT: (call $trivial-binary-instruction-large + ;; shrink1-NEXT: (i32.const 6) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (call $drop-import + ;; shrink1-NEXT: (call $non-trivial-binary-instruction + ;; shrink1-NEXT: (i32.const 7) + ;; shrink1-NEXT: (i32.const 8) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (call $drop-import + ;; shrink1-NEXT: (call $non-trivial-binary-instruction + ;; shrink1-NEXT: (i32.const 9) + ;; shrink1-NEXT: (i32.const 10) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (call $drop-import + ;; shrink1-NEXT: (block $__inlined_func$trivial-unary-instruction$2 (result i32) + ;; shrink1-NEXT: (local.set $4 + ;; shrink1-NEXT: (i32.const 11) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (i32.eqz + ;; shrink1-NEXT: (local.get $4) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (call $drop-import + ;; shrink1-NEXT: (block $__inlined_func$trivial-unary-instruction$3 (result i32) + ;; shrink1-NEXT: (local.set $5 + ;; shrink1-NEXT: (i32.const 12) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: (i32.eqz + ;; shrink1-NEXT: (local.get $5) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; shrink1-NEXT: ) + ;; optimize3: (func $main (type $3) + ;; optimize3-NEXT: (local $0 i32) + ;; optimize3-NEXT: (local $1 i32) + ;; optimize3-NEXT: (local $2 i32) + ;; optimize3-NEXT: (local $3 i32) + ;; optimize3-NEXT: (local $4 i32) + ;; optimize3-NEXT: (local $5 i32) + ;; optimize3-NEXT: (local $6 i32) + ;; optimize3-NEXT: (local $7 i32) + ;; optimize3-NEXT: (local $8 i32) + ;; optimize3-NEXT: (local $9 i32) + ;; optimize3-NEXT: (local $10 i32) + ;; optimize3-NEXT: (local $11 i32) + ;; optimize3-NEXT: (call $drop-import + ;; optimize3-NEXT: (block $__inlined_func$trivial-binary-instruction-small (result i32) + ;; optimize3-NEXT: (local.set $0 + ;; optimize3-NEXT: (i32.const 1) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (local.set $1 + ;; optimize3-NEXT: (i32.const 2) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (i32.add + ;; optimize3-NEXT: (local.get $0) + ;; optimize3-NEXT: (local.get $1) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (call $drop-import + ;; optimize3-NEXT: (block $__inlined_func$trivial-binary-instruction-small$1 (result i32) + ;; optimize3-NEXT: (local.set $2 + ;; optimize3-NEXT: (i32.const 3) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (local.set $3 + ;; optimize3-NEXT: (i32.const 4) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (i32.add + ;; optimize3-NEXT: (local.get $2) + ;; optimize3-NEXT: (local.get $3) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (call $drop-import + ;; optimize3-NEXT: (block $__inlined_func$trivial-binary-instruction-large$2 (result i32) + ;; optimize3-NEXT: (local.set $4 + ;; optimize3-NEXT: (i32.const 5) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (i32.add + ;; optimize3-NEXT: (local.get $4) + ;; optimize3-NEXT: (i32.const 2147483647) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (call $drop-import + ;; optimize3-NEXT: (block $__inlined_func$trivial-binary-instruction-large$3 (result i32) + ;; optimize3-NEXT: (local.set $5 + ;; optimize3-NEXT: (i32.const 6) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (i32.add + ;; optimize3-NEXT: (local.get $5) + ;; optimize3-NEXT: (i32.const 2147483647) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (call $drop-import + ;; optimize3-NEXT: (block $__inlined_func$non-trivial-binary-instruction$4 (result i32) + ;; optimize3-NEXT: (local.set $6 + ;; optimize3-NEXT: (i32.const 7) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (local.set $7 + ;; optimize3-NEXT: (i32.const 8) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (i32.add + ;; optimize3-NEXT: (local.get $7) + ;; optimize3-NEXT: (local.get $6) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (call $drop-import + ;; optimize3-NEXT: (block $__inlined_func$non-trivial-binary-instruction$5 (result i32) + ;; optimize3-NEXT: (local.set $8 + ;; optimize3-NEXT: (i32.const 9) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (local.set $9 + ;; optimize3-NEXT: (i32.const 10) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (i32.add + ;; optimize3-NEXT: (local.get $9) + ;; optimize3-NEXT: (local.get $8) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (call $drop-import + ;; optimize3-NEXT: (block $__inlined_func$trivial-unary-instruction$6 (result i32) + ;; optimize3-NEXT: (local.set $10 + ;; optimize3-NEXT: (i32.const 11) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (i32.eqz + ;; optimize3-NEXT: (local.get $10) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (call $drop-import + ;; optimize3-NEXT: (block $__inlined_func$trivial-unary-instruction$7 (result i32) + ;; optimize3-NEXT: (local.set $11 + ;; optimize3-NEXT: (i32.const 12) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: (i32.eqz + ;; optimize3-NEXT: (local.get $11) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + ;; optimize3-NEXT: ) + (func $main (type $3) + (call $drop-import + (call $trivial-binary-instruction-small + (i32.const 1) + (i32.const 2) + ) + ) + (call $drop-import + (call $trivial-binary-instruction-small + (i32.const 3) + (i32.const 4) + ) + ) + (call $drop-import + (call $trivial-binary-instruction-large + (i32.const 5) + ) + ) + (call $drop-import + (call $trivial-binary-instruction-large + (i32.const 6) + ) + ) + (call $drop-import + (call $non-trivial-binary-instruction + (i32.const 7) + (i32.const 8) + ) + ) + (call $drop-import + (call $non-trivial-binary-instruction + (i32.const 9) + (i32.const 10) + ) + ) + (call $drop-import + (call $trivial-unary-instruction + (i32.const 11) + ) + ) + (call $drop-import + (call $trivial-unary-instruction + (i32.const 12) + ) + ) + ) +) From ea477c79abf24dc5aa0f3c9905e10e4edc7740d4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Jul 2025 15:56:46 -0700 Subject: [PATCH 583/622] [Branch Hinting] Add useful passes to generate and log branch hints (#7695) --instrument-branch-hints adds logging on every branch hint, which at runtime reports a unique ID, the branch hint prediction, and whether the branch was taken at runtime. This can be used to verify that branch hints are actually valid in practice. Also add --randomize-branch-hints, which just adds random hints in code, basically sets things up for fuzzing. Of course, branch hints may be inaccurate 50% of the time, but we can still fuzz and see if the inaccuracy was preserved. --- scripts/test/fuzzing.py | 2 + src/passes/CMakeLists.txt | 2 + src/passes/InstrumentBranchHints.cpp | 162 ++++++ src/passes/RandomizeBranchHints.cpp | 71 +++ src/passes/pass.cpp | 6 + src/passes/passes.h | 2 + test/lit/help/wasm-metadce.test | 3 + test/lit/help/wasm-opt.test | 3 + test/lit/help/wasm2js.test | 3 + test/lit/passes/instrument-branch-hints.wast | 499 +++++++++++++++++++ test/lit/passes/randomize-branch-hints.wast | 308 ++++++++++++ 11 files changed, 1061 insertions(+) create mode 100644 src/passes/InstrumentBranchHints.cpp create mode 100644 src/passes/RandomizeBranchHints.cpp create mode 100644 test/lit/passes/instrument-branch-hints.wast create mode 100644 test/lit/passes/randomize-branch-hints.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 4e8582b78d8..4efa7273f7c 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -133,6 +133,8 @@ 'string-lifting-section.wast', # TODO: fuzzer support for uninhabitable imported globals 'exact-references.wast', + # We do not have full suppor for these imports in all parts of the fuzzer. + 'instrument-branch-hints.wast', ] diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 6fab09b1bc2..16891caade3 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -52,6 +52,7 @@ set(passes_SOURCES HeapStoreOptimization.cpp I64ToI32Lowering.cpp Inlining.cpp + InstrumentBranchHints.cpp InstrumentLocals.cpp InstrumentMemory.cpp Intrinsics.cpp @@ -102,6 +103,7 @@ set(passes_SOURCES Strip.cpp StripTargetFeatures.cpp TraceCalls.cpp + RandomizeBranchHints.cpp RedundantSetElimination.cpp RemoveImports.cpp RemoveMemoryInit.cpp diff --git a/src/passes/InstrumentBranchHints.cpp b/src/passes/InstrumentBranchHints.cpp new file mode 100644 index 00000000000..35d2dbabb15 --- /dev/null +++ b/src/passes/InstrumentBranchHints.cpp @@ -0,0 +1,162 @@ +/* + * Copyright 2025 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. + */ + +// +// Instruments branch hints and their targets, adding logging that allows us to +// see if the hints were valid or not. We turn +// +// @metadata.branch.hint B +// if (condition) { +// X +// } else { +// Y +// } +// +// into +// +// @metadata.branch.hint B +// ;; log the ID of the condition (123), the prediction (B), and the actual +// ;; runtime result (temp == condition). +// if (temp = condition; log(123, B, temp); temp) { +// X +// } else { +// Y +// } +// +// Concretely, we emit calls to this logging function: +// +// (import "fuzzing-support" "log-branch" +// (func $log-branch (param i32 i32 i32)) ;; ID, prediction, actual +// ) +// +// This can be used to verify that branch hints are accurate, by implementing +// the import like this for example: +// +// imports['fuzzing-support']['log-branch'] = (id, prediction, actual) => { +// // We only care about truthiness of the expected and actual values. +// expected = +!!expected; +// actual = +!!actual; +// // Throw if the hint said this branch would be taken, but it was not, or +// // vice versa. +// if (expected != actual) throw `Bad branch hint! (${id})`; +// }; +// + +#include "ir/eh-utils.h" +#include "ir/names.h" +#include "ir/properties.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" + +namespace wasm { + +namespace { + +// The branch id, which increments as we go. +int branchId = 1; + +struct InstrumentBranchHints + : public WalkerPass> { + + using Super = WalkerPass>; + + // The module and base names of our import. + const Name MODULE = "fuzzing-support"; + const Name BASE = "log-branch"; + + // The internal name of our import. + Name logBranch; + + void visitIf(If* curr) { processCondition(curr); } + + void visitBreak(Break* curr) { + if (curr->condition) { + processCondition(curr); + } + } + + // TODO: BrOn, but the condition there is not an i32 + + bool addedInstrumentation = false; + + template void processCondition(T* curr) { + if (curr->condition->type == Type::unreachable) { + // This branch is not even reached. + return; + } + + auto likely = getFunction()->codeAnnotations[curr].branchLikely; + if (!likely) { + return; + } + + Builder builder(*getModule()); + + // Pick an ID for this branch. + int id = branchId++; + + // Instrument the condition. + auto tempLocal = builder.addVar(getFunction(), Type::i32); + auto* set = builder.makeLocalSet(tempLocal, curr->condition); + auto* idConst = builder.makeConst(Literal(int32_t(id))); + auto* guess = builder.makeConst(Literal(int32_t(*likely))); + auto* get1 = builder.makeLocalGet(tempLocal, Type::i32); + auto* log = builder.makeCall(logBranch, {idConst, guess, get1}, Type::none); + auto* get2 = builder.makeLocalGet(tempLocal, Type::i32); + curr->condition = builder.makeBlock({set, log, get2}); + addedInstrumentation = true; + } + + void doWalkFunction(Function* func) { + Super::doWalkFunction(func); + + // Our added blocks may have caused nested pops. + if (addedInstrumentation) { + EHUtils::handleBlockNestedPops(func, *getModule()); + addedInstrumentation = false; + } + } + + void doWalkModule(Module* module) { + // Find our import, if we were already run on this module. + for (auto& func : module->functions) { + if (func->module == MODULE && func->base == BASE) { + logBranch = func->name; + break; + } + } + // Otherwise, add it. + if (!logBranch) { + auto* func = module->addFunction(Builder::makeFunction( + Names::getValidFunctionName(*module, BASE), + Signature({Type::i32, Type::i32, Type::i32}, Type::none), + {})); + func->module = MODULE; + func->base = BASE; + logBranch = func->name; + } + + // Walk normally, using logBranch as we go. + Super::doWalkModule(module); + } +}; + +} // anonymous namespace + +Pass* createInstrumentBranchHintsPass() { return new InstrumentBranchHints(); } + +} // namespace wasm diff --git a/src/passes/RandomizeBranchHints.cpp b/src/passes/RandomizeBranchHints.cpp new file mode 100644 index 00000000000..7b013fc7ebf --- /dev/null +++ b/src/passes/RandomizeBranchHints.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2025 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. + */ + +// +// Apply random branch hints. This is really only useful for fuzzing. The +// randomness here is deterministic, so that reducing can work. +// + +#include "pass.h" +#include "support/hash.h" +#include "wasm-builder.h" +#include "wasm.h" + +namespace wasm { + +struct RandomizeBranchHints + : public WalkerPass< + PostWalker>> { + + uint64_t hash = 42; + + void visitExpression(Expression* curr) { + // Add some deterministic randomness as we go. + deterministic_hash_combine(hash, curr->_id); + } + + void visitIf(If* curr) { + deterministic_hash_combine(hash, 1337); + processCondition(curr); + } + + void visitBreak(Break* curr) { + deterministic_hash_combine(hash, 99999); + if (curr->condition) { + processCondition(curr); + } + } + + template void processCondition(T* curr) { + auto& likely = getFunction()->codeAnnotations[curr].branchLikely; + switch (hash % 3) { + case 0: + likely = true; + break; + case 1: + likely = false; + break; + case 2: + likely = {}; + break; + } + } +}; + +Pass* createRandomizeBranchHintsPass() { return new RandomizeBranchHints(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 2042bc71d3a..5e98ef5d086 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -259,6 +259,9 @@ void PassRegistry::registerPasses() { "trace-calls", "instrument the build with code to intercept specific function calls", createTraceCallsPass); + registerPass("instrument-branch-hints", + "instrument branch hints so we can see which guessed right", + createInstrumentBranchHintsPass); registerPass( "instrument-locals", "instrument the build with code to intercept all loads and stores", @@ -409,6 +412,9 @@ void PassRegistry::registerPasses() { registerPass("propagate-globals-globally", "propagate global values to other globals (useful for tests)", createPropagateGlobalsGloballyPass); + registerTestPass("randomize-branch-hints", + "randomize branch hints (for fuzzing)", + createRandomizeBranchHintsPass); registerPass("remove-non-js-ops", "removes operations incompatible with js", createRemoveNonJSOpsPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index e051e466e72..e0c03bad8d7 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -79,6 +79,7 @@ Pass* createLocalSubtypingPass(); Pass* createLogExecutionPass(); Pass* createIntrinsicLoweringPass(); Pass* createTraceCallsPass(); +Pass* createInstrumentBranchHintsPass(); Pass* createInstrumentLocalsPass(); Pass* createInstrumentMemoryPass(); Pass* createLLVMMemoryCopyFillLoweringPass(); @@ -130,6 +131,7 @@ Pass* createPrintCallGraphPass(); Pass* createPrintFeaturesPass(); Pass* createPrintFunctionMapPass(); Pass* createPropagateGlobalsGloballyPass(); +Pass* createRandomizeBranchHintsPass(); Pass* createRemoveNonJSOpsPass(); Pass* createRemoveImportsPass(); Pass* createRemoveMemoryInitPass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 5870b5c9d45..4c727bcbd1f 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -208,6 +208,9 @@ ;; CHECK-NEXT: --inlining-optimizing inline functions and optimizes ;; CHECK-NEXT: where we inlined ;; CHECK-NEXT: +;; CHECK-NEXT: --instrument-branch-hints instrument branch hints so we +;; CHECK-NEXT: can see which guessed right +;; CHECK-NEXT: ;; CHECK-NEXT: --instrument-locals instrument the build with code ;; CHECK-NEXT: to intercept all loads and ;; CHECK-NEXT: stores diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 34a2ab2f25c..a0d8d199f94 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -232,6 +232,9 @@ ;; CHECK-NEXT: --inlining-optimizing inline functions and optimizes ;; CHECK-NEXT: where we inlined ;; CHECK-NEXT: +;; CHECK-NEXT: --instrument-branch-hints instrument branch hints so we +;; CHECK-NEXT: can see which guessed right +;; CHECK-NEXT: ;; CHECK-NEXT: --instrument-locals instrument the build with code ;; CHECK-NEXT: to intercept all loads and ;; CHECK-NEXT: stores diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index ac68667554b..881e18950e7 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -172,6 +172,9 @@ ;; CHECK-NEXT: --inlining-optimizing inline functions and optimizes ;; CHECK-NEXT: where we inlined ;; CHECK-NEXT: +;; CHECK-NEXT: --instrument-branch-hints instrument branch hints so we +;; CHECK-NEXT: can see which guessed right +;; CHECK-NEXT: ;; CHECK-NEXT: --instrument-locals instrument the build with code ;; CHECK-NEXT: to intercept all loads and ;; CHECK-NEXT: stores diff --git a/test/lit/passes/instrument-branch-hints.wast b/test/lit/passes/instrument-branch-hints.wast new file mode 100644 index 00000000000..565bcf78716 --- /dev/null +++ b/test/lit/passes/instrument-branch-hints.wast @@ -0,0 +1,499 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --instrument-branch-hints -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (type $2 (func (result f64))) + + ;; CHECK: (type $3 (func (param anyref))) + + ;; CHECK: (type $4 (func (param i32 i32 i32))) + + ;; CHECK: (import "fuzzing-support" "log-branch" (func $log-branch (type $4) (param i32 i32 i32))) + + ;; CHECK: (tag $i32 (type $1) (param i32)) + (tag $i32 (param i32)) + + ;; CHECK: (func $if (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 99) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 142) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 11337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 199) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if + ;; An if with a 0 hint and another with a 1 hint. + (@metadata.code.branch_hint "\00") + (if + (i32.const 42) + (then + (drop (i32.const 1337)) + ) + (else + (drop (i32.const 99)) + ) + ) + (@metadata.code.branch_hint "\01") + (if + (i32.const 142) + (then + (drop (i32.const 11337)) + ) + (else + (drop (i32.const 199)) + ) + ) + ) + + ;; CHECK: (func $if-2 (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 242) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 21337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 299) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 342) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 31337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 399) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-2 + ;; An if with no hint, and another with 0 for more coverage. + (if + (i32.const 242) + (then + (drop (i32.const 21337)) + ) + (else + (drop (i32.const 299)) + ) + ) + (@metadata.code.branch_hint "\00") + (if + (i32.const 342) + (then + (drop (i32.const 31337)) + ) + (else + (drop (i32.const 399)) + ) + ) + ) + + ;; CHECK: (func $br (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $out1 + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out1 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 142) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 11337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br + ;; As above, with br_if, hints of 0 and 1. + (block $out + (@metadata.code.branch_hint "\00") + (br_if $out + (i32.const 42) + ) + (drop (i32.const 1337)) + ) + (block $out1 + (@metadata.code.branch_hint "\01") + (br_if $out1 + (i32.const 142) + ) + (drop (i32.const 11337)) + ) + ) + + ;; CHECK: (func $br-no (type $0) + ;; CHECK-NEXT: (block $out2 + ;; CHECK-NEXT: (br_if $out2 + ;; CHECK-NEXT: (i32.const 242) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 21337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-no + ;; A br_if with no hint. + (block $out2 + (br_if $out2 + (i32.const 242) + ) + (drop (i32.const 21337)) + ) + ) + + ;; CHECK: (func $br_value (type $2) (result f64) + ;; CHECK-NEXT: (local $scratch f64) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (block $out (result f64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_value (result f64) + ;; As above, but now with a value. + (block $out (result f64) + (@metadata.code.branch_hint "\00") + (br_if $out + (f64.const 3.14159) + (i32.const 42) + ) + (drop (i32.const 1337)) + ) + ) + + ;; CHECK: (func $nested (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 142) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 242) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 9) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 342) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested + ;; We should instrument all these, even the nested ones. + (@metadata.code.branch_hint "\00") + (if + (@metadata.code.branch_hint "\01") + (if (result i32) + (i32.const 42) + (then + (i32.const 142) + ) + (else + (i32.const 242) + ) + ) + (then + (@metadata.code.branch_hint "\00") + (if + (i32.const 342) + (then + (drop (i32.const 1337)) + ) + ) + ) + ) + ) + + ;; CHECK: (func $br_on (type $3) (param $x anyref) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_on_null $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on (param $x anyref) + ;; We do not instrument BrOn yet: the condition is not an i32 in this case, + ;; so logging is trickier. TODO + (block $out + (drop + (@metadata.code.branch_hint "\00") + (br_on_null $out + (local.get $x) + ) + ) + ) + ) + + ;; CHECK: (func $eh-pop (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (block $label + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $i32 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log-branch + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $eh-pop + (block $label + (try + (do + (nop) + ) + (catch $i32 + (@metadata.code.branch_hint "\00") + (br_if $label + ;; This pop will end up in a block after our instrumentation, which + ;; requires fixups. + (pop i32) + ) + ) + ) + ) + ) +) + +;; This module has our import, but with a minified internal name. We should use +;; that import. +(module + ;; CHECK: (type $0 (func (param i32 i32 i32))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "fuzzing-support" "log-branch" (func $min (type $0) (param i32 i32 i32))) + (import "fuzzing-support" "log-branch" (func $min (param i32 i32 i32))) + + ;; CHECK: (func $if (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $min + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $min + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if + (local $x i32) + (@metadata.code.branch_hint "\01") + (if + (block (result i32) + (local.set $x + (i32.const 42) + ) + (call $min + (i32.const 42) + (i32.const 1) + (local.get $x) + ) + (local.get $x) + ) + (then + (drop (i32.const 1337)) + ) + ) + ) +) diff --git a/test/lit/passes/randomize-branch-hints.wast b/test/lit/passes/randomize-branch-hints.wast new file mode 100644 index 00000000000..b423f848b9a --- /dev/null +++ b/test/lit/passes/randomize-branch-hints.wast @@ -0,0 +1,308 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --randomize-branch-hints -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (result f64))) + + ;; CHECK: (func $if + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 99) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 142) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 11337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 199) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 242) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 21337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 299) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 342) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 31337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 399) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if + ;; We should see various branch hints appear, both true and false, and also + ;; some instructions with no hint. + (if + (i32.const 42) + (then + (drop (i32.const 1337)) + ) + (else + (drop (i32.const 99)) + ) + ) + (if + (i32.const 142) + (then + (drop (i32.const 11337)) + ) + (else + (drop (i32.const 199)) + ) + ) + (if + (i32.const 242) + (then + (drop (i32.const 21337)) + ) + (else + (drop (i32.const 299)) + ) + ) + (if + (i32.const 342) + (then + (drop (i32.const 31337)) + ) + (else + (drop (i32.const 399)) + ) + ) + ) + + ;; CHECK: (func $if-existing + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 99) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 142) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 11337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 199) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 242) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 21337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 299) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 342) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 31337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 399) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-existing + ;; We do not error on existing hints, and trample/remove them. + (@metadata.code.branch_hint "\01") + (if + (i32.const 42) + (then + (drop (i32.const 1337)) + ) + (else + (drop (i32.const 99)) + ) + ) + (@metadata.code.branch_hint "\00") + (if + (i32.const 142) + (then + (drop (i32.const 11337)) + ) + (else + (drop (i32.const 199)) + ) + ) + (@metadata.code.branch_hint "\01") + (if + (i32.const 242) + (then + (drop (i32.const 21337)) + ) + (else + (drop (i32.const 299)) + ) + ) + (@metadata.code.branch_hint "\00") + (if + (i32.const 342) + (then + (drop (i32.const 31337)) + ) + (else + (drop (i32.const 399)) + ) + ) + ) + + ;; CHECK: (func $br_if + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $out1 + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out1 + ;; CHECK-NEXT: (i32.const 142) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 11337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $out2 + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out2 + ;; CHECK-NEXT: (i32.const 242) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 21337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_if + ;; As above, with br_if. + (block $out + (br_if $out + (i32.const 42) + ) + (drop (i32.const 1337)) + ) + (block $out1 + (br_if $out1 + (i32.const 142) + ) + (drop (i32.const 11337)) + ) + (block $out2 + ;; Existing hint. + (@metadata.code.branch_hint "\01") + (br_if $out2 + (i32.const 242) + ) + (drop (i32.const 21337)) + ) + ) + + ;; CHECK: (func $br_value (result f64) + ;; CHECK-NEXT: (local $scratch f64) + ;; CHECK-NEXT: (block $out (result f64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_value (result f64) + ;; As above, but now with a value. We should not error. + (block $out (result f64) + (br_if $out + (f64.const 3.14159) + (i32.const 42) + ) + (drop (i32.const 1337)) + ) + ) + + ;; CHECK: (func $br + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br + ;; As above, but now without a condition. We should not error. + (block $out + (br $out + (i32.const 42) + ) + (drop (i32.const 1337)) + ) + ) +) From 7f642c975c17f52a5a923d182b2387860cbf53a3 Mon Sep 17 00:00:00 2001 From: Andy Simpson Date: Tue, 8 Jul 2025 02:15:56 +0100 Subject: [PATCH 584/622] Fix build failure with Xcode/AppleClang 17 (#7699) Addresses the issue reported in #7606 "Cannot build on MacOS". A header included from LLVM includes a reference to `std::iterator` which is deprecated in C++17; this trips a `deprecated-declarations` warning which becomes a compile error via `-Werror`. This patch selectively causes that warning to be ignored. Fixes #7606. --- src/wasm/wasm-debug.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/wasm/wasm-debug.cpp b/src/wasm/wasm-debug.cpp index abe1cd05c1f..557d6a58a4a 100644 --- a/src/wasm/wasm-debug.cpp +++ b/src/wasm/wasm-debug.cpp @@ -18,7 +18,22 @@ #include "wasm.h" #ifdef BUILD_LLVM_DWARF + +#ifdef __clang__ +// DWARFEmitter.h transitively includes llvm/ADT/iterator.h, which uses +// std::iterator, which is deprecated in C++17. This can trigger a deprecation +// warning which if -Werror is enabled can cause the build to fail. That warning +// is suppressed while including DWARFEmitter.h to allow the build to succeed. +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wdeprecated-declarations" +#endif + #include "llvm/ObjectYAML/DWARFEmitter.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + #include "llvm/ObjectYAML/DWARFYAML.h" #include "llvm/include/llvm/DebugInfo/DWARFContext.h" From 3db6220b8abb03eef649cea4c42706caac6f9b9b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 7 Jul 2025 20:25:39 -0700 Subject: [PATCH 585/622] [Custom Descriptors] Fix bug optimizing struct.new (#7701) When all of the non-descriptor operands to struct.new have default values, we can optimize the struct.new to struct.new_default in OptimizeInstructions. When we do this, we drop all the children with side effects. This previously included the descriptor operand, even though we did not clear the descriptor operand from the StructNew parent expression, causing the descriptor operand to appear twice in the IR. This is invalid and could lead to assertion failures elsewhere. To avoid the problem, clear the descriptor operand while we call the utility to drop the StructNew children and restore it afterward. This is somewhat ugly, but better than duplicating the functionality of the utility, except ignoring the descriptor. --- src/passes/OptimizeInstructions.cpp | 8 +++- .../passes/optimize-instructions-desc.wast | 39 +++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index f53987829ad..e4d5a94339c 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1863,9 +1863,15 @@ struct OptimizeInstructions } } - // Success! Drop the children and return a struct.new_with_default. + // Success! Drop the children and return a struct.new_with_default. We don't + // want the descriptor to be dropped, however, so temporarily remove it + // while we drop the other children. If the descriptor is null, this makes + // no difference. + auto* desc = curr->desc; + curr->desc = nullptr; auto* rep = getDroppedChildrenAndAppend(curr, curr); curr->operands.clear(); + curr->desc = desc; assert(curr->isWithDefault()); replaceCurrent(rep); } diff --git a/test/lit/passes/optimize-instructions-desc.wast b/test/lit/passes/optimize-instructions-desc.wast index 0c00fcc1318..76654119f3d 100644 --- a/test/lit/passes/optimize-instructions-desc.wast +++ b/test/lit/passes/optimize-instructions-desc.wast @@ -11,7 +11,18 @@ (type $desc (describes $struct (struct))) ) - ;; CHECK: (func $trap-null-desc (type $2) (result (ref (exact $struct))) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct-i32 (descriptor $struct-i32.desc (struct (field i32)))) + (type $struct-i32 (descriptor $struct-i32.desc (struct (field i32)))) + ;; CHECK: (type $struct-i32.desc (describes $struct-i32 (struct))) + (type $struct-i32.desc (describes $struct-i32 (struct))) + ) + + ;; CHECK: (import "" "" (func $effect (type $5))) + (import "" "" (func $effect)) + + ;; CHECK: (func $trap-null-desc (type $4) (result (ref (exact $struct))) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $trap-null-desc (result (ref (exact $struct))) @@ -20,7 +31,7 @@ ) ) - ;; CHECK: (func $trap-null-desc-fallthrough (type $2) (result (ref (exact $struct))) + ;; CHECK: (func $trap-null-desc-fallthrough (type $4) (result (ref (exact $struct))) ;; CHECK-NEXT: (local $desc (ref null (exact $desc))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $desc @@ -38,7 +49,7 @@ ) ) - ;; CHECK: (func $nonnull-cast-desc (type $3) (param $desc (ref null (exact $desc))) (result (ref (exact $struct))) + ;; CHECK: (func $nonnull-cast-desc (type $6) (param $desc (ref null (exact $desc))) (result (ref (exact $struct))) ;; CHECK-NEXT: (struct.new_default $struct ;; CHECK-NEXT: (local.get $desc) ;; CHECK-NEXT: ) @@ -50,4 +61,26 @@ ) ) ) + + ;; Test that when we optimize a struct.new to a struct.new_default, we drop + ;; the field operands but keep the descriptor. + ;; CHECK: (func $new-default-keep-desc (type $7) (result anyref) + ;; CHECK-NEXT: (struct.new_default $struct-i32 + ;; CHECK-NEXT: (block (result (ref (exact $struct-i32.desc))) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (struct.new_default $struct-i32.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $new-default-keep-desc (result anyref) + (struct.new $struct-i32 + (i32.const 0) + (block (result (ref (exact $struct-i32.desc))) + ;; This would cause the descriptor to be dropped if it were dropped with + ;; the other children. + (call $effect) + (struct.new_default $struct-i32.desc) + ) + ) + ) ) From 9c5a5ad5f109f6a2146d7b8f8ad69b087dfc7d1b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 8 Jul 2025 13:55:06 -0700 Subject: [PATCH 586/622] [Custom Descriptors] Do not optimize in TypeSSA (#7702) Optimizing allocations of descriptor or described types in TypeSSA would be complicated. Creating a subtype of a described type would necesitate creating a subtype of its descriptor type as well, possibly in an arbitrarily long chain. For now, simply do not optimize these types. Add tests to exercise interesting corner cases that would come up if we did optimize, though. These tests also show that we successfully avoid crashing when optimizing modules with descriptor and described types. --- scripts/test/fuzzing.py | 1 + src/passes/TypeSSA.cpp | 10 +- test/lit/passes/type-ssa-desc.wast | 224 +++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 test/lit/passes/type-ssa-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 4efa7273f7c..c14dcac786c 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -128,6 +128,7 @@ 'simplify-locals-desc.wast', 'optimize-instructions-desc.wast', 'gto-desc.wast', + 'type-ssa-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 1bfd815292d..99221ab0e38 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -460,11 +460,17 @@ struct TypeSSA : public Pass { return false; } - if (!curr->type.getHeapType().isOpen()) { + auto type = curr->type.getHeapType(); + if (!type.isOpen()) { // We can't create new subtypes of a final type anyway. return false; } + // Do not attempt to optimize types with descriptors or describees yet. + if (type.getDescribedType() || type.getDescriptorType()) { + return false; + } + // Look for at least one interesting operand. We will consider each operand // against the declared type, that is, the type declared for where it is // stored. If it has more information than the declared type then it is @@ -491,8 +497,6 @@ struct TypeSSA : public Pass { return false; }; - auto type = curr->type.getHeapType(); - if (auto* structNew = curr->dynCast()) { if (structNew->isWithDefault()) { // This starts with all default values - zeros and nulls - and that diff --git a/test/lit/passes/type-ssa-desc.wast b/test/lit/passes/type-ssa-desc.wast new file mode 100644 index 00000000000..b4d5f972470 --- /dev/null +++ b/test/lit/passes/type-ssa-desc.wast @@ -0,0 +1,224 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --type-ssa -all -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) + (type $struct (descriptor $desc (struct))) + ;; CHECK: (type $desc (sub (describes $struct (struct (field i32))))) + (type $desc (sub (describes $struct (struct (field i32))))) + ) + ;; CHECK: (type $2 (func (result (ref $desc)))) + + ;; CHECK: (func $no-opt-desc (type $2) (result (ref $desc)) + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + (func $no-opt-desc (result (ref $desc)) + ;; If we tried to optimize this allocation, we would have to create a new + ;; type for our new descriptor type to describe. This isn't very useful + ;; unless we can optimize a corresponding allocation of the described type, + ;; so do not optimize here. + (struct.new_default $desc) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct (field i32))))) + (type $struct (sub (descriptor $desc (struct (field i32))))) + ;; CHECK: (type $desc (describes $struct (struct))) + (type $desc (sub final (describes $struct (struct)))) + ) + + ;; CHECK: (type $2 (func (result (ref $struct)))) + + ;; CHECK: (func $final-desc (type $2) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $final-desc (result (ref $struct)) + ;; We could optimize the allocation of the struct, but we would need to + ;; update the descriptor to be a corresponding new subtype. The descriptor + ;; is final, so this is not posssible and we cannot optimize. + (struct.new_default $struct + (struct.new $desc) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct (field i32))))) + (type $struct (sub (descriptor $desc (struct (field i32))))) + ;; CHECK: (type $desc (sub (describes $struct (struct)))) + (type $desc (sub (describes $struct (struct)))) + ) + + ;; CHECK: (type $2 (func (result (ref $struct)))) + + ;; CHECK: (func $opt (type $2) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $opt (result (ref $struct)) + ;; Now we could optimize, but we would still have to introduce a new + ;; descriptor subtype. We do not support this yet. + (struct.new_default $struct + (struct.new $desc) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $B (struct (field i32))))) + (type $A (sub (descriptor $B (struct (field i32))))) + ;; CHECK: (type $B (sub (describes $A (descriptor $C (struct))))) + (type $B (sub (describes $A (descriptor $C (struct))))) + ;; CHECK: (type $C (sub (describes $B (descriptor $D (struct))))) + (type $C (sub (describes $B (descriptor $D (struct))))) + ;; CHECK: (type $D (sub (describes $C (struct)))) + (type $D (sub (describes $C (struct)))) + ) + + ;; CHECK: (type $4 (func (result (ref $A)))) + + ;; CHECK: (func $opt-chain (type $4) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A + ;; CHECK-NEXT: (struct.new_default $B + ;; CHECK-NEXT: (struct.new_default $C + ;; CHECK-NEXT: (struct.new_default $D) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $opt-chain (result (ref $A)) + ;; Now optimizing would require updating a whole chain of descriptors. + (struct.new_default $A + (struct.new $B + (struct.new $C + (struct.new $D) + ) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct (field i32))))) + (type $struct (sub (descriptor $desc (struct (field i32))))) + ;; CHECK: (type $desc (sub (describes $struct (struct)))) + (type $desc (sub (describes $struct (struct)))) + ) + + ;; CHECK: (type $2 (func (result (ref $struct)))) + + ;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc)) + (global $desc (ref (exact $desc)) (struct.new $desc)) + + ;; CHECK: (func $global-desc (type $2) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $global-desc (result (ref $struct)) + ;; Optimizing below would require creating a new global to hold the new + ;; descriptor subtype. + (struct.new_default $struct + (global.get $desc) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct (field i32))))) + (type $struct (sub (descriptor $desc (struct (field i32))))) + ;; CHECK: (type $desc (sub (describes $struct (struct)))) + (type $desc (sub (describes $struct (struct)))) + ) + + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc)) + (global $desc (ref (exact $desc)) (struct.new $desc)) + + ;; CHECK: (func $no-opt-desc-identity (type $2) (result i32) + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (ref.get_desc $struct + ;; CHECK-NEXT: (block (result (ref (exact $struct))) + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $no-opt-desc-identity (result i32) + ;; Same as above, but now we cannot optimize because the identity of the + ;; descriptors matters and we cannot replace them with different subtypes. + (ref.eq + (ref.get_desc $struct + ;; Use a block to stop the ref.get_desc from observing the exactness of + ;; the allocation, which would separately inhibit optimization. + (block (result (ref $struct)) + (struct.new_default $struct + (global.get $desc) + ) + ) + ) + (global.get $desc) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $B (struct (field i32))))) + (type $A (sub (descriptor $B (struct (field i32))))) + ;; CHECK: (type $B (sub (describes $A (descriptor $C (struct))))) + (type $B (sub (describes $A (descriptor $C (struct))))) + ;; CHECK: (type $C (sub (describes $B (descriptor $D (struct))))) + (type $C (sub (describes $B (descriptor $D (struct))))) + ;; CHECK: (type $D (sub (describes $C (struct)))) + (type $D (sub (describes $C (struct)))) + ) + + ;; CHECK: (type $4 (func (result (ref $A)))) + + ;; CHECK: (global $D (ref (exact $D)) (struct.new_default $D)) + (global $D (ref (exact $D)) (struct.new $D)) + ;; CHECK: (global $C (ref (exact $C)) (struct.new_default $C + ;; CHECK-NEXT: (global.get $D) + ;; CHECK-NEXT: )) + (global $C (ref (exact $C)) (struct.new $C (global.get $D))) + ;; CHECK: (global $B (ref (exact $B)) (struct.new_default $B + ;; CHECK-NEXT: (global.get $C) + ;; CHECK-NEXT: )) + (global $B (ref (exact $B)) (struct.new $B (global.get $C))) + + ;; CHECK: (func $opt-global-chain (type $4) (result (ref $A)) + ;; CHECK-NEXT: (struct.new_default $A + ;; CHECK-NEXT: (global.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $opt-global-chain (result (ref $A)) + ;; Same as above, but now we would have to copy a whole chain of descriptor + ;; globals. + (struct.new_default $A + (global.get $B) + ) + ) +) From d5e7f187d21320725a80227e9c9261660f338172 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jul 2025 08:12:31 -0700 Subject: [PATCH 587/622] [Fuzzing] Add an env var to avoid modifying the given wasm file in fuzz_opt.py (#7703) Normally we want to fix it up (remove NaNs etc., as we do normally), but the changes in each fuzzing iteration can make things hard to debug. Add an env var to simply trust the given wasm file and use it without changes. --- scripts/fuzz_opt.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 8f832187f5c..5c06ef33995 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1874,15 +1874,21 @@ def test_one(random_input, given_wasm): print() if given_wasm: - # if given a wasm file we want to use it as is, but we also want to - # apply properties like not having any NaNs, which the original fuzz - # wasm had applied. that is, we need to preserve properties like not - # having nans through reduction. - try: - run([in_bin('wasm-opt'), given_wasm, '-o', abspath('a.wasm')] + GEN_ARGS + FEATURE_OPTS) - except Exception as e: - print("Internal error in fuzzer! Could not run given wasm") - raise e + # We are given a wasm file to operate on. By default we modify it in the + # usual ways, like running DeNAN on it, which is important in many cases + # (imagine the reducer generates a NAN, then we need to restore the + # property of not having any). However, in some cases we do need to + # trust the wasm is correct, or it is simpler to debug things without + # constant changes in each reduction cycle, so we have an env var to + # control that, BINARYEN_TRUST_GIVEN_WASM. + if os.environ.get('BINARYEN_TRUST_GIVEN_WASM'): + shutil.copyfile(given_wasm, abspath('a.wasm')) + else: + try: + run([in_bin('wasm-opt'), given_wasm, '-o', abspath('a.wasm')] + GEN_ARGS + FEATURE_OPTS) + except Exception as e: + print("Internal error in fuzzer! Could not run given wasm") + raise e else: # emit the target features section so that reduction can work later, # without needing to specify the features @@ -2351,7 +2357,9 @@ def get_random_opts(): (If it does not, then one possible issue is that the fuzzer fails to write a valid binary. If so, you can print the output of the fuzzer's first command (using -ttf / --translate-to-fuzz) in text form and run the reduction from that, -passing --text to the reducer.) +passing --text to the reducer. Another possible fix is to avoid re-processing +the wasm for fuzzing in each iteration, by adding +BINARYEN_TRUST_GIVEN_WASM=1 in the env.) You can also read "%(reduce_sh)s" which has been filled out for you and includes docs and suggestions. From c3f7e2e2144998c1e5b9284c386fdbf1871b98bd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jul 2025 16:51:24 -0700 Subject: [PATCH 588/622] [Branch Hinting] Add branch hint handling in RemoveUnusedBrs (#7706) Add some utilities for easily updating/copying/clearing branch hints, then use those in the pass. As a drive-by, move flip() from wasm-builder.h, which everyone was including but only this one cpp file was using, and update it in the new location (this avoids including the new branch hints header in a central place). --- src/ir/branch-hints.h | 126 +++ src/passes/RemoveUnusedBrs.cpp | 36 +- src/wasm-builder.h | 5 - ...remove-unused-brs_branch-hints-shrink.wast | 249 ++++++ .../remove-unused-brs_branch-hints.wast | 724 ++++++++++++++++++ 5 files changed, 1129 insertions(+), 11 deletions(-) create mode 100644 src/ir/branch-hints.h create mode 100644 test/lit/passes/remove-unused-brs_branch-hints-shrink.wast create mode 100644 test/lit/passes/remove-unused-brs_branch-hints.wast diff --git a/src/ir/branch-hints.h b/src/ir/branch-hints.h new file mode 100644 index 00000000000..a15198f54a8 --- /dev/null +++ b/src/ir/branch-hints.h @@ -0,0 +1,126 @@ +/* + * Copyright 2025 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_ir_branch_hint_h +#define wasm_ir_branch_hint_h + +#include "wasm.h" + +// +// Branch hint utilities to get them, set, flip, etc. +// + +namespace wasm::BranchHints { + +// Get the branch hint for an expression. +inline std::optional get(Expression* expr, Function* func) { + auto iter = func->codeAnnotations.find(expr); + if (iter == func->codeAnnotations.end()) { + // No annotations at all. + return {}; + } + return iter->second.branchLikely; +} + +// Set the branch hint for an expression, trampling anything existing before. +inline void set(Expression* expr, std::optional likely, Function* func) { + // When we are writing an empty hint, do not create an empty annotation if one + // did not exist. + if (!likely && !func->codeAnnotations.count(expr)) { + return; + } + func->codeAnnotations[expr].branchLikely = likely; +} + +// Clear the branch hint for an expression. +inline void clear(Expression* expr, Function* func) { + // Do not create an empty annotation if one did not exist. + auto iter = func->codeAnnotations.find(expr); + if (iter == func->codeAnnotations.end()) { + return; + } + iter->second.branchLikely = {}; +} + +// Copy the branch hint for an expression to another, trampling anything +// existing before for the latter. +inline void copyTo(Expression* from, Expression* to, Function* func) { + auto fromLikely = get(from, func); + set(to, fromLikely, func); +} + +// Flip the branch hint for an expression (if it exists). +inline void flip(Expression* expr, Function* func) { + if (auto likely = get(expr, func)) { + set(expr, !*likely, func); + } +} + +// Copy the branch hint for an expression to another, flipping it while we do +// so. +inline void copyFlippedTo(Expression* from, Expression* to, Function* func) { + copyTo(from, to, func); + flip(to, func); +} + +// Given two expressions to read from, apply the AND hint to a target. That is, +// the target will be true when both inputs are true. |to| may be equal to +// |from1| or |from2|. The hint of |to| is trampled. +inline void applyAndTo(Expression* from1, + Expression* from2, + Expression* to, + Function* func) { + // If from1 and from2 are both likely, then from1 && from2 is slightly less + // likely, but we assume our hints are nearly certain, so we apply it. And, + // converse, if from1 and from2 and both unlikely, then from1 && from2 is even + // less likely, so we can once more apply a hint. If the hints differ, then + // one is unlikely or unknown, and we can't say anything about from1 && from2. + auto from1Hint = BranchHints::get(from1, func); + auto from2Hint = BranchHints::get(from2, func); + if (from1Hint == from2Hint) { + set(to, from1Hint, func); + } else { + // The hints do not even match. + BranchHints::clear(to, func); + } +} + +// As |applyAndTo|, but now the condition on |to| the OR of |from1| and |from2|. +inline void applyOrTo(Expression* from1, + Expression* from2, + Expression* to, + Function* func) { + // If one is likely then so is the from1 || from2. If both are unlikely then + // from1 || from2 is slightly more likely, but we assume our hints are nearly + // certain, so we apply it. + auto from1Hint = BranchHints::get(from1, func); + auto from2Hint = BranchHints::get(from2, func); + if ((from1Hint && *from1Hint) || (from2Hint && *from2Hint)) { + set(to, true, func); + } else if (from1Hint && from2Hint) { + // We ruled out that either one is present and true, so if both are present, + // both must be false. + assert(!*from1Hint && !*from2Hint); + set(to, false, func); + } else { + // We don't know. + BranchHints::clear(to, func); + } +} + +} // namespace wasm::BranchHints + +#endif // wasm_ir_branch_hint_h diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 64c68f2354e..5b505cf1d60 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -18,6 +18,7 @@ // Removes branches for which we go to where they go anyhow // +#include "ir/branch-hints.h" #include "ir/branch-utils.h" #include "ir/cost.h" #include "ir/drop.h" @@ -396,6 +397,7 @@ struct RemoveUnusedBrs : public WalkerPass> { curr->condition, br->value, getPassOptions(), *getModule())) { if (!br->condition) { br->condition = curr->condition; + BranchHints::copyTo(curr, br, getFunction()); } else { // In this case we can replace // if (condition1) br_if (condition2) @@ -427,6 +429,7 @@ struct RemoveUnusedBrs : public WalkerPass> { // That keeps the order of the two conditions as it was originally. br->condition = builder.makeSelect(br->condition, curr->condition, zero); + BranchHints::applyAndTo(curr, br, br, getFunction()); } br->finalize(); replaceCurrent(Builder(*getModule()).dropIfConcretelyTyped(br)); @@ -459,6 +462,7 @@ struct RemoveUnusedBrs : public WalkerPass> { Builder builder(*getModule()); curr->condition = builder.makeSelect( child->condition, curr->condition, builder.makeConst(int32_t(0))); + BranchHints::applyAndTo(curr, child, curr, getFunction()); curr->ifTrue = child->ifTrue; } } @@ -689,6 +693,7 @@ struct RemoveUnusedBrs : public WalkerPass> { brIf->condition = builder.makeUnary(EqZInt32, brIf->condition); last->name = brIf->name; brIf->name = loop->name; + BranchHints::flip(brIf, getFunction()); return true; } else { // there are elements in the middle, @@ -709,6 +714,7 @@ struct RemoveUnusedBrs : public WalkerPass> { builder.makeIf(brIf->condition, builder.makeBreak(brIf->name), stealSlice(builder, block, i + 1, list.size())); + BranchHints::copyTo(brIf, list[i], getFunction()); block->finalize(); return true; } @@ -1210,6 +1216,7 @@ struct RemoveUnusedBrs : public WalkerPass> { // we are an if-else where the ifTrue is a break without a // condition, so we can do this ifTrueBreak->condition = iff->condition; + BranchHints::copyTo(iff, ifTrueBreak, getFunction()); ifTrueBreak->finalize(); list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifTrueBreak); ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifFalse); @@ -1224,6 +1231,7 @@ struct RemoveUnusedBrs : public WalkerPass> { *getModule())) { ifFalseBreak->condition = Builder(*getModule()).makeUnary(EqZInt32, iff->condition); + BranchHints::copyFlippedTo(iff, ifFalseBreak, getFunction()); ifFalseBreak->finalize(); list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifFalseBreak); ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifTrue); @@ -1256,7 +1264,9 @@ struct RemoveUnusedBrs : public WalkerPass> { Builder builder(*getModule()); br1->condition = builder.makeBinary(OrInt32, br1->condition, br2->condition); + BranchHints::applyOrTo(br1, br2, br1, getFunction()); ExpressionManipulator::nop(br2); + BranchHints::clear(br2, getFunction()); } } } else { @@ -1396,9 +1406,12 @@ struct RemoveUnusedBrs : public WalkerPass> { // no other breaks to that name, so we can do this if (!drop) { assert(!br->value); - replaceCurrent(builder.makeIf( - builder.makeUnary(EqZInt32, br->condition), curr)); + auto* iff = builder.makeIf( + builder.makeUnary(EqZInt32, br->condition), curr); + replaceCurrent(iff); + BranchHints::copyFlippedTo(br, iff, getFunction()); ExpressionManipulator::nop(br); + BranchHints::clear(br, getFunction()); curr->finalize(curr->type); } else { // To use an if, the value must have no side effects, as in the @@ -1409,8 +1422,9 @@ struct RemoveUnusedBrs : public WalkerPass> { if (EffectAnalyzer::canReorder( passOptions, *getModule(), br->condition, br->value)) { ExpressionManipulator::nop(list[0]); - replaceCurrent( - builder.makeIf(br->condition, br->value, curr)); + auto* iff = builder.makeIf(br->condition, br->value, curr); + BranchHints::copyTo(br, iff, getFunction()); + replaceCurrent(iff); } } else { // The value has side effects, so it must always execute. We @@ -1529,6 +1543,14 @@ struct RemoveUnusedBrs : public WalkerPass> { optimizeSetIf(getCurrentPointer()); } + // Flip an if's condition with an eqz, and flip its arms. + void flip(If* iff) { + std::swap(iff->ifTrue, iff->ifFalse); + iff->condition = + Builder(*getModule()).makeUnary(EqZInt32, iff->condition); + BranchHints::flip(iff, getFunction()); + } + void optimizeSetIf(Expression** currp) { if (optimizeSetIfWithBrArm(currp)) { return; @@ -1570,9 +1592,10 @@ struct RemoveUnusedBrs : public WalkerPass> { // Wonderful, do it! Builder builder(*getModule()); if (flipCondition) { - builder.flip(iff); + flip(iff); } br->condition = iff->condition; + BranchHints::copyTo(iff, br, getFunction()); br->finalize(); set->value = two; auto* block = builder.makeSequence(br, set); @@ -1640,7 +1663,7 @@ struct RemoveUnusedBrs : public WalkerPass> { Builder builder(*getModule()); LocalGet* get = iff->ifTrue->dynCast(); if (get && get->index == set->index) { - builder.flip(iff); + flip(iff); } else { get = iff->ifFalse->dynCast(); if (get && get->index != set->index) { @@ -1901,6 +1924,7 @@ struct RemoveUnusedBrs : public WalkerPass> { curr->type = Type::unreachable; block->list.push_back(curr); block->finalize(); + BranchHints::clear(curr, getFunction()); // The type changed, so refinalize. refinalize = true; } else { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 00612a4020b..dc877bd0223 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1481,11 +1481,6 @@ class Builder { return makeDrop(curr); } - void flip(If* iff) { - 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 best we can. As a replacement, this may reuse the input node. template Expression* replaceWithIdenticalType(T* curr) { diff --git a/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast b/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast new file mode 100644 index 00000000000..21c08e8cd4c --- /dev/null +++ b/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast @@ -0,0 +1,249 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-brs -all --shrink-level=1 -S -o - \ +;; RUN: | filecheck %s + +(module + ;; CHECK: (import "a" "b" (func $none (type $1))) + (import "a" "b" (func $none)) + + ;; CHECK: (func $join-br_ifs (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs (param $x i32) (param $y i32) + ;; The br_ifs will be joined into a single one. The hint should propagate, + ;; as it matches. + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + ;; Extra code so that the entire testcase does not get optimized out as + ;; trivial. + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-0 (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-0 (param $x i32) (param $y i32) + ;; The hints once more match, but now are 0. We still propagate. + (block $out + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-no (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-no (param $x i32) (param $y i32) + ;; One is missing a hint, so we clear the hint. + (block $out + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-no-flip (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-no-flip (param $x i32) (param $y i32) + ;; The other one is missing the hint, so we clear the hint. + (block $out + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-yes-one (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-yes-one (param $x i32) (param $y i32) + ;; One has a 1 hint, so we can use that. + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $x) + ) + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-yes-other (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-yes-other (param $x i32) (param $y i32) + ;; As above, but the other has the 1, which we can still use. + (block $out + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-mismatch (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-mismatch (param $x i32) (param $y i32) + ;; The hints do not match, but we can still use the 1 hint. + (block $out + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) +) diff --git a/test/lit/passes/remove-unused-brs_branch-hints.wast b/test/lit/passes/remove-unused-brs_branch-hints.wast new file mode 100644 index 00000000000..321fbab8146 --- /dev/null +++ b/test/lit/passes/remove-unused-brs_branch-hints.wast @@ -0,0 +1,724 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-brs -all -S -o - \ +;; RUN: | filecheck %s + +(module + ;; CHECK: (import "a" "b" (func $i32 (type $3) (result i32))) + (import "a" "b" (func $i32 (result i32))) + ;; CHECK: (import "a" "b" (func $none (type $2))) + (import "a" "b" (func $none)) + + ;; CHECK: (tag $e (type $2)) + (tag $e) + + ;; CHECK: (func $if-br (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br (param $x i32) (param $y i32) + (block $out + ;; This nop prevents the entire testcase from being trivial. + (nop) + ;; The if-br will turn into a br_if. The branch hint should then go on the + ;; br_if, and remain 01. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (br $out) + ) + ) + ) + ) + + ;; CHECK: (func $if-br_0 (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br_0 (param $x i32) (param $y i32) + (block $out + (nop) + ;; As above, but a hint of 0. + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (br $out) + ) + ) + ) + ) + + ;; CHECK: (func $if-br_if (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br_if (param $x i32) (param $y i32) + (block $out + (nop) + ;; As above, but the br has a condition. We can merge conditions (using a + ;; select), and then move the hint to the br_if, as the br_if has the + ;; same hint as the if. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-br_if-no (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br_if-no (param $x i32) (param $y i32) + (block $out + (nop) + ;; As above, but the br lacks a hint, so we emit no hint. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (br_if $out + (local.get $y) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-br_if-no-2 (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br_if-no-2 (param $x i32) (param $y i32) + (block $out + (nop) + ;; As above, but now the if lacks a hint, so we emit no hint. + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-if-1* (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-if-1* (param $x i32) (param $y i32) + ;; Both ifs have a hint of 1, so after we merge the ifs the combined + ;; condition remains likely. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ;; The outer if still has a hint of 1, but the inner is 0. We emit no hint. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\00") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ;; The outer if still has a hint of 1, but the inner has none. We emit no + ;; hint. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-if-0* (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-if-0* (param $x i32) (param $y i32) + ;; As above, but now the outer if has hints of 0. + + ;; The hints do not match, so we emit no hint. + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ;; The hints match, so the combined condition is unlikely. + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\00") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ;; Inner lacks a hint, so we emit nothing. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-if-?* (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-if-?* (param $x i32) (param $y i32) + ;; As above, but now the outer if has no hint. We emit no hints here. + + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\00") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + (if + (local.get $x) + (then + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ) + + ;; CHECK: (func $loop-br_if-flip (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-br_if-flip (param $x i32) + (block $block + (loop $loop + ;; This br_if's condition will flip when it is turned from a break out + ;; of the loop to a continue inside it. The hint should flip too. + (@metadata.code.branch_hint "\00") + (br_if $block + (local.get $x) + ) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $loop-br_if-flip-reverse (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-br_if-flip-reverse (param $x i32) + ;; As above, with a hint of 1, that should flip to 0. + (block $block + (loop $loop + (@metadata.code.branch_hint "\01") + (br_if $block + (local.get $x) + ) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $loop-br_if-if (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-br_if-if (param $x i32) + (loop $loop + (block $block + ;; This br_if will turn into an if with the same condition. The hint can + ;; be copied over. + (@metadata.code.branch_hint "\00") + (br_if $block + (local.get $x) + ) + ;; Extra code so simpler optimizations do not kick in. + (drop (i32.const 42)) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $throw-if-br_if-0 (type $0) (param $x i32) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $catch + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-if-br_if-0 (param $x i32) + (block $catch + (try_table (catch_all $catch) + ;; This if can turn into a br_if. The branch hint should be copied. + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (throw $e) + ) + ) + ) + ) + ) + + ;; CHECK: (func $throw-if-br_if-1 (type $0) (param $x i32) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $catch + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-if-br_if-1 (param $x i32) + ;; As above, but the hint is 1. + (block $catch + (try_table (catch_all $catch) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (throw $e) + ) + ) + ) + ) + ) + + ;; CHECK: (func $throw-if-br_if-no (type $0) (param $x i32) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (br_if $catch + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-if-br_if-no (param $x i32) + ;; As above, but there is no branch hint, so we should emit none. + (block $catch + (try_table (catch_all $catch) + (if + (local.get $x) + (then + (throw $e) + ) + ) + ) + ) + ) + + ;; CHECK: (func $loop-if-br (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-if-br (param $x i32) + ;; This if with a br arm can turn into a br_if. The hint should be copied. + (loop $loop + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (br $loop) + ) + (else + ;; This call, and the one below, are needed for the pattern that is + ;; recognized here. + (call $none) + ) + ) + (call $none) + ) + ) + + ;; CHECK: (func $loop-if-br-reverse (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-if-br-reverse (param $x i32) + ;; As above, with arms flipped. Now the condition flips. + (loop $loop + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (call $none) + ) + (else + (br $loop) + ) + ) + (call $none) + ) + ) + + ;; CHECK: (func $restructure-if (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-if (param $x i32) + (block $block + ;; We will emit an if with flipped condition, which should get a flipped + ;; hint. + (@metadata.code.branch_hint "\01") + (br_if $block + (local.get $x) + ) + (call $none) + ) + ) + + ;; CHECK: (func $restructure-if-value (type $4) (param $x i32) (result i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (block $value (result i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-if-value (param $x i32) (result i32) + ;; We will emit an if with the same condition, which should get the same + ;; hint. + (block $value (result i32) + (drop + (@metadata.code.branch_hint "\01") + (br_if $value + (i32.const 0) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $set-if-br-arm (type $0) (param $x i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-if-br-arm (param $x i32) + (local $temp i32) + ;; The if will turn into a br_if, with the same hint. + (block $out + (local.set $temp + (@metadata.code.branch_hint "\00") + (if (result i32) + (local.get $x) + (then + (br $out) + ) + (else + (i32.const 0) + ) + ) + ) + ) + ) + + ;; CHECK: (func $set-if-br-arm-flip (type $0) (param $x i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-if-br-arm-flip (param $x i32) + (local $temp i32) + ;; As above, but with arms reversed. + ;; The if will turn into a flipped br_if, with a flipped hint. + (block $out + (local.set $temp + (@metadata.code.branch_hint "\00") + (if (result i32) + (local.get $x) + (then + (i32.const 0) + ) + (else + (br $out) + ) + ) + ) + ) + ) +) From ad67cf46c3ca2529e6cafdbb99b199222588eb37 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Jul 2025 11:14:05 -0700 Subject: [PATCH 589/622] Binary Reading: Avoid overlapping internal names between imports and non-imports (#7700) We handled name overlaps (name section name that happens to collide with the next internal name we invent for a non-named thing), but we did it separately for imports and non-imports, allowing a collision between them in very odd situations. To fix that, maintain a single shared list of used names for imports and non-imports. --- src/wasm-binary.h | 5 +++++ src/wasm/wasm-binary.cpp | 35 ++++++++++++++--------------------- test/lit/name-overlap.wast | 25 +++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 21 deletions(-) create mode 100644 test/lit/name-overlap.wast diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 21b53f34329..8cadd5c0072 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1658,6 +1658,11 @@ class WasmBinaryReader { std::unordered_map dataNames; std::unordered_map elemNames; + // The names that are already used (either from the names section, or that we + // generate as internal names for un-named things). + std::unordered_set usedFunctionNames, usedTableNames, usedMemoryNames, + usedGlobalNames, usedTagNames; + Function* currFunction = nullptr; // before we see a function (like global init expressions), there is no end of // function to check diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 39b5e7fc77f..1437f2a8219 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2529,17 +2529,15 @@ getOrMakeName(const std::unordered_map& nameMap, void WasmBinaryReader::readMemories() { auto num = getU32LEB(); auto numImports = wasm.memories.size(); - std::unordered_set usedNames; for (auto& [index, name] : memoryNames) { if (index >= num + numImports) { std::cerr << "warning: memory index out of bounds in name section: " << name << " at index " << index << '\n'; } - usedNames.insert(name); } for (size_t i = 0; i < num; i++) { - auto [name, isExplicit] = - getOrMakeName(memoryNames, numImports + i, makeName("", i), usedNames); + auto [name, isExplicit] = getOrMakeName( + memoryNames, numImports + i, makeName("", i), usedMemoryNames); auto memory = Builder::makeMemory(name); memory->hasExplicitName = isExplicit; getResizableLimits(memory->initial, @@ -2871,8 +2869,6 @@ void WasmBinaryReader::getResizableLimits(Address& initial, void WasmBinaryReader::readImports() { size_t num = getU32LEB(); Builder builder(wasm); - std::unordered_set usedFunctionNames, usedTableNames, usedMemoryNames, - usedGlobalNames, usedTagNames; for (size_t i = 0; i < num; i++) { auto module = getInlineString(); auto base = getInlineString(); @@ -3007,13 +3003,11 @@ void WasmBinaryReader::setLocalNames(Function& func, Index i) { void WasmBinaryReader::readFunctionSignatures() { size_t num = getU32LEB(); auto numImports = wasm.functions.size(); - std::unordered_set usedNames; for (auto& [index, name] : functionNames) { if (index >= num + numImports) { std::cerr << "warning: function index out of bounds in name section: " << name << " at index " << index << '\n'; } - usedNames.insert(name); } // Also check that the function indices in the local names subsection are // in-bounds, even though we don't use them here. @@ -3025,8 +3019,8 @@ void WasmBinaryReader::readFunctionSignatures() { } } for (size_t i = 0; i < num; i++) { - auto [name, isExplicit] = - getOrMakeName(functionNames, numImports + i, makeName("", i), usedNames); + auto [name, isExplicit] = getOrMakeName( + functionNames, numImports + i, makeName("", i), usedFunctionNames); auto index = getU32LEB(); HeapType type = getTypeByIndex(index); functionTypes.push_back(type); @@ -4761,17 +4755,15 @@ Name WasmBinaryReader::getIndexedString() { void WasmBinaryReader::readGlobals() { size_t num = getU32LEB(); auto numImports = wasm.globals.size(); - std::unordered_set usedNames; for (auto& [index, name] : globalNames) { if (index >= num + numImports) { std::cerr << "warning: global index out of bounds in name section: " << name << " at index " << index << '\n'; } - usedNames.insert(name); } for (size_t i = 0; i < num; i++) { auto [name, isExplicit] = getOrMakeName( - globalNames, numImports + i, makeName("global$", i), usedNames); + globalNames, numImports + i, makeName("global$", i), usedGlobalNames); auto type = getConcreteType(); auto mutable_ = getU32LEB(); if (mutable_ & ~1) { @@ -4860,17 +4852,15 @@ void WasmBinaryReader::readDataSegments() { void WasmBinaryReader::readTableDeclarations() { auto num = getU32LEB(); auto numImports = wasm.tables.size(); - std::unordered_set usedNames; for (auto& [index, name] : tableNames) { if (index >= num + numImports) { std::cerr << "warning: table index out of bounds in name section: " << name << " at index " << index << '\n'; } - usedNames.insert(name); } for (size_t i = 0; i < num; i++) { - auto [name, isExplicit] = - getOrMakeName(tableNames, numImports + i, makeName("", i), usedNames); + auto [name, isExplicit] = getOrMakeName( + tableNames, numImports + i, makeName("", i), usedTableNames); auto elemType = getType(); if (!elemType.isRef()) { throwError("Table type must be a reference type"); @@ -4977,18 +4967,16 @@ void WasmBinaryReader::readElementSegments() { void WasmBinaryReader::readTags() { size_t num = getU32LEB(); auto numImports = wasm.tags.size(); - std::unordered_set usedNames; for (auto& [index, name] : tagNames) { if (index >= num + numImports) { std::cerr << "warning: tag index out of bounds in name section: " << name << " at index " << index << '\n'; } - usedNames.insert(name); } for (size_t i = 0; i < num; i++) { getInt8(); // Reserved 'attribute' field - auto [name, isExplicit] = - getOrMakeName(tagNames, numImports + i, makeName("tag$", i), usedNames); + auto [name, isExplicit] = getOrMakeName( + tagNames, numImports + i, makeName("tag$", i), usedTagNames); auto typeIndex = getU32LEB(); auto tag = Builder::makeTag(name, getSignatureByTypeIndex(typeIndex)); tag->hasExplicitName = isExplicit; @@ -5081,6 +5069,7 @@ void WasmBinaryReader::readNames(size_t sectionPos, size_t payloadLen) { auto rawName = getInlineString(); auto name = processor.process(rawName); functionNames[index] = name; + usedFunctionNames.insert(name); } } else if (nameType == Subsection::NameLocal) { auto numFuncs = getU32LEB(); @@ -5112,6 +5101,7 @@ void WasmBinaryReader::readNames(size_t sectionPos, size_t payloadLen) { auto rawName = getInlineString(); auto name = processor.process(rawName); tableNames[index] = name; + usedTableNames.insert(name); } } else if (nameType == Subsection::NameElem) { auto num = getU32LEB(); @@ -5130,6 +5120,7 @@ void WasmBinaryReader::readNames(size_t sectionPos, size_t payloadLen) { auto rawName = getInlineString(); auto name = processor.process(rawName); memoryNames[index] = name; + usedMemoryNames.insert(name); } } else if (nameType == Subsection::NameData) { auto num = getU32LEB(); @@ -5148,6 +5139,7 @@ void WasmBinaryReader::readNames(size_t sectionPos, size_t payloadLen) { auto rawName = getInlineString(); auto name = processor.process(rawName); globalNames[index] = name; + usedGlobalNames.insert(name); } } else if (nameType == Subsection::NameField) { auto numTypes = getU32LEB(); @@ -5170,6 +5162,7 @@ void WasmBinaryReader::readNames(size_t sectionPos, size_t payloadLen) { auto rawName = getInlineString(); auto name = processor.process(rawName); tagNames[index] = name; + usedTagNames.insert(name); } } else { std::cerr << "warning: unknown name subsection with id " diff --git a/test/lit/name-overlap.wast b/test/lit/name-overlap.wast new file mode 100644 index 00000000000..4caa4785e56 --- /dev/null +++ b/test/lit/name-overlap.wast @@ -0,0 +1,25 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --instrument-branch-hints --roundtrip -S -o - | filecheck %s + +;; Two imports exist here, and instrument-branch-hints will add another. The +;; name "fimport$2" happens to be the name that would be chosen for that new +;; import, leading to a situation that the existing import has a forced name +;; from the names section (it is named here in the wat) while we pick an +;; internal name (not from the name section) that overlaps with it, causing an +;; error if we do not make sure to avoid duplication between import and non- +;; import names. + +(module + ;; CHECK: (type $0 (func (param i64))) + + ;; CHECK: (type $1 (func (param f32))) + + ;; CHECK: (type $2 (func (param i32 i32 i32))) + + ;; CHECK: (import "fuzzing-support" "log-i64" (func $fimport$2 (type $0) (param i64))) + (import "fuzzing-support" "log-i64" (func $fimport$2 (param i64))) + ;; CHECK: (import "fuzzing-support" "log-f32" (func $fimport$3 (type $1) (param f32))) + (import "fuzzing-support" "log-f32" (func $fimport$3 (param f32))) +) +;; CHECK: (import "fuzzing-support" "log-branch" (func $fimport$2_2 (type $2) (param i32 i32 i32))) From 1d2e23d5e55788091a51420ba3a9889d4efe7509 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 10 Jul 2025 17:26:17 -0700 Subject: [PATCH 590/622] [Custom Descriptors] Merge descriptors correctly (#7709) TypeMerging cannot merge one type in a descriptor chain into another type without merging the type's full descriptor chain into the other type's full descriptor chain. Because of this, each descriptor chain acts as a single unit in the DFA minimization algorithm. Update TypeMerging so that descriptor types do not get their own shapes, but rather are included in the shapes of their base described types. To make this simpler, add a new utility for easily iterating over a type's descriptor chain. --- src/passes/TypeMerging.cpp | 221 ++++++++++++----- src/wasm-type.h | 45 ++++ test/lit/passes/type-merging-desc.wast | 324 +++++++++++++++++++++++-- 3 files changed, 503 insertions(+), 87 deletions(-) diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index f47a212f30c..5fded79d89c 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -36,6 +36,9 @@ // passes in between. // +#include +#include + #include "ir/module-utils.h" #include "ir/type-updating.h" #include "ir/utils.h" @@ -44,6 +47,7 @@ #include "support/small_set.h" #include "wasm-builder.h" #include "wasm-type-ordering.h" +#include "wasm-type.h" #include "wasm.h" #define TYPE_MERGING_DEBUG 0 @@ -115,6 +119,17 @@ struct CastFinder : public PostWalker { } }; +HeapType getBaseDescribedType(HeapType type) { + while (true) { + if (auto next = type.getDescribedType()) { + type = *next; + continue; + } + break; + } + return type; +} + // We are going to treat the type graph as a partitioned DFA where each type is // a state with transitions to its children. We will partition the DFA states so // that types that may be mergeable will be in the same partition and types that @@ -339,14 +354,30 @@ bool TypeMerging::merge(MergeKind kind) { // For each type, either create a new partition or add to its supertype's // partition. for (auto type : mergeableSupertypesFirst(mergeable)) { + // Skip descriptor types. Since types in descriptor chains all have to be + // merged into matching descriptor chains together, only the base described + // type in each chain is considered, and its DFA state will include the + // shape of its entire descriptor chain. + if (type.getDescribedType()) { + continue; + } // We need partitions for any public children of this type since those - // children will participate in the DFA we're creating. - for (auto child : getPublicChildren(type)) { - ensurePartition(child); + // children will participate in the DFA we're creating. We use the base + // described type of the child because that's the type that the DFA state + // for the current type will point to. + for (auto t : type.getDescriptorChain()) { + for (auto child : getPublicChildren(t)) { + ensurePartition(getBaseDescribedType(child)); + } } // If the type is distinguished by the module or public, we cannot merge it, // so create a new partition for it. - if (castTypes.count(type) || !privateTypes.count(type)) { + auto chain = type.getDescriptorChain(); + bool hasCast = + std::any_of(chain.begin(), chain.end(), [&](HeapType t) -> bool { + return castTypes.count(type); + }); + if (hasCast || !privateTypes.count(type)) { ensurePartition(type); continue; } @@ -354,7 +385,12 @@ bool TypeMerging::merge(MergeKind kind) { switch (kind) { case Supertypes: { auto super = type.getDeclaredSuperType(); - bool superHasExactCast = super && exactCastTypes.count(*super); + bool superHasExactCast = + super && + std::any_of(chain.begin(), chain.end(), [&](HeapType t) -> bool { + auto super = t.getDeclaredSuperType(); + return super && exactCastTypes.count(*super); + }); if (!super || !shapeEq(type, *super) || superHasExactCast) { // Create a new partition for this type and bail. ensurePartition(type); @@ -556,10 +592,20 @@ DFA::State TypeMerging::makeDFAState(HeapType type) { // other direction, including the children is not necessary to differentiate // types reached by the public types because all such reachable types are also // public and not eligible to be merged. + // + // For private types, full descriptor chains are included in a single DFA + // represented by their base described type. if (privateTypes.count(type)) { - for (auto child : type.getHeapTypeChildren()) { - if (!child.isBasic()) { - succs.push_back(getMerged(child)); + assert(!type.getDescribedType()); + for (auto t : type.getDescriptorChain()) { + for (auto child : t.getHeapTypeChildren()) { + if (!child.isBasic()) { + // The child's partition is represented by the base of its descriptor + // chain. Different child types in the same descriptor chain are + // differentiated by including their chain index in the hashed + // top-level shape of the parent. + succs.push_back(getMerged(getBaseDescribedType(child))); + } } } } @@ -571,77 +617,104 @@ void TypeMerging::applyMerges() { return; } - // Flatten merges, which might be an arbitrary tree at this point. + // Flatten merges, which might be an arbitrary tree at this point. Also expand + // the mapping to cover every type in each descriptor chain. + std::unordered_map replacements; for (auto [type, _] : merges) { - merges[type] = getMerged(type); + auto target = getMerged(type); + auto chain = type.getDescriptorChain(); + auto targetChain = target.getDescriptorChain(); + auto targetIt = targetChain.begin(); + for (auto it = chain.begin(); it != chain.end(); ++it) { + assert(targetIt != targetChain.end()); + replacements[*it] = *targetIt++; + } } // We found things to optimize! Rewrite types in the module to apply those // changes. - TypeMapper(*module, merges).map(); + TypeMapper(*module, replacements).map(); } bool shapeEq(HeapType a, HeapType b) { // Check whether `a` and `b` have the same top-level structure, including the // position and identity of any children that are not included as transitions - // in the DFA, i.e. any children that are not nontrivial references. - if (a.isOpen() != b.isOpen()) { - return false; - } - if (a.isShared() != b.isShared()) { - return false; - } - // Ignore supertype because we want to be able to merge into parents. - if (!!a.getDescriptorType() != !!b.getDescriptorType()) { - return false; - } - if (!!a.getDescribedType() != !!b.getDescribedType()) { - return false; - } - auto aKind = a.getKind(); - auto bKind = b.getKind(); - if (aKind != bKind) { - return false; + // in the DFA, i.e. any children that are not nontrivial references. We treat + // full descriptor chains as single units, so compare the shape of every type + // in the chains rooted at `a` and `b`. + assert(!a.getDescribedType() && !b.getDescribedType()); + auto chainA = a.getDescriptorChain(); + auto chainB = b.getDescriptorChain(); + auto itA = chainA.begin(); + auto itB = chainB.begin(); + while (itA != chainA.end() && itB != chainB.end()) { + a = *itA++; + b = *itB++; + if (a.isOpen() != b.isOpen()) { + return false; + } + if (a.isShared() != b.isShared()) { + return false; + } + // Ignore supertype because we want to be able to merge into parents. + auto aKind = a.getKind(); + auto bKind = b.getKind(); + if (aKind != bKind) { + return false; + } + switch (aKind) { + case HeapTypeKind::Func: + if (!shapeEq(a.getSignature(), b.getSignature())) { + return false; + } + break; + case HeapTypeKind::Struct: + if (!shapeEq(a.getStruct(), b.getStruct())) { + return false; + } + break; + case HeapTypeKind::Array: + if (!shapeEq(a.getArray(), b.getArray())) { + return false; + } + break; + case HeapTypeKind::Cont: + WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Basic: + WASM_UNREACHABLE("unexpected kind"); + } } - switch (aKind) { - case HeapTypeKind::Func: - return shapeEq(a.getSignature(), b.getSignature()); - case HeapTypeKind::Struct: - return shapeEq(a.getStruct(), b.getStruct()); - case HeapTypeKind::Array: - return shapeEq(a.getArray(), b.getArray()); - case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); - case HeapTypeKind::Basic: - WASM_UNREACHABLE("unexpected kind"); - } - return false; + return itA == chainA.end() && itB == chainB.end(); } size_t shapeHash(HeapType a) { - size_t digest = hash(a.isOpen()); - rehash(digest, a.isShared()); - // Ignore supertype because we want to be able to merge into parents. - rehash(digest, !!a.getDescriptorType()); - rehash(digest, !!a.getDescribedType()); - auto kind = a.getKind(); - rehash(digest, kind); - switch (kind) { - case HeapTypeKind::Func: - hash_combine(digest, shapeHash(a.getSignature())); - return digest; - case HeapTypeKind::Struct: - hash_combine(digest, shapeHash(a.getStruct())); - return digest; - case HeapTypeKind::Array: - hash_combine(digest, shapeHash(a.getArray())); - return digest; - case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); - case HeapTypeKind::Basic: - break; + assert(!a.getDescribedType()); + size_t digest = 0xA76F35EC; + for (auto type : a.getDescriptorChain()) { + rehash(digest, 0xCC6B0DD9); + rehash(digest, type.isOpen()); + rehash(digest, type.isShared()); + // Ignore supertype because we want to be able to merge into parents. + auto kind = type.getKind(); + rehash(digest, kind); + switch (kind) { + case HeapTypeKind::Func: + hash_combine(digest, shapeHash(type.getSignature())); + continue; + case HeapTypeKind::Struct: + hash_combine(digest, shapeHash(type.getStruct())); + continue; + case HeapTypeKind::Array: + hash_combine(digest, shapeHash(type.getArray())); + continue; + case HeapTypeKind::Cont: + WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Basic: + continue; + } + WASM_UNREACHABLE("unexpected kind"); } - WASM_UNREACHABLE("unexpected kind"); + return digest; } bool shapeEq(const Struct& a, const Struct& b) { @@ -690,6 +763,18 @@ size_t shapeHash(Field a) { return digest; } +Index chainIndex(HeapType type) { + Index i = 0; + while (true) { + if (auto next = type.getDescribedType()) { + type = *next; + ++i; + continue; + } + return i; + } +} + bool shapeEq(Type a, Type b) { if (a == b) { return true; @@ -713,6 +798,13 @@ bool shapeEq(Type a, Type b) { if (a.getExactness() != b.getExactness()) { return false; } + // Since partition refinement treats descriptor chains as units, it cannot + // differentiate between different types in the same chain. Two types in the + // same chain will never be merged, so we can differentiate them here by index + // in their chain instead. + if (chainIndex(a.getHeapType()) != chainIndex(b.getHeapType())) { + return false; + } return true; } @@ -735,6 +827,7 @@ size_t shapeHash(Type a) { rehash(digest, 4); rehash(digest, (int)a.getNullability()); rehash(digest, (int)a.getExactness()); + rehash(digest, chainIndex(a.getHeapType())); return digest; } diff --git a/src/wasm-type.h b/src/wasm-type.h index b5e9a88a3c6..a8eff5f9c2b 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -57,6 +57,7 @@ struct Continuation; struct Field; struct Struct; struct Array; +struct DescriptorChain; using TypeList = std::vector; using Tuple = TypeList; @@ -201,6 +202,7 @@ class HeapType { // Get this type's descriptor or described types if they exist. std::optional getDescriptorType() const; std::optional getDescribedType() const; + DescriptorChain getDescriptorChain() const; // Return the depth of this heap type in the nominal type hierarchy, i.e. the // number of supertypes in its supertype chain. @@ -946,6 +948,49 @@ struct TypeBuilder { void dump(); }; +// An iterable providing access to a heap type's descriptor chain, starting from +// itself and iterating through each successive descriptor type. +struct DescriptorChain { + HeapType base; + struct Iterator { + using iterator_category = std::forward_iterator_tag; + using value_type = HeapType; + using difference_type = std::ptrdiff_t; + using pointer = const HeapType*; + using reference = const HeapType&; + + // The current type. An end iterator contains no type. + std::optional type; + + reference operator*() const { return *type; } + + pointer operator->() const { return &*type; } + + Iterator& operator++() { + type = type->getDescriptorType(); + return *this; + } + + Iterator operator++(int) { + Iterator it = *this; + ++(*this); + return it; + } + + bool operator==(const Iterator& other) const { return type == other.type; } + + bool operator!=(const Iterator& other) const { return !(*this == other); } + }; + + Iterator begin() const { return Iterator{base}; } + + Iterator end() const { return Iterator{std::nullopt}; } +}; + +inline DescriptorChain HeapType::getDescriptorChain() const { + return DescriptorChain{*this}; +} + // We consider certain specific types to always be public, to allow closed- // world to operate even if they escape. Specifically, "plain old data" types // like array of i8 and i16, which are used to represent strings, may cross diff --git a/test/lit/passes/type-merging-desc.wast b/test/lit/passes/type-merging-desc.wast index ba09ab87481..6339f7a790d 100644 --- a/test/lit/passes/type-merging-desc.wast +++ b/test/lit/passes/type-merging-desc.wast @@ -23,53 +23,331 @@ ) (module + ;; $B can be merged with $A, but $B.desc cannot be merged with $A.desc. If $C + ;; became an immediate subtype of $A, but $C.desc was still a subtype of + ;; $B.desc, then the types would be invalid. To avoid this kind of error, we + ;; cannot merge $B into $A without also merging their full descriptor chains. (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (descriptor $A.desc (struct (field i32)))) - (type $A (descriptor $A.desc (struct (field i32)))) - ;; CHECK: (type $A.desc (describes $A (struct))) - (type $A.desc (describes $A (struct))) + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; CHECK: (type $C (sub $B (descriptor $C.desc (struct (field i32))))) + (type $C (sub $B (descriptor $C.desc (struct (field i32))))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct (field anyref))))) + (type $B.desc (sub $A.desc (describes $B (struct (field anyref))))) + ;; CHECK: (type $C.desc (sub $B.desc (describes $C (struct (field eqref))))) + (type $C.desc (sub $B.desc (describes $C (struct (field eqref))))) + ) + + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $B (ref null $B) (ref.null none)) + (global $B (ref null $B) (ref.null none)) + ;; CHECK: (global $C (ref null $C) (ref.null none)) + (global $C (ref null $C) (ref.null none)) + ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) + (global $A.desc (ref null $A.desc) (ref.null none)) + ;; CHECK: (global $B.desc (ref null $B.desc) (ref.null none)) + (global $B.desc (ref null $B.desc) (ref.null none)) + ;; CHECK: (global $C.desc (ref null $C.desc) (ref.null none)) + (global $C.desc (ref null $C.desc) (ref.null none)) +) + +(module + ;; $B can be merged with $A, but $B.desc has a descriptor while $A.desc does + ;; not, so they cannot be merged. Furthermore, $B.meta has no corresponding + ;; $A.meta to be merged into. We cannot optimize here. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (descriptor $B.meta (struct))))) + (type $B.desc (sub $A.desc (describes $B (descriptor $B.meta (struct))))) + ;; CHECK: (type $B.meta (describes $B.desc (struct))) + (type $B.meta (describes $B.desc (struct))) + ) + + ;; CHECK: (type $5 (func (result (ref $B.meta)))) + + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) + (global $A.desc (ref null $A.desc) (ref.null none)) + + ;; CHECK: (func $meta (type $5) (result (ref $B.meta)) + ;; CHECK-NEXT: (ref.get_desc $B.desc + ;; CHECK-NEXT: (ref.get_desc $B + ;; CHECK-NEXT: (struct.new_default $B + ;; CHECK-NEXT: (struct.new_default $B.desc + ;; CHECK-NEXT: (struct.new_default $B.meta) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $meta (result (ref $B.meta)) + ;; If we did merge $B into $A, this IR would become invalid and cause + ;; assertion failures because $A's descriptor does not itself have a + ;; descriptor. + (ref.get_desc $B.desc + (ref.get_desc $B + (struct.new $B + (struct.new $B.desc + (struct.new $B.meta) + ) + ) + ) + ) + ) +) + +(module + ;; We cannot optimize because $B has an extra field. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct (field i32))))) + (type $B (sub $A (descriptor $B.desc (struct (field i32))))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) + ) + + ;; CHECK: (type $4 (func (result (ref $B.desc)))) + + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) + (global $A.desc (ref null $A.desc) (ref.null none)) + + ;; CHECK: (func $desc (type $4) (result (ref $B.desc)) + ;; CHECK-NEXT: (ref.get_desc $B + ;; CHECK-NEXT: (struct.new_default $B + ;; CHECK-NEXT: (struct.new_default $B.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $desc (result (ref $B.desc)) + (ref.get_desc $B + (struct.new_default $B + (struct.new_default $B.desc) + ) + ) + ) +) + +(module + ;; We cannot optimize because $B.desc has an extra field. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct (field i32))))) + (type $B.desc (sub $A.desc (describes $B (struct (field i32))))) + ) + + ;; CHECK: (type $4 (func (result (ref $B.desc)))) + + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) + (global $A.desc (ref null $A.desc) (ref.null none)) + + ;; CHECK: (func $desc (type $4) (result (ref $B.desc)) + ;; CHECK-NEXT: (ref.get_desc $B + ;; CHECK-NEXT: (struct.new_default $B + ;; CHECK-NEXT: (struct.new_default $B.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $desc (result (ref $B.desc)) + (ref.get_desc $B + (struct.new_default $B + (struct.new_default $B.desc) + ) + ) + ) +) - ;; CHECK: (type $B (descriptor $B.desc (struct (field f64)))) - (type $B (descriptor $B.desc (struct (field f64)))) - ;; CHECK: (type $B.desc (describes $B (struct))) - (type $B.desc (describes $B (struct))) +(module + ;; We can optimize because $B matches $A and $B.desc matches $A.desc. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) + ) + + ;; CHECK: (type $2 (func (result (ref $A.desc)))) + + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) + (global $A.desc (ref null $A.desc) (ref.null none)) + + ;; CHECK: (func $desc (type $2) (result (ref $A.desc)) + ;; CHECK-NEXT: (ref.get_desc $A + ;; CHECK-NEXT: (struct.new_default $A + ;; CHECK-NEXT: (struct.new_default $A.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $desc (result (ref $B.desc)) + (ref.get_desc $B + (struct.new_default $B + (struct.new_default $B.desc) + ) + ) + ) +) + +(module + ;; The two chains have the same sequence of children, but they are divided + ;; among the types in the chain differently. We cannot optimize. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (descriptor $A.desc (struct (field i32) (field f32) (field i64)))) + (type $A (descriptor $A.desc (struct (field i32 f32 i64)))) + ;; CHECK: (type $A.desc (describes $A (struct (field f64)))) + (type $A.desc (describes $A (struct (field f64)))) + ;; CHECK: (type $B (descriptor $B.desc (struct (field i32)))) + (type $B (descriptor $B.desc (struct (field i32)))) + ;; CHECK: (type $B.desc (describes $B (struct (field f32) (field i64) (field f64)))) + (type $B.desc (describes $B (struct (field f32 i64 f64)))) ) - ;; $A and $B have different shapes and should not be merged, so therefore - ;; $A.desc and $B.desc should not be merged. ;; CHECK: (global $A (ref null $A) (ref.null none)) (global $A (ref null $A) (ref.null none)) ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) (global $A.desc (ref null $A.desc) (ref.null none)) ;; CHECK: (global $B (ref null $B) (ref.null none)) (global $B (ref null $B) (ref.null none)) - ;; CHECK: (global $B.desc (ref null $A.desc) (ref.null none)) + ;; CHECK: (global $B.desc (ref null $B.desc) (ref.null none)) (global $B.desc (ref null $B.desc) (ref.null none)) ) (module + ;; These chains could be merged except for the exact cast to the descriptor in + ;; the supertype chain. (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (descriptor $A.desc (struct))) - (type $A (descriptor $A.desc (struct))) - ;; CHECK: (type $A.desc (describes $A (struct (field i32)))) - (type $A.desc (describes $A (struct (field i32)))) - - ;; CHECK: (type $B (descriptor $B.desc (struct))) - (type $B (descriptor $B.desc (struct))) - ;; CHECK: (type $B.desc (describes $B (struct (field f64)))) - (type $B.desc (describes $B (struct (field f64)))) + ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) ) - ;; $A.desc and $B.desc have different shapes and should not be merged, so - ;; therefore $A and $B should not be merged. + ;; CHECK: (type $4 (func)) + ;; CHECK: (global $A (ref null $A) (ref.null none)) (global $A (ref null $A) (ref.null none)) ;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none)) (global $A.desc (ref null $A.desc) (ref.null none)) - ;; CHECK: (global $B (ref null $A) (ref.null none)) + ;; CHECK: (global $B (ref null $B) (ref.null none)) (global $B (ref null $B) (ref.null none)) ;; CHECK: (global $B.desc (ref null $B.desc) (ref.null none)) (global $B.desc (ref null $B.desc) (ref.null none)) + + ;; CHECK: (func $cast-desc (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test (ref (exact $A.desc)) + ;; CHECK-NEXT: (struct.new_default $A.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc + (drop + (ref.test (ref (exact $A.desc)) + (struct.new $A.desc) + ) + ) + ) +) + +(module + ;; $A chain and $B differ only in the in index they reference in the $X chain. + ;; They should not be merged. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $X1 (descriptor $X2 (struct))) + (type $X1 (descriptor $X2 (struct))) + ;; CHECK: (type $X2 (describes $X1 (struct))) + (type $X2 (describes $X1 (struct))) + ;; CHECK: (type $A (struct (field (ref $X1)))) + (type $A (struct (ref $X1))) + ;; CHECK: (type $B (struct (field (ref $X2)))) + (type $B (struct (ref $X2))) + ) + + ;; CHECK: (global $A (ref null $A) (ref.null none)) + (global $A (ref null $A) (ref.null none)) + ;; CHECK: (global $B (ref null $B) (ref.null none)) + (global $B (ref null $B) (ref.null none)) +) + +(module + ;; CHECK: (type $public (sub (struct))) + (type $public (sub (struct))) + + ;; Referring to a public child only later in a chain should not cause a crash. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $use-public (descriptor $use-public.desc (struct))) + (type $use-public (descriptor $use-public.desc (struct))) + ;; CHECK: (type $use-public.desc (describes $use-public (struct (field (ref $public))))) + (type $use-public.desc (describes $use-public (struct (field (ref $public))))) + ) + + ;; CHECK: (global $public (ref null $public) (ref.null none)) + (global $public (ref null $public) (ref.null none)) + + ;; CHECK: (global $use-public (ref null $use-public) (ref.null none)) + (global $use-public (ref null $use-public) (ref.null none)) + + ;; CHECK: (export "public" (global $public)) + (export "public" (global $public)) +) + +(module + ;; Referring to a public child that is itself later in a chain should not + ;; cause a crash. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public (descriptor $public.desc (struct))) + (type $public (descriptor $public.desc (struct))) + ;; CHECK: (type $public.desc (describes $public (struct))) + (type $public.desc (describes $public (struct))) + ) + ;; CHECK: (type $use-public (struct (field (ref null $public.desc)))) + (type $use-public (struct (ref null $public.desc))) + + ;; CHECK: (global $public (ref null $public) (ref.null none)) + (global $public (ref null $public) (ref.null none)) + + ;; CHECK: (global $use-public (ref null $use-public) (ref.null none)) + (global $use-public (ref null $use-public) (ref.null none)) + + ;; CHECK: (export "public" (global $public)) + (export "public" (global $public)) ) From e652f79ad2ca1632013482e5102417fb17dd2fbf Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 11 Jul 2025 08:52:26 -0700 Subject: [PATCH 591/622] [Custom Descriptors] Do not optimize in AbstractTypeRefining (#7710) AbstractTypeRefining rewrites subtype relationships, and letting it optimize described or descriptor types while rewriting those subtypes would be complicated. The likely path forward is to stop rewriting subtype relationships in AbstractTypeRefining, but for now just skip optimizing these types. --- scripts/test/fuzzing.py | 1 + src/passes/AbstractTypeRefining.cpp | 13 +++++- .../passes/abstract-type-refining-desc.wast | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/abstract-type-refining-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index c14dcac786c..e878d1f9a13 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -129,6 +129,7 @@ 'optimize-instructions-desc.wast', 'gto-desc.wast', 'type-ssa-desc.wast', + 'abstract-type-refining-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index 5094488516c..d93895243bb 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -112,8 +112,17 @@ struct AbstractTypeRefining : public Pass { // module, given closed world, but we'd also need to make sure that // we don't need to make any changes to public types that refer to // them. - for (auto type : ModuleUtils::getPublicHeapTypes(*module)) { - createdTypes.insert(type); + // Similarly, treat all descriptor and described types as allocated because + // we cannot yet optimize them correctly. + auto heapTypes = ModuleUtils::collectHeapTypeInfo( + *module, + ModuleUtils::TypeInclusion::AllTypes, + ModuleUtils::VisibilityHandling::FindVisibility); + for (auto& [type, info] : heapTypes) { + if (info.visibility == ModuleUtils::Visibility::Public || + type.getDescribedType() || type.getDescriptorType()) { + createdTypes.insert(type); + } } SubTypes subTypes(*module); diff --git a/test/lit/passes/abstract-type-refining-desc.wast b/test/lit/passes/abstract-type-refining-desc.wast new file mode 100644 index 00000000000..95e555e1c75 --- /dev/null +++ b/test/lit/passes/abstract-type-refining-desc.wast @@ -0,0 +1,40 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --abstract-type-refining --remove-unused-types --traps-never-happen \ +;; RUN: -all --closed-world --preserve-type-order -S -o - | filecheck %s --check-prefix=YESTNH +;; RUN: foreach %s %t wasm-opt --abstract-type-refining --remove-unused-types \ +;; RUN: -all --closed-world --preserve-type-order -S -o - | filecheck %s --check-prefix=NO_TNH + +;; Run in both TNH and non-TNH mode. + +(module + ;; We should not try to generate invalid types by removing the subtype + ;; relation between $B.desc and $A.desc. + (rec + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + ;; NO_TNH: (rec + ;; NO_TNH-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + (type $A (sub (descriptor $A.desc (struct)))) + ;; YESTNH: (type $A.desc (sub (describes $A (struct)))) + ;; NO_TNH: (type $A.desc (sub (describes $A (struct)))) + (type $A.desc (sub (describes $A (struct)))) + ;; YESTNH: (type $B (sub $A (descriptor $B.desc (struct)))) + ;; NO_TNH: (type $B (sub $A (descriptor $B.desc (struct)))) + (type $B (sub $A (descriptor $B.desc (struct)))) + ;; YESTNH: (type $B.desc (sub $A.desc (describes $B (struct)))) + ;; NO_TNH: (type $B.desc (sub $A.desc (describes $B (struct)))) + (type $B.desc (sub $A.desc (describes $B (struct)))) + ) + ;; YESTNH: (global $g (ref (exact $B)) (struct.new_default $B + ;; YESTNH-NEXT: (ref.null none) + ;; YESTNH-NEXT: )) + ;; NO_TNH: (global $g (ref (exact $B)) (struct.new_default $B + ;; NO_TNH-NEXT: (ref.null none) + ;; NO_TNH-NEXT: )) + (global $g (ref (exact $B)) + (struct.new_default $B + (ref.null none) + ) + ) +) From fc29e302e6708e71cc0a0058fcf4aee97140a750 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 11 Jul 2025 10:18:17 -0700 Subject: [PATCH 592/622] [Branch Hints] Fix if hint flip in OptimizeInstructions (#7711) --- src/ir/branch-hints.h | 2 +- src/passes/OptimizeInstructions.cpp | 2 ++ .../optimize-instructions-branch-hints.wast | 32 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/optimize-instructions-branch-hints.wast diff --git a/src/ir/branch-hints.h b/src/ir/branch-hints.h index a15198f54a8..2cd13916e94 100644 --- a/src/ir/branch-hints.h +++ b/src/ir/branch-hints.h @@ -103,7 +103,7 @@ inline void applyOrTo(Expression* from1, Expression* from2, Expression* to, Function* func) { - // If one is likely then so is the from1 || from2. If both are unlikely then + // If one is likely then so is from1 || from2. If both are unlikely then // from1 || from2 is slightly more likely, but we assume our hints are nearly // certain, so we apply it. auto from1Hint = BranchHints::get(from1, func); diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index e4d5a94339c..b521f9ae45f 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -1171,6 +1172,7 @@ struct OptimizeInstructions // flip if-else arms to get rid of an eqz curr->condition = unary->value; std::swap(curr->ifTrue, curr->ifFalse); + BranchHints::flip(curr, getFunction()); } } if (curr->condition->type != Type::unreachable && diff --git a/test/lit/passes/optimize-instructions-branch-hints.wast b/test/lit/passes/optimize-instructions-branch-hints.wast new file mode 100644 index 00000000000..270bf179216 --- /dev/null +++ b/test/lit/passes/optimize-instructions-branch-hints.wast @@ -0,0 +1,32 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --optimize-instructions -all -S -o - | filecheck %s + +(module + ;; CHECK: (func $conditionals (type $0) (param $x i32) (result i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $conditionals (param $x i32) (result i32) + ;; When we flip the if, the hint should flip too. + (@metadata.code.branch_hint "\00") + (if (result i32) + (i32.eqz + (local.get $x) + ) + (then + (i32.const 42) + ) + (else + (i32.const 1337) + ) + ) + ) +) From f7c285114420d859934db49637f560b6306d9c77 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 11 Jul 2025 11:55:27 -0700 Subject: [PATCH 593/622] [Branch Hints] Fix if hint flip in Vacuum (#7713) --- src/passes/Vacuum.cpp | 2 ++ test/lit/passes/vacuum-branch-hints.wast | 38 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 test/lit/passes/vacuum-branch-hints.wast diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 0e49f19c89f..b5e223d9f21 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -19,6 +19,7 @@ // #include +#include #include #include #include @@ -297,6 +298,7 @@ struct Vacuum : public WalkerPass> { curr->ifFalse = nullptr; curr->condition = Builder(*getModule()).makeUnary(EqZInt32, curr->condition); + BranchHints::flip(curr, getFunction()); } else if (curr->ifTrue->is() && curr->ifFalse->is()) { // instead of dropping both sides, drop the if, if they are the same // type diff --git a/test/lit/passes/vacuum-branch-hints.wast b/test/lit/passes/vacuum-branch-hints.wast new file mode 100644 index 00000000000..39d1381f9dd --- /dev/null +++ b/test/lit/passes/vacuum-branch-hints.wast @@ -0,0 +1,38 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s + +(module + ;; CHECK: (func $if (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if (param $x i32) + ;; When we flip the if, the hint should flip too. + (@metadata.code.branch_hint "\00") + (if + (i32.eqz + (local.get $x) + ) + (then + (nop) + ) + (else + (call $if + (local.get $x) + ) + ) + ) + ) +) + From 025ed9f8caf05464d828665859d7e2e93be183a4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 11 Jul 2025 13:58:34 -0700 Subject: [PATCH 594/622] [Branch Hinting] Fix Inlining's flipping of ifs, and add hint copying (#7714) Split inlining will flip conditions in the copied code. To do this, add support for copying code annotations when we copy instructions (this is the first PR that can test that). This is done in a new metadata.h helper, that subsumes part of the previous debuginfo.h, that is, the new metadata::copyBetweenFunctions will copy all metadata, both debug info and code annotations. --- src/ir/CMakeLists.txt | 2 +- src/ir/debuginfo.h | 6 -- src/ir/{debuginfo.cpp => metadata.cpp} | 37 ++++++--- src/ir/metadata.h | 33 ++++++++ src/ir/module-utils.cpp | 4 +- src/passes/Inlining.cpp | 6 +- .../inlining_splitting_branch-hints.wast | 75 +++++++++++++++++++ 7 files changed, 143 insertions(+), 20 deletions(-) rename src/ir/{debuginfo.cpp => metadata.cpp} (58%) create mode 100644 src/ir/metadata.h create mode 100644 test/lit/passes/inlining_splitting_branch-hints.wast diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index a74c06897f8..2dce7c22509 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -2,7 +2,6 @@ FILE(GLOB ir_HEADERS *.h) set(ir_SOURCES ExpressionAnalyzer.cpp ExpressionManipulator.cpp - debuginfo.cpp drop.cpp effects.cpp eh-utils.cpp @@ -10,6 +9,7 @@ set(ir_SOURCES intrinsics.cpp lubs.cpp memory-utils.cpp + metadata.cpp module-utils.cpp names.cpp possible-contents.cpp diff --git a/src/ir/debuginfo.h b/src/ir/debuginfo.h index 96c4d8c2a92..143675cb3b8 100644 --- a/src/ir/debuginfo.h +++ b/src/ir/debuginfo.h @@ -61,12 +61,6 @@ inline void copyOriginalToReplacement(Expression* original, } } -// Given an expression and a copy of it in another function, copy the debug -// info into the second function. -void copyBetweenFunctions(Expression* origin, - Expression* copy, - Function* originFunc, - Function* copyFunc); } // namespace wasm::debuginfo #endif // wasm_ir_debuginfo_h diff --git a/src/ir/debuginfo.cpp b/src/ir/metadata.cpp similarity index 58% rename from src/ir/debuginfo.cpp rename to src/ir/metadata.cpp index a5fe92d54f4..94d44683a84 100644 --- a/src/ir/debuginfo.cpp +++ b/src/ir/metadata.cpp @@ -14,20 +14,28 @@ * limitations under the License. */ -#include "ir/debuginfo.h" +#include "ir/metadata.h" #include "wasm-traversal.h" #include "wasm.h" -namespace wasm::debuginfo { +namespace wasm::metadata { void copyBetweenFunctions(Expression* origin, Expression* copy, Function* originFunc, Function* copyFunc) { - if (originFunc->debugLocations.empty()) { - return; // No debug info to copy + if (originFunc->debugLocations.empty() && + originFunc->codeAnnotations.empty()) { + // Nothing to copy. + return; } + // List out instructions serially, so we can match them between the old and + // new copies. + // + // This is not that efficient, and in theory we could copy this in the + // caller context as the code is copied. However, we assume that most + // functions have no metadata, so this is faster in that common case. struct Lister : public PostWalker> { std::vector list; void visitExpression(Expression* curr) { list.push_back(curr); } @@ -41,14 +49,25 @@ void copyBetweenFunctions(Expression* origin, auto& originDebug = originFunc->debugLocations; auto& copyDebug = copyFunc->debugLocations; + auto& originAnnotations = originFunc->codeAnnotations; + auto& copyAnnotations = copyFunc->codeAnnotations; + assert(originList.list.size() == copyList.list.size()); for (Index i = 0; i < originList.list.size(); i++) { - auto iter = originDebug.find(originList.list[i]); - if (iter != originDebug.end()) { - auto location = iter->second; - copyDebug[copyList.list[i]] = location; + { + auto iter = originDebug.find(originList.list[i]); + if (iter != originDebug.end()) { + copyDebug[copyList.list[i]] = iter->second; + } + } + + { + auto iter = originAnnotations.find(originList.list[i]); + if (iter != originAnnotations.end()) { + copyAnnotations[copyList.list[i]] = iter->second; + } } } } -} // namespace wasm::debuginfo +} // namespace wasm::metadata diff --git a/src/ir/metadata.h b/src/ir/metadata.h new file mode 100644 index 00000000000..99fdb9ff48b --- /dev/null +++ b/src/ir/metadata.h @@ -0,0 +1,33 @@ +/* + * Copyright 2019 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_ir_metadata_h +#define wasm_ir_metadata_h + +#include "wasm.h" + +namespace wasm::metadata { + +// Given an expression and a copy of it in another function, copy the all +// metadata - debug info, code annotations - into the second function. +void copyBetweenFunctions(Expression* origin, + Expression* copy, + Function* originFunc, + Function* copyFunc); + +} // namespace wasm::metadata + +#endif // wasm_ir_metadata_h diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index c19ae369eb9..582a2ad82df 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -15,9 +15,9 @@ */ #include "module-utils.h" -#include "ir/debuginfo.h" #include "ir/intrinsics.h" #include "ir/manipulation.h" +#include "ir/metadata.h" #include "ir/properties.h" #include "support/insert_ordered.h" #include "support/topological_sort.h" @@ -72,7 +72,7 @@ copyFunctionWithoutAdd(Function* func, ret->localNames = func->localNames; ret->localIndices = func->localIndices; ret->body = ExpressionManipulator::copy(func->body, out); - debuginfo::copyBetweenFunctions(func->body, ret->body, func, ret.get()); + metadata::copyBetweenFunctions(func->body, ret->body, func, ret.get()); ret->prologLocation = func->prologLocation; ret->epilogLocation = func->epilogLocation; // Update file indices if needed diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index bc8dfb950aa..a4ce4df556f 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -30,14 +30,15 @@ #include +#include "ir/branch-hints.h" #include "ir/branch-utils.h" -#include "ir/debuginfo.h" #include "ir/drop.h" #include "ir/eh-utils.h" #include "ir/element-utils.h" #include "ir/find_all.h" #include "ir/literal-utils.h" #include "ir/localize.h" +#include "ir/metadata.h" #include "ir/module-utils.h" #include "ir/names.h" #include "ir/properties.h" @@ -634,7 +635,7 @@ static void doCodeInlining(Module* module, // Generate and update the inlined contents auto* contents = ExpressionManipulator::copy(from->body, *module); - debuginfo::copyBetweenFunctions(from->body, contents, from, into); + metadata::copyBetweenFunctions(from->body, contents, from, into); updater.walk(contents); block->list.push_back(contents); block->type = retType; @@ -1097,6 +1098,7 @@ struct FunctionSplitter { auto* inlineableIf = getIf(inlineable->body); inlineableIf->condition = builder.makeUnary(EqZInt32, inlineableIf->condition); + BranchHints::flip(inlineableIf, inlineable); inlineableIf->ifTrue = builder.makeCall( outlined->name, getForwardedArgs(func, builder), Type::none); inlineable->body = inlineableIf; diff --git a/test/lit/passes/inlining_splitting_branch-hints.wast b/test/lit/passes/inlining_splitting_branch-hints.wast new file mode 100644 index 00000000000..73db81da78e --- /dev/null +++ b/test/lit/passes/inlining_splitting_branch-hints.wast @@ -0,0 +1,75 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --inlining --optimize-level=3 --partial-inlining-ifs=1 --all-features -S -o - | filecheck %s + +;; The function we partially inline here has an if, which we emit as flipped +;; afterwards. The new ifs should have flipped hints. + +(module + (func $func (param $0 i32) + (@metadata.code.branch_hint "\01") + (if + (local.get $0) + (then + (return) + ) + ) + ;; More code, so this is not trivial. + (loop $l + (nop) + ) + ) + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (func $caller (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$func + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$func + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-A$func$1 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-A$func + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (i32.const 0) + ) + (call $func + (i32.const 0) + ) + ) +) +;; CHECK: (func $byn-split-outlined-A$func (type $1) (param $0 i32) +;; CHECK-NEXT: (loop $l +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) From e20ea419c95487f0ed7a09052cf370dcbf62a7f3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 14 Jul 2025 12:36:00 -0700 Subject: [PATCH 595/622] [Custom Descriptors] Fix ref.cast_desc branching descriptors (#7717) The interpreter was returning the wrong value when evaluating a branching descriptor operand to a descriptor cast. Adding tests for this fix also exposed a bug where the validator did not allow for unreachable descriptor operands without also having unreachable ref operands. --- src/wasm-interpreter.h | 2 +- src/wasm/wasm-validator.cpp | 2 +- test/spec/ref.cast_desc.wast | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2325fba340f..d079e759bdb 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1658,7 +1658,7 @@ class ExpressionRunner : public OverriddenVisitor { } Flow desc = self()->visit(curr->desc); if (desc.breaking()) { - return typename Cast::Breaking{ref}; + return typename Cast::Breaking{desc}; } auto expected = desc.getSingleValue().getGCData(); if (!expected) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a570b990271..c4416a9b39e 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2936,7 +2936,7 @@ void FunctionValidator::visitRefTest(RefTest* curr) { void FunctionValidator::visitRefCast(RefCast* curr) { shouldBeTrue( getModule()->features.hasGC(), curr, "ref.cast requires gc [--enable-gc]"); - if (curr->ref->type == Type::unreachable) { + if (curr->type == Type::unreachable) { return; } if (!shouldBeTrue( diff --git a/test/spec/ref.cast_desc.wast b/test/spec/ref.cast_desc.wast index c4e72e35814..1380ea01aa1 100644 --- a/test/spec/ref.cast_desc.wast +++ b/test/spec/ref.cast_desc.wast @@ -145,6 +145,25 @@ ) ) ) + + (func (export "cast-branch-ref") (result i32) + (drop + (ref.cast_desc (ref $super) + (return (i32.const 1)) + (ref.null none) + ) + ) + (i32.const 0) + ) + (func (export "cast-branch-desc") (result i32) + (drop + (ref.cast_desc (ref $super) + (ref.null none) + (return (i32.const 1)) + ) + ) + (i32.const 0) + ) ) (assert_return (invoke "cast-success")) @@ -157,6 +176,8 @@ (assert_trap (invoke "cast-nn-fail-null") "cast error") (assert_trap (invoke "cast-nn-fail-wrong-desc") "cast error") (assert_trap (invoke "cast-nn-fail-null-desc") "null descriptor") +(assert_return (invoke "cast-branch-ref") (i32.const 1)) +(assert_return (invoke "cast-branch-desc") (i32.const 1)) (assert_malformed ;; Cast type must be a reference. From c017f8d247009f79239933ef5efe3e16321f9082 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 14 Jul 2025 16:29:42 -0700 Subject: [PATCH 596/622] [Branch Hints] Fix DuplicateFunctionElimination and function comparisons (#7715) This adds metadata comparisons to `FunctionUtils::equal`: if two functions differ in metadata, they are different, and should not be merged in DuplicateFunctionElimination even if identical otherwise. (In theory, they could be identical in all but branch hints, and called from different places which cause the differing branch hints to both be correct.) --- src/ir/function-utils.h | 13 +- src/ir/hashed.h | 5 +- src/ir/metadata.cpp | 99 ++++++-- src/ir/metadata.h | 7 + src/wasm.h | 6 +- ...ate-function-elimination_branch-hints.wast | 220 ++++++++++++++++++ 6 files changed, 330 insertions(+), 20 deletions(-) create mode 100644 test/lit/passes/duplicate-function-elimination_branch-hints.wast diff --git a/src/ir/function-utils.h b/src/ir/function-utils.h index ebdef0cd4bc..1496b49c8a5 100644 --- a/src/ir/function-utils.h +++ b/src/ir/function-utils.h @@ -17,6 +17,7 @@ #ifndef wasm_ir_function_h #define wasm_ir_function_h +#include "ir/metadata.h" #include "ir/utils.h" #include "wasm.h" @@ -37,10 +38,16 @@ inline bool equal(Function* left, Function* right) { return false; } } - if (!left->imported() && !right->imported()) { - return ExpressionAnalyzer::equal(left->body, right->body); + if (!metadata::equal(left, right)) { + return false; + } + if (left->imported() && right->imported()) { + return true; + } + if (left->imported() || right->imported()) { + return false; } - return left->imported() && right->imported(); + return ExpressionAnalyzer::equal(left->body, right->body); } } // namespace wasm::FunctionUtils diff --git a/src/ir/hashed.h b/src/ir/hashed.h index 4e90951f518..8fb31956b87 100644 --- a/src/ir/hashed.h +++ b/src/ir/hashed.h @@ -17,10 +17,11 @@ #ifndef _wasm_ir_hashed_h #define _wasm_ir_hashed_h +#include + #include "ir/utils.h" #include "support/hash.h" #include "wasm.h" -#include namespace wasm { @@ -65,6 +66,8 @@ struct FunctionHasher : public WalkerPass> { } hash_combine(digest, ExpressionAnalyzer::flexibleHash(func->body, customHasher)); + // TODO: Hash metadata (debug info, code annotations), though it would be + // very rare to get a false collision for these reasons. return digest; } diff --git a/src/ir/metadata.cpp b/src/ir/metadata.cpp index 94d44683a84..e66a961342d 100644 --- a/src/ir/metadata.cpp +++ b/src/ir/metadata.cpp @@ -20,6 +20,25 @@ namespace wasm::metadata { +namespace { + +// List out instructions serially, so we can match them between the old and +// new copies. +// +// This is not that efficient, and in theory we could copy this in the +// caller context as the code is copied. However, we assume that most +// functions have no metadata, so this is faster in that common case. +struct Serializer + : public PostWalker> { + Serializer(Expression* expr) { walk(expr); } + + std::vector list; + + void visitExpression(Expression* curr) { list.push_back(curr); } +}; + +} // anonymous namespace + void copyBetweenFunctions(Expression* origin, Expression* copy, Function* originFunc, @@ -30,21 +49,8 @@ void copyBetweenFunctions(Expression* origin, return; } - // List out instructions serially, so we can match them between the old and - // new copies. - // - // This is not that efficient, and in theory we could copy this in the - // caller context as the code is copied. However, we assume that most - // functions have no metadata, so this is faster in that common case. - struct Lister : public PostWalker> { - std::vector list; - void visitExpression(Expression* curr) { list.push_back(curr); } - }; - - Lister originList; - originList.walk(origin); - Lister copyList; - copyList.walk(copy); + Serializer originList(origin); + Serializer copyList(copy); auto& originDebug = originFunc->debugLocations; auto& copyDebug = copyFunc->debugLocations; @@ -70,4 +76,67 @@ void copyBetweenFunctions(Expression* origin, } } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + +// Given two expressions to use as keys, see if they have identical values (or +// are both absent) in two maps. +template +bool compare(Expression* a, + Expression* b, + const T& aMap, + const T& bMap, + const V defaultValue) { + auto aIter = aMap.find(a); + auto aItem = aIter != aMap.end() ? aIter->second : defaultValue; + auto bIter = bMap.find(b); + auto bItem = bIter != bMap.end() ? bIter->second : defaultValue; + return aItem == bItem; +} + +bool equal(Function* a, Function* b) { + if (a->imported() && b->imported()) { + // No code metadata, and we don't yet store function-level metadata. + return true; + } + if (a->imported() || b->imported()) { + // See comment on declaration, we consider such a difference as making them + // unequal. + return false; + } + + if (a->debugLocations.empty() && b->debugLocations.empty() && + a->codeAnnotations.empty() && b->codeAnnotations.empty()) { + // Nothing to compare; no differences. + return true; + } + + Serializer aList(a->body); + Serializer bList(b->body); + + if (aList.list.size() != bList.list.size()) { + return false; + } + + assert(aList.list.size() == bList.list.size()); + for (Index i = 0; i < aList.list.size(); i++) { + if (!compare(aList.list[i], + bList.list[i], + a->debugLocations, + b->debugLocations, + Function::DebugLocation()) || + !compare(aList.list[i], + bList.list[i], + a->codeAnnotations, + b->codeAnnotations, + Function::CodeAnnotation())) { + return false; + } + } + + return true; +} + +#pragma GCC diagnostic pop + } // namespace wasm::metadata diff --git a/src/ir/metadata.h b/src/ir/metadata.h index 99fdb9ff48b..ef18a8172c9 100644 --- a/src/ir/metadata.h +++ b/src/ir/metadata.h @@ -28,6 +28,13 @@ void copyBetweenFunctions(Expression* origin, Function* originFunc, Function* copyFunc); +// Check if two functions have identical metadata. We consider differences like +// one being imported and the other not, or having different numbers of +// instructions, to mean they are not equal (as the meaning of comparisons +// becomes hard in such cases, and the main use here is to compare metadata +// after all else is known equal). +bool equal(Function* a, Function* b); + } // namespace wasm::metadata #endif // wasm_ir_metadata_h diff --git a/src/wasm.h b/src/wasm.h index e1bd262400b..42de5ce890d 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2233,7 +2233,7 @@ class Function : public Importable { // Source maps debugging info: map expression nodes to their file, line, col, // symbol name. struct DebugLocation { - BinaryLocation fileIndex, lineNumber, columnNumber; + BinaryLocation fileIndex = -1, lineNumber = -1, columnNumber = -1; std::optional symbolNameIndex; bool operator==(const DebugLocation& other) const { return fileIndex == other.fileIndex && lineNumber == other.lineNumber && @@ -2279,6 +2279,10 @@ class Function : public Importable { static const uint8_t NeverInline = 0; static const uint8_t AlwaysInline = 127; std::optional inline_; + + bool operator==(const CodeAnnotation& other) const { + return branchLikely == other.branchLikely && inline_ == other.inline_; + } }; // Function-level annotations are implemented with a key of nullptr, matching diff --git a/test/lit/passes/duplicate-function-elimination_branch-hints.wast b/test/lit/passes/duplicate-function-elimination_branch-hints.wast new file mode 100644 index 00000000000..60f86e2eb86 --- /dev/null +++ b/test/lit/passes/duplicate-function-elimination_branch-hints.wast @@ -0,0 +1,220 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. + +;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --all-features -S -o - | filecheck %s + +;; The functions here differ in branch hints, and should not be merged. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (export "a" (func $a)) + + ;; CHECK: (export "b" (func $b)) + + ;; CHECK: (func $a (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (export "a") (param $x i32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) + + ;; CHECK: (func $b (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $b (export "b") (param $x i32) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) +) + +;; These also differ, now one is missing a hint, and they should not be merged. +;; TODO: Perhaps when optimizing for size, we should merge and drop the hint? +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (export "a" (func $a)) + + ;; CHECK: (export "b" (func $b)) + + ;; CHECK: (func $a (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (export "a") (param $x i32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) + + ;; CHECK: (func $b (type $0) (param $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $b (export "b") (param $x i32) + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) +) + +;; Flipped case of the above, now the other one is the only one with a hint, +;; and that hint is flipped. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (export "a" (func $a)) + + ;; CHECK: (export "b" (func $b)) + + ;; CHECK: (func $a (type $0) (param $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (export "a") (param $x i32) + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) + + ;; CHECK: (func $b (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $b (export "b") (param $x i32) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) +) + +;; Identical branch hints: We can merge here. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (export "a" (func $a)) + + ;; CHECK: (export "b" (func $a)) + + ;; CHECK: (func $a (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (export "a") (param $x i32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) + + (func $b (export "b") (param $x i32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) +) + +;; Ditto, with identical hints of 1. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (export "a" (func $a)) + + ;; CHECK: (export "b" (func $a)) + + ;; CHECK: (func $a (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (export "a") (param $x i32) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) + + (func $b (export "b") (param $x i32) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) +) + From 93399c3de5767f56c5ebfaf171a0256d1fe0e5d7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Jul 2025 10:09:26 -0700 Subject: [PATCH 597/622] [Custom Descriptors] Optimize ref.cast_desc (#7718) Update the cast optimizations in OptimizeInstructions to correctly optimize ref.cast_desc. The optimizations differ from the optimizations of normal casts because they have to be careful not to skip any side effects in the descriptor operand and because type information alone is not sufficient to prove that a cast will succeed. --- src/passes/OptimizeInstructions.cpp | 227 ++-- .../optimize-instructions-call_ref.wast | 9 +- .../passes/optimize-instructions-desc.wast | 1043 ++++++++++++++++- .../passes/optimize-instructions-gc-iit.wast | 14 +- .../passes/optimize-instructions-gc-tnh.wast | 75 +- test/lit/passes/optimize-instructions-gc.wast | 41 +- 6 files changed, 1206 insertions(+), 203 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index b521f9ae45f..9ad27dc67ab 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -48,6 +48,7 @@ #include #include "call-utils.h" +#include "support/utilities.h" // TODO: Use the new sign-extension opcodes where appropriate. This needs to be // conditionalized on the availability of atomics. @@ -1613,10 +1614,12 @@ struct OptimizeInstructions } // Appends a result after the dropped children, if we need them. - Expression* getDroppedChildrenAndAppend(Expression* curr, - Expression* result) { + Expression* + getDroppedChildrenAndAppend(Expression* curr, + Expression* result, + DropMode mode = DropMode::NoticeParentEffects) { return wasm::getDroppedChildrenAndAppend( - curr, *getModule(), getPassOptions(), result); + curr, *getModule(), getPassOptions(), result, mode); } Expression* getDroppedChildrenAndAppend(Expression* curr, Literal value) { @@ -2269,54 +2272,40 @@ struct OptimizeInstructions // TODO: more opts like StructCmpxchg } - void visitRefCast(RefCast* curr) { - // Note we must check the ref's type here and not our own, since we only - // refinalize at the end, which means our type may not have been updated yet - // after a change in the child. - // TODO: we could update unreachability up the stack perhaps, or just move - // all patterns that can add unreachability to a pass that does so - // already like vacuum or dce. - if (curr->ref->type == Type::unreachable) { - return; - } - - if (curr->type.isNonNullable() && trapOnNull(curr, curr->ref)) { - return; - } - + bool optimizeKnownCastResult(RefCast* curr, Type refType) { Builder builder(*getModule()); - - // Look at all the fallthrough values to get the most precise possible type - // of the value we are casting. local.tee, br_if, and blocks can all "lose" - // type information, so looking at all the fallthrough values can give us a - // more precise type than is stored in the IR. - Type refType = getFallthroughType(curr->ref); - - // As a first step, we can tighten up the cast type to be the greatest lower - // bound of the original cast type and the type we know the cast value to - // have. We know any less specific type either cannot appear or will fail - // the cast anyways. - auto glb = Type::getGreatestLowerBound(curr->type, refType); - if (glb != Type::unreachable && glb != curr->type) { - curr->type = glb; - refinalize = true; - // Call replaceCurrent() to make us re-optimize this node, as we may have - // just unlocked further opportunities. (We could just continue down to - // the rest, but we'd need to do more work to make sure all the local - // state in this function is in sync which this change; it's easier to - // just do another clean pass on this node.) - replaceCurrent(curr); - return; - } - // Given what we know about the type of the value, determine what we know // about the results of the cast and optimize accordingly. switch (GCTypeUtils::evaluateCastCheck(refType, curr->type)) { case GCTypeUtils::Unknown: // The cast may or may not succeed, so we cannot optimize. - break; + return false; case GCTypeUtils::Success: case GCTypeUtils::SuccessOnlyIfNonNull: { + // Knowing the types match is not sufficient to know a descriptor cast + // succeeds. We must also know that the descriptor values match. + // However, if traps never happen, we can assume the descriptors will + // match and optimize anyway. + // TODO: Maybe we can determine that the descriptors values match in + // some cases. + if (curr->desc && !getPassOptions().trapsNeverHappen) { + // As a special case, we can still optimize if we know the value is + // null, because then we never get around to comparing the + // descriptors. We still need to preserve the trap on null + // descriptors, though. + if (refType.isNull()) { + assert(curr->type.isNullable()); + if (curr->desc->type.isNullable()) { + curr->desc = builder.makeRefAs(RefAsNonNull, curr->desc); + } + replaceCurrent(getDroppedChildrenAndAppend( + curr, + builder.makeRefNull(curr->type.getHeapType()), + DropMode::IgnoreParentEffects)); + return true; + } + return false; + } // We know the cast will succeed, or at most requires a null check, so // we can try to optimize it out. Find the best-typed fallthrough value // to propagate. @@ -2341,11 +2330,21 @@ struct OptimizeInstructions // exactness. if (ref == curr->ref && !needsExactCast) { if (needsNullCheck) { - replaceCurrent(builder.makeRefAs(RefAsNonNull, curr->ref)); + curr->ref = builder.makeRefAs(RefAsNonNull, curr->ref); + } + if (curr->desc) { + // We must move the ref past the descriptor operand. + auto* block = + ChildLocalizer( + curr, getFunction(), *getModule(), getPassOptions()) + .getChildrenReplacement(); + block->list.push_back(curr->ref); + block->type = curr->ref->type; + replaceCurrent(block); } else { - replaceCurrent(ref); + replaceCurrent(curr->ref); } - return; + return true; } // Otherwise we can't just remove the cast and replace it with `ref` // because the intermediate expressions might have had side effects or @@ -2370,9 +2369,11 @@ struct OptimizeInstructions // Unreachable, so we'll not hit this assertion. assert(curr->type.isNullable()); auto nullType = curr->type.getHeapType().getBottom(); - replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), - builder.makeRefNull(nullType))); - return; + replaceCurrent( + getDroppedChildrenAndAppend(curr, + builder.makeRefNull(nullType), + DropMode::IgnoreParentEffects)); + return true; } // At this point we know the cast will succeed as long as nullability @@ -2380,7 +2381,7 @@ struct OptimizeInstructions // is not present in the value's static type, so there's nothing we // can do. if (needsExactCast) { - return; + return false; } // We need to use a tee to return the value since we can't materialize @@ -2391,9 +2392,9 @@ struct OptimizeInstructions if (needsNullCheck) { get = builder.makeRefAs(RefAsNonNull, get); } - replaceCurrent( - builder.makeSequence(builder.makeDrop(curr->ref), get)); - return; + replaceCurrent(getDroppedChildrenAndAppend( + curr, get, DropMode::IgnoreParentEffects)); + return true; } // If we get here, then we know that the heap type of the cast input is // more refined than the heap type of the best available fallthrough @@ -2417,48 +2418,114 @@ struct OptimizeInstructions // The cast either returns null or traps. In trapsNeverHappen mode // we know the result, since by assumption it will not trap. if (getPassOptions().trapsNeverHappen) { - replaceCurrent( - builder.makeBlock({builder.makeDrop(curr->ref), - builder.makeRefNull(curr->type.getHeapType())}, - curr->type)); - return; + replaceCurrent(getDroppedChildrenAndAppend( + curr, + builder.makeRefNull(curr->type.getHeapType()), + DropMode::IgnoreParentEffects)); + return true; } - // Otherwise, we should have already refined the cast type to cast - // directly to null. We do not further refine the cast type to exact - // null because the extra precision is not useful and doing so would - // increase the size of the instruction encoding. - assert(curr->type.isNull()); - break; + return false; } case GCTypeUtils::Unreachable: case GCTypeUtils::Failure: // This cast cannot succeed, or it cannot even be reached, so we can - // trap. Make sure to emit a block with the same type as us; leave - // updating types for other passes. - replaceCurrent(builder.makeBlock( - {builder.makeDrop(curr->ref), builder.makeUnreachable()}, - curr->type)); - return; + // trap. + replaceCurrent(getDroppedChildrenAndAppend( + curr, builder.makeUnreachable(), DropMode::IgnoreParentEffects)); + return true; + } + WASM_UNREACHABLE("unexpected result"); + } + + void visitRefCast(RefCast* curr) { + // Note we must check the ref's type here and not our own, since we only + // refinalize at the end, which means our type may not have been updated yet + // after a change in the child. + // TODO: we could update unreachability up the stack perhaps, or just move + // all patterns that can add unreachability to a pass that does so + // already like vacuum or dce. + if (curr->ref->type == Type::unreachable || + (curr->desc && curr->desc->type == Type::unreachable)) { + return; + } + + if (curr->type.isNonNullable() && trapOnNull(curr, curr->ref)) { + return; + } + + if (curr->desc && trapOnNull(curr, curr->desc)) { + return; + } + + Builder builder(*getModule()); + + // Look at all the fallthrough values to get the most precise possible type + // of the value we are casting. + Type refType = getFallthroughType(curr->ref); + + // As a first step, we can tighten up the cast type. For normal casts we can + // use the greatest lower bound of the original cast type and the type we + // know the cast value to have. For descriptor casts we cannot change the + // target heap type because it is controlled by the descriptor operand, but + // we can improve nullability. + Type improvedType = curr->type; + if (curr->desc) { + if (curr->type.isNullable() && refType.isNonNullable()) { + improvedType = curr->type.with(NonNullable); + } + } else { + improvedType = Type::getGreatestLowerBound(curr->type, refType); + } + if (improvedType != Type::unreachable && improvedType != curr->type) { + curr->type = improvedType; + refinalize = true; + // Call replaceCurrent() to make us re-optimize this node, as we may + // have just unlocked further opportunities. (We could just continue + // down to the rest, but we'd need to do more work to make sure all the + // local state in this function is in sync which this change; it's + // easier to just do another clean pass on this node.) + replaceCurrent(curr); + return; + } + + // Try to optimize based on what we know statically about the result of the + // cast. + if (optimizeKnownCastResult(curr, refType)) { + return; } // If we got past the optimizations above, it must be the case that we - // cannot tell from the static types whether the cast will succeed or not, - // which means we must have a proper down cast. - assert(Type::isSubType(curr->type, curr->ref->type)); + // cannot tell statically whether the cast will succeed or not. if (auto* child = curr->ref->dynCast()) { - // Repeated casts can be removed, leaving just the most demanding of them. - // Since we know the current cast is a downcast, it must be strictly - // stronger than its child cast and we can remove the child cast entirely. - curr->ref = child->ref; - return; + // If the current cast is at least as strong as the child cast, then we + // can remove the child cast. If the child cast is a descriptor cast and + // traps are allowed, then we cannot remove the potentially-trapping + // child, though. + bool notWeaker = Type::isSubType(curr->type, child->type); + bool safe = !child->desc || getPassOptions().trapsNeverHappen; + if (notWeaker && safe) { + if (child->desc) { + // Reorder the child's reference past its dropped descriptor if + // necessary. + auto* block = + ChildLocalizer(child, getFunction(), *getModule(), getPassOptions()) + .getChildrenReplacement(); + block->list.push_back(child->ref); + block->type = child->ref->type; + curr->ref = block; + } else { + curr->ref = child->ref; + } + return; + } } // Similarly, ref.cast can be combined with ref.as_non_null. // - // (ref.cast null (ref.as_non_null ..)) + // (ref.cast (ref null T) (ref.as_non_null ..)) // => - // (ref.cast ..) + // (ref.cast (ref T) ..) // if (auto* as = curr->ref->dynCast(); as && as->op == RefAsNonNull) { curr->ref = as->value; diff --git a/test/lit/passes/optimize-instructions-call_ref.wast b/test/lit/passes/optimize-instructions-call_ref.wast index 66e651a0694..cec258ee080 100644 --- a/test/lit/passes/optimize-instructions-call_ref.wast +++ b/test/lit/passes/optimize-instructions-call_ref.wast @@ -27,7 +27,7 @@ (elem $elem-1 (table $table-1) (i32.const 0) (ref null $i32_i32_=>_none) (ref.func $foo)) - ;; CHECK: (elem declare func $bar $fallthrough-no-params $fallthrough-non-nullable $return-nothing) + ;; CHECK: (elem declare func $bar $fallthrough-no-params $fallthrough-non-nullable) ;; CHECK: (func $foo (type $i32_i32_=>_none) (param $0 i32) (param $1 i32) ;; CHECK-NEXT: (unreachable) @@ -163,12 +163,7 @@ ;; CHECK: (func $fallthrough-bad-type (type $none_=>_i32) (result i32) ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref nofunc)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.func $return-nothing) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-desc.wast b/test/lit/passes/optimize-instructions-desc.wast index 76654119f3d..9a83660c6f1 100644 --- a/test/lit/passes/optimize-instructions-desc.wast +++ b/test/lit/passes/optimize-instructions-desc.wast @@ -1,37 +1,59 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. ;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --optimize-instructions -tnh -S -o - | filecheck %s --check-prefix=NTRAP (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) - (type $struct (descriptor $desc (struct))) - ;; CHECK: (type $desc (describes $struct (struct))) - (type $desc (describes $struct (struct))) + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct)))) + ;; NTRAP: (rec + ;; NTRAP-NEXT: (type $struct (sub (descriptor $desc (struct)))) + (type $struct (sub (descriptor $desc (struct)))) + ;; CHECK: (type $desc (sub (describes $struct (struct)))) + ;; NTRAP: (type $desc (sub (describes $struct (struct)))) + (type $desc (sub (describes $struct (struct)))) + ) + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $sub (sub $struct (descriptor $sub.desc (struct)))) + ;; NTRAP: (rec + ;; NTRAP-NEXT: (type $sub (sub $struct (descriptor $sub.desc (struct)))) + (type $sub (sub $struct (descriptor $sub.desc (struct)))) + ;; CHECK: (type $sub.desc (sub $desc (describes $sub (struct)))) + ;; NTRAP: (type $sub.desc (sub $desc (describes $sub (struct)))) + (type $sub.desc (sub $desc (describes $sub (struct)))) ) (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $struct-i32 (descriptor $struct-i32.desc (struct (field i32)))) + ;; NTRAP: (rec + ;; NTRAP-NEXT: (type $struct-i32 (descriptor $struct-i32.desc (struct (field i32)))) (type $struct-i32 (descriptor $struct-i32.desc (struct (field i32)))) ;; CHECK: (type $struct-i32.desc (describes $struct-i32 (struct))) + ;; NTRAP: (type $struct-i32.desc (describes $struct-i32 (struct))) (type $struct-i32.desc (describes $struct-i32 (struct))) ) - ;; CHECK: (import "" "" (func $effect (type $5))) + ;; CHECK: (import "" "" (func $effect (type $6))) + ;; NTRAP: (import "" "" (func $effect (type $6))) (import "" "" (func $effect)) - ;; CHECK: (func $trap-null-desc (type $4) (result (ref (exact $struct))) + ;; CHECK: (func $trap-null-desc (type $9) (result (ref (exact $struct))) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NTRAP: (func $trap-null-desc (type $9) (result (ref (exact $struct))) + ;; NTRAP-NEXT: (unreachable) + ;; NTRAP-NEXT: ) (func $trap-null-desc (result (ref (exact $struct))) (struct.new $struct (ref.null none) ) ) - ;; CHECK: (func $trap-null-desc-fallthrough (type $4) (result (ref (exact $struct))) + ;; CHECK: (func $trap-null-desc-fallthrough (type $9) (result (ref (exact $struct))) ;; CHECK-NEXT: (local $desc (ref null (exact $desc))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $desc @@ -40,6 +62,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; NTRAP: (func $trap-null-desc-fallthrough (type $9) (result (ref (exact $struct))) + ;; NTRAP-NEXT: (local $desc (ref null (exact $desc))) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (local.tee $desc + ;; NTRAP-NEXT: (ref.null none) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (unreachable) + ;; NTRAP-NEXT: ) (func $trap-null-desc-fallthrough (result (ref (exact $struct))) (local $desc (ref null (exact $desc))) (struct.new $struct @@ -49,11 +80,16 @@ ) ) - ;; CHECK: (func $nonnull-cast-desc (type $6) (param $desc (ref null (exact $desc))) (result (ref (exact $struct))) + ;; CHECK: (func $nonnull-cast-desc (type $17) (param $desc (ref null (exact $desc))) (result (ref (exact $struct))) ;; CHECK-NEXT: (struct.new_default $struct ;; CHECK-NEXT: (local.get $desc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; NTRAP: (func $nonnull-cast-desc (type $17) (param $desc (ref null (exact $desc))) (result (ref (exact $struct))) + ;; NTRAP-NEXT: (struct.new_default $struct + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) (func $nonnull-cast-desc (param $desc (ref null (exact $desc))) (result (ref (exact $struct))) (struct.new $struct (ref.as_non_null @@ -64,7 +100,7 @@ ;; Test that when we optimize a struct.new to a struct.new_default, we drop ;; the field operands but keep the descriptor. - ;; CHECK: (func $new-default-keep-desc (type $7) (result anyref) + ;; CHECK: (func $new-default-keep-desc (type $18) (result anyref) ;; CHECK-NEXT: (struct.new_default $struct-i32 ;; CHECK-NEXT: (block (result (ref (exact $struct-i32.desc))) ;; CHECK-NEXT: (call $effect) @@ -72,6 +108,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; NTRAP: (func $new-default-keep-desc (type $18) (result anyref) + ;; NTRAP-NEXT: (struct.new_default $struct-i32 + ;; NTRAP-NEXT: (block (result (ref (exact $struct-i32.desc))) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (struct.new_default $struct-i32.desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) (func $new-default-keep-desc (result anyref) (struct.new $struct-i32 (i32.const 0) @@ -83,4 +127,985 @@ ) ) ) + + ;; CHECK: (func $cast-desc-null-desc (type $6) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-null-desc (type $6) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (unreachable) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-null-desc + (drop + (ref.cast_desc (ref (exact $struct)) + (struct.new $struct + (struct.new $desc) + ) + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $cast-desc-no-glb (type $8) (param $nn-sub (ref $sub)) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (local.get $nn-sub) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-no-glb (type $8) (param $nn-sub (ref $sub)) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (local.get $nn-sub) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-no-glb (param $nn-sub (ref $sub)) (param $desc (ref $desc)) + (drop + ;; We cannot improve the cast target heap type, but we can improve the + ;; nullability. With traps-never-happen we can optimize fully. + (ref.cast_desc (ref null $struct) + (local.get $nn-sub) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-improve-nullability (type $19) (param $nn-any (ref any)) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (local.get $nn-any) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-improve-nullability (type $19) (param $nn-any (ref any)) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.cast_desc (ref $struct) + ;; NTRAP-NEXT: (local.get $nn-any) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-improve-nullability (param $nn-any (ref any)) (param $desc (ref $desc)) + (drop + ;; Like above, but the ref isn't a subtype of the cast type. We can still + ;; improve the nullability. + (ref.cast_desc (ref null $struct) + (local.get $nn-any) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-only-improve-nullability (type $7) (param $any anyref) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-only-improve-nullability (type $7) (param $any anyref) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.cast_desc (ref $struct) + ;; NTRAP-NEXT: (local.get $any) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-only-improve-nullability (param $any anyref) (param $desc (ref $desc)) + (drop + ;; Now the cast target is already non-nullable. We shouldn't make it + ;; nullable. + (ref.cast_desc (ref $struct) + (local.get $any) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-unrelated-type (type $10) (param $struct (ref $struct)) (param $desc-i32 (ref (exact $struct-i32.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-unrelated-type (type $10) (param $struct (ref $struct)) (param $desc-i32 (ref (exact $struct-i32.desc))) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (unreachable) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-unrelated-type (param $struct (ref $struct)) (param $desc-i32 (ref (exact $struct-i32.desc))) + (drop + ;; We know this cast will fail so we can optimize. + (ref.cast_desc (ref (exact $struct-i32)) + (local.get $struct) + (local.get $desc-i32) + ) + ) + ) + + ;; CHECK: (func $cast-desc-unrelated-type-effects (type $10) (param $struct (ref $struct)) (param $desc-i32 (ref (exact $struct-i32.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref (exact $struct-i32.desc))) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-unrelated-type-effects (type $10) (param $struct (ref $struct)) (param $desc-i32 (ref (exact $struct-i32.desc))) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $struct)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $struct) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref (exact $struct-i32.desc))) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $desc-i32) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (unreachable) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-unrelated-type-effects (param $struct (ref $struct)) (param $desc-i32 (ref (exact $struct-i32.desc))) + (drop + ;; As above, but now with effects we need to keep. + (ref.cast_desc (ref (exact $struct-i32)) + (block (result (ref $struct)) + (call $effect) + (local.get $struct) + ) + (block (result (ref (exact $struct-i32.desc))) + (call $effect) + (local.get $desc-i32) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-unrelated-type-nullable (type $11) (param $struct-i32 (ref null $struct-i32)) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref null $struct) + ;; CHECK-NEXT: (local.get $struct-i32) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-unrelated-type-nullable (type $11) (param $struct-i32 (ref null $struct-i32)) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.null none) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-unrelated-type-nullable (param $struct-i32 (ref null $struct-i32)) (param $desc (ref $desc)) + (drop + ;; Same as above, but now we allow nulls. We can optimize with traps-never-happen. + (ref.cast_desc (ref null $struct) + (local.get $struct-i32) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-unrelated-type-nullable-effects (type $11) (param $struct-i32 (ref null $struct-i32)) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref null $struct) + ;; CHECK-NEXT: (block (result (ref null $struct-i32)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $struct-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref $desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-unrelated-type-nullable-effects (type $11) (param $struct-i32 (ref null $struct-i32)) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result nullref) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref null $struct-i32)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $struct-i32) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $desc)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (ref.null none) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-unrelated-type-nullable-effects (param $struct-i32 (ref null $struct-i32)) (param $desc (ref $desc)) + (drop + ;; Same as above, but now with effects we cannot drop. + (ref.cast_desc (ref null $struct) + (block (result (ref null $struct-i32)) + (call $effect) + (local.get $struct-i32) + ) + (block (result (ref $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-wrong-desc (type $6) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref (exact $struct)) + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-wrong-desc (type $6) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref (exact $struct))) + ;; NTRAP-NEXT: (struct.new_default $struct + ;; NTRAP-NEXT: (struct.new_default $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-wrong-desc + (drop + ;; With traps-never-happen we assume the cast will succeed and optimize it + ;; out, even even though it would actually fail. + ;; TODO: We could see that the descriptor used in the allocation and the + ;; descriptor used in the cast are different and optimize without TNH. + (ref.cast_desc (ref (exact $struct)) + (struct.new $struct + (struct.new $desc) + ) + (struct.new $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-wrong-desc-effects (type $6) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref (exact $struct)) + ;; CHECK-NEXT: (block (result (ref (exact $struct))) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref (exact $desc))) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-wrong-desc-effects (type $6) + ;; NTRAP-NEXT: (local $0 (ref (exact $struct))) + ;; NTRAP-NEXT: (local $1 (ref (exact $desc))) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref (exact $struct))) + ;; NTRAP-NEXT: (local.set $0 + ;; NTRAP-NEXT: (block (result (ref (exact $struct))) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (struct.new_default $struct + ;; NTRAP-NEXT: (struct.new_default $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (local.set $1 + ;; NTRAP-NEXT: (block (result (ref (exact $desc))) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (struct.new_default $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (local.get $0) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-wrong-desc-effects + (drop + ;; Same, but with effects. + (ref.cast_desc (ref (exact $struct)) + (block (result (ref (exact $struct))) + (call $effect) + (struct.new $struct + (struct.new $desc) + ) + ) + (block (result (ref (exact $desc))) + (call $effect) + (struct.new $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-weaker-nondesc-child (type $12) (param $ref anyref) (param $desc (ref $sub.desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $sub) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-weaker-nondesc-child (type $12) (param $ref anyref) (param $desc (ref $sub.desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.cast_desc (ref $sub) + ;; NTRAP-NEXT: (local.get $ref) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-weaker-nondesc-child (param $ref anyref) (param $desc (ref $sub.desc)) + (drop + ;; Optimize out the weaker child cast. + (ref.cast_desc (ref $sub) + (ref.cast (ref any) + (local.get $ref) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-weaker-nondesc-child-effects (type $12) (param $ref anyref) (param $desc (ref $sub.desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $sub) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref $sub.desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-weaker-nondesc-child-effects (type $12) (param $ref anyref) (param $desc (ref $sub.desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.cast_desc (ref $sub) + ;; NTRAP-NEXT: (block (result anyref) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $ref) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (block (result (ref $sub.desc)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-weaker-nondesc-child-effects (param $ref anyref) (param $desc (ref $sub.desc)) + (drop + ;; Same, but with effects. + (ref.cast_desc (ref $sub) + (ref.cast (ref any) + (block (result anyref) + (call $effect) + (local.get $ref) + ) + ) + (block (result (ref $sub.desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-nondesc-child (type $7) (param $ref anyref) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-nondesc-child (type $7) (param $ref anyref) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (ref.cast (ref $sub) + ;; NTRAP-NEXT: (local.get $ref) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-nondesc-child (param $ref anyref) (param $desc (ref $desc)) + (drop + ;; With traps-never-happen we assume the outer cast will succeed and + ;; optimize it out. + (ref.cast_desc (ref $struct) + (ref.cast (ref $sub) + (local.get $ref) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-nondesc-child-effects (type $7) (param $ref anyref) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref $desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-nondesc-child-effects (type $7) (param $ref anyref) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (local $2 (ref $sub)) + ;; NTRAP-NEXT: (local $3 (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (local.set $2 + ;; NTRAP-NEXT: (ref.cast (ref $sub) + ;; NTRAP-NEXT: (block (result anyref) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $ref) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (local.set $3 + ;; NTRAP-NEXT: (block (result (ref $desc)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (local.get $2) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-nondesc-child-effects (param $ref anyref) (param $desc (ref $desc)) + (drop + ;; Same, but with effects. + (ref.cast_desc (ref $struct) + (ref.cast (ref $sub) + (block (result anyref) + (call $effect) + (local.get $ref) + ) + ) + (block (result (ref $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-weaker-desc-child (type $13) (param $ref anyref) (param $desc (ref $desc)) (param $sub.desc (ref $sub.desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $sub) + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $sub.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-weaker-desc-child (type $13) (param $ref anyref) (param $desc (ref $desc)) (param $sub.desc (ref $sub.desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.cast_desc (ref $sub) + ;; NTRAP-NEXT: (block (result anyref) + ;; NTRAP-NEXT: (local.get $ref) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (local.get $sub.desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-weaker-desc-child (param $ref anyref) (param $desc (ref $desc)) (param $sub.desc (ref $sub.desc)) + (drop + ;; We can only optimize the weaker child cast with traps-never-happen + ;; because it might fail due to an incorrect descriptor even when the + ;; parent cast would succeed. + (ref.cast_desc (ref $sub) + (ref.cast_desc (ref $struct) + (local.get $ref) + (local.get $desc) + ) + (local.get $sub.desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-desc-child (type $13) (param $ref anyref) (param $desc (ref $desc)) (param $sub.desc (ref $sub.desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (ref.cast_desc (ref $sub) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $sub.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-desc-child (type $13) (param $ref anyref) (param $desc (ref $desc)) (param $sub.desc (ref $sub.desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (ref.cast_desc (ref $sub) + ;; NTRAP-NEXT: (local.get $ref) + ;; NTRAP-NEXT: (local.get $sub.desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-desc-child (param $ref anyref) (param $desc (ref $desc)) (param $sub.desc (ref $sub.desc)) + (drop + ;; Like above, but now when we optimize with traps-never-happen we replace + ;; the parent with the child. + (ref.cast_desc (ref $struct) + (ref.cast_desc (ref $sub) + (local.get $ref) + (local.get $sub.desc) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-fallthrough (type $8) (param $sub (ref $sub)) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-fallthrough (type $8) (param $sub (ref $sub)) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (local $2 (ref $sub)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (local.tee $2 + ;; NTRAP-NEXT: (local.get $sub) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (local.get $2) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-fallthrough (param $sub (ref $sub)) (param $desc (ref $desc)) + (drop + ;; Like above, but now the stronger child is deeper. We use a tee to + ;; extract it. + (ref.cast_desc (ref $struct) + (block (result anyref) + (local.get $sub) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-fallthrough-effects (type $8) (param $sub (ref $sub)) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref $desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-fallthrough-effects (type $8) (param $sub (ref $sub)) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (local $2 (ref $sub)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.tee $2 + ;; NTRAP-NEXT: (local.get $sub) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $desc)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (local.get $2) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-fallthrough-effects (param $sub (ref $sub)) (param $desc (ref $desc)) + (drop + ;; Like above, but now with effects we cannot drop. + (ref.cast_desc (ref $struct) + (block (result anyref) + (call $effect) + (local.get $sub) + ) + (block (result (ref $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-fallthrough-nullck (type $14) (param $sub (ref null $sub)) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-fallthrough-nullck (type $14) (param $sub (ref null $sub)) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (local $2 (ref null $sub)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref null $sub)) + ;; NTRAP-NEXT: (local.tee $2 + ;; NTRAP-NEXT: (local.get $sub) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (ref.as_non_null + ;; NTRAP-NEXT: (local.get $2) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-fallthrough-nullck (param $sub (ref null $sub)) (param $desc (ref $desc)) + (drop + ;; Like above, but now we also need a null check. + (ref.cast_desc (ref $struct) + (block (result anyref) + (local.get $sub) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-fallthrough-nullck-effects (type $14) (param $sub (ref null $sub)) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref $desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-fallthrough-nullck-effects (type $14) (param $sub (ref null $sub)) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (local $2 (ref null $sub)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $sub)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref null $sub)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.tee $2 + ;; NTRAP-NEXT: (local.get $sub) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $desc)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (ref.as_non_null + ;; NTRAP-NEXT: (local.get $2) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-fallthrough-nullck-effects (param $sub (ref null $sub)) (param $desc (ref $desc)) + (drop + ;; Like above, but now with effects. + (ref.cast_desc (ref $struct) + (block (result anyref) + (call $effect) + (local.get $sub) + ) + (block (result (ref $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-fallthrough-null (type $15) (param $null nullref) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-fallthrough-null (type $15) (param $null nullref) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.null none) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-fallthrough-null (param $null nullref) (param $desc (ref $desc)) + (drop + ;; Like above, but now the value itself is null and we allow it. + (ref.cast_desc (ref null $struct) + (block (result anyref) + (local.get $null) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-fallthrough-null-effects (type $15) (param $null nullref) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $null) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-fallthrough-null-effects (type $15) (param $null nullref) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result nullref) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result nullref) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $null) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref $desc)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (ref.null none) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-fallthrough-null-effects (param $null nullref) (param $desc (ref $desc)) + (drop + ;; Like above, but now with effects. + (ref.cast_desc (ref null $struct) + (block (result anyref) + (call $effect) + (local.get $null) + ) + (block (result (ref $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-fallthrough-null-null-desc (type $16) (param $null nullref) (param $desc (ref null $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-fallthrough-null-null-desc (type $16) (param $null nullref) (param $desc (ref null $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.null none) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-fallthrough-null-null-desc (param $null nullref) (param $desc (ref null $desc)) + (drop + ;; Like above, but now the descriptor is nullable so we have to add a null + ;; check to it. + (ref.cast_desc (ref null $struct) + (block (result anyref) + (local.get $null) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stronger-fallthrough-null-null-desc-effects (type $16) (param $null nullref) (param $desc (ref null $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $null) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result (ref null $desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-stronger-fallthrough-null-null-desc-effects (type $16) (param $null nullref) (param $desc (ref null $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result nullref) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result nullref) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $null) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block (result (ref null $desc)) + ;; NTRAP-NEXT: (call $effect) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (ref.null none) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-stronger-fallthrough-null-null-desc-effects (param $null nullref) (param $desc (ref null $desc)) + (drop + ;; Like above, but now with effects. + (ref.cast_desc (ref null $struct) + (block (result anyref) + (call $effect) + (local.get $null) + ) + (block (result (ref null $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-ref-as-non-null (type $7) (param $any anyref) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref $struct) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-ref-as-non-null (type $7) (param $any anyref) (param $desc (ref $desc)) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.cast_desc (ref $struct) + ;; NTRAP-NEXT: (local.get $any) + ;; NTRAP-NEXT: (local.get $desc) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-ref-as-non-null (param $any anyref) (param $desc (ref $desc)) + (drop + ;; We can roll the ref.as_non_null into the cast. + (ref.cast_desc (ref null $struct) + (ref.as_non_null + (local.get $any) + ) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-unreachable-desc (type $6) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NTRAP: (func $cast-desc-unreachable-desc (type $6) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (ref.null none) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (drop + ;; NTRAP-NEXT: (unreachable) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: (unreachable) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + ;; NTRAP-NEXT: ) + (func $cast-desc-unreachable-desc + (drop + ;; Don't crash on an unreachable descriptor. + (ref.cast_desc (ref $struct) + (ref.null none) + (unreachable) + ) + ) + ) ) diff --git a/test/lit/passes/optimize-instructions-gc-iit.wast b/test/lit/passes/optimize-instructions-gc-iit.wast index 224e53d4050..feefa3435b7 100644 --- a/test/lit/passes/optimize-instructions-gc-iit.wast +++ b/test/lit/passes/optimize-instructions-gc-iit.wast @@ -37,12 +37,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $child) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; TNH: (func $ref-cast-iit (type $4) (param $parent (ref $parent)) (param $child (ref $child)) (param $other (ref $other)) @@ -58,12 +53,7 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result (ref none)) - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (local.get $child) - ;; TNH-NEXT: ) - ;; TNH-NEXT: (unreachable) - ;; TNH-NEXT: ) + ;; TNH-NEXT: (unreachable) ;; TNH-NEXT: ) ;; TNH-NEXT: ) (func $ref-cast-iit diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index 56826da24a3..9323488e336 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -651,14 +651,6 @@ ) ;; TNH: (func $cast-if-null (type $5) (param $x (ref none)) (result (ref $struct)) - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (i32.const 1) - ;; TNH-NEXT: ) - ;; TNH-NEXT: (unreachable) - ;; TNH-NEXT: ) - ;; TNH-NEXT: ) ;; TNH-NEXT: (unreachable) ;; TNH-NEXT: ) ;; NO_TNH: (func $cast-if-null (type $5) (param $x (ref none)) (result (ref $struct)) @@ -676,7 +668,7 @@ ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) (func $cast-if-null (param $x (ref none)) (result (ref $struct)) - ;; We can remove the unreachable arm of the if here in TNH mode. While doing + ;; We can remove the reachable arm of the if here in TNH mode. While doing ;; so we must refinalize properly or else we'll hit an error in pass-debug ;; mode. (ref.cast (ref $struct) @@ -693,14 +685,6 @@ ) ;; TNH: (func $cast-if-null-flip (type $5) (param $x (ref none)) (result (ref $struct)) - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (i32.const 1) - ;; TNH-NEXT: ) - ;; TNH-NEXT: (unreachable) - ;; TNH-NEXT: ) - ;; TNH-NEXT: ) ;; TNH-NEXT: (unreachable) ;; TNH-NEXT: ) ;; NO_TNH: (func $cast-if-null-flip (type $5) (param $x (ref none)) (result (ref $struct)) @@ -734,62 +718,27 @@ ;; TNH: (func $cast-to-bottom (type $11) (param $ref (ref any)) (param $nullable-ref anyref) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result (ref none)) - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (local.get $ref) - ;; TNH-NEXT: ) - ;; TNH-NEXT: (unreachable) - ;; TNH-NEXT: ) + ;; TNH-NEXT: (unreachable) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result (ref none)) - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (local.get $nullable-ref) - ;; TNH-NEXT: ) - ;; TNH-NEXT: (unreachable) - ;; TNH-NEXT: ) + ;; TNH-NEXT: (unreachable) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result (ref none)) - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (local.get $ref) - ;; TNH-NEXT: ) - ;; TNH-NEXT: (unreachable) - ;; TNH-NEXT: ) + ;; TNH-NEXT: (unreachable) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result nullref) - ;; TNH-NEXT: (drop - ;; TNH-NEXT: (local.get $nullable-ref) - ;; TNH-NEXT: ) - ;; TNH-NEXT: (ref.null none) - ;; TNH-NEXT: ) + ;; TNH-NEXT: (ref.null none) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; NO_TNH: (func $cast-to-bottom (type $11) (param $ref (ref any)) (param $nullable-ref anyref) ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result (ref none)) - ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (local.get $ref) - ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (unreachable) - ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result (ref none)) - ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (local.get $nullable-ref) - ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (unreachable) - ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result (ref none)) - ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (local.get $ref) - ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (unreachable) - ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.cast nullref @@ -958,9 +907,9 @@ ;; TNH: (func $if.null.child.but.no.flow (type $void) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result (ref nofunc)) + ;; TNH-NEXT: (block ;; TNH-NEXT: (drop - ;; TNH-NEXT: (if (result (ref nofunc)) + ;; TNH-NEXT: (if ;; TNH-NEXT: (i32.const 1) ;; TNH-NEXT: (then ;; TNH-NEXT: (return) @@ -976,9 +925,9 @@ ;; TNH-NEXT: ) ;; NO_TNH: (func $if.null.child.but.no.flow (type $void) ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result (ref nofunc)) + ;; NO_TNH-NEXT: (block ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (if (result (ref nofunc)) + ;; NO_TNH-NEXT: (if ;; NO_TNH-NEXT: (i32.const 1) ;; NO_TNH-NEXT: (then ;; NO_TNH-NEXT: (return) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index d9010a2c445..8d413df37aa 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -605,7 +605,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref i31) ;; CHECK-NEXT: (local.get $x) @@ -1133,12 +1133,7 @@ ;; CHECK: (func $incompatible-cast-of-non-null (type $37) (param $struct (ref $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $incompatible-cast-of-non-null (param $struct (ref $struct)) @@ -2033,7 +2028,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $array) ;; CHECK-NEXT: (local.get $x) @@ -2043,7 +2038,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $array) ;; CHECK-NEXT: (local.get $x) @@ -2053,7 +2048,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $array) ;; CHECK-NEXT: (local.get $x) @@ -2365,28 +2360,13 @@ ;; CHECK: (func $ref-cast-heap-type-incompatible (type $13) (param $null-b (ref null $B)) (param $b (ref $B)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $b) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $null-b) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $b) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast nullref @@ -2929,7 +2909,7 @@ ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $0 - ;; CHECK-NEXT: (loop (result (ref nofunc)) + ;; CHECK-NEXT: (loop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2954,9 +2934,6 @@ ) ;; CHECK: (func $non-null-bottom-cast (type $46) (result (ref nofunc)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.func $non-null-bottom-cast) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $non-null-bottom-cast (result (ref nofunc)) From dcaf83303810da9f5c04d747490796844b1e02e3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Jul 2025 13:10:49 -0700 Subject: [PATCH 598/622] [Custom Descriptors] Fix ref.cast_desc of i31ref (#7719) We previously hit an assertion failure when accessing the GCData of the ref literal because i31 literals do not have GCData. --- src/wasm-interpreter.h | 4 ++++ test/spec/ref.cast_desc.wast | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d079e759bdb..701a89199fe 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1665,6 +1665,10 @@ class ExpressionRunner : public OverriddenVisitor { trap("null descriptor"); } Literal val = ref.getSingleValue(); + if (!val.isData() && !val.isNull()) { + // For example, i31ref. + return typename Cast::Failure{val}; + } auto data = val.getGCData(); if (!data) { // Check whether null is allowed. diff --git a/test/spec/ref.cast_desc.wast b/test/spec/ref.cast_desc.wast index 1380ea01aa1..6fc2710310f 100644 --- a/test/spec/ref.cast_desc.wast +++ b/test/spec/ref.cast_desc.wast @@ -164,6 +164,15 @@ ) (i32.const 0) ) + + (func (export "cast-i31ref") + (drop + (ref.cast_desc (ref $super) + (ref.i31 (i32.const 0)) + (struct.new $super.desc) + ) + ) + ) ) (assert_return (invoke "cast-success")) @@ -178,6 +187,7 @@ (assert_trap (invoke "cast-nn-fail-null-desc") "null descriptor") (assert_return (invoke "cast-branch-ref") (i32.const 1)) (assert_return (invoke "cast-branch-desc") (i32.const 1)) +(assert_trap (invoke "cast-i31ref") "cast error") (assert_malformed ;; Cast type must be a reference. From c303248584c412384f82f9b0c2fcf83933adf906 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Jul 2025 17:13:51 -0700 Subject: [PATCH 599/622] [Custom Descriptors] Optimize branching descriptor casts (#7727) Update RemoveUnusedBrs to optimize br_on_cast_desc and br_on_cast_desc_fail in addition to the existing branching casts. --- scripts/test/fuzzing.py | 1 + src/passes/RemoveUnusedBrs.cpp | 394 +++++----- test/lit/passes/remove-unused-brs-desc.wast | 750 ++++++++++++++++++++ 3 files changed, 983 insertions(+), 162 deletions(-) create mode 100644 test/lit/passes/remove-unused-brs-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index e878d1f9a13..c31fcf85f6d 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -130,6 +130,7 @@ 'gto-desc.wast', 'type-ssa-desc.wast', 'abstract-type-refining-desc.wast', + 'remove-unused-brs-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 5b505cf1d60..b2237dcb823 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -32,6 +32,7 @@ #include "pass.h" #include "support/small_set.h" #include "wasm-builder.h" +#include "wasm-type.h" #include "wasm.h" namespace wasm { @@ -874,180 +875,249 @@ struct RemoveUnusedBrs : public WalkerPass> { return builder.makeRefCast(expr, type); }; - if (curr->op == BrOnNull) { - if (refType.isNull()) { - // The branch will definitely be taken. - replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), - builder.makeBreak(curr->name))); - worked = true; - return; - } - if (refType.isNonNullable()) { - // The branch will definitely not be taken. - replaceCurrent(maybeCast(curr->ref, curr->type)); - worked = true; - return; + // When we optimize out a cast, we still need the ref value to either + // send on the optimized branch or return from the current expression. + // When we have a descriptor cast, the ref value needs to be moved + // across the descriptor value, which might have side effects. If so, we + // need to use a scratch local. + auto getRefValue = [&]() -> Expression* { + if (curr->desc) { + // Preserve the trap on a null descriptor. + if (curr->desc->type.isNullable()) { + curr->desc = builder.makeRefAs(RefAsNonNull, curr->desc); + } + Block* ref = + ChildLocalizer(curr, getFunction(), *getModule(), passOptions) + .getChildrenReplacement(); + ref->list.push_back(curr->ref); + ref->type = curr->ref->type; + return ref; } - return; - } + return curr->ref; + }; - if (curr->op == BrOnNonNull) { - if (refType.isNull()) { - // Definitely not taken. - replaceCurrent(builder.makeDrop(curr->ref)); - worked = true; + switch (curr->op) { + case BrOnNull: + if (refType.isNull()) { + // The branch will definitely be taken. + replaceCurrent(builder.makeSequence( + builder.makeDrop(curr->ref), builder.makeBreak(curr->name))); + worked = true; + return; + } + if (refType.isNonNullable()) { + // The branch will definitely not be taken. + replaceCurrent(maybeCast(curr->ref, curr->type)); + worked = true; + return; + } return; - } - if (refType.isNonNullable()) { - // Definitely taken. - replaceCurrent(builder.makeBreak( - curr->name, maybeCast(curr->ref, curr->getSentType()))); - worked = true; + case BrOnNonNull: + if (refType.isNull()) { + // Definitely not taken. + replaceCurrent(builder.makeDrop(curr->ref)); + worked = true; + return; + } + if (refType.isNonNullable()) { + // Definitely taken. + replaceCurrent(builder.makeBreak( + curr->name, maybeCast(curr->ref, curr->getSentType()))); + worked = true; + return; + } return; - } - return; - } + case BrOnCast: + case BrOnCastFail: + case BrOnCastDesc: + case BrOnCastDescFail: { + bool onFail = + curr->op == BrOnCastFail || curr->op == BrOnCastDescFail; + bool isDesc = + curr->op == BrOnCastDesc || curr->op == BrOnCastDescFail; + + // Improve the cast target type as much as possible given what we + // know about the input. Unlike in BrOn::finalize(), we consider + // type information from all the fallthrough values here. We can + // continue to further optimizations after this, and those + // optimizations might even benefit from this improvement. + auto improvedType = curr->castType; + if (!isDesc) { + improvedType = Type::getGreatestLowerBound(improvedType, refType); + } else { + // For descriptor casts, the target heap type is controlled by the + // descriptor operand, but we can still improve nullability. + if (improvedType.isNullable() && refType.isNonNullable()) { + improvedType = improvedType.with(NonNullable); + } + } + if (!curr->castType.isExact()) { + // When custom descriptors is not enabled, nontrivial exact casts + // are not allowed. + improvedType = + improvedType.withInexactIfNoCustomDescs(getModule()->features); + } + if (onFail) { + // BrOnCastFail sends the input type, with adjusted nullability. + // The input heap type makes sense for the branch target, and we + // will not change it anyhow, but we need to be careful with + // nullability: if the cast type was nullable, then we were + // sending a non-nullable value to the branch, and if we refined + // the cast type to non- nullable, we would no longer be doing + // that. In other words, we must not refine the nullability, as + // that would *un*refine the send type. + // TODO: Consider allowing this change if the branch target + // expects a nullable type anyway. + if (curr->castType.isNullable() && improvedType.isNonNullable()) { + improvedType = improvedType.with(Nullable); + } + } + if (improvedType != Type::unreachable && + improvedType != curr->castType) { + curr->castType = improvedType; + auto oldType = curr->type; + curr->finalize(); + worked = true; + + // We refined the castType, which may *un*-refine the BrOn itself. + // Imagine the castType was nullable before, then nulls would go + // on the branch, and so the BrOn could only flow out a + // non-nullable value, and that was its type. If we refine the + // castType to be non-nullable then nulls no longer go through, + // making the BrOn itself nullable. This should not normally + // happen, but can occur because we look at the fallthrough of the + // ref: + // + // (br_on_cast + // (local.tee $unrefined + // (refined + // + // That is, we may see a more refined type for our GLB computation + // than the wasm type system does, if a local.tee or such ends up + // unrefining the type. + // + // To check for this and fix it, see if we need a cast in order to + // be a subtype of the old type. + auto* rep = maybeCast(curr, oldType); + if (rep != curr) { + replaceCurrent(rep); + // Exit after doing so, leaving further work for other cycles. + return; + } + } - // Improve the cast target type as much as possible given what we know - // about the input. Unlike in BrOn::finalize(), we consider type - // information from all the fallthrough values here. We can continue to - // further optimizations after this, and those optimizations might even - // benefit from this improvement. - auto glb = Type::getGreatestLowerBound(curr->castType, refType); - if (!curr->castType.isExact()) { - // When custom descriptors is not enabled, nontrivial exact casts are - // not allowed. - glb = glb.withInexactIfNoCustomDescs(getModule()->features); - } - if (curr->op == BrOnCastFail) { - // BrOnCastFail sends the input type, with adjusted nullability. The - // input heap type makes sense for the branch target, and we will not - // change it anyhow, but we need to be careful with nullability: if - // the cast type was nullable, then we were sending a non-nullable - // value to the branch, and if we refined the cast type to non- - // nullable, we would no longer be doing that. In other words, we must - // not refine the nullability, as that would *un*refine the send type. - if (curr->castType.isNullable() && glb.isNonNullable()) { - glb = glb.with(Nullable); - } - } - if (glb != Type::unreachable && glb != curr->castType) { - curr->castType = glb; - auto oldType = curr->type; - curr->finalize(); - worked = true; - - // We refined the castType, which may *un*-refine the BrOn itself. - // Imagine the castType was nullable before, then nulls would go on - // the branch, and so the BrOn could only flow out a non-nullable - // value, and that was its type. If we refine the castType to be - // non-nullable then nulls no longer go through, making the BrOn - // itself nullable. This should not normally happen, but can occur - // because we look at the fallthrough of the ref: - // - // (br_on_cast - // (local.tee $unrefined - // (refined - // - // That is, we may see a more refined type for our GLB computation - // than the wasm type system does, if a local.tee or such ends up - // unrefining the type. - // - // To check for this and fix it, see if we need a cast in order to be - // a subtype of the old type. - auto* rep = maybeCast(curr, oldType); - if (rep != curr) { - replaceCurrent(rep); - // Exit after doing so, leaving further work for other cycles. - return; - } - } + // Depending on what we know about the cast results, we may be able + // to optimize. + auto result = + GCTypeUtils::evaluateCastCheck(refType, curr->castType); + + if (isDesc) { + // Knowing that the types work out is insufficient to know that a + // descriptor cast will succeed. We would need to know that the + // descriptor values match as well. + switch (result) { + case GCTypeUtils::Success: + case GCTypeUtils::SuccessOnlyIfNonNull: + // We cannot optimize. + return; + case GCTypeUtils::Unknown: + case GCTypeUtils::Failure: + case GCTypeUtils::Unreachable: + case GCTypeUtils::SuccessOnlyIfNull: + break; + } + } - // Depending on what we know about the cast results, we may be able to - // optimize. - auto result = GCTypeUtils::evaluateCastCheck(refType, curr->castType); - if (curr->op == BrOnCastFail) { - result = GCTypeUtils::flipEvaluationResult(result); - } + if (onFail) { + result = GCTypeUtils::flipEvaluationResult(result); + } - switch (result) { - case GCTypeUtils::Unknown: - // Anything could happen, so we cannot optimize. - return; - case GCTypeUtils::Success: { - replaceCurrent(builder.makeBreak( - curr->name, maybeCast(curr->ref, curr->getSentType()))); - worked = true; - return; - } - case GCTypeUtils::Failure: { - replaceCurrent(maybeCast(curr->ref, curr->type)); - worked = true; - return; - } - case GCTypeUtils::SuccessOnlyIfNull: { - // TODO: optimize this case using the following replacement, which - // avoids using any scratch locals and only does a single null - // check, but does require generating a fresh label: - // - // (br_on_cast $l (ref null $X) (ref null $Y) - // (...) - // ) - // => - // (block $l' (result (ref $X)) - // (br_on_non_null $l' ;; reuses `curr` - // (...) - // ) - // (br $l - // (ref.null bot) - // ) - // ) - return; - } - case GCTypeUtils::SuccessOnlyIfNonNull: { - // Perform this replacement: - // - // (br_on_cast $l (ref null $X') (ref $X)) - // (...) - // ) - // => - // (block (result (ref bot)) - // (br_on_non_null $l ;; reuses `curr` - // (...) - // (ref.null bot) - // ) - // - // A RefCast is added in some cases, but this is still generally - // worth doing as the BrOnNonNull and the appended null may end up - // optimized with surrounding code. - auto* casted = - maybeCast(curr->ref, curr->getSentType().with(Nullable)); - curr->ref = casted; - curr->op = BrOnNonNull; - curr->castType = Type::none; - curr->type = Type::none; - - assert(curr->ref->type.isRef()); - auto* refNull = builder.makeRefNull(curr->ref->type.getHeapType()); - replaceCurrent(builder.makeBlock({curr, refNull}, refNull->type)); - worked = true; - return; - } - case GCTypeUtils::Unreachable: { - // The cast is never executed, possibly because its input type is - // uninhabitable. Replace it with unreachable. - auto* drop = builder.makeDrop(curr->ref); - auto* unreachable = ExpressionManipulator::unreachable(curr); - replaceCurrent(builder.makeBlock({drop, unreachable})); - worked = true; - return; + switch (result) { + case GCTypeUtils::Unknown: + // Anything could happen, so we cannot optimize. + return; + case GCTypeUtils::Success: { + replaceCurrent(builder.makeBreak( + curr->name, maybeCast(getRefValue(), curr->getSentType()))); + worked = true; + return; + } + case GCTypeUtils::Failure: { + replaceCurrent(maybeCast(getRefValue(), curr->type)); + worked = true; + return; + } + case GCTypeUtils::SuccessOnlyIfNull: { + // TODO: optimize this case using the following replacement, + // which avoids using any scratch locals and only does a single + // null check, but does require generating a fresh label: + // + // (br_on_cast $l (ref null $X) (ref null $Y) + // (...) + // ) + // => + // (block $l' (result (ref $X)) + // (br_on_non_null $l' ;; reuses `curr` + // (...) + // ) + // (br $l + // (ref.null bot) + // ) + // ) + return; + } + case GCTypeUtils::SuccessOnlyIfNonNull: { + // Perform this replacement: + // + // (br_on_cast $l (ref null $X') (ref $X)) + // (...) + // ) + // => + // (block (result (ref bot)) + // (br_on_non_null $l ;; reuses `curr` + // (...) + // (ref.null bot) + // ) + // + // A RefCast is added in some cases, but this is still generally + // worth doing as the BrOnNonNull and the appended null may end + // up optimized with surrounding code. + auto* casted = + maybeCast(getRefValue(), curr->getSentType().with(Nullable)); + curr->ref = casted; + curr->desc = nullptr; + curr->op = BrOnNonNull; + curr->castType = Type::none; + curr->type = Type::none; + + assert(curr->ref->type.isRef()); + auto* refNull = + builder.makeRefNull(curr->ref->type.getHeapType()); + replaceCurrent( + builder.makeBlock({curr, refNull}, refNull->type)); + worked = true; + return; + } + case GCTypeUtils::Unreachable: { + // The cast is never executed, possibly because its input type + // is uninhabitable. Replace it with unreachable. + replaceCurrent( + getDroppedChildrenAndAppend(curr, + *getModule(), + passOptions, + builder.makeUnreachable(), + DropMode::IgnoreParentEffects)); + worked = true; + return; + } + } + break; } } } } optimizer(getPassOptions()); - optimizer.setModule(getModule()); - optimizer.doWalkFunction(func); + optimizer.walkFunctionInModule(func, getModule()); // If we removed any BrOn instructions, that might affect the reachability // of the things they used to break to, so update types. diff --git a/test/lit/passes/remove-unused-brs-desc.wast b/test/lit/passes/remove-unused-brs-desc.wast new file mode 100644 index 00000000000..94e50775424 --- /dev/null +++ b/test/lit/passes/remove-unused-brs-desc.wast @@ -0,0 +1,750 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt -all %s --remove-unused-brs -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super.desc (struct)))) + (type $super (sub (descriptor $super.desc (struct)))) + ;; CHECK: (type $super.desc (sub (describes $super (struct)))) + (type $super.desc (sub (describes $super (struct)))) + ;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub (sub $super (descriptor $sub.desc (struct)))) + ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + + ;; CHECK: (type $other (descriptor $other.desc (struct))) + (type $other (descriptor $other.desc (struct))) + ;; CHECK: (type $other.desc (describes $other (struct))) + (type $other.desc (describes $other (struct))) + ) + + ;; CHECK: (import "" "" (func $effect (type $14))) + (import "" "" (func $effect)) + + ;; CHECK: (func $no-glb (type $6) (param $sub (ref $sub)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc $l (ref $sub) (ref $super) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $no-glb (param $sub (ref $sub)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We cannot improve the cast type even though it is a supertype of the + ;; ref type because the cast type is determined by the descriptor. + (br_on_cast_desc $l (ref $sub) (ref $super) + (local.get $sub) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $improve-nullability (type $6) (param $sub (ref $sub)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result (ref null $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (br_on_cast_desc $l (ref $sub) (ref $super) + ;; CHECK-NEXT: (block (result (ref $sub)) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $improve-nullability (param $sub (ref $sub)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We can improve the nullability, though, even via fallthrough. + (br_on_cast_desc $l anyref (ref null $super) + (block (result anyref) + (local.get $sub) + ) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $only-improve-nullability (type $10) (param $sub (ref null $sub)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc $l anyref (ref $super) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $only-improve-nullability (param $sub (ref null $sub)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We do not flip the nullability if the target is already non-nullable. + (br_on_cast_desc $l anyref (ref $super) + (block (result anyref) + (local.get $sub) + ) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-no-glb (type $6) (param $sub (ref $sub)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc_fail $l (ref $sub) (ref $super) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-no-glb (param $sub (ref $sub)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We cannot improve the cast type even though it is a supertype of the + ;; ref type because the cast type is determined by the descriptor. + (br_on_cast_desc_fail $l (ref $sub) (ref $super) + (local.get $sub) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-no-improve-nullability (type $6) (param $sub (ref $sub)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc_fail $l anyref (ref null $super) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-no-improve-nullability (param $sub (ref $sub)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; In the fail case we cannot improve the nullability, either, because + ;; that would make the sent values nullable, which might not be allowed. + (br_on_cast_desc_fail $l anyref (ref null $super) + (block (result anyref) + (local.get $sub) + ) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-only-improve-nullability (type $10) (param $sub (ref null $sub)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc_fail $l anyref (ref $super) + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-only-improve-nullability (param $sub (ref null $sub)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We do not flip the nullability if the target is already non-nullable. + (br_on_cast_desc_fail $l anyref (ref $super) + (block (result anyref) + (local.get $sub) + ) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $unknown-result (type $11) (param $super (ref $super)) (param $sub.desc (ref $sub.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc $l (ref $super) (ref $sub) + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (local.get $sub.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unknown-result (param $super (ref $super)) (param $sub.desc (ref $sub.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We do not know anything about the result of this cast and cannot + ;; optimize. + (br_on_cast_desc $l anyref (ref $sub) + (local.get $super) + (local.get $sub.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-unknown-result (type $11) (param $super (ref $super)) (param $sub.desc (ref $sub.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc_fail $l (ref $super) (ref $sub) + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (local.get $sub.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-unknown-result (param $super (ref $super)) (param $sub.desc (ref $sub.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We do not know anything about the result of this cast and cannot + ;; optimize. + (br_on_cast_desc_fail $l anyref (ref $sub) + (local.get $super) + (local.get $sub.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $uninhabitable-source (type $7) (param $uninhabitable (ref none)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $uninhabitable-source (param $uninhabitable (ref none)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; The cast cannot be reached, so we can optimize it out entirely. + (br_on_cast_desc $l anyref (ref $super) + (local.get $uninhabitable) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $uninhabitable-source-effects (type $7) (param $uninhabitable (ref none)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $uninhabitable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $super.desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $uninhabitable-source-effects (param $uninhabitable (ref none)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; Same, but now there are effects we must preserve. + (br_on_cast_desc $l anyref (ref $super) + (block (result (ref none)) + (call $effect) + (local.get $uninhabitable) + ) + (block (result (ref $super.desc)) + (call $effect) + (local.get $super.desc) + ) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-uninhabitable-source (type $7) (param $uninhabitable (ref none)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-uninhabitable-source (param $uninhabitable (ref none)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; The cast cannot be reached, so we can optimize it out entirely. + (br_on_cast_desc_fail $l anyref (ref $super) + (local.get $uninhabitable) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-uninhabitable-source-effects (type $7) (param $uninhabitable (ref none)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $uninhabitable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $super.desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-uninhabitable-source-effects (param $uninhabitable (ref none)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; Same, but now there are effects we must preserve. + (br_on_cast_desc_fail $l anyref (ref $super) + (block (result (ref none)) + (call $effect) + (local.get $uninhabitable) + ) + (block (result (ref $super.desc)) + (call $effect) + (local.get $super.desc) + ) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $cast-success-on-null (type $9) (param $other (ref null $other)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc $l (ref null $other) (ref null $super) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-success-on-null (param $other (ref null $other)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We know the cast can only succeed if the value is null. We can + ;; optimize to a branch on null in principle, but we do not do this yet + ;; TODO. + (br_on_cast_desc $l anyref (ref null $super) + (local.get $other) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-cast-success-on-null (type $9) (param $other (ref null $other)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result (ref null $other)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (br_on_non_null $l + ;; CHECK-NEXT: (block (result (ref null $other)) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-cast-success-on-null (param $other (ref null $other)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We know the cast can only succeed if the value is null. We can + ;; optimize to a branch on non-null. + (br_on_cast_desc_fail $l anyref (ref null $super) + (local.get $other) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-cast-success-on-null-nullable-desc (type $15) (param $other (ref null $other)) (param $super.desc (ref null $super.desc)) (result anyref) + ;; CHECK-NEXT: (local $2 (ref $super.desc)) + ;; CHECK-NEXT: (block $l (result (ref null $other)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (br_on_non_null $l + ;; CHECK-NEXT: (block (result (ref null $other)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-cast-success-on-null-nullable-desc (param $other (ref null $other)) (param $super.desc (ref null $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; Same as above, but now the descriptor is nullable so we have to + ;; insert a ref.as_non_null to preserve the trap on a null descriptor. + (br_on_cast_desc_fail $l anyref (ref null $super) + (local.get $other) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-cast-success-on-null-effect (type $9) (param $other (ref null $other)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (local $2 (ref null $other)) + ;; CHECK-NEXT: (local $3 (ref $super.desc)) + ;; CHECK-NEXT: (block $l (result (ref null $other)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (br_on_non_null $l + ;; CHECK-NEXT: (block (result (ref null $other)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block (result (ref null $other)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result (ref $super.desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-cast-success-on-null-effect (param $other (ref null $other)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; Same, but now there are other effects we must preserve instead. + (br_on_cast_desc_fail $l anyref (ref null $super) + (block (result (ref null $other)) + (call $effect) + (local.get $other) + ) + (block (result (ref $super.desc)) + (call $effect) + (local.get $super.desc) + ) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $cast-success-on-nonnull (type $12) (param $super (ref null $super)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc $l (ref null $super) (ref $super) + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-success-on-nonnull (param $super (ref null $super)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We cannot replace this with a br_on_non_null because we would also + ;; have to check that the descriptor values match. + (br_on_cast_desc $l anyref (ref $super) + (local.get $super) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-cast-success-on-nonnull (type $12) (param $super (ref null $super)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc_fail $l (ref null $super) (ref $super) + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-cast-success-on-nonnull (param $super (ref null $super)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We cannot replace this with a br_on_null because we would also have + ;; to check that the descriptor values match. + (br_on_cast_desc_fail $l anyref (ref $super) + (local.get $super) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $cast-failure (type $8) (param $other (ref $other)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $other)) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-failure (param $other (ref $other)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We know based on the types that the cast will fail. We can remove it. + (br_on_cast_desc $l anyref (ref $super) + (local.get $other) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $cast-failure-nullable-desc (type $13) (param $other (ref $other)) (param $super.desc (ref null $super.desc)) (result anyref) + ;; CHECK-NEXT: (local $2 (ref $super.desc)) + ;; CHECK-NEXT: (block $l (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $other)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-failure-nullable-desc (param $other (ref $other)) (param $super.desc (ref null $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; Same, but the descriptor is now nullable, so we need to insert a + ;; ref.as_non_null to preserve the trap on null descriptor. + (br_on_cast_desc $l anyref (ref $super) + (local.get $other) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $cast-failure-effect (type $8) (param $other (ref $other)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (local $2 (ref $other)) + ;; CHECK-NEXT: (local $3 (ref $super.desc)) + ;; CHECK-NEXT: (block $l (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $other)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block (result (ref $other)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result (ref $super.desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-failure-effect (param $other (ref $other)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; Same, but now there are other effects to preserve. + (br_on_cast_desc $l anyref (ref $super) + (block (result (ref $other)) + (call $effect) + (local.get $other) + ) + (block (result (ref $super.desc)) + (call $effect) + (local.get $super.desc) + ) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-cast-failure (type $8) (param $other (ref $other)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (block $l (result (ref null $other)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br $l + ;; CHECK-NEXT: (block (result (ref $other)) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-cast-failure (param $other (ref $other)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; We know based on the types that the cast will fail. We can replace it + ;; with an unconditional branch. + (br_on_cast_desc_fail $l anyref (ref $super) + (local.get $other) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-cast-failure-nullable-desc (type $13) (param $other (ref $other)) (param $super.desc (ref null $super.desc)) (result anyref) + ;; CHECK-NEXT: (local $2 (ref $super.desc)) + ;; CHECK-NEXT: (block $l (result (ref null $other)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br $l + ;; CHECK-NEXT: (block (result (ref $other)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-cast-failure-nullable-desc (param $other (ref $other)) (param $super.desc (ref null $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; Same, but now the descriptor is nullable and we need to preserve the + ;; trap on null descriptor. + (br_on_cast_desc_fail $l anyref (ref $super) + (local.get $other) + (local.get $super.desc) + ) + ) + (ref.null none) + ) + ) + + ;; CHECK: (func $fail-cast-failure-effect (type $8) (param $other (ref $other)) (param $super.desc (ref $super.desc)) (result anyref) + ;; CHECK-NEXT: (local $2 (ref $other)) + ;; CHECK-NEXT: (local $3 (ref $super.desc)) + ;; CHECK-NEXT: (block $l (result (ref null $other)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br $l + ;; CHECK-NEXT: (block (result (ref $other)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block (result (ref $other)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result (ref $super.desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fail-cast-failure-effect (param $other (ref $other)) (param $super.desc (ref $super.desc)) (result anyref) + (block $l (result anyref) + (drop + ;; Same, but now there are other effects to preserve. + (br_on_cast_desc_fail $l anyref (ref $super) + (block (result (ref $other)) + (call $effect) + (local.get $other) + ) + (block (result (ref $super.desc)) + (call $effect) + (local.get $super.desc) + ) + ) + ) + (ref.null none) + ) + ) +) From 6f4e4f7d022a025bfd06e5c605d718596701c6aa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 16 Jul 2025 15:31:52 -0700 Subject: [PATCH 600/622] [Debug Info] Prioritize optimization over debug info in metadata comparisons (#7732) Ignore debug info there, allowing e.g. DuplicateFunctionElimination to merge functions identical in all ways but for debug info. --- src/ir/metadata.cpp | 15 +++---- ...ate-function-elimination_branch-hints.wast | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/ir/metadata.cpp b/src/ir/metadata.cpp index e66a961342d..f69927ecc10 100644 --- a/src/ir/metadata.cpp +++ b/src/ir/metadata.cpp @@ -105,8 +105,14 @@ bool equal(Function* a, Function* b) { return false; } - if (a->debugLocations.empty() && b->debugLocations.empty() && - a->codeAnnotations.empty() && b->codeAnnotations.empty()) { + // TODO: We do not consider debug locations here. This is often what is + // desired in optimized builds (e.g. if we are trying to fold two + // pieces of code together, that benefit outweighs slightly inaccurate + // debug info). If we find that non-optimizer locations call this in + // ways that lead to degraded debug info, we could add an option to + // control it. + + if (a->codeAnnotations.empty() && b->codeAnnotations.empty()) { // Nothing to compare; no differences. return true; } @@ -121,11 +127,6 @@ bool equal(Function* a, Function* b) { assert(aList.list.size() == bList.list.size()); for (Index i = 0; i < aList.list.size(); i++) { if (!compare(aList.list[i], - bList.list[i], - a->debugLocations, - b->debugLocations, - Function::DebugLocation()) || - !compare(aList.list[i], bList.list[i], a->codeAnnotations, b->codeAnnotations, diff --git a/test/lit/passes/duplicate-function-elimination_branch-hints.wast b/test/lit/passes/duplicate-function-elimination_branch-hints.wast index 60f86e2eb86..83b64f7e47c 100644 --- a/test/lit/passes/duplicate-function-elimination_branch-hints.wast +++ b/test/lit/passes/duplicate-function-elimination_branch-hints.wast @@ -218,3 +218,43 @@ ) ) +;; Source file location (debug info) does *not* prevent optimization. We +;; prioritize optimization over debug info quality. +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (export "a" (func $a)) + + ;; CHECK: (export "b" (func $a)) + + ;; CHECK: (func $a (type $0) (param $x i32) + ;; CHECK-NEXT: ;;@ src.cpp:10:1 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (export "a") (param $x i32) + ;; After we merge, this hint will remain in the single function. + ;;@ src.cpp:10:1 + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) + + (func $b (export "b") (param $x i32) + ;;@ src.cpp:20:1 + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) +) + From ce799f57703ebf2fe32d5f57dba8121f00889dec Mon Sep 17 00:00:00 2001 From: Matthias Liedtke Date: Mon, 21 Jul 2025 20:53:04 +0200 Subject: [PATCH 601/622] Assert unreachable cases in getBasicHeapTypeLUB (#7735) This currently seems to be a bit inconsistent with the null types that already use assertions / unreachable for cases that are already handled above: ```c++ // Bottom types already handled. WASM_UNREACHABLE("unexpected basic type"); ``` It isn't a very meaningful change, I'd just like to verify that I don't misunderstand the implementation. :face_with_peeking_eye: --- src/wasm/wasm-type.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index c4787f07312..a57b9a1a94d 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -407,15 +407,13 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, HeapType lubUnshared; switch (HeapType(a).getBasic(Unshared)) { case HeapType::ext: - if (bUnshared != HeapType::string) { - return std::nullopt; - } + assert(bUnshared == HeapType::string); lubUnshared = HeapType::ext; break; case HeapType::func: case HeapType::cont: case HeapType::exn: - return std::nullopt; + WASM_UNREACHABLE("Unexpected non-bottom type in same hierarchy"); case HeapType::any: lubUnshared = HeapType::any; break; From b440e68c1c5350a3536e3b17a2a1696921193903 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 21 Jul 2025 16:46:02 -0700 Subject: [PATCH 602/622] [NFC] Speed up Unsubtyping (#7734) Speed up Unsubtyping by over 2x via algorithmic and other improvements. The most expensive part of the Unsubtyping analysis is the handling of casts. For each cast source and destination pair, each type that remains a subtype of the source and was originally a subtype of the destination must remain a subtype of the destination for the cast to continue succeeding. Previously, Unsubtyping analyzed these cast relationships for all types as a single unit of work whenever it reached a fixed point from examining other sources of subtyping constraints. This led to duplicated work because the subtype, cast source, and cast destination triples analyzed once would be analyzed again the next time casts were considered. Avoid this duplicated cast analysis by incrementally analyzing casts whenever a new subtyping is discovered. Maintain the invariant that each new subtyping either joins a subtyping tree rooted at the discovered subtype into the discovered supertype's tree, or reparents the subtype below some (possibly indirect) subtype of its old parent. In the former case, the subtype and all of its descendents are evaluated against all casts originating from all their new supertypes in the tree they have joined. In the latter case, they must already have been evaluated against all casts originating at the old supertype and its ancestors, so they need only to be evaluated against their new supertypes up to the old supertype. Once a particular type is evaluated against casts originating from a particular supertype, that type will never be evaluated against those casts again. This algorithmic improvement accounts for most of the speedup. The rest of the speedup is from doing less work while collecting the initial subtyping constraints in a parallel function analysis. The old implementation used an instance of Unsubtyping to collect constraints in each function, which would end up doing some analysis to find additional constraints. The new implementation does not do any analysis of transitively required constraints during the initial parallel function analysis. --- src/passes/Unsubtyping.cpp | 625 +++++++++++++++++++++---------- test/lit/passes/unsubtyping.wast | 22 ++ 2 files changed, 440 insertions(+), 207 deletions(-) diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index 729dd18d488..17171ffc3b2 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -14,18 +14,28 @@ * limitations under the License. */ +#define UNSUBTYPING_DEBUG 0 + +#include + +#if !UNSUBTYPING_DEBUG #include +#include +#endif #include "ir/subtype-exprs.h" -#include "ir/subtypes.h" #include "ir/type-updating.h" #include "ir/utils.h" #include "pass.h" -#include "support/unique_deferring_queue.h" +#include "support/index.h" #include "wasm-traversal.h" #include "wasm-type.h" #include "wasm.h" +#if UNSUBTYPING_DEBUG +#include "support/insert_ordered.h" +#endif + // Compute and use the minimal subtype relation required to maintain module // validity and behavior. This minimal relation will be a subset of the original // subtype relation. Start by walking the IR and collecting pairs of types that @@ -96,76 +106,225 @@ // // Starting with the initial subtype relation determined by walking the IR, // repeatedly search for new subtypings by analyzing type definitions and casts -// in lock step until we reach a fixed point. This is the minimal subtype -// relation that preserves module validity and behavior that can be found -// without a more precise analysis of types that might flow into each cast. +// until we reach a fixed point. This is the minimal subtype relation that +// preserves module validity and behavior that can be found without a more +// precise analysis of types that might flow into each cast. namespace wasm { namespace { -struct Unsubtyping - : WalkerPass< - ControlFlowWalker>> { - // The new set of supertype relations. - std::unordered_map supertypes; +#if UNSUBTYPING_DEBUG +template using Map = InsertOrderedMap; +template using Set = InsertOrderedSet; +#else +template using Map = std::unordered_map; +template using Set = std::unordered_set; +#endif +// A tree (or rather a forest) of types with the ability to query and set +// supertypes in constant time and efficiently iterate over supertypes and +// subtypes. +struct TypeTree { + struct Node { + // The type represented by this node. + HeapType type; + // The index of the parent (supertype) in the list of nodes. Set to the + // index of this node if there is no parent. + Index parent; + // The index of this node in the parent's list of children, if any, enabling + // O(1) updates. + Index indexInParent = 0; + // The indices of the children (subtypes) in the list of nodes. + std::vector children; - // Map from cast source types to their destinations. - std::unordered_map> castTypes; + Node(HeapType type, Index index) : type(type), parent(index) {} + }; + + std::vector nodes; + Map indices; + + void setSupertype(HeapType sub, HeapType super) { + auto childIndex = getIndex(sub); + auto parentIndex = getIndex(super); + auto& childNode = nodes[childIndex]; + auto& parentNode = nodes[parentIndex]; + // Remove sub from its old supertype if necessary. + if (auto oldParentIndex = childNode.parent; oldParentIndex != childIndex) { + auto& oldParentNode = nodes[oldParentIndex]; + // Move sub to the back of its parent's children and then pop it. + auto& children = oldParentNode.children; + assert(children[childNode.indexInParent] == childIndex); + auto& swappedNode = nodes[children.back()]; + assert(swappedNode.indexInParent == children.size() - 1); + // Swap the indices in the parent's child vector. + std::swap(children[childNode.indexInParent], children.back()); + // Swap the index in the kept child. + swappedNode.indexInParent = childNode.indexInParent; + children.pop_back(); + } + childNode.parent = parentIndex; + childNode.indexInParent = parentNode.children.size(); + parentNode.children.push_back(childIndex); + } + + std::optional getSupertype(HeapType type) { + auto index = getIndex(type); + auto parentIndex = nodes[index].parent; + if (parentIndex == index) { + return std::nullopt; + } + return nodes[parentIndex].type; + } + + struct SupertypeIterator { + using value_type = const HeapType; + using difference_type = std::ptrdiff_t; + using reference = const HeapType&; + using pointer = const HeapType*; + using iterator_category = std::input_iterator_tag; - // The set of subtypes that need to have their type definitions analyzed to - // transitively find other subtype relations they depend on. We add to it - // every time we find a new subtype relationship we need to keep. - UniqueDeferredQueue work; + TypeTree* parent; + std::optional index; + + bool operator==(const SupertypeIterator& other) { + return index == other.index; + } + bool operator!=(const SupertypeIterator& other) { + return !(*this == other); + } + const HeapType& operator*() const { return parent->nodes[*index].type; } + const HeapType* operator->() const { return &*(*this); } + SupertypeIterator& operator++() { + auto parentIndex = parent->nodes[*index].parent; + if (parentIndex == *index) { + index = std::nullopt; + } else { + index = parentIndex; + } + return *this; + } + SupertypeIterator operator++(int) { + auto it = *this; + ++(*this); + return it; + } + }; + + struct Supertypes { + TypeTree* parent; + Index index; + SupertypeIterator begin() { return {parent, index}; } + SupertypeIterator end() { return {parent, std::nullopt}; } + }; + + Supertypes supertypes(HeapType type) { return {this, getIndex(type)}; } + + struct SubtypeIterator { + using value_type = const HeapType; + using difference_type = std::ptrdiff_t; + using reference = const HeapType&; + using pointer = const HeapType*; + using iterator_category = std::input_iterator_tag; + + TypeTree* parent; + + // DFS stack of (node index, child index) pairs. + std::vector> stack; + + bool operator==(const SubtypeIterator& other) { + return stack == other.stack; + } + bool operator!=(const SubtypeIterator& other) { return !(*this == other); } + const HeapType& operator*() const { + return parent->nodes[stack.back().first].type; + } + const HeapType* operator->() const { return &*(*this); } + SubtypeIterator& operator++() { + while (true) { + if (stack.empty()) { + return *this; + } + auto& [index, childIndex] = stack.back(); + auto& children = parent->nodes[index].children; + if (childIndex == children.size()) { + stack.pop_back(); + } else { + auto child = children[childIndex++]; + stack.push_back({child, 0u}); + return *this; + } + } + } + SubtypeIterator operator++(int) { + auto it = *this; + ++(*this); + return it; + } + }; + + struct Subtypes { + TypeTree* parent; + Index index; + SubtypeIterator begin() { return {parent, {std::make_pair(index, 0u)}}; } + SubtypeIterator end() { return {parent, {}}; } + }; + + Subtypes subtypes(HeapType type) { return {this, getIndex(type)}; } + +private: + Index getIndex(HeapType type) { + auto [it, inserted] = indices.insert({type, nodes.size()}); + if (inserted) { + nodes.emplace_back(type, nodes.size()); + } + return it->second; + } +}; + +struct Unsubtyping : Pass { + // (sub, super) pairs that we have discovered but not yet processed. + std::vector> work; + + // Record the type tree with supertype and subtype relations in such a way + // that we can add new supertype relationships in constant time. + TypeTree types; + + // Map from cast source types to their destinations. + Map> casts; void run(Module* wasm) override { if (!wasm->features.hasGC()) { return; } + + // Initialize the subtype relation based on what is immediately required to + // keep the code and public types valid. analyzePublicTypes(*wasm); - walkModule(wasm); - analyzeTransitiveDependencies(); - optimizeTypes(*wasm); + analyzeModule(*wasm); + + // Find further subtypings and iterate to a fixed point. + while (!work.empty()) { + auto [sub, super] = work.back(); + work.pop_back(); + process(sub, super); + } + + rewriteTypes(*wasm); + // Cast types may be refinable if their source and target types are no // longer related. TODO: Experiment with running this only after checking // whether it is necessary. ReFinalize().run(getPassRunner(), wasm); } - // Note that sub must remain a subtype of super. void noteSubtype(HeapType sub, HeapType super) { - if (sub == super || sub.isBottom() || super.isBottom()) { + // Bottom types are uninteresting, but other basic heap types can be + // interesting because of their interactions with casts. + if (sub == super || sub.isBottom()) { return; } - auto [it, inserted] = supertypes.insert({sub, super}); - if (inserted) { - work.push(sub); - // TODO: Incrementally check all subtypes (inclusive) of sub against super - // and all its supertypes if we have already analyzed casts. - return; - } - // We already had a recorded supertype. The new supertype might be deeper, - // shallower, or identical to the old supertype. - auto oldSuper = it->second; - if (super == oldSuper) { - return; - } - // There are two different supertypes, but each type can only have a single - // direct subtype so the supertype chain cannot fork and one of the - // supertypes must be a supertype of the other. Recursively record that - // relationship as well. - if (HeapType::isSubType(super, oldSuper)) { - // sub <: super <: oldSuper - it->second = super; - work.push(sub); - // TODO: Incrementally check all subtypes (inclusive) of sub against super - // if we have already analyzed casts. - noteSubtype(super, oldSuper); - } else { - // sub <: oldSuper <: super - noteSubtype(oldSuper, super); - } + work.push_back({sub, super}); } void noteSubtype(Type sub, Type super) { @@ -182,201 +341,253 @@ struct Unsubtyping noteSubtype(sub.getHeapType(), super.getHeapType()); } - // Note a subtyping where one or both sides are expressions. - void noteSubtype(Expression* sub, Type super) { - noteSubtype(sub->type, super); - } - void noteSubtype(Type sub, Expression* super) { - noteSubtype(sub, super->type); - } - void noteSubtype(Expression* sub, Expression* super) { - noteSubtype(sub->type, super->type); + void analyzePublicTypes(Module& wasm) { + // We cannot change supertypes for anything public. + for (auto type : ModuleUtils::getPublicHeapTypes(wasm)) { + if (auto super = type.getDeclaredSuperType()) { + noteSubtype(type, *super); + } + } } - void noteNonFlowSubtype(Expression* sub, Type super) { - // This expression's type must be a subtype of |super|, but the value does - // not flow anywhere - this is a static constraint. As the value does not - // flow, it cannot reach anywhere else, which means we need this in order to - // validate but it does not interact with casts. Given that, if super is a - // basic type then we can simply ignore this: we only remove subtyping - // between user types, so subtyping wrt basic types is unchanged, and so - // this constraint will never be a problem. - // - // This is sort of a hack because in general to be precise we should not - // just consider basic types here - in general, we should note for each - // constraint whether it is a flow-based one or not, and only take the - // flow-based ones into account when looking at the impact of casts. - // However, in practice this is enough as the only non-trivial case of - // |noteNonFlowSubtype| is for RefEq, which uses a basic type (eqref). Other - // cases of non-flow subtyping end up trivial, e.g., the target of a - // CallRef is compared to itself (and we ignore constraints of A :> A). - // However, if we change how |noteNonFlowSubtype| is used in - // SubtypingDiscoverer then we may need to generalize this. - if (super.isRef() && super.getHeapType().isBasic()) { - return; - } + void analyzeModule(Module& wasm) { + struct Info { + // (source, target) pairs for casts. + Set> casts; - // Otherwise, we must take this into account. - noteSubtype(sub, super); - } + // Observed (sub, super) subtype constraints. + Set> subtypings; + }; - void noteCast(HeapType src, HeapType dest) { - if (src == dest || dest.isBottom()) { - return; + struct Collector + : ControlFlowWalker> { + Info& info; + Collector(Info& info) : info(info) {} + void noteSubtype(Type sub, Type super) { + if (sub.isTuple()) { + assert(super.isTuple() && sub.size() == super.size()); + for (size_t i = 0, size = sub.size(); i < size; ++i) { + noteSubtype(sub[i], super[i]); + } + return; + } + if (!sub.isRef() || !super.isRef()) { + return; + } + noteSubtype(sub.getHeapType(), super.getHeapType()); + } + void noteSubtype(HeapType sub, HeapType super) { + if (sub == super || sub.isBottom()) { + return; + } + info.subtypings.insert({sub, super}); + } + void noteSubtype(Type sub, Expression* super) { + noteSubtype(sub, super->type); + } + void noteSubtype(Expression* sub, Type super) { + noteSubtype(sub->type, super); + } + void noteSubtype(Expression* sub, Expression* super) { + noteSubtype(sub->type, super->type); + } + void noteNonFlowSubtype(Expression* sub, Type super) { + // This expression's type must be a subtype of |super|, but the value + // does not flow anywhere - this is a static constraint. As the value + // does not flow, it cannot reach anywhere else, which means we need + // this in order to validate but it does not interact with casts. Given + // that, if super is a basic type then we can simply ignore this: we + // only remove subtyping between user types, so subtyping wrt basic + // types is unchanged, and so this constraint will never be a problem. + // + // This is sort of a hack because in general to be precise we should not + // just consider basic types here - in general, we should note for each + // constraint whether it is a flow-based one or not, and only take the + // flow-based ones into account when looking at the impact of casts. + // However, in practice this is enough as the only non-trivial case of + // |noteNonFlowSubtype| is for RefEq, which uses a basic type (eqref). + // Other cases of non-flow subtyping end up trivial, e.g., the target of + // a CallRef is compared to itself (and we ignore constraints of A :> + // A). However, if we change how |noteNonFlowSubtype| is used in + // SubtypingDiscoverer then we may need to generalize this. + if (super.isRef() && super.getHeapType().isBasic()) { + return; + } + + // Otherwise, we must take this into account. + noteSubtype(sub, super); + } + void noteCast(HeapType src, HeapType dst) { + // Casts to self and casts that must fail because they have incompatible + // types are uninteresting. + if (dst == src) { + return; + } + if (HeapType::isSubType(dst, src)) { + info.casts.insert({src, dst}); + return; + } + if (HeapType::isSubType(src, dst)) { + // This is an upcast that will always succeed, but only if we ensure + // src <: dst. + info.subtypings.insert({src, dst}); + } + } + void noteCast(Expression* src, Type dst) { + if (src->type.isRef() && dst.isRef()) { + noteCast(src->type.getHeapType(), dst.getHeapType()); + } + } + void noteCast(Expression* src, Expression* dst) { + if (src->type.isRef() && dst->type.isRef()) { + noteCast(src->type.getHeapType(), dst->type.getHeapType()); + } + } + }; + + // Collect subtyping constraints and casts from functions in parallel. + ModuleUtils::ParallelFunctionAnalysis analysis( + wasm, [&](Function* func, Info& info) { + if (!func->imported()) { + Collector(info).walkFunctionInModule(func, &wasm); + } + }); + + Info collectedInfo; + for (auto& [_, info] : analysis.map) { + collectedInfo.casts.insert(info.casts.begin(), info.casts.end()); + collectedInfo.subtypings.insert(info.subtypings.begin(), + info.subtypings.end()); } - assert(HeapType::isSubType(dest, src)); - castTypes[src].insert(dest); - } - void noteCast(Type src, Type dest) { - assert(!src.isTuple() && !dest.isTuple()); - if (src == Type::unreachable) { - return; + // Collect constraints from module-level code as well. + Collector collector(collectedInfo); + collector.walkModuleCode(&wasm); + collector.setModule(&wasm); + for (auto& global : wasm.globals) { + collector.visitGlobal(global.get()); + } + for (auto& segment : wasm.elementSegments) { + collector.visitElementSegment(segment.get()); } - assert(src.isRef() && dest.isRef()); - noteCast(src.getHeapType(), dest.getHeapType()); - } - // Note a cast where one or both sides are expressions. - void noteCast(Expression* src, Type dest) { noteCast(src->type, dest); } - void noteCast(Expression* src, Expression* dest) { - noteCast(src->type, dest->type); + // Prepare the collected information for the upcoming processing loop. + for (auto& [sub, super] : collectedInfo.subtypings) { + noteSubtype(sub, super); + } + for (auto [src, dst] : collectedInfo.casts) { + casts[src].push_back(dst); + } } - void analyzePublicTypes(Module& wasm) { - // We cannot change supertypes for anything public. - for (auto type : ModuleUtils::getPublicHeapTypes(wasm)) { - if (auto super = type.getDeclaredSuperType()) { - noteSubtype(type, *super); + void process(HeapType sub, HeapType super) { + auto oldSuper = types.getSupertype(sub); + if (oldSuper) { + // We already had a recorded supertype. The new supertype might be + // deeper,shallower, or equal to the old supertype. We must recursively + // note the relationship between the old and new supertypes. + if (super == *oldSuper) { + // Nothing new to do here. + return; + } + if (HeapType::isSubType(*oldSuper, super)) { + // sub <: oldSuper <: super + noteSubtype(*oldSuper, super); + // We already handled sub <: oldSuper, so we're done. + return; } + // sub <: super <: oldSuper + // Eagerly process super <: oldSuper first. This ensures that sub and + // super will already be in the same tree when we process them below, so + // when we process casts we will know that we only need to process up to + // oldSuper. + process(super, *oldSuper); } + + types.setSupertype(sub, super); + + // We have a new supertype. Find the implied subtypings from the type + // definitions and casts. + processDefinitions(sub, super); + processCasts(sub, super, oldSuper); } - void analyzeTransitiveDependencies() { - // While we have found new subtypings and have not reached a fixed point... - while (!work.empty()) { - // Subtype relationships that we are keeping might depend on other subtype - // relationships that we are not yet planning to keep. Transitively find - // all the relationships we need to keep all our type definitions valid. - while (!work.empty()) { - auto type = work.pop(); - auto super = supertypes.at(type); - if (super.isBasic()) { - continue; - } - switch (type.getKind()) { - case HeapTypeKind::Func: { - auto sig = type.getSignature(); - auto superSig = super.getSignature(); - noteSubtype(superSig.params, sig.params); - noteSubtype(sig.results, superSig.results); - break; - } - case HeapTypeKind::Struct: { - const auto& fields = type.getStruct().fields; - const auto& superFields = super.getStruct().fields; - for (size_t i = 0, size = superFields.size(); i < size; ++i) { - noteSubtype(fields[i].type, superFields[i].type); - } - break; - } - case HeapTypeKind::Array: { - auto elem = type.getArray().element; - noteSubtype(elem.type, super.getArray().element.type); - break; - } - case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); - case HeapTypeKind::Basic: - WASM_UNREACHABLE("unexpected kind"); - } - if (auto desc = type.getDescriptorType()) { - if (auto superDesc = super.getDescriptorType()) { - noteSubtype(*desc, *superDesc); - } + void processDefinitions(HeapType sub, HeapType super) { + if (super.isBasic()) { + return; + } + switch (sub.getKind()) { + case HeapTypeKind::Func: { + auto sig = sub.getSignature(); + auto superSig = super.getSignature(); + noteSubtype(superSig.params, sig.params); + noteSubtype(sig.results, superSig.results); + break; + } + case HeapTypeKind::Struct: { + const auto& fields = sub.getStruct().fields; + const auto& superFields = super.getStruct().fields; + for (size_t i = 0, size = superFields.size(); i < size; ++i) { + noteSubtype(fields[i].type, superFields[i].type); } + break; + } + case HeapTypeKind::Array: { + auto elem = sub.getArray().element; + noteSubtype(elem.type, super.getArray().element.type); + break; + } + case HeapTypeKind::Cont: + WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Basic: + WASM_UNREACHABLE("unexpected kind"); + } + if (auto desc = sub.getDescriptorType()) { + if (auto superDesc = super.getDescriptorType()) { + noteSubtype(*desc, *superDesc); } - - // Analyze all casts at once. - // TODO: This is expensive. Analyze casts incrementally after we - // initially analyze them. - analyzeCasts(); } } - void analyzeCasts() { - // For each cast (src, dest) pair, any type that remains a subtype of src - // (meaning its values can inhabit locations typed src) and that was - // originally a subtype of dest (meaning its values would have passed the - // cast) should remain a subtype of dest so that its values continue to pass - // the cast. - // - // For every type, walk up its new supertype chain to find cast sources and - // compare against their associated cast destinations. - for (auto it = supertypes.begin(); it != supertypes.end(); ++it) { - auto type = it->first; - for (auto srcIt = it; srcIt != supertypes.end(); - srcIt = supertypes.find(srcIt->second)) { - auto src = srcIt->second; - auto destsIt = castTypes.find(src); - if (destsIt == castTypes.end()) { - continue; + void + processCasts(HeapType sub, HeapType super, std::optional oldSuper) { + // We are either attaching the one tree rooted at `sub` under a new + // supertype in another tree, or we are reparenting `sub` below a + // descendent of `oldSuper` in the same tree. In the former case, we must + // evaluate `sub` and all its subtypes against all its new supertypes and + // their cast destinations. In the latter case, `sub` and all its subtypes + // must have already been evaluated against `oldSuper` and its supertypes, + // so we only need to additionally evaluate them against supertypes up to + // `oldSuper`. + for (auto type : types.subtypes(sub)) { + for (auto src : types.supertypes(super)) { + if (oldSuper && src == *oldSuper) { + break; } - for (auto dest : destsIt->second) { - if (HeapType::isSubType(type, dest)) { - noteSubtype(type, dest); + for (auto dst : casts[src]) { + if (HeapType::isSubType(type, dst)) { + noteSubtype(type, dst); } } } } } - void optimizeTypes(Module& wasm) { + void rewriteTypes(Module& wasm) { struct Rewriter : GlobalTypeRewriter { Unsubtyping& parent; Rewriter(Unsubtyping& parent, Module& wasm) : GlobalTypeRewriter(wasm), parent(parent) {} std::optional getDeclaredSuperType(HeapType type) override { - if (auto it = parent.supertypes.find(type); - it != parent.supertypes.end() && !it->second.isBasic()) { - return it->second; + if (auto super = parent.types.getSupertype(type); + super && !super->isBasic()) { + return *super; } return std::nullopt; } }; Rewriter(*this, wasm).update(); } - - void doWalkModule(Module* wasm) { - // Visit the functions in parallel, filling in `supertypes` and `castTypes` - // on separate instances which will later be merged. - ModuleUtils::ParallelFunctionAnalysis analysis( - *wasm, [&](Function* func, Unsubtyping& unsubtyping) { - if (!func->imported()) { - unsubtyping.walkFunctionInModule(func, wasm); - } - }); - // Collect the results from the functions. - for (auto& [_, unsubtyping] : analysis.map) { - for (auto [sub, super] : unsubtyping.supertypes) { - noteSubtype(sub, super); - } - for (auto& [src, dests] : unsubtyping.castTypes) { - for (auto dest : dests) { - noteCast(src, dest); - } - } - } - // Collect constraints from top-level items. - for (auto& global : wasm->globals) { - visitGlobal(global.get()); - } - for (auto& seg : wasm->elementSegments) { - visitElementSegment(seg.get()); - } - // Visit the rest of the code that is not in functions. - walkModuleCode(wasm); - } }; } // anonymous namespace diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index 971fe63da3c..dc365943f82 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -1850,3 +1850,25 @@ ) ) ) + +;; Regression test for assertion failure on incorrect updating of type tree +;; state. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $0 (sub (struct))) + (type $0 (sub (struct))) + ;; CHECK: (type $1 (sub $0 (struct (field (ref null $0))))) + (type $1 (sub $0 (struct (field (ref null $0))))) + ;; CHECK: (type $2 (sub $1 (struct (field (ref null $3))))) + (type $2 (sub $1 (struct (field (ref null $3))))) + ;; CHECK: (type $3 (sub $0 (struct))) + (type $3 (sub $0 (struct))) + ) + ;; CHECK: (global $g (ref struct) (struct.new_default $2)) + (global $g (ref struct) (struct.new_default $2)) + ;; CHECK: (global $g2 (ref null $1) (ref.null none)) + (global $g2 (ref null $1) (ref.null none)) + ;; CHECK: (export "" (global $g2)) + (export "" (global $g2)) +) From a84b0260ad8b96cbf13ad9c0f38bd4edcf455839 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 21 Jul 2025 16:47:11 -0700 Subject: [PATCH 603/622] Do not rewrite supertypes in AbstractTypeRefining (#7720) Keeping descriptor chains valid while rewriting subtyping is complicated. Make it simpler to optimize descriptor and described types in a future PR by not rewriting supertypes at all in AbstractTypeRefining. To avoid losing optimization power in the default optimization pipelines, run Unsubtyping after AbstractTypeRefining. This makes -O3 slightly faster when optimizing Sheets calcengine (possibly because having fewer subtype relationships leads to less work in later optimizations) and improves code size by 0.1%. The improvements increase when running -O3 -O3, with a 1.7% code size improvement relative to the same pipeline before this change. --- src/passes/AbstractTypeRefining.cpp | 27 +++++----- src/passes/pass.cpp | 1 + test/lit/passes/abstract-type-refining.wast | 59 ++++++++++++--------- 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index d93895243bb..eba5accf2a3 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -275,29 +275,26 @@ struct AbstractTypeRefining : public Pass { return; } - // A TypeMapper that handles the patterns we have in our mapping, where we - // end up mapping a type to a *subtype*. We need to properly create - // supertypes while doing this rewriting. For example, say we have this: + // Rewriting types can usually rewrite subtype relationships. For example, + // if we have this: // - // A :> B :> C + // C <: B <: A // - // Say we see B is never created, so we want to map B to its subtype C. C's - // supertype must now be A. + // And we see that B is never created, we would naively map B to its subtype + // C. But if we rewrote C's supertype, C would declare itself to be its own + // supertype, which is not allowed. We could fix this by walking up the + // supertype chain to find a supertype that is not being rewritten, but + // changing subtype relationships and keeping descriptor chains valid is + // nontrivial. Instead, avoid changing subtype relationships entirely: leave + // that for Unsubtyping. class AbstractTypeRefiningTypeMapper : public TypeMapper { public: AbstractTypeRefiningTypeMapper(Module& wasm, const TypeUpdates& mapping) : TypeMapper(wasm, mapping) {} std::optional getDeclaredSuperType(HeapType oldType) override { - auto super = oldType.getDeclaredSuperType(); - - // Go up the chain of supertypes, skipping things we are mapping away, - // as those things will not appear in the output. This skips B in the - // example above. - while (super && mapping.count(*super)) { - super = super->getDeclaredSuperType(); - } - return super; + // We do not want to update subtype relationships. + return oldType.getDeclaredSuperType(); } }; diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 5e98ef5d086..e0b7595b0b5 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -761,6 +761,7 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { addIfNoDWARFIssues("cfp"); addIfNoDWARFIssues("gsi"); addIfNoDWARFIssues("abstract-type-refining"); + addIfNoDWARFIssues("unsubtyping"); } } // TODO: generate-global-effects here, right before function passes, then diff --git a/test/lit/passes/abstract-type-refining.wast b/test/lit/passes/abstract-type-refining.wast index 5f6010ca07c..0639ac71060 100644 --- a/test/lit/passes/abstract-type-refining.wast +++ b/test/lit/passes/abstract-type-refining.wast @@ -13,12 +13,13 @@ ;; actually refer to a subtype of them (that has a struct.new). As a result, in ;; TNH mode $A and $D will also not be emitted in the output anymore. (module + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $A (sub (struct))) ;; NO_TNH: (rec ;; NO_TNH-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) - ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $B (sub (struct))) + ;; YESTNH: (type $B (sub $A (struct))) ;; NO_TNH: (type $B (sub $A (struct))) (type $B (sub $A (struct))) @@ -26,16 +27,17 @@ ;; NO_TNH: (type $C (sub $B (struct))) (type $C (sub $B (struct))) + ;; YESTNH: (type $D (sub $C (struct))) ;; NO_TNH: (type $D (sub $C (struct))) (type $D (sub $C (struct))) - ;; YESTNH: (type $E (sub $C (struct))) + ;; YESTNH: (type $E (sub $D (struct))) ;; NO_TNH: (type $E (sub $D (struct))) (type $E (sub $D (struct))) - ;; YESTNH: (type $3 (func (param anyref))) + ;; YESTNH: (type $5 (func (param anyref))) - ;; YESTNH: (type $4 (func)) + ;; YESTNH: (type $6 (func)) ;; YESTNH: (global $global anyref (struct.new_default $B)) ;; NO_TNH: (type $5 (func (param anyref))) @@ -45,7 +47,7 @@ ;; NO_TNH: (global $global anyref (struct.new_default $B)) (global $global anyref (struct.new $B)) - ;; YESTNH: (func $new (type $3) (param $x anyref) + ;; YESTNH: (func $new (type $5) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (struct.new_default $C) ;; YESTNH-NEXT: ) @@ -70,7 +72,7 @@ ) ) - ;; YESTNH: (func $ref.cast (type $3) (param $x anyref) + ;; YESTNH: (func $ref.cast (type $5) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $B) ;; YESTNH-NEXT: (local.get $x) @@ -154,7 +156,7 @@ ) ) - ;; YESTNH: (func $ref.test (type $3) (param $x anyref) + ;; YESTNH: (func $ref.test (type $5) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.test (ref $B) ;; YESTNH-NEXT: (local.get $x) @@ -176,7 +178,7 @@ ) ) - ;; YESTNH: (func $br_on (type $3) (param $x anyref) + ;; YESTNH: (func $br_on (type $5) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (block $block (result (ref $B)) ;; YESTNH-NEXT: (drop @@ -213,7 +215,7 @@ ) ) - ;; YESTNH: (func $basic (type $3) (param $x anyref) + ;; YESTNH: (func $basic (type $5) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref struct) ;; YESTNH-NEXT: (local.get $x) @@ -236,7 +238,7 @@ ) ) - ;; YESTNH: (func $locals (type $4) + ;; YESTNH: (func $locals (type $6) ;; YESTNH-NEXT: (local $A (ref $B)) ;; YESTNH-NEXT: (local $B (ref $B)) ;; YESTNH-NEXT: (local $C (ref $C)) @@ -359,21 +361,22 @@ ;; $B1. (module (rec + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $A (sub (struct))) ;; NO_TNH: (rec ;; NO_TNH-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) (type $B (sub $A (struct))) - ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $B1 (sub (struct))) + ;; YESTNH: (type $B1 (sub $A (struct))) ;; NO_TNH: (type $B1 (sub $A (struct))) (type $B1 (sub $A (struct))) ;; this is a new type ) - ;; YESTNH: (type $1 (func (param anyref))) + ;; YESTNH: (type $2 (func (param anyref))) - ;; YESTNH: (func $new (type $1) (param $x anyref) + ;; YESTNH: (func $new (type $2) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (struct.new_default $B1) ;; YESTNH-NEXT: ) @@ -391,7 +394,7 @@ ) ) - ;; YESTNH: (func $ref.cast (type $1) (param $x anyref) + ;; YESTNH: (func $ref.cast (type $2) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $B1) ;; YESTNH-NEXT: (local.get $x) @@ -446,21 +449,23 @@ ;; A chain, $A :> $B :> $C, where we can optimize $A all the way to $C. (module + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $A (sub (struct))) ;; NO_TNH: (rec ;; NO_TNH-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) + ;; YESTNH: (type $B (sub $A (struct))) ;; NO_TNH: (type $B (sub $A (struct))) (type $B (sub $A (struct))) - ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $C (sub (struct))) + ;; YESTNH: (type $C (sub $B (struct))) ;; NO_TNH: (type $C (sub $B (struct))) (type $C (sub $B (struct))) - ;; YESTNH: (type $1 (func (param anyref))) + ;; YESTNH: (type $3 (func (param anyref))) - ;; YESTNH: (func $new (type $1) (param $x anyref) + ;; YESTNH: (func $new (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (struct.new_default $C) ;; YESTNH-NEXT: ) @@ -478,7 +483,7 @@ ) ) - ;; YESTNH: (func $ref.cast (type $1) (param $x anyref) + ;; YESTNH: (func $ref.cast (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $C) ;; YESTNH-NEXT: (local.get $x) @@ -817,22 +822,24 @@ ;; As above, but now $C1 is created. (module (rec + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $A (sub (struct))) ;; NO_TNH: (rec ;; NO_TNH-NEXT: (type $A (sub (struct))) (type $A (sub (struct))) + ;; YESTNH: (type $B (sub $A (struct))) ;; NO_TNH: (type $B (sub $A (struct))) (type $B (sub $A (struct))) - ;; YESTNH: (rec - ;; YESTNH-NEXT: (type $C1 (sub (struct))) + ;; YESTNH: (type $C1 (sub $B (struct))) ;; NO_TNH: (type $C1 (sub $B (struct))) (type $C1 (sub $B (struct))) (type $C2 (sub $B (struct))) ) - ;; YESTNH: (type $1 (func (param anyref))) + ;; YESTNH: (type $3 (func (param anyref))) ;; YESTNH: (global $global anyref (struct.new_default $C1)) ;; NO_TNH: (type $3 (func (param anyref))) @@ -840,7 +847,7 @@ ;; NO_TNH: (global $global anyref (struct.new_default $C1)) (global $global anyref (struct.new $C1)) - ;; YESTNH: (func $ref.cast (type $1) (param $x anyref) + ;; YESTNH: (func $ref.cast (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref $C1) ;; YESTNH-NEXT: (local.get $x) @@ -909,7 +916,7 @@ ) ) - ;; YESTNH: (func $ref.cast.null (type $1) (param $x anyref) + ;; YESTNH: (func $ref.cast.null (type $3) (param $x anyref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (ref.cast (ref null $C1) ;; YESTNH-NEXT: (local.get $x) From 8fd7cef98ef673c8b3c010a41a3b1f9ffc377d4c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 21 Jul 2025 16:47:42 -0700 Subject: [PATCH 604/622] [Custom Descriptors] Optimize descriptors in AbstractTypeRefining (#7738) Now that we no longer change subtype relationships in AbstractTypeRefining, it is straightforward to optimize descriptor and described types. --- src/passes/AbstractTypeRefining.cpp | 5 +-- .../passes/abstract-type-refining-desc.wast | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index eba5accf2a3..c27485d1e43 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -112,15 +112,12 @@ struct AbstractTypeRefining : public Pass { // module, given closed world, but we'd also need to make sure that // we don't need to make any changes to public types that refer to // them. - // Similarly, treat all descriptor and described types as allocated because - // we cannot yet optimize them correctly. auto heapTypes = ModuleUtils::collectHeapTypeInfo( *module, ModuleUtils::TypeInclusion::AllTypes, ModuleUtils::VisibilityHandling::FindVisibility); for (auto& [type, info] : heapTypes) { - if (info.visibility == ModuleUtils::Visibility::Public || - type.getDescribedType() || type.getDescriptorType()) { + if (info.visibility == ModuleUtils::Visibility::Public) { createdTypes.insert(type); } } diff --git a/test/lit/passes/abstract-type-refining-desc.wast b/test/lit/passes/abstract-type-refining-desc.wast index 95e555e1c75..1264ea01ea7 100644 --- a/test/lit/passes/abstract-type-refining-desc.wast +++ b/test/lit/passes/abstract-type-refining-desc.wast @@ -38,3 +38,37 @@ ) ) ) + +(module + ;; Same as above, but now we should see references to $A.desc and $B.desc + ;; optimized to nullref because they are not instantiated. Similarly, $A is + ;; optimized to $B with TNH. + (rec + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $A (sub (descriptor $A.desc (struct (field (ref null $B)) (field (ref null $B)))))) + ;; NO_TNH: (rec + ;; NO_TNH-NEXT: (type $A (sub (descriptor $A.desc (struct (field (ref null $A)) (field (ref null $B)))))) + (type $A (sub (descriptor $A.desc (struct (field (ref null $A) (ref null $B)))))) + ;; YESTNH: (type $A.desc (sub (describes $A (struct (field nullref) (field nullref))))) + ;; NO_TNH: (type $A.desc (sub (describes $A (struct (field nullref) (field nullref))))) + (type $A.desc (sub (describes $A (struct (field (ref null $A.desc) (ref null $B.desc)))))) + ;; YESTNH: (type $B (sub $A (descriptor $B.desc (struct (field (ref null $B)) (field (ref null $B)))))) + ;; NO_TNH: (type $B (sub $A (descriptor $B.desc (struct (field (ref null $A)) (field (ref null $B)))))) + (type $B (sub $A (descriptor $B.desc (struct (field (ref null $A) (ref null $B)))))) + ;; YESTNH: (type $B.desc (sub $A.desc (describes $B (struct (field nullref) (field nullref))))) + ;; NO_TNH: (type $B.desc (sub $A.desc (describes $B (struct (field nullref) (field nullref))))) + (type $B.desc (sub $A.desc (describes $B (struct (field (ref null $A.desc) (ref null $B.desc)))))) + ) + + ;; YESTNH: (global $g (ref (exact $B)) (struct.new_default $B + ;; YESTNH-NEXT: (ref.null none) + ;; YESTNH-NEXT: )) + ;; NO_TNH: (global $g (ref (exact $B)) (struct.new_default $B + ;; NO_TNH-NEXT: (ref.null none) + ;; NO_TNH-NEXT: )) + (global $g (ref (exact $B)) + (struct.new_default $B + (ref.null none) + ) + ) +) From ca59e791a20633f0f567a4d103f385ecd8c0aed6 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 21 Jul 2025 16:48:11 -0700 Subject: [PATCH 605/622] [Custom Descriptors] Effects for br_on_cast_desc (#7739) We previously handled trapping effects for null descriptors for struct.new and ref.cast_desc, but we were missing the effects for br_on_cast_desc. Unify the handling of the effects for all these instructions and add more tests for the new and existing functionality. --- scripts/test/fuzzing.py | 2 +- src/ir/effects.h | 25 ++- src/passes/OptimizeCasts.cpp | 1 + test/lit/passes/simplify-locals-desc.wast | 31 --- test/lit/passes/vacuum-desc.wast | 260 ++++++++++++++++++++++ 5 files changed, 277 insertions(+), 42 deletions(-) delete mode 100644 test/lit/passes/simplify-locals-desc.wast create mode 100644 test/lit/passes/vacuum-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index c31fcf85f6d..f061f2897b4 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -125,12 +125,12 @@ 'minimize-rec-groups-desc.wast', 'precompute-desc.wast', 'gc-desc.wast', - 'simplify-locals-desc.wast', 'optimize-instructions-desc.wast', 'gto-desc.wast', 'type-ssa-desc.wast', 'abstract-type-refining-desc.wast', 'remove-unused-brs-desc.wast', + 'vacuum-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/ir/effects.h b/src/ir/effects.h index 9f08a3b922b..57a12720f71 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -849,25 +849,30 @@ class EffectAnalyzer { } } void visitRefTest(RefTest* curr) {} + void maybeHandleDescriptor(Expression* desc) { + if (desc) { + // Traps when the descriptor is null. + if (desc->type.isNull()) { + parent.trap = true; + } else if (desc->type.isNullable()) { + parent.implicitTrap = true; + } + } + } void visitRefCast(RefCast* curr) { // Traps if the cast fails. parent.implicitTrap = true; + maybeHandleDescriptor(curr->desc); } void visitRefGetDesc(RefGetDesc* curr) { // Traps if the ref is null. parent.implicitTrap = true; } - void visitBrOn(BrOn* curr) { parent.breakTargets.insert(curr->name); } - void visitStructNew(StructNew* curr) { - if (curr->desc) { - // Traps when the descriptor is null. - if (curr->desc->type.isNull()) { - parent.trap = true; - } else if (curr->desc->type.isNullable()) { - parent.implicitTrap = true; - } - } + void visitBrOn(BrOn* curr) { + parent.breakTargets.insert(curr->name); + maybeHandleDescriptor(curr->desc); } + void visitStructNew(StructNew* curr) { maybeHandleDescriptor(curr->desc); } void visitStructGet(StructGet* curr) { if (curr->ref->type == Type::unreachable) { return; diff --git a/src/passes/OptimizeCasts.cpp b/src/passes/OptimizeCasts.cpp index c4487f8b4b6..aa7168f97c2 100644 --- a/src/passes/OptimizeCasts.cpp +++ b/src/passes/OptimizeCasts.cpp @@ -167,6 +167,7 @@ struct EarlyCastFinder // TODO: generalize this when we handle more than RefAsNonNull. RefCast dummyRefCast(module->allocator); + dummyRefCast.desc = nullptr; RefAs dummyRefAs(module->allocator); dummyRefAs.op = RefAsNonNull; diff --git a/test/lit/passes/simplify-locals-desc.wast b/test/lit/passes/simplify-locals-desc.wast deleted file mode 100644 index b4fa8a13f61..00000000000 --- a/test/lit/passes/simplify-locals-desc.wast +++ /dev/null @@ -1,31 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s - -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) - (type $struct (descriptor $desc (struct))) - ;; CHECK: (type $desc (sub (describes $struct (struct)))) - (type $desc (sub (describes $struct (struct)))) - ) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $l (ref null $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $struct - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (export "test") - (local $l (ref null $struct)) - (local.set $l - ;; We should preserve this trap. - (struct.new_default $struct - (ref.null none) - ) - ) - ) -) diff --git a/test/lit/passes/vacuum-desc.wast b/test/lit/passes/vacuum-desc.wast new file mode 100644 index 00000000000..912d61c3567 --- /dev/null +++ b/test/lit/passes/vacuum-desc.wast @@ -0,0 +1,260 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt -all %s --vacuum -S -o - | filecheck %s +;; RUN: wasm-opt -all %s --vacuum --ignore-implicit-traps -S -o - | filecheck %s --check-prefix=CKIIT +;; RUN: wasm-opt -all %s --vacuum --traps-never-happen -S -o - | filecheck %s --check-prefix=CKTNH + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc (struct))) + ;; CKIIT: (rec + ;; CKIIT-NEXT: (type $struct (descriptor $desc (struct))) + ;; CKTNH: (rec + ;; CKTNH-NEXT: (type $struct (descriptor $desc (struct))) + (type $struct (descriptor $desc (struct))) + ;; CHECK: (type $desc (describes $struct (struct))) + ;; CKIIT: (type $desc (describes $struct (struct))) + ;; CKTNH: (type $desc (describes $struct (struct))) + (type $desc (describes $struct (struct))) + ) + + ;; CHECK: (func $new-null-desc (type $5) (param $desc nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $new-null-desc (type $5) (param $desc nullref) + ;; CKIIT-NEXT: (drop + ;; CKIIT-NEXT: (struct.new_default $struct + ;; CKIIT-NEXT: (local.get $desc) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $new-null-desc (type $5) (param $desc nullref) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $new-null-desc (param $desc nullref) + (drop + (struct.new $struct + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $new-nullable-desc (type $6) (param $desc (ref null (exact $desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $struct + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $new-nullable-desc (type $6) (param $desc (ref null (exact $desc))) + ;; CKIIT-NEXT: (nop) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $new-nullable-desc (type $6) (param $desc (ref null (exact $desc))) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $new-nullable-desc (param $desc (ref null (exact $desc))) + (drop + (struct.new $struct + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $new-non-nullable-desc (type $7) (param $desc (ref (exact $desc))) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $new-non-nullable-desc (type $7) (param $desc (ref (exact $desc))) + ;; CKIIT-NEXT: (nop) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $new-non-nullable-desc (type $7) (param $desc (ref (exact $desc))) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $new-non-nullable-desc (param $desc (ref (exact $desc))) + (drop + (struct.new $struct + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-null-desc (type $2) (param $ref anyref) (param $desc nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $cast-null-desc (type $2) (param $ref anyref) (param $desc nullref) + ;; CKIIT-NEXT: (drop + ;; CKIIT-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CKIIT-NEXT: (drop + ;; CKIIT-NEXT: (local.get $ref) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: (drop + ;; CKIIT-NEXT: (local.get $desc) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: (unreachable) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $cast-null-desc (type $2) (param $ref anyref) (param $desc nullref) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $cast-null-desc (param $ref anyref) (param $desc nullref) + (drop + (ref.cast_desc (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-nullable-desc (type $3) (param $ref anyref) (param $desc (ref null $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref null $struct) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $cast-nullable-desc (type $3) (param $ref anyref) (param $desc (ref null $desc)) + ;; CKIIT-NEXT: (nop) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $cast-nullable-desc (type $3) (param $ref anyref) (param $desc (ref null $desc)) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $cast-nullable-desc (param $ref anyref) (param $desc (ref null $desc)) + (drop + (ref.cast_desc (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-non-nullable-desc (type $4) (param $ref anyref) (param $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref null $struct) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $cast-non-nullable-desc (type $4) (param $ref anyref) (param $desc (ref $desc)) + ;; CKIIT-NEXT: (nop) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $cast-non-nullable-desc (type $4) (param $ref anyref) (param $desc (ref $desc)) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $cast-non-nullable-desc (param $ref anyref) (param $desc (ref $desc)) + (drop + ;; The cast can still trap on failure, so by default we cannot remove it. + (ref.cast_desc (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $br-on-cast-null-desc (type $2) (param $ref anyref) (param $desc nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (block ;; (replaces unreachable BrOn we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $br-on-cast-null-desc (type $2) (param $ref anyref) (param $desc nullref) + ;; CKIIT-NEXT: (drop + ;; CKIIT-NEXT: (block $l (result anyref) + ;; CKIIT-NEXT: (block ;; (replaces unreachable BrOn we can't emit) + ;; CKIIT-NEXT: (drop + ;; CKIIT-NEXT: (local.get $ref) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: (drop + ;; CKIIT-NEXT: (local.get $desc) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: (unreachable) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: ) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $br-on-cast-null-desc (type $2) (param $ref anyref) (param $desc nullref) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $br-on-cast-null-desc (param $ref anyref) (param $desc nullref) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $br-on-cast-nullable-desc (type $3) (param $ref anyref) (param $desc (ref null $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (br_on_cast_desc $l anyref (ref null $struct) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $br-on-cast-nullable-desc (type $3) (param $ref anyref) (param $desc (ref null $desc)) + ;; CKIIT-NEXT: (nop) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $br-on-cast-nullable-desc (type $3) (param $ref anyref) (param $desc (ref null $desc)) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $br-on-cast-nullable-desc (param $ref anyref) (param $desc (ref null $desc)) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $br-on-cast-non-nullable-desc (type $4) (param $ref anyref) (param $desc (ref $desc)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CKIIT: (func $br-on-cast-non-nullable-desc (type $4) (param $ref anyref) (param $desc (ref $desc)) + ;; CKIIT-NEXT: (nop) + ;; CKIIT-NEXT: ) + ;; CKTNH: (func $br-on-cast-non-nullable-desc (type $4) (param $ref anyref) (param $desc (ref $desc)) + ;; CKTNH-NEXT: (nop) + ;; CKTNH-NEXT: ) + (func $br-on-cast-non-nullable-desc (param $ref anyref) (param $desc (ref $desc)) + (drop + (block $l (result anyref) + (br_on_cast_desc $l anyref (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + ) +) From d97dae95ca790051d5a8801d7e2307d39c8bcfc8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 21 Jul 2025 16:52:09 -0700 Subject: [PATCH 606/622] Revert part of #7715: Ignore metadata in function comparisons (#7737) Our comparisons are only used in optimization atm, and we decided not to consider metadata when merging functions, as that is what LLVM does: it will fold code together without carefully checking if profiling info matches, and without even trying to fix up that profiling info later. For more on LLVM, see discussion in #7733, but basically LLVM will just merge two instructions with different branch_weights and keep the first of the two, no matter what. --- src/ir/function-utils.h | 8 +-- ...ate-function-elimination_branch-hints.wast | 49 +++++-------------- 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/src/ir/function-utils.h b/src/ir/function-utils.h index 1496b49c8a5..29aca8885ae 100644 --- a/src/ir/function-utils.h +++ b/src/ir/function-utils.h @@ -17,7 +17,6 @@ #ifndef wasm_ir_function_h #define wasm_ir_function_h -#include "ir/metadata.h" #include "ir/utils.h" #include "wasm.h" @@ -38,9 +37,10 @@ inline bool equal(Function* left, Function* right) { return false; } } - if (!metadata::equal(left, right)) { - return false; - } + // We could in principle compare metadata here, but intentionally do not, as + // for optimization purposes we do want to e.g. merge functions that differ + // only in metadata (following LLVM's example). If we have a non-optimization + // reason for comparing metadata here then we could add a flag for it. if (left->imported() && right->imported()) { return true; } diff --git a/test/lit/passes/duplicate-function-elimination_branch-hints.wast b/test/lit/passes/duplicate-function-elimination_branch-hints.wast index 83b64f7e47c..75a16f148bb 100644 --- a/test/lit/passes/duplicate-function-elimination_branch-hints.wast +++ b/test/lit/passes/duplicate-function-elimination_branch-hints.wast @@ -3,13 +3,16 @@ ;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --all-features -S -o - | filecheck %s -;; The functions here differ in branch hints, and should not be merged. +;; Test that we merge functions even if they differ in branch hints. This is +;; good for code size, and follows what LLVM does. + +;; The functions here differ in branch hints (but we still merge). (module ;; CHECK: (type $0 (func (param i32))) ;; CHECK: (export "a" (func $a)) - ;; CHECK: (export "b" (func $b)) + ;; CHECK: (export "b" (func $a)) ;; CHECK: (func $a (type $0) (param $x i32) ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") @@ -30,15 +33,6 @@ ) ) - ;; CHECK: (func $b (type $0) (param $x i32) - ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) (func $b (export "b") (param $x i32) (@metadata.code.branch_hint "\01") (if @@ -50,14 +44,13 @@ ) ) -;; These also differ, now one is missing a hint, and they should not be merged. -;; TODO: Perhaps when optimizing for size, we should merge and drop the hint? +;; These also differ, now one is missing a hint (but we still merge). (module ;; CHECK: (type $0 (func (param i32))) ;; CHECK: (export "a" (func $a)) - ;; CHECK: (export "b" (func $b)) + ;; CHECK: (export "b" (func $a)) ;; CHECK: (func $a (type $0) (param $x i32) ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") @@ -78,14 +71,6 @@ ) ) - ;; CHECK: (func $b (type $0) (param $x i32) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) (func $b (export "b") (param $x i32) (if (local.get $x) @@ -97,13 +82,13 @@ ) ;; Flipped case of the above, now the other one is the only one with a hint, -;; and that hint is flipped. +;; and that hint is flipped (but we still merge). (module ;; CHECK: (type $0 (func (param i32))) ;; CHECK: (export "a" (func $a)) - ;; CHECK: (export "b" (func $b)) + ;; CHECK: (export "b" (func $a)) ;; CHECK: (func $a (type $0) (param $x i32) ;; CHECK-NEXT: (if @@ -122,15 +107,6 @@ ) ) - ;; CHECK: (func $b (type $0) (param $x i32) - ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) (func $b (export "b") (param $x i32) (@metadata.code.branch_hint "\01") (if @@ -142,7 +118,7 @@ ) ) -;; Identical branch hints: We can merge here. +;; Identical branch hints: We can definitely merge here. (module ;; CHECK: (type $0 (func (param i32))) @@ -218,8 +194,9 @@ ) ) -;; Source file location (debug info) does *not* prevent optimization. We -;; prioritize optimization over debug info quality. +;; Source file location (debug info) does not prevent optimization (and has +;; even less reason to do so than branch hints, as we prioritize optimization +;; over debug info quality). (module ;; CHECK: (type $0 (func (param i32))) From 452e3ee04157b71eeed6e6b6f372e901fcd5a65b Mon Sep 17 00:00:00 2001 From: Derek Mauro <761129+derekmauro@users.noreply.github.com> Date: Tue, 22 Jul 2025 11:29:02 -0400 Subject: [PATCH 607/622] Fix undefined behavior in construction of wasm::NameType (#7736) `wasm::Name::Name(nullptr)` ended up calling `std::string_view(nullptr)`, which is undefined and triggers an assertion in some standard libraries. https://en.cppreference.com/w/cpp/string/basic_string_view/basic_string_view.html --- src/wasm-builder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-builder.h b/src/wasm-builder.h index dc877bd0223..134409bb4cf 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -29,7 +29,7 @@ namespace wasm { struct NameType { Name name; Type type; - NameType() : name(nullptr), type(Type::none) {} + NameType() : type(Type::none) {} NameType(Name name, Type type) : name(name), type(type) {} }; From dd473d47a24f39bc2ea66f4277e0ecf8937af801 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 22 Jul 2025 09:44:44 -0700 Subject: [PATCH 608/622] Track indirect call types in RemoveUnusedModuleElements (#7728) An indirect call to a type in a table now only forces functions of that type to be marked as used: functions of other types are left alone, potentially leaving them unreached. This is more precise than assuming any indirect call can reach anywhere, which is more or less what we did before. The downside is that this makes the pass is 10% slower. This is one of our faster passes, however, so the cost seems worth it, given that this can noticeably help in some cases, e.g., 6.5% code size reduction on Poppler, and 20% on an embind testcase. In general, however, most real-world codebases benefit little if at all, because there is too much overlap in indirect call signatures: Statistically, when you generate random graphs of size `n` and chance for each edge to exist `p`, then even if `p` decreases to 0 the graph will tend to end up fully connected [1]. And, in wasm, `p` does not even decrease to 0: * Consider some common signature like `{i32} -> {}` (i32 param, no result). * In real-world code, there is some chance `q>0` for that signature to be called, and some chance `r>0` for that signature to exist in the code. * `p >= O(rq) > 0` because all it takes for a connection to exist is that that signature exists on one side and is called on the other. That is, in large codebases there is an overlap in signatures, and statistically this means that all the code will end up reachable, at least in the limit. But some programs do get lucky and benefit from this PR. [1] https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93R%C3%A9nyi_model#Properties_of_G(n,_p) --- src/passes/RemoveUnusedModuleElements.cpp | 170 ++++-- test/ctor-eval/bad-indirect-call3.wast.out | 14 +- test/ctor-eval/basics-flatten.wast | 3 +- test/ctor-eval/basics-flatten.wast.out | 10 +- test/ctor-eval/basics.wast | 3 +- test/ctor-eval/basics.wast.out | 10 +- test/ctor-eval/indirect-call3.wast | 3 +- test/ctor-eval/indirect-call3.wast.out | 11 +- test/lit/passes/extract-function.wast | 2 - ...e-unused-module-elements_all-features.wast | 13 + ...emove-unused-module-elements_ci-types.wast | 548 ++++++++++++++++++ .../remove-unused-module-elements_tnh.wast | 29 +- test/wasm2js/br_table_temp.2asm.js | 7 - 13 files changed, 703 insertions(+), 120 deletions(-) create mode 100644 test/lit/passes/remove-unused-module-elements_ci-types.wast diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 27162278456..c9b7a4474e0 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -24,7 +24,8 @@ // * No references at all. We can simply remove it. // * References, but no uses. We can't remove it, but we can change it (see // below). -// * Uses (which imply references). We must keep it as it is. +// * Uses (which imply references). We must keep it as it is, because it is +// fully used (e.g. for a function, it is called and may execute). // // An example of something with a reference but *not* a use is a RefFunc to a // function that has no corresponding CallRef to that type. We cannot just @@ -62,25 +63,38 @@ using ModuleElementKind = ModuleItemKind; // name of the particular element. using ModuleElement = std::pair; +// Information from an indirect call: the name of the table, and the heap type. +using IndirectCall = std::pair; + // Visit or walk an expression to find what things are referenced. struct ReferenceFinder : public PostWalker> { // Our findings are placed in these data structures, which the user of this - // code can then process. - std::vector elements; + // code can then process. We mark both uses and references, and also note + // uses of specific things that require special handling, like refFuncs. + std::vector used, referenced; std::vector callRefTypes; std::vector refFuncs; std::vector structFields; + std::vector indirectCalls; // Add an item to the output data structures. - void note(ModuleElement element) { elements.push_back(element); } - void noteCallRef(HeapType type) { callRefTypes.push_back(type); } - void noteRefFunc(Name refFunc) { refFuncs.push_back(refFunc); } - void note(StructField structField) { structFields.push_back(structField); } - - // Generic visitor + void use(ModuleElement element) { used.push_back(element); } + void reference(ModuleElement element) { referenced.push_back(element); } + void useCallRef(HeapType type) { callRefTypes.push_back(type); } + void useRefFunc(Name refFunc) { refFuncs.push_back(refFunc); } + void useStructField(StructField structField) { + structFields.push_back(structField); + } + void useIndirectCall(Name table, HeapType type) { + indirectCalls.push_back({table, type}); + } + // Generic visitor: Use all the things referenced. This handles e.g. using the + // table of a table.get. When we do not want such unconditional use, we + // override (e.g. for call_indirect, we don't want to mark the entire table as + // used, see below). void visitExpression(Expression* curr) { #define DELEGATE_ID curr->_id @@ -101,7 +115,7 @@ struct ReferenceFinder #define DELEGATE_FIELD_NAME_KIND(id, field, kind) \ if (cast->field.is()) { \ - note({kind, cast->field}); \ + use({kind, cast->field}); \ } #include "wasm-delegations-fields.def" @@ -110,7 +124,7 @@ struct ReferenceFinder // Specific visitors void visitCall(Call* curr) { - note({ModuleElementKind::Function, curr->target}); + use({ModuleElementKind::Function, curr->target}); if (Intrinsics(*getModule()).isCallWithoutEffects(curr)) { // A call-without-effects receives a function reference and calls it, the @@ -137,12 +151,15 @@ struct ReferenceFinder } void visitCallIndirect(CallIndirect* curr) { - note({ModuleElementKind::Table, curr->table}); + // We refer to the table, but may not use all parts of it, that depends on + // the heap type we call with. + reference({ModuleElementKind::Table, curr->table}); + useIndirectCall(curr->table, curr->heapType); // Note a possible call of a function reference as well, as something might // be written into the table during runtime. With precise tracking of what // is written into the table we could do better here; we could also see // which tables are immutable. TODO - noteCallRef(curr->heapType); + useCallRef(curr->heapType); } void visitCallRef(CallRef* curr) { @@ -151,17 +168,17 @@ struct ReferenceFinder return; } - noteCallRef(curr->target->type.getHeapType()); + useCallRef(curr->target->type.getHeapType()); } - void visitRefFunc(RefFunc* curr) { noteRefFunc(curr->func); } + void visitRefFunc(RefFunc* curr) { useRefFunc(curr->func); } void visitStructGet(StructGet* curr) { if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) { return; } auto type = curr->ref->type.getHeapType(); - note(StructField{type, curr->index}); + useStructField(StructField{type, curr->index}); } }; @@ -262,9 +279,12 @@ struct Analyzer { ReferenceFinder finder; finder.setModule(module); finder.visit(curr); - for (auto element : finder.elements) { + for (auto element : finder.used) { use(element); } + for (auto element : finder.referenced) { + reference(element); + } for (auto type : finder.callRefTypes) { useCallRefType(type); } @@ -274,6 +294,9 @@ struct Analyzer { for (auto structField : finder.structFields) { useStructField(structField); } + for (auto call : finder.indirectCalls) { + useIndirectCall(call); + } // Scan the children to continue our work. scanChildren(curr); @@ -316,6 +339,37 @@ struct Analyzer { } } + std::unordered_set usedIndirectCalls; + + void useIndirectCall(IndirectCall call) { + auto [_, inserted] = usedIndirectCalls.insert(call); + if (!inserted) { + return; + } + + // TODO: use structured bindings with c++20, needed for the capture below + auto table = call.first; + auto type = call.second; + + // Any function in the table of that signature may be called. + ModuleUtils::iterTableSegments( + *module, table, [&](ElementSegment* segment) { + auto segmentReferenced = false; + for (auto* item : segment->data) { + if (auto* refFunc = item->dynCast()) { + auto* func = module->getFunction(refFunc->func); + if (HeapType::isSubType(func->type, type)) { + use({ModuleElementKind::Function, refFunc->func}); + segmentReferenced = true; + } + } + } + if (segmentReferenced) { + reference({ModuleElementKind::ElementSegment, segment->name}); + } + }); + } + void useRefFunc(Name func) { if (!options.closedWorld) { // The world is open, so assume the worst and something (inside or outside @@ -341,7 +395,7 @@ struct Analyzer { // We've never seen a CallRef for this, but might see one later. uncalledRefFuncMap[type].insert(func); - referenced.insert(element); + reference(element); } } @@ -554,34 +608,11 @@ struct Analyzer { finder.setModule(module); finder.walk(curr); - for (auto element : finder.elements) { - // Avoid repeated work. Note that globals with multiple references to - // previous globals can lead to exponential work, so this is important. - // (If C refers twice to B, and B refers twice to A, then when we process - // C we would, naively, scan B twice and A four times.) - auto [_, inserted] = referenced.insert(element); - if (!inserted) { - continue; - } - - auto& [kind, value] = element; - if (kind == ModuleElementKind::Global) { - // Like functions, (non-imported) globals have contents. For functions, - // things are simple: if a function ends up with references but no uses - // then we can simply empty out the function (by setting its body to an - // unreachable). We don't have a simple way to do the same for globals, - // unfortunately. For now, scan the global's contents and add references - // as needed. - // TODO: We could try to empty the global out, for example, replace it - // with a null if it is nullable, or replace all gets of it with - // something else, but that is not trivial. - auto* global = module->getGlobal(value); - if (!global->imported()) { - // Note that infinite recursion is not a danger here since a global - // can only refer to previous globals. - addReferences(global->init); - } - } + for (auto element : finder.used) { + reference(element); + } + for (auto element : finder.referenced) { + reference(element); } for (auto func : finder.refFuncs) { @@ -594,7 +625,7 @@ struct Analyzer { // just adding a reference to the function, and not actually using the // RefFunc. (Only useRefFunc() + a CallRef of the proper type are enough // to make a function itself used.) - referenced.insert({ModuleElementKind::Function, func}); + reference({ModuleElementKind::Function, func}); } // Note: nothing to do with |callRefTypes| and |structFields|, which only @@ -603,6 +634,44 @@ struct Analyzer { // handled in an entirely different way in Binaryen IR, and we don't need to // worry about it.) } + + void reference(ModuleElement element) { + // Avoid repeated work. Note that globals with multiple references to + // previous globals can lead to exponential work, so this is important. + // (If C refers twice to B, and B refers twice to A, then when we process + // C we would, naively, scan B twice and A four times.) + auto [_, inserted] = referenced.insert(element); + if (!inserted) { + return; + } + + // Some references force references to their internals, just by being + // referenced and present in the output. + auto& [kind, value] = element; + if (kind == ModuleElementKind::Global) { + // Like functions, (non-imported) globals have contents. For functions, + // things are simple: if a function ends up with references but no uses + // then we can simply empty out the function (by setting its body to an + // unreachable). We don't have a simple way to do the same for globals, + // unfortunately. For now, scan the global's contents and add references + // as needed. + // TODO: We could try to empty the global out, for example, replace it + // with a null if it is nullable, or replace all gets of it with + // something else, but that is not trivial. + auto* global = module->getGlobal(value); + if (!global->imported()) { + // Note that infinite recursion is not a danger here since a global + // can only refer to previous globals. + addReferences(global->init); + } + } else if (kind == ModuleElementKind::ElementSegment) { + // TODO: We could empty out parts of the segment we don't need. + auto* segment = module->getElementSegment(value); + for (auto* item : segment->data) { + addReferences(item); + } + } + } }; struct RemoveUnusedModuleElements : public Pass { @@ -709,13 +778,6 @@ struct RemoveUnusedModuleElements : public Pass { } }); - // For now, all functions that can be called indirectly are marked as roots. - // TODO: Compute this based on which ElementSegments are actually used, - // and which functions have a call_indirect of the proper type. - ElementUtils::iterAllElementFunctionNames(module, [&](Name name) { - roots.emplace_back(ModuleElementKind::Function, name); - }); - // Just as out-of-bound segments may cause observable traps at instantiation // time, so can struct.new instructions with null descriptors cause traps in // global or element segment initializers. diff --git a/test/ctor-eval/bad-indirect-call3.wast.out b/test/ctor-eval/bad-indirect-call3.wast.out index 1d67f071525..8e97a494da2 100644 --- a/test/ctor-eval/bad-indirect-call3.wast.out +++ b/test/ctor-eval/bad-indirect-call3.wast.out @@ -1,19 +1,9 @@ (module - (type $0 (func (param externref))) - (type $1 (func)) + (type $0 (func)) (type $funcref_=>_none (func (param funcref))) - (memory $0 256 256) - (data $0 (i32.const 10) "waka waka waka waka waka") (table $0 1 1 funcref) - (elem $implicit-elem (i32.const 0) $callee) (export "sig_mismatch" (func $sig_mismatch)) - (func $callee (type $0) (param $0 externref) - (i32.store8 - (i32.const 40) - (i32.const 67) - ) - ) - (func $sig_mismatch (type $1) + (func $sig_mismatch (type $0) (call_indirect $0 (type $funcref_=>_none) (ref.null nofunc) (i32.const 0) diff --git a/test/ctor-eval/basics-flatten.wast b/test/ctor-eval/basics-flatten.wast index f53b772f047..5c1fb69a8b7 100644 --- a/test/ctor-eval/basics-flatten.wast +++ b/test/ctor-eval/basics-flatten.wast @@ -1,6 +1,6 @@ (module (type $v (func)) - (memory 256 256) + (memory $m 256 256) ;; test flattening of multiple segments (data (i32.const 10) "waka ") (data (i32.const 15) "waka") ;; skip a byte here @@ -10,6 +10,7 @@ (export "test1" (func $test1)) (export "test2" (func $test2)) (export "test3" (func $test3)) + (export "memory" (memory $m)) ;; export memory so we can see the flattened data (func $test1 (drop (i32.const 0)) ;; no work at all, really (call $safe-to-call) ;; safe to call diff --git a/test/ctor-eval/basics-flatten.wast.out b/test/ctor-eval/basics-flatten.wast.out index 69c857733bb..0492e538dd3 100644 --- a/test/ctor-eval/basics-flatten.wast.out +++ b/test/ctor-eval/basics-flatten.wast.out @@ -1,11 +1,5 @@ (module - (type $v (func)) - (memory $0 256 256) + (memory $m 256 256) (data $0 (i32.const 10) "nas\00\00\00aka\00yzkx waka wakm\00\00\00\00\00\00C") - (func $call-indirect (type $v) - (i32.store8 - (i32.const 40) - (i32.const 67) - ) - ) + (export "memory" (memory $m)) ) diff --git a/test/ctor-eval/basics.wast b/test/ctor-eval/basics.wast index ff138de24ac..4e306b4bc7f 100644 --- a/test/ctor-eval/basics.wast +++ b/test/ctor-eval/basics.wast @@ -1,12 +1,13 @@ (module (type $v (func)) - (memory 256 256) + (memory $m 256 256) (data (i32.const 10) "waka waka waka waka waka") (table 1 1 funcref) (elem (i32.const 0) $call-indirect) (export "test1" (func $test1)) (export "test2" (func $test2)) (export "test3" (func $test3)) + (export "memory" (memory $m)) ;; export memory so we can see the updated data (func $test1 (drop (i32.const 0)) ;; no work at all, really (call $safe-to-call) ;; safe to call diff --git a/test/ctor-eval/basics.wast.out b/test/ctor-eval/basics.wast.out index 1b2c7405089..0741b983a1b 100644 --- a/test/ctor-eval/basics.wast.out +++ b/test/ctor-eval/basics.wast.out @@ -1,11 +1,5 @@ (module - (type $v (func)) - (memory $0 256 256) + (memory $m 256 256) (data $0 (i32.const 10) "nas\00\00\00aka yzkx waka wakm\00\00\00\00\00\00C") - (func $call-indirect (type $v) - (i32.store8 - (i32.const 40) - (i32.const 67) - ) - ) + (export "memory" (memory $m)) ) diff --git a/test/ctor-eval/indirect-call3.wast b/test/ctor-eval/indirect-call3.wast index 1c01513b105..6276f75d0ef 100644 --- a/test/ctor-eval/indirect-call3.wast +++ b/test/ctor-eval/indirect-call3.wast @@ -1,11 +1,12 @@ (module (import "env" "_abort" (func $_abort)) (type $v (func)) - (memory 256 256) + (memory $m 256 256) (data (i32.const 10) "waka waka waka waka waka") (table 2 2 funcref) (elem (i32.const 0) $_abort $call-indirect) (export "test1" (func $test1)) + (export "memory" (memory $m)) ;; export memory so we can see the updated data (func $test1 (call_indirect (type $v) (i32.const 1)) ;; safe to call (i32.store8 (i32.const 20) (i32.const 120)) diff --git a/test/ctor-eval/indirect-call3.wast.out b/test/ctor-eval/indirect-call3.wast.out index 19e8d9499ec..792894c3afd 100644 --- a/test/ctor-eval/indirect-call3.wast.out +++ b/test/ctor-eval/indirect-call3.wast.out @@ -1,12 +1,5 @@ (module - (type $v (func)) - (import "env" "_abort" (func $_abort (type $v))) - (memory $0 256 256) + (memory $m 256 256) (data $0 (i32.const 10) "waka waka xaka waka waka\00\00\00\00\00\00C") - (func $call-indirect (type $v) - (i32.store8 - (i32.const 40) - (i32.const 67) - ) - ) + (export "memory" (memory $m)) ) diff --git a/test/lit/passes/extract-function.wast b/test/lit/passes/extract-function.wast index 9258f8c63cf..b76aae4823a 100644 --- a/test/lit/passes/extract-function.wast +++ b/test/lit/passes/extract-function.wast @@ -36,8 +36,6 @@ ;; CHECK: (type $0 (func)) - ;; CHECK: (import "env" "other" (func $other)) - ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo diff --git a/test/lit/passes/remove-unused-module-elements_all-features.wast b/test/lit/passes/remove-unused-module-elements_all-features.wast index 8b4d7f18638..816feb6d7c9 100644 --- a/test/lit/passes/remove-unused-module-elements_all-features.wast +++ b/test/lit/passes/remove-unused-module-elements_all-features.wast @@ -624,6 +624,12 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call_indirect $defined-used (type $0) + ;; CHECK-NEXT: (f64.const 1) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if (result f64) ;; CHECK-NEXT: (f64.eq ;; CHECK-NEXT: (f64.const 1) @@ -643,6 +649,13 @@ (i32.const 0) ) ) + ;; An indirect call, so the segment is used. + (drop + (call_indirect $defined-used (type $0) + (f64.const 1) + (i32.const -1) + ) + ) (if (result f64) (f64.eq (f64.const 1) diff --git a/test/lit/passes/remove-unused-module-elements_ci-types.wast b/test/lit/passes/remove-unused-module-elements_ci-types.wast new file mode 100644 index 00000000000..f4ea6788b3f --- /dev/null +++ b/test/lit/passes/remove-unused-module-elements_ci-types.wast @@ -0,0 +1,548 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements -all -S -o - | filecheck %s + +;; Test for precise handling of call_indirect types. + +;; We have an indirect call of $A, but not of $B. This keeps alive segments with +;; relevant functions, but not other segments. It also does not keep alive other +;; functions in those segments (we refer to them, but can empty out their +;; contents). Specifically: +;; +;; * elem $t1-withA contains $A, so it keeps alive func $A1 and $subA1, and +;; emptied stubs for $B1 and $C1. +;; * elem $t1-noA has no $A or a subtype, so the segment is removed entirely. +;; * elem $t1-withSubA has a subtype of $A, so it is similar to $t1-withA. +;; * elem $t2-withA has $A, but no call goes to that table. +;; +(module + ;; CHECK: (type $A (sub (func))) + (type $A (sub (func))) + + ;; CHECK: (type $B (sub (func (param f64)))) + (type $B (sub (func (param f64)))) + + ;; CHECK: (type $subA (sub $A (func))) + (type $subA (sub $A (func))) + + ;; CHECK: (type $3 (func (param f32))) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (table $t1 60 60 funcref) + (table $t1 60 60 funcref) + + (table $t2 60 60 funcref) + + ;; CHECK: (elem $t1-withA (i32.const 0) $A1 $B1 $subA1 $C1) + (elem $t1-withA (table $t1) (i32.const 0) func $A1 $B1 $subA1 $C1) + + (elem $t1-noA (table $t1) (i32.const 10) func $B2 $C2) + + ;; CHECK: (elem $t1-withSubA (i32.const 20) $B3 $subA3 $C3) + (elem $t1-withSubA (table $t1) (i32.const 20) func $B3 $subA3 $C3) + + (elem $t2-withA (table $t2) (i32.const 0) func $A2) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $4) + ;; CHECK-NEXT: (call_indirect $t1 (type $A) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $export (export "export") + (call_indirect $t1 (type $A) + (i32.const -1) + ) + ) + + ;; CHECK: (func $A1 (type $A) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $A1 (type $A) + (drop (i32.const 10)) + ) + + ;; CHECK: (func $B1 (type $B) (param $p f64) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $B1 (type $B) (param $p f64) + ;; We can empty this out, as while a segment references it, no call_indirect + ;; exists. + (drop (i32.const 20)) + ) + + ;; CHECK: (func $subA1 (type $subA) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subA1 (type $subA) + (drop (i32.const 30)) + ) + + ;; CHECK: (func $C1 (type $3) (param $p f32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $C1 (param $p f32) + ;; We can empty this out, as while a segment references it, no call_indirect + ;; exists. + (drop (i32.const 40)) + ) + + (func $B2 (type $B) (param $p f64) + ;; No viable uses exist of this, it is in a segment with no uses. + (drop (i32.const 50)) + ) + + (func $C2 (param $p f32) + ;; No viable uses exist of this, it is in a segment with no uses. + (drop (i32.const 60)) + ) + + ;; CHECK: (func $B3 (type $B) (param $p f64) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $B3 (type $B) (param $p f64) + ;; We can empty this out, as while a segment references it, no call_indirect + ;; exists. + (drop (i32.const 70)) + ) + + ;; CHECK: (func $subA3 (type $subA) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 80) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subA3 (type $subA) + (drop (i32.const 80)) + ) + + ;; CHECK: (func $C3 (type $3) (param $p f32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $C3 (param $p f32) + ;; We can empty this out, as while a segment references it, no call_indirect + ;; exists. We can also remove the thing it calls. + (drop (call $C3-called)) + ) + + (func $C3-called (result i32) + ;; The only caller of this is emptied out, so we don't need it. + (i32.const 100) + ) + + (func $A2 (type $A) + ;; No viable uses exist of this, it is in the wrong table. + (drop (i32.const 110)) + ) +) + +;; Similar to above, but now the table is exported, and the test is a bit +;; simplified to focus on the changes from that. Given the export, we must +;; assume the outside can call anything in the table. +(module + ;; CHECK: (type $A (sub (func))) + (type $A (sub (func))) + + ;; CHECK: (type $B (sub (func (param f64)))) + (type $B (sub (func (param f64)))) + + ;; CHECK: (type $2 (func (param f32))) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $subA (sub $A (func))) + (type $subA (sub $A (func))) + + ;; CHECK: (table $t1 60 60 funcref) + (table $t1 60 60 funcref) + + ;; CHECK: (elem $t1-withA (i32.const 0) $A1 $B1 $subA1 $C1) + (elem $t1-withA (table $t1) (i32.const 0) func $A1 $B1 $subA1 $C1) + + ;; CHECK: (elem $t1-noA (i32.const 10) $B2 $C2) + (elem $t1-noA (table $t1) (i32.const 10) func $B2 $C2) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (export "table" (table $t1)) + (export "table" (table $t1)) + + ;; CHECK: (func $export (type $3) + ;; CHECK-NEXT: (call_indirect $t1 (type $A) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $export (export "export") + (call_indirect $t1 (type $A) + (i32.const -1) + ) + ) + + ;; CHECK: (func $A1 (type $A) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $A1 (type $A) + (drop (i32.const 10)) + ) + + ;; CHECK: (func $B1 (type $B) (param $p f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $B1 (type $B) (param $p f64) + ;; The table export causes this to be kept in full. + (drop (i32.const 20)) + ) + + ;; CHECK: (func $subA1 (type $subA) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subA1 (type $subA) + (drop (i32.const 30)) + ) + + ;; CHECK: (func $C1 (type $2) (param $p f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $C1 (param $p f32) + ;; The table export causes this to be kept in full. + (drop (i32.const 40)) + ) + + ;; CHECK: (func $B2 (type $B) (param $p f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 50) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $B2 (type $B) (param $p f64) + ;; The table export causes this to be kept in full. + (drop (i32.const 50)) + ) + + ;; CHECK: (func $C2 (type $2) (param $p f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $C2 (param $p f32) + ;; The table export causes this to be kept in full. + (drop (i32.const 60)) + ) +) + +;; As above, but the table is imported instead of exported. That still means it +;; is externally accessible, so we must root its contents. +(module + ;; CHECK: (type $A (sub (func))) + (type $A (sub (func))) + + ;; CHECK: (type $B (sub (func (param f64)))) + (type $B (sub (func (param f64)))) + + ;; CHECK: (type $2 (func (param f32))) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $subA (sub $A (func))) + (type $subA (sub $A (func))) + + ;; CHECK: (import "a" "b" (table $t1 60 60 funcref)) + (import "a" "b" (table $t1 60 60 funcref)) + + ;; CHECK: (elem $t1-withA (i32.const 0) $A1 $B1 $subA1 $C1) + (elem $t1-withA (table $t1) (i32.const 0) func $A1 $B1 $subA1 $C1) + + ;; CHECK: (elem $t1-noA (i32.const 10) $B2 $C2) + (elem $t1-noA (table $t1) (i32.const 10) func $B2 $C2) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $3) + ;; CHECK-NEXT: (call_indirect $t1 (type $A) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $export (export "export") + (call_indirect $t1 (type $A) + (i32.const -1) + ) + ) + + ;; CHECK: (func $A1 (type $A) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $A1 (type $A) + (drop (i32.const 10)) + ) + + ;; CHECK: (func $B1 (type $B) (param $p f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $B1 (type $B) (param $p f64) + ;; The table import causes this to be kept in full. + (drop (i32.const 20)) + ) + + ;; CHECK: (func $subA1 (type $subA) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subA1 (type $subA) + (drop (i32.const 30)) + ) + + ;; CHECK: (func $C1 (type $2) (param $p f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $C1 (param $p f32) + ;; The table import causes this to be kept in full. + (drop (i32.const 40)) + ) + + ;; CHECK: (func $B2 (type $B) (param $p f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 50) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $B2 (type $B) (param $p f64) + ;; The table import causes this to be kept in full. + (drop (i32.const 50)) + ) + + ;; CHECK: (func $C2 (type $2) (param $p f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $C2 (param $p f32) + ;; The table import causes this to be kept in full. + (drop (i32.const 60)) + ) +) + +;; A chain of indirect calls: an export calls type $A, and a function of type $A +;; calls $B, and $B calls $C. All those are used, but a final function of type +;; $D is not, and can be cleared out. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (func)) + (type $A (func)) + ;; CHECK: (type $B (func)) + (type $B (func)) + ;; CHECK: (type $C (func)) + (type $C (func)) + ;; CHECK: (type $D (func)) + (type $D (func)) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (table $t 60 60 funcref) + (table $t 60 60 funcref) + + ;; CHECK: (elem $elem (i32.const 0) $A $B $C $D) + (elem $elem (table $t) (i32.const 0) func $A $B $C $D) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $4) + ;; CHECK-NEXT: (call_indirect $t (type $A) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $export (export "export") + (call_indirect $t (type $A) + (i32.const -1) + ) + ) + + ;; CHECK: (func $A (type $A) + ;; CHECK-NEXT: (call_indirect $t (type $B) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $A (type $A) + (call_indirect $t (type $B) + (i32.const -1) + ) + ) + + ;; CHECK: (func $B (type $B) + ;; CHECK-NEXT: (call_indirect $t (type $C) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $B (type $B) + (call_indirect $t (type $C) + (i32.const -1) + ) + ) + + ;; CHECK: (func $C (type $C) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $C (type $C) + ;; Chain breaks here, no call to $D. + (drop (i32.const 30)) + ) + + ;; CHECK: (func $D (type $D) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $D (type $D) + ;; Add calls to all types, to check unreached code does not confuse us. + (call_indirect $t (type $A) + (i32.const -1) + ) + (call_indirect $t (type $B) + (i32.const -1) + ) + (call_indirect $t (type $C) + (i32.const -1) + ) + (call_indirect $t (type $D) + (i32.const -1) + ) + ) +) + +;; Check that functions applied using table.init are handled properly. The +;; function $A will be called after the table.init, so we cannot empty it or +;; remove it. +(module + ;; CHECK: (type $A (func)) + (type $A (func)) + + ;; CHECK: (table $table 22 funcref) + (table $table 22 funcref) + + ;; CHECK: (elem $later func $A) + (elem $later $A) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $A) + ;; CHECK-NEXT: (table.init $table $later + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $table (type $A) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $export (export "export") + (table.init $table $later + (i32.const 0) + (i32.const 0) + (i32.const 1) + ) + (call_indirect $table (type $A) + (i32.const 0) + ) + ) + + ;; CHECK: (func $A (type $A) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $A (type $A) + (drop (i32.const 42)) + ) +) + +;; As above, but without table.init. We could optimize here, but do not yet - +;; we would need to track table.* operations fully. +(module + ;; CHECK: (type $A (func)) + (type $A (func)) + + ;; CHECK: (table $table 22 funcref) + (table $table 22 funcref) + + ;; CHECK: (elem $later func $A) + (elem $later $A) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $A) + ;; CHECK-NEXT: (call_indirect $table (type $A) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (elem.drop $later) + ;; CHECK-NEXT: ) + (func $export (export "export") + (call_indirect $table (type $A) + (i32.const 0) + ) + ;; No table.init, so that elem is never written. We do use it so it doesn't + ;; vanish entirely. + (elem.drop $later) + ) + + ;; CHECK: (func $A (type $A) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $A (type $A) + ;; TODO: optimize this + (drop (i32.const 42)) + ) +) + +;; Test that table.get ensures that elements are kept around, so that their +;; data can be read. +(module + ;; CHECK: (type $0 (func (result i32))) + + ;; CHECK: (table $t 354 354 i31ref) + (table $t 354 354 i31ref) + + ;; CHECK: (elem $e (table $t) (i32.const 0) i31ref (item (ref.i31 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ))) + (elem $e (table $t) (i32.const 0) i31ref (item (ref.i31 + (i32.const 0) + ))) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $0) (result i32) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (table.get $t + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $export (export "export") (result i32) + (i31.get_u + (table.get $t + (i32.const 0) + ) + ) + ) +) diff --git a/test/lit/passes/remove-unused-module-elements_tnh.wast b/test/lit/passes/remove-unused-module-elements_tnh.wast index 5378431912e..76275742c63 100644 --- a/test/lit/passes/remove-unused-module-elements_tnh.wast +++ b/test/lit/passes/remove-unused-module-elements_tnh.wast @@ -163,10 +163,6 @@ ;; CHECK: (func $func (type $0) ;; CHECK-NEXT: ) - ;; T_N_H: (type $0 (func)) - - ;; T_N_H: (func $func (type $0) - ;; T_N_H-NEXT: ) (func $func) ) @@ -189,14 +185,10 @@ ;; CHECK: (func $func (type $0) ;; CHECK-NEXT: ) - ;; T_N_H: (type $0 (func)) - - ;; T_N_H: (func $func (type $0) - ;; T_N_H-NEXT: ) (func $func) ) -;; No bad segments: all element segments vanish. TODO: the function could too +;; No bad segments: all element segments vanish. (module (table 10 10 funcref) @@ -204,14 +196,6 @@ (elem $ok2 (i32.const 8) $func $func) (elem $ok3 (i32.const 9) $func) - ;; CHECK: (type $0 (func)) - - ;; CHECK: (func $func (type $0) - ;; CHECK-NEXT: ) - ;; T_N_H: (type $0 (func)) - - ;; T_N_H: (func $func (type $0) - ;; T_N_H-NEXT: ) (func $func) ) @@ -241,3 +225,14 @@ (data $b (memory $big) (i32.const 100000) "cd") ) + +;; Add a module T_N_H cannot remove, as otherwise lit errors on not finding +;; any check strings with that prefix. +(module + ;; CHECK: (memory $mem 2 2) + ;; T_N_H: (memory $mem 2 2) + (memory $mem 2 2) + ;; CHECK: (export "mem" (memory $mem)) + ;; T_N_H: (export "mem" (memory $mem)) + (export "mem" (memory $mem)) +) diff --git a/test/wasm2js/br_table_temp.2asm.js b/test/wasm2js/br_table_temp.2asm.js index 704ec068775..c7c875fc07b 100644 --- a/test/wasm2js/br_table_temp.2asm.js +++ b/test/wasm2js/br_table_temp.2asm.js @@ -12768,13 +12768,6 @@ function asmFunc(imports) { return $1_1 | 0; } - function f($0_1, $1_1, $2_1) { - $0_1 = $0_1 | 0; - $1_1 = $1_1 | 0; - $2_1 = $2_1 | 0; - return -1 | 0; - } - function $36() { var $1_1 = 0; block : { From 90e98db623b90bcdf7357122d09722d7696d2136 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 22 Jul 2025 11:25:25 -0700 Subject: [PATCH 609/622] Improve ReFinalize::replaceUntaken (#7740) ReFinalize replaces unreachable branches with unreachable blocks so that it can possibly propagate the unreachability up to the former branch target. It previously only did this for branches whose sent values (as opposed to their conditions) were unreachable, except for BrOn, where it could erroneously optimize when the sent value was reachable and the descriptor was unreachable. This was incorrect because `replaceUntaken` assumed that the sent value was unreachable. Rather than fix the bug for BrOn, generalize `replaceUntaken` to work no matter whether the sent value or the other child exists or is the source of unreachability. --- src/ir/ReFinalize.cpp | 43 +++++++++---------- test/lit/passes/remove-unused-brs-desc.wast | 36 ++++++++++++++++ test/lit/passes/vacuum_all-features.wast | 6 +-- test/passes/fuzz_metrics_noprint.bin.txt | 5 +-- .../fuzz_metrics_passes_noprint.bin.txt | 8 ++-- test/passes/precompute_all-features.txt | 10 ++--- .../remove-unused-brs_enable-multivalue.txt | 30 +++++-------- ...d-brs_generate-stack-ir_print-stack-ir.txt | 24 +++++------ 8 files changed, 90 insertions(+), 72 deletions(-) diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 43dd53ab9d4..cbb5c40f4f6 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "ir/branch-utils.h" -#include "ir/find_all.h" #include "ir/utils.h" namespace wasm { @@ -60,7 +58,8 @@ void ReFinalize::visitLoop(Loop* curr) { curr->finalize(); } void ReFinalize::visitBreak(Break* curr) { curr->finalize(); auto valueType = getValueType(curr->value); - if (valueType == Type::unreachable) { + if (valueType == Type::unreachable || + getValueType(curr->condition) == Type::unreachable) { replaceUntaken(curr->value, curr->condition); } else { updateBreakValueType(curr->name, valueType); @@ -69,7 +68,8 @@ void ReFinalize::visitBreak(Break* curr) { void ReFinalize::visitSwitch(Switch* curr) { curr->finalize(); auto valueType = getValueType(curr->value); - if (valueType == Type::unreachable) { + if (valueType == Type::unreachable || + getValueType(curr->condition) == Type::unreachable) { replaceUntaken(curr->value, curr->condition); } else { for (auto target : curr->targets) { @@ -221,29 +221,26 @@ void ReFinalize::updateBreakValueType(Name name, Type type) { } } -// Replace an untaken branch/switch with an unreachable value. -// Another child may also exist and may or may not be unreachable. +// Replace a branch/switch that is untaken because it is unreachable with an +// unreachable non-branching expression. There is one or both of a value and +// condition/descriptor, at least one of which is unreachable. void ReFinalize::replaceUntaken(Expression* value, Expression* otherChild) { - assert(value->type == Type::unreachable); - auto* replacement = value; - if (otherChild) { - Builder builder(*getModule()); - // Even if we have - // (block - // (unreachable) - // (i32.const 1) - // ) - // we want the block type to be unreachable. That is valid as - // the value is unreachable, and necessary since the type of - // the condition did not have an impact before (the break/switch - // type was unreachable), and might not fit in. - if (otherChild->type.isConcrete()) { + assert((value && value->type == Type::unreachable) || + (otherChild && otherChild->type == Type::unreachable)); + Builder builder(*getModule()); + if (value && otherChild) { + if (value->type.isConcrete()) { + value = builder.makeDrop(value); + } else if (otherChild->type.isConcrete()) { otherChild = builder.makeDrop(otherChild); } - replacement = builder.makeSequence(value, otherChild); - assert(replacement->type.isBasic() && "Basic type expected"); + replaceCurrent(builder.makeBlock({value, otherChild}, Type::unreachable)); + } else if (value) { + replaceCurrent(value); + } else { + assert(otherChild); + replaceCurrent(otherChild); } - replaceCurrent(replacement); } } // namespace wasm diff --git a/test/lit/passes/remove-unused-brs-desc.wast b/test/lit/passes/remove-unused-brs-desc.wast index 94e50775424..cfc75dc1687 100644 --- a/test/lit/passes/remove-unused-brs-desc.wast +++ b/test/lit/passes/remove-unused-brs-desc.wast @@ -747,4 +747,40 @@ (ref.null none) ) ) + + ;; CHECK: (func $refinalize-untaken (type $16) (result anyref) + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $block0 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $super.desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $refinalize-untaken (result anyref) + (block $block (result anyref) + ;; 3) ReFinalize will make this unreachable BrOn a block. The ref.null + ;; below needs to be dropped for the block to be valid. + (br_on_cast_desc $block nullref (ref null $super) + (ref.null none) + ;; 2) ReFinalize will make this block unreachable because it is no + ;; longer a branch target. + (block $block (result (ref $super.desc)) + (drop + ;; 1) This branch will be optimized out, prompting ReFinalization. + (br_on_cast_fail $block (ref $super.desc) (ref $super.desc) + (struct.new_default $super.desc) + ) + ) + (unreachable) + ) + ) + ) + ) ) diff --git a/test/lit/passes/vacuum_all-features.wast b/test/lit/passes/vacuum_all-features.wast index 636814f3de3..e689b853668 100644 --- a/test/lit/passes/vacuum_all-features.wast +++ b/test/lit/passes/vacuum_all-features.wast @@ -982,10 +982,8 @@ ;; CHECK-NEXT: (block $label$0 ;; CHECK-NEXT: (loop $label$1 ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_if $label$0 - ;; CHECK-NEXT: (loop $label$9 - ;; CHECK-NEXT: (br $label$9) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label$9 + ;; CHECK-NEXT: (br $label$9) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index c23a9ac4eb7..0c204cff762 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -9,11 +9,11 @@ total [table-data] : 25 [tables] : 1 [tags] : 0 - [total] : 6800 + [total] : 6791 [vars] : 256 Binary : 454 Block : 1201 - Break : 196 + Break : 188 Call : 205 CallIndirect : 61 Const : 1131 @@ -30,6 +30,5 @@ total Return : 58 Select : 52 Store : 41 - Switch : 1 Unary : 451 Unreachable : 246 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index 934a25abe09..29d1cfcf2b5 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -9,15 +9,15 @@ total [table-data] : 28 [tables] : 1 [tags] : 0 - [total] : 9402 + [total] : 9390 [vars] : 189 Binary : 651 - Block : 1534 - Break : 332 + Block : 1536 + Break : 316 Call : 296 CallIndirect : 91 Const : 1666 - Drop : 64 + Drop : 66 GlobalGet : 650 GlobalSet : 582 If : 506 diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index c11e8332248..ee79a5c26f1 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -123,9 +123,7 @@ (func $refinalize-br-condition-unreachable (type $1) (block $label$1 (drop - (br_if $label$1 - (unreachable) - ) + (unreachable) ) ) ) @@ -133,8 +131,10 @@ (drop (block $label$1 (result i32) (drop - (br_if $label$1 - (i32.const 100) + (block + (drop + (i32.const 100) + ) (block $label$3 (unreachable) ) diff --git a/test/passes/remove-unused-brs_enable-multivalue.txt b/test/passes/remove-unused-brs_enable-multivalue.txt index 321b061e524..4a6384b221b 100644 --- a/test/passes/remove-unused-brs_enable-multivalue.txt +++ b/test/passes/remove-unused-brs_enable-multivalue.txt @@ -1154,9 +1154,7 @@ (block $label$8 (block $label$11 (block $label$14 - (br_if $label$8 - (br $label$8) - ) + (br $label$8) ) ) ) @@ -2199,13 +2197,9 @@ (block $label$2 (local.tee $0 (loop $label$5 - (br_if $label$5 - (block - (unreachable) - (drop - (i32.const 0) - ) - ) + (unreachable) + (drop + (i32.const 0) ) ) ) @@ -2433,18 +2427,16 @@ (else (drop (loop $label$3 (result i64) - (br_if $label$3 - (if - (i32.const 0) - (then - (block $label$4 - (unreachable) - ) - ) - (else + (if + (i32.const 0) + (then + (block $label$4 (unreachable) ) ) + (else + (unreachable) + ) ) (i64.const -9) ) diff --git a/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt b/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt index 2eb5aa7da4e..8d4847a976b 100644 --- a/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt +++ b/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt @@ -2,17 +2,15 @@ (type $0 (func (param i64))) (func $0 (param $var$0 i64) (block $label$1 - (br_if $label$1 - (block $label$2 - (loop $label$3 - (local.tee $var$0 - (block $label$4 - (unreachable) - ) + (block $label$2 + (loop $label$3 + (local.tee $var$0 + (block $label$4 + (unreachable) ) ) - (unreachable) ) + (unreachable) ) ) ) @@ -20,17 +18,15 @@ (module (type $0 (func (param i64))) (func $0 (param $var$0 i64) - block $label$1 - block $label$2 - loop $label$3 - block $label$4 - unreachable - end + block $label$2 + loop $label$3 + block $label$4 unreachable end unreachable end unreachable end + unreachable ) ) From 6c5480362ac01cd0eadd568cc7e5880bf3f5c024 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 22 Jul 2025 11:43:43 -0700 Subject: [PATCH 610/622] Fix skipping of nested/internal passes (#7741) When --skip-pass is used, it identifies the pass to skip by name. We did not update the name of nested/internal passes, so their name was empty, causing skipping to fail (such passes are wrapped by another struct, which had an empty name; to fix it, copy the name to the wrapper). As a drive-by, rename the existing skip-pass tests, which mentioned -O1 even though they use -O2, and the opt level isn't even worth mentioning. --- src/passes/pass-utils.h | 7 +- test/lit/passes/skip-pass-inlining.wast | 67 +++++++++++++++++++ .../passes/{O1_skip.wast => skip-pass.wast} | 0 .../{O1_skip_passes.wast => skip-passes.wast} | 0 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/skip-pass-inlining.wast rename test/lit/passes/{O1_skip.wast => skip-pass.wast} (100%) rename test/lit/passes/{O1_skip_passes.wast => skip-passes.wast} (100%) diff --git a/src/passes/pass-utils.h b/src/passes/pass-utils.h index 1db4f561479..12b33fb398f 100644 --- a/src/passes/pass-utils.h +++ b/src/passes/pass-utils.h @@ -35,8 +35,11 @@ struct FilteredPass : public Pass { return std::make_unique(pass->create(), relevantFuncs); } - FilteredPass(std::unique_ptr&& pass, const FuncSet& relevantFuncs) - : pass(std::move(pass)), relevantFuncs(relevantFuncs) {} + FilteredPass(std::unique_ptr&& pass_, const FuncSet& relevantFuncs) + : pass(std::move(pass_)), relevantFuncs(relevantFuncs) { + // Copy the pass's name, for debugging and for --skip-pass support. + name = pass->name; + } bool isFunctionParallel() override { assert(pass->isFunctionParallel()); diff --git a/test/lit/passes/skip-pass-inlining.wast b/test/lit/passes/skip-pass-inlining.wast new file mode 100644 index 00000000000..ab0f64a7944 --- /dev/null +++ b/test/lit/passes/skip-pass-inlining.wast @@ -0,0 +1,67 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: foreach %s %t wasm-opt --inlining-optimizing --optimize-level=3 --skip-pass=coalesce-locals -S -o - 2>&1 | filecheck %s + +;; We should skip coalese-locals when it is run as an internal sub-pass. Here +;; we inline and optimize afterwards, which normally runs the full set of +;; function passes, but skip pass skips it even there. + +(module + ;; CHECK: (import "a" "b" (func $log (param i32 i32))) + (import "a" "b" (func $log (param i32 i32))) + + (func $foo (param $p i32) + ;; The locals $x and $y can be coalesced into a single local, but as we do not + ;; run that pass, they will not be. Other minor optimizations will occur here, + ;; such as using a tee. + (local $x i32) + (local $y i32) + + (local.set $x + (i32.add + (local.get $p) + (i32.const 1) + ) + ) + (call $log + (local.get $x) + (local.get $x) + ) + + (local.set $y + (i32.add + (local.get $p) + (i32.const 1) + ) + ) + (call $log + (local.get $y) + (local.get $y) + ) + ) + + ;; CHECK: (func $call-foo (param $p i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (call $log + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-foo (export "call-foo") (param $p i32) + (call $foo + (local.get $p) + ) + ) +) diff --git a/test/lit/passes/O1_skip.wast b/test/lit/passes/skip-pass.wast similarity index 100% rename from test/lit/passes/O1_skip.wast rename to test/lit/passes/skip-pass.wast diff --git a/test/lit/passes/O1_skip_passes.wast b/test/lit/passes/skip-passes.wast similarity index 100% rename from test/lit/passes/O1_skip_passes.wast rename to test/lit/passes/skip-passes.wast From 95704773bd7c30c0591b406ad61a2e0cace104ed Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 22 Jul 2025 17:45:25 -0700 Subject: [PATCH 611/622] [Custom Descriptors] Fix desc casts in AbstractTypeRefining (#7742) When a described type is never allocated, AbstractTypeRefining will optimize it to the bottom heap type `none`. However, if that type is used as the target in any descriptor casts, subsequent refinalization will restore the original type as derived from the type of the descriptor operand. To avoid this reintroduction of optimized types, fix up affected descriptor casts to be non-descriptor casts before refinalizing. This is valid because we know that only null values can ever pass these casts, so the descriptors are never used. As a drive by, use a more efficient (and less verbose) method of collecting the public types in AbstractTypeRefining as well. --- src/passes/AbstractTypeRefining.cpp | 80 ++- .../passes/abstract-type-refining-desc.wast | 628 ++++++++++++++++++ 2 files changed, 700 insertions(+), 8 deletions(-) diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index c27485d1e43..0c78895f687 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -33,6 +33,9 @@ // must fail unless it allows null. // +#include + +#include "ir/localize.h" #include "ir/module-utils.h" #include "ir/subtypes.h" #include "ir/type-updating.h" @@ -112,14 +115,8 @@ struct AbstractTypeRefining : public Pass { // module, given closed world, but we'd also need to make sure that // we don't need to make any changes to public types that refer to // them. - auto heapTypes = ModuleUtils::collectHeapTypeInfo( - *module, - ModuleUtils::TypeInclusion::AllTypes, - ModuleUtils::VisibilityHandling::FindVisibility); - for (auto& [type, info] : heapTypes) { - if (info.visibility == ModuleUtils::Visibility::Public) { - createdTypes.insert(type); - } + for (auto type : ModuleUtils::getPublicHeapTypes(*module)) { + createdTypes.insert(type); } SubTypes subTypes(*module); @@ -243,6 +240,12 @@ struct AbstractTypeRefining : public Pass { TypeMapper::TypeUpdates mapping; + // Track whether we optimize any described types to bottom. If we do, then + // we could end up with descriptor casts to nullref, which need to be fixed + // up before ReFinalize reintroduces the cast result type that was supposed + // to be optimized out. + bool optimizedDescribedType = false; + for (auto type : subTypes.types) { if (!type.isStruct()) { // TODO: support arrays and funcs @@ -259,6 +262,9 @@ struct AbstractTypeRefining : public Pass { // We check this first as it is the most powerful change. if (createdTypesOrSubTypes.count(type) == 0) { mapping[type] = type.getBottom(); + if (type.getDescriptorType()) { + optimizedDescribedType = true; + } continue; } @@ -297,11 +303,69 @@ struct AbstractTypeRefining : public Pass { AbstractTypeRefiningTypeMapper(*module, mapping).map(); + if (optimizedDescribedType) { + // At this point we may have casts like this: + // + // (ref.cast_desc nullref + // (some struct...) + // (some desc...) + // ) + // + // ReFinalize would fix up the cast target to be the struct type described + // by the descriptor, but that struct type was supposed to have been + // optimized out. Optimize out the cast (which we know must either be a + // null check or unconditional trap) before ReFinalize can get to it. + fixupDescriptorCasts(*module); + } + // Refinalize to propagate the type changes we made. For example, a refined // cast may lead to a struct.get reading a more refined type using that // type. ReFinalize().run(getPassRunner(), module); } + + void fixupDescriptorCasts(Module& module) { + struct CastFixer : WalkerPass> { + bool isFunctionParallel() override { return true; } + std::unique_ptr create() override { + return std::make_unique(); + } + void visitRefCast(RefCast* curr) { + if (!curr->desc || !curr->type.isNull()) { + return; + } + Block* replacement = + ChildLocalizer(curr, getFunction(), *getModule(), getPassOptions()) + .getChildrenReplacement(); + // Reuse `curr` as the cast to nullref. Leave further optimization of + // the cast to OptimizeInstructions. + curr->desc = nullptr; + replacement->list.push_back(curr); + replacement->type = curr->type; + replaceCurrent(replacement); + } + void visitBrOn(BrOn* curr) { + if (curr->op != BrOnCastDesc && curr->op != BrOnCastDescFail) { + return; + } + if (!curr->castType.isNull()) { + return; + } + bool isFail = curr->op == BrOnCastDescFail; + Block* replacement = + ChildLocalizer(curr, getFunction(), *getModule(), getPassOptions()) + .getChildrenReplacement(); + // Reuse `curr` as a br_on_cast to nullref. Leave further optimization + // of the branch to RemoveUnusedBrs. + curr->desc = nullptr; + curr->op = isFail ? BrOnCastFail : BrOnCast; + replacement->list.push_back(curr); + replacement->type = curr->type; + replaceCurrent(replacement); + } + } fixer; + fixer.run(getPassRunner(), &module); + } }; } // anonymous namespace diff --git a/test/lit/passes/abstract-type-refining-desc.wast b/test/lit/passes/abstract-type-refining-desc.wast index 1264ea01ea7..c08845cccd7 100644 --- a/test/lit/passes/abstract-type-refining-desc.wast +++ b/test/lit/passes/abstract-type-refining-desc.wast @@ -72,3 +72,631 @@ ) ) ) + +(module + (rec + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $struct (descriptor $desc (struct))) + ;; NO_TNH: (rec + ;; NO_TNH-NEXT: (type $struct (descriptor $desc (struct))) + (type $struct (descriptor $desc (struct))) + ;; YESTNH: (type $desc (describes $struct (struct))) + ;; NO_TNH: (type $desc (describes $struct (struct))) + (type $desc (describes $struct (struct))) + ) + + ;; YESTNH: (type $2 (func (param anyref (ref null $desc)) (result nullref))) + + ;; YESTNH: (type $3 (func)) + + ;; YESTNH: (import "" "" (func $effect (type $3))) + ;; NO_TNH: (type $2 (func (param anyref (ref null $desc)) (result nullref))) + + ;; NO_TNH: (type $3 (func)) + + ;; NO_TNH: (import "" "" (func $effect (type $3))) + (import "" "" (func $effect)) + + ;; YESTNH: (global $desc (ref $desc) (struct.new_default $desc)) + ;; NO_TNH: (global $desc (ref $desc) (struct.new_default $desc)) + (global $desc (ref $desc) (struct.new $desc)) + + ;; YESTNH: (func $cast-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (ref.cast nullref + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $cast-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (ref.cast nullref + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $cast-nullable (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + ;; $struct will be optimized to nullref, but $desc will not. We must + ;; optimize out this cast because otherwise ReFinalization would make its + ;; result (ref $struct) again and it would not be valid for the optimized + ;; function result type. + (ref.cast_desc (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + + ;; YESTNH: (func $cast-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (local $2 anyref) + ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (local.set $2 + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (local.set $3 + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (ref.cast nullref + ;; YESTNH-NEXT: (local.get $2) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $cast-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 anyref) + ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (local.set $3 + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (ref.cast nullref + ;; NO_TNH-NEXT: (local.get $2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $cast-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + ;; Same, but with side effects we cannot drop. + (ref.cast_desc (ref null $struct) + (block (result anyref) + (call $effect) + (local.get $ref) + ) + (block (result (ref null $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + + ;; YESTNH: (func $cast-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (ref.cast (ref none) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $cast-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (ref.cast (ref none) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $cast-non-nullable (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + ;; Same, but now the cast does not admit null. + (ref.cast_desc (ref $struct) + (local.get $ref) + (local.get $desc) + ) + ) + + ;; YESTNH: (func $cast-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (local $2 anyref) + ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (local.set $2 + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (local.set $3 + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (ref.cast (ref none) + ;; YESTNH-NEXT: (local.get $2) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $cast-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 anyref) + ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (local.set $3 + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (ref.cast (ref none) + ;; NO_TNH-NEXT: (local.get $2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $cast-non-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + ;; Same, but with side effects we cannot drop. + (ref.cast_desc (ref $struct) + (block (result anyref) + (call $effect) + (local.get $ref) + ) + (block (result (ref null $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + + ;; YESTNH: (func $branch-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (block $block (result nullref) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block (result (ref any)) + ;; YESTNH-NEXT: (br_on_cast $block anyref nullref + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (block $block (result nullref) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block (result (ref any)) + ;; NO_TNH-NEXT: (br_on_cast $block anyref nullref + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $branch-nullable (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (block $block (result (ref null $struct)) + (drop + ;; Same, but with br_on_cast_desc. + (br_on_cast_desc $block anyref (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + (unreachable) + ) + ) + + ;; YESTNH: (func $branch-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (local $2 anyref) + ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (block $block (result nullref) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block (result (ref any)) + ;; YESTNH-NEXT: (local.set $2 + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (local.set $3 + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (br_on_cast $block anyref nullref + ;; YESTNH-NEXT: (local.get $2) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 anyref) + ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (block $block (result nullref) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block (result (ref any)) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (local.set $3 + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (br_on_cast $block anyref nullref + ;; NO_TNH-NEXT: (local.get $2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $branch-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (block $block (result (ref null $struct)) + (drop + ;; Same, but with effects we cannot drop. + (br_on_cast_desc $block anyref (ref null $struct) + (block (result anyref) + (call $effect) + (local.get $ref) + ) + (block (result (ref null $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + (unreachable) + ) + ) + + ;; YESTNH: (func $branch-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (block $block (result (ref none)) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (br_on_cast $block anyref (ref none) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (block $block (result (ref none)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (br_on_cast $block anyref (ref none) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $branch-non-nullable (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (block $block (result (ref null $struct)) + (drop + ;; Same, but now the cast does not admit nulls. + (br_on_cast_desc $block anyref (ref $struct) + (local.get $ref) + (local.get $desc) + ) + ) + (unreachable) + ) + ) + + ;; YESTNH: (func $branch-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (local $2 anyref) + ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (block $block (result (ref none)) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (local.set $2 + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (local.set $3 + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (br_on_cast $block anyref (ref none) + ;; YESTNH-NEXT: (local.get $2) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 anyref) + ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (block $block (result (ref none)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (local.set $3 + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (br_on_cast $block anyref (ref none) + ;; NO_TNH-NEXT: (local.get $2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $branch-non-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (block $block (result (ref null $struct)) + (drop + ;; Same, but with effects we cannot drop. + (br_on_cast_desc $block anyref (ref $struct) + (block (result anyref) + (call $effect) + (local.get $ref) + ) + (block (result (ref null $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + (unreachable) + ) + ) + + ;; YESTNH: (func $branch-fail-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block $block (result (ref any)) + ;; YESTNH-NEXT: (return + ;; YESTNH-NEXT: (block (result nullref) + ;; YESTNH-NEXT: (br_on_cast_fail $block anyref nullref + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-fail-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block $block (result (ref any)) + ;; NO_TNH-NEXT: (return + ;; NO_TNH-NEXT: (block (result nullref) + ;; NO_TNH-NEXT: (br_on_cast_fail $block anyref nullref + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + (func $branch-fail-nullable (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (drop + (block $block (result anyref) + (return + ;; Same, but with br_on_cast_desc_fail. + (br_on_cast_desc_fail $block anyref (ref null $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + ) + (unreachable) + ) + + ;; YESTNH: (func $branch-fail-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (local $2 anyref) + ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block $block (result (ref any)) + ;; YESTNH-NEXT: (return + ;; YESTNH-NEXT: (block (result nullref) + ;; YESTNH-NEXT: (local.set $2 + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (local.set $3 + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (br_on_cast_fail $block anyref nullref + ;; YESTNH-NEXT: (local.get $2) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-fail-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 anyref) + ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block $block (result (ref any)) + ;; NO_TNH-NEXT: (return + ;; NO_TNH-NEXT: (block (result nullref) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (local.set $3 + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (br_on_cast_fail $block anyref nullref + ;; NO_TNH-NEXT: (local.get $2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + (func $branch-fail-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (drop + (block $block (result anyref) + (return + ;; Same, but with effects. + (br_on_cast_desc_fail $block anyref (ref null $struct) + (block (result anyref) + (call $effect) + (local.get $ref) + ) + (block (result (ref null $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + ) + (unreachable) + ) + + ;; YESTNH: (func $branch-fail-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block $block (result anyref) + ;; YESTNH-NEXT: (return + ;; YESTNH-NEXT: (block (result (ref none)) + ;; YESTNH-NEXT: (br_on_cast_fail $block anyref (ref none) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-fail-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block $block (result anyref) + ;; NO_TNH-NEXT: (return + ;; NO_TNH-NEXT: (block (result (ref none)) + ;; NO_TNH-NEXT: (br_on_cast_fail $block anyref (ref none) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + (func $branch-fail-non-nullable (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (drop + (block $block (result anyref) + (return + ;; Same, but now without admitting null. + (br_on_cast_desc_fail $block anyref (ref $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + ) + (unreachable) + ) + + ;; YESTNH: (func $branch-fail-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH-NEXT: (local $2 anyref) + ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block $block (result anyref) + ;; YESTNH-NEXT: (return + ;; YESTNH-NEXT: (block (result (ref none)) + ;; YESTNH-NEXT: (local.set $2 + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (local.set $3 + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (br_on_cast_fail $block anyref (ref none) + ;; YESTNH-NEXT: (local.get $2) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-fail-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 anyref) + ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block $block (result anyref) + ;; NO_TNH-NEXT: (return + ;; NO_TNH-NEXT: (block (result (ref none)) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (local.set $3 + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (br_on_cast_fail $block anyref (ref none) + ;; NO_TNH-NEXT: (local.get $2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + (func $branch-fail-non-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (drop + (block $block (result anyref) + (return + ;; Same, but with effects. + (br_on_cast_desc_fail $block anyref (ref $struct) + (block (result anyref) + (call $effect) + (local.get $ref) + ) + (block (result (ref null $desc)) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + ) + (unreachable) + ) +) From bd1e6855e4797464ec60617a6978bac96a9ad26b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 23 Jul 2025 14:01:20 -0700 Subject: [PATCH 612/622] [Metadce] Report removed imports due to RemoveUnusedModuleElements (#7748) wasm-metadce does a graph analysis to find unreached things, and then cleans up using RemoveUnusedModuleElements. That pass become more powerful in #7728, which led to a situation where an import was removed from the wasm, but wasm-metadce did not report that it had removed it. This led to unneeded code in the JS (it kept sending that import, unnecessarily). This was a harmless minor waste of JS size, but it did cause a test error on Emscripten (#7747), as it parses that JS to check some things, and it found an import in JS without a use in wasm. To fix that, check if that pass removed imports, and report them. --- src/tools/wasm-metadce.cpp | 55 ++++++++++++++++++- .../metadce/remove-unused-module-elements.wat | 40 ++++++++++++++ .../remove-unused-module-elements.wat.json | 13 +++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 test/lit/metadce/remove-unused-module-elements.wat create mode 100644 test/lit/metadce/remove-unused-module-elements.wat.json diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index 3572cd5f201..b677e575abf 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -89,6 +89,12 @@ struct MetaDCEGraph { // import module.base => DCE name std::unordered_map importIdToDCENode; + // import DCE name => items in the wasm { kind, internal name } + // (a vector is needed here as an import from the outside may be imported + // multiple times inside the wasm, and we can only remove it from the + // outside if all wasm uses go away) + std::unordered_map> DCENodeToImports; + Module& wasm; MetaDCEGraph(Module& wasm) : wasm(wasm) {} @@ -112,9 +118,16 @@ struct MetaDCEGraph { ModuleUtils::iterModuleItems(wasm, [&](ModuleItemKind kind, Named* item) { if (auto* import = wasm.getImportOrNull(kind, item->name)) { auto id = getImportId(import->module, import->base); - if (importIdToDCENode.find(id) == importIdToDCENode.end()) { + auto iter = importIdToDCENode.find(id); + if (iter == importIdToDCENode.end()) { + // This is a new import, not mentioned in the graph we were given + // (i.e., this import was not referred to from outside the wasm). auto dceName = getName("importId", import->name.toString()); importIdToDCENode[id] = dceName; + DCENodeToImports[dceName].push_back({kind, item->name}); + } else { + // This is an existing import, mentioned in the outside graph. + DCENodeToImports[iter->second].push_back({kind, item->name}); } return; } @@ -319,6 +332,46 @@ struct MetaDCEGraph { // removing functions may alter the optimum order, as # of calls can change passRunner.add("reorder-functions"); passRunner.run(); + + // Standard optimizations might succeed in removing even more than our own + // analysis found. That is, we build a graph of connections between things + // and find which are not reached, but --remove-unused-module-elements can + // use detailed understanding of wasm semantics (like how call_indirect + // signatures work, traps-never-happen, etc.) which can lead to even more + // things vanishing. Anything it removes, we can remove from our graph. + // + // The only things of interest are imports: exports are not removed by that + // pass, but imports might no longer have any uses. To find imports that + // were removed, scan the nodes and see what is no longer in the module. + for (auto& [_, dceName] : importIdToDCENode) { + auto iter = DCENodeToImports.find(dceName); + if (iter == DCENodeToImports.end()) { + // This appears in the graph, but did not even begin in the wasm. That + // is, the outside was sending it to the wasm, but the wasm never + // imported it, which means the graph was not very optimized. Just + // ignore this. + continue; + } + + // If all uses of this import went away, we can remove it. + bool used = false; + for (auto [kind, internalName] : iter->second) { + // Only function imports are important here, as we do things like + // generate minification maps for them, etc., but we could add others as + // well. + // TODO: use something like iterImportable, abstracted over + // ExternalKind, to get*OrNull(), and to remove*(). + if (kind != ModuleItemKind::Function || + wasm.getFunctionOrNull(internalName)) { + used = true; + break; + } + } + if (!used) { + // This was removed from the wasm. Remove it from the graph. + reached.erase(dceName); + } + } } // Print out everything we found is not used, and so can be diff --git a/test/lit/metadce/remove-unused-module-elements.wat b/test/lit/metadce/remove-unused-module-elements.wat new file mode 100644 index 00000000000..7600a752b04 --- /dev/null +++ b/test/lit/metadce/remove-unused-module-elements.wat @@ -0,0 +1,40 @@ +;; RUN: wasm-metadce %s --graph-file %s.json -all -S -o - | filecheck %s + +;; A testcase where after metadce removes things from the graph, +;; remove-unused-module-elements (which is run internally) manages to remove an +;; additional import. We should report that import is removed as well. + +;; The export "used" is used, based on the graph file, while the other export +;; "unused" is not. Metadce itself can remove $unused. After that, +;; remove-unused-module-elements sees that no call_indirect exists that can +;; reach $A, even though it is in the table, and we can remove $A. Removing +;; $A then removes the import, which we should report. +(module + (type $A (func)) + + (import "module" "base" (func $import)) + + (table $t 60 60 funcref) + + (elem $elem (table $t) (i32.const 0) func $A) + + (func $used (export "used") + (drop + (i32.const 42) + ) + ) + + (func $unused (export "unused") + (call_indirect $t (type $A) + (i32.const -1) + ) + ) + + (func $A (type $A) + (call $import) + ) +) + +;; CHECK: unused: export$unused +;; CHECK: unused: importId$import + diff --git a/test/lit/metadce/remove-unused-module-elements.wat.json b/test/lit/metadce/remove-unused-module-elements.wat.json new file mode 100644 index 00000000000..3c8143457a5 --- /dev/null +++ b/test/lit/metadce/remove-unused-module-elements.wat.json @@ -0,0 +1,13 @@ +[ + { + "name": "root", + "reaches": [ + "used" + ], + "root": true + }, + { + "name": "used", + "export": "used" + } +] From 4844d90c6dfd4904753871e9246f7542ab2db121 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 23 Jul 2025 18:00:25 -0700 Subject: [PATCH 613/622] [Custom Descriptors] Ensure descriptor validity in Unsubtyping (#7743) We already partially handled the interaction between descriptors and subtyping by ensuring that B <: A implies B.desc <: A.desc if B.desc and A.desc both exist. However, there was a subtler situation we did not handle: A -> A.desc ^ ^ | B.desc | ^ C -> C.desc This subtyping violates the constraint that the descriptor of C's immediate supertype must be the immediate supertype of C's descriptor. To fix this, find the described type B to insert between C and A before establishing C.desc <: B.desc <: A.desc. --- src/passes/Unsubtyping.cpp | 38 ++++++++++++++ test/lit/passes/unsubtyping-desc.wast | 72 +++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index 17171ffc3b2..230fc76e45d 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -492,6 +492,7 @@ struct Unsubtyping : Pass { } if (HeapType::isSubType(*oldSuper, super)) { // sub <: oldSuper <: super + processDescribed(sub, *oldSuper, super); noteSubtype(*oldSuper, super); // We already handled sub <: oldSuper, so we're done. return; @@ -501,6 +502,7 @@ struct Unsubtyping : Pass { // super will already be in the same tree when we process them below, so // when we process casts we will know that we only need to process up to // oldSuper. + processDescribed(sub, super, *oldSuper); process(super, *oldSuper); } @@ -512,6 +514,42 @@ struct Unsubtyping : Pass { processCasts(sub, super, oldSuper); } + void processDescribed(HeapType sub, HeapType mid, HeapType super) { + // We are establishing sub <: mid <: super. If super describes the immediate + // supertype of the type sub describes, then once we insert mid between them + // we would have this: + // + // A -> super + // ^ ^ + // | mid + // | ^ + // C -> sub + // + // This violates the requirement that the descriptor of C's immediate + // supertype must be the immediate supertype of C's descriptor. To fix it, + // we have to find the type B that mid describes and insert it between A and + // C: + // + // A -> super + // ^ ^ + // B -> mid + // ^ ^ + // C -> sub + // + // We do this eagerly before we establish sub <: mid <: super so that if + // establishing that subtyping requires recursively establishing other + // subtypings, we can depend on the invariant that the described types are + // always set up correctly beforehand. + auto subDescribed = sub.getDescribedType(); + auto superDescribed = super.getDescribedType(); + if (subDescribed && superDescribed && + types.getSupertype(*subDescribed) == superDescribed) { + auto midDescribed = mid.getDescribedType(); + assert(midDescribed); + process(*subDescribed, *midDescribed); + } + } + void processDefinitions(HeapType sub, HeapType super) { if (super.isBasic()) { return; diff --git a/test/lit/passes/unsubtyping-desc.wast b/test/lit/passes/unsubtyping-desc.wast index b33c8ba6464..69c8d3f4d56 100644 --- a/test/lit/passes/unsubtyping-desc.wast +++ b/test/lit/passes/unsubtyping-desc.wast @@ -62,3 +62,75 @@ ;; CHECK: (global $A.desc (ref null $A.desc) (global.get $B.desc)) (global $A.desc (ref null $A.desc) (global.get $B.desc)) ) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct)))) + (type $top (sub (descriptor $top.desc (struct)))) + ;; CHECK: (type $mid (sub $top (descriptor $mid.desc (struct)))) + (type $mid (sub $top (descriptor $mid.desc (struct)))) + ;; CHECK: (type $bot (sub $mid (descriptor $bot.desc (struct)))) + (type $bot (sub $mid (descriptor $bot.desc (struct)))) + ;; CHECK: (type $top.desc (sub (describes $top (struct)))) + (type $top.desc (sub (describes $top (struct)))) + ;; CHECK: (type $mid.desc (sub $top.desc (describes $mid (struct)))) + (type $mid.desc (sub $top.desc (describes $mid (struct)))) + ;; CHECK: (type $bot.desc (sub $mid.desc (describes $bot (struct)))) + (type $bot.desc (sub $mid.desc (describes $bot (struct)))) + ) + + ;; top -> top.desc + ;; ^ + ;; |(2) mid -> mid.desc + ;; | ^ (1) + ;; bot -> bot.desc + ;; + ;; bot <: top implies bot.desc <: top.desc, but we already have + ;; bot.desc <: mid.desc, so that gives us bot.desc <: mid.desc <: top.desc. + ;; This is only valid if we also have bot <: mid <: top. + + ;; CHECK: (global $bot-mid-desc (ref null $mid.desc) (struct.new_default $bot.desc)) + (global $bot-mid-desc (ref null $mid.desc) (struct.new $bot.desc)) + ;; CHECK: (global $bot-top (ref null $top) (struct.new_default $bot + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $bot-top (ref null $top) (struct.new $bot (ref.null none))) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct)))) + (type $top (sub (descriptor $top.desc (struct)))) + ;; CHECK: (type $mid (sub $top (descriptor $mid.desc (struct)))) + (type $mid (sub $top (descriptor $mid.desc (struct)))) + ;; CHECK: (type $bot (sub $mid (descriptor $bot.desc (struct)))) + (type $bot (sub $mid (descriptor $bot.desc (struct)))) + ;; CHECK: (type $top.desc (sub (describes $top (struct)))) + (type $top.desc (sub (describes $top (struct)))) + ;; CHECK: (type $mid.desc (sub $top.desc (describes $mid (struct)))) + (type $mid.desc (sub $top.desc (describes $mid (struct)))) + ;; CHECK: (type $bot.desc (sub $mid.desc (describes $bot (struct)))) + (type $bot.desc (sub $mid.desc (describes $bot (struct)))) + ) + + ;; Same as above, but the order of the initial subtypings is reversed. + ;; + ;; top -> top.desc + ;; ^ + ;; |(1) mid -> mid.desc + ;; | ^ (2) + ;; bot -> bot.desc + ;; + ;; bot <: top implies bot.desc <: top.desc. When we add bot.desc <: mid.desc, + ;; that gives us bot.desc <: mid.desc <: top.desc. This is only valid if we + ;; also have bot <: mid <: top. + + ;; CHECK: (global $bot-top (ref null $top) (struct.new_default $bot + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $bot-top (ref null $top) (struct.new $bot (ref.null none))) + ;; CHECK: (global $bot-mid-desc (ref null $mid.desc) (struct.new_default $bot.desc)) + (global $bot-mid-desc (ref null $mid.desc) (struct.new $bot.desc)) +) From e3798b43741c56ad9c9cee13879911c8e7971103 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 24 Jul 2025 10:56:03 -0700 Subject: [PATCH 614/622] [NFC] Simplify abstract heap type LUB (#7749) There was some complexity left over from when we modeled `string` as being a subtype of `any` but not of `eq`. --- src/wasm/wasm-type.cpp | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index a57b9a1a94d..70d7925474e 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -403,11 +403,9 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, if (unsigned(a) > unsigned(b)) { std::swap(a, b); } - auto bUnshared = HeapType(b).getBasic(Unshared); HeapType lubUnshared; switch (HeapType(a).getBasic(Unshared)) { case HeapType::ext: - assert(bUnshared == HeapType::string); lubUnshared = HeapType::ext; break; case HeapType::func: @@ -418,36 +416,16 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, lubUnshared = HeapType::any; break; case HeapType::eq: - if (bUnshared == HeapType::i31 || bUnshared == HeapType::struct_ || - bUnshared == HeapType::array) { - lubUnshared = HeapType::eq; - } else { - lubUnshared = HeapType::any; - } - break; case HeapType::i31: - if (bUnshared == HeapType::struct_ || bUnshared == HeapType::array) { - lubUnshared = HeapType::eq; - } else { - lubUnshared = HeapType::any; - } - break; case HeapType::struct_: - if (bUnshared == HeapType::array) { - lubUnshared = HeapType::eq; - } else { - lubUnshared = HeapType::any; - } + lubUnshared = HeapType::eq; break; case HeapType::array: - lubUnshared = HeapType::any; - break; case HeapType::string: - // String has already been handled: we sorted before in a way that ensures - // the type the string is compared to is of a higher index, which means it - // is a bottom type (string is the last type that is not a bottom type), - // but we have handled the case of either a or b being a bottom type - // earlier already. + // As the last non-bottom types in their hierarchies, it should not be + // possible for `a` to be array or string. We know that `b` != `a` and + // that `b` is not bottom, but that `b` and `a` are in the same hierarchy, + // so there are no possible value for `b`. case HeapType::none: case HeapType::noext: case HeapType::nofunc: From d5e89184629df04ffb171e885f8aeb9cc37e786e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 24 Jul 2025 12:04:51 -0700 Subject: [PATCH 615/622] [Custom Descriptors] Handle nullable ref.cast_desc in Heap2Local (#7744) Heap2Local previously assumed that if an optimized allocation flowed into a ref.cast_desc as the descriptor operand, the cast must fail because the same allocation is known not to be the the descriptor of the cast value. This did not account for the case where the cast value is null and the cast allows nulls, though. Fix the output in that case to cast the value to null and avoid trapping where the original descriptor cast would not have trapped. --- src/passes/Heap2Local.cpp | 54 +++-- test/lit/passes/heap2local-desc.wast | 290 ++++++++++++++++++++++++++- 2 files changed, 325 insertions(+), 19 deletions(-) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index af94877080a..afeb3c2b293 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -154,7 +154,6 @@ #include "ir/bits.h" #include "ir/branch-utils.h" #include "ir/eh-utils.h" -#include "ir/find_all.h" #include "ir/local-graph.h" #include "ir/parents.h" #include "ir/properties.h" @@ -402,7 +401,11 @@ struct EscapeAnalyzer { fullyConsumes = true; } } else { - assert(curr->desc == child); + // Either the child is the descriptor, in which case we consume it, or + // we have already optimized this ref.cast_desc for an allocation that + // flowed through as its `ref`. In the latter case the current child + // must have originally been the descriptor, so we can still say it's + // fully consumed, but we cannot assert that curr->desc == child. fullyConsumes = true; } } @@ -850,19 +853,42 @@ struct Struct2Local : PostWalker { } if (curr->desc) { - // If we are doing a ref.cast_desc of the optimized allocation, but we - // know it does not have a descriptor, then we know the cast must fail. We - // also know the cast must fail if the optimized allocation flows in as - // the descriptor, since it cannot possibly have been used in the - // allocation of the cast value without having been considered to escape. - if (!allocation->desc || analyzer.getInteraction(curr->desc) == - ParentChildInteraction::Flows) { - // The allocation does not have a descriptor, so there is no way for the - // cast to succeed. - replaceCurrent(builder.blockify(builder.makeDrop(curr->ref), - builder.makeDrop(curr->desc), - builder.makeUnreachable())); + // If we are doing a ref.cast_desc of the optimized allocation, but the + // allocation does not have a descriptor, then we know the cast must fail. + // We also know the cast must fail (except for nulls it might let through) + // if the optimized allocation flows in as the descriptor, since it cannot + // possibly have been used in the allocation of the cast value without + // having been considered to escape. + bool allocIsCastDesc = + analyzer.getInteraction(curr->desc) == ParentChildInteraction::Flows; + if (!allocation->desc || allocIsCastDesc) { + // It would seem convenient to use ChildLocalizer here, but we cannot. + // ChildLocalizer would create a local.set for a desc operand with + // side effects, but that local.set would not be reflected in the parent + // map, so it would not be updated if the allocation flowing through + // that desc operand were later optimized. + if (allocIsCastDesc && curr->type.isNullable()) { + // There might be a null value to let through. Reuse curr as a cast to + // null. Use a scratch local to move the reference value past the desc + // value. + Index scratch = builder.addVar(func, curr->ref->type); + replaceCurrent( + builder.blockify(builder.makeLocalSet(scratch, curr->ref), + builder.makeDrop(curr->desc), + curr)); + curr->desc = nullptr; + curr->type = curr->type.with(curr->type.getHeapType().getBottom()); + curr->ref = builder.makeLocalGet(scratch, curr->ref->type); + } else { + // Either the cast does not allow nulls or we know the value isn't + // null anyway, so the cast certainly fails. + replaceCurrent(builder.blockify(builder.makeDrop(curr->ref), + builder.makeDrop(curr->desc), + builder.makeUnreachable())); + } } else { + assert(analyzer.getInteraction(curr->ref) == + ParentChildInteraction::Flows); // The cast succeeds iff the optimized allocation's descriptor is the // same as the given descriptor and traps otherwise. auto type = allocation->desc->type; diff --git a/test/lit/passes/heap2local-desc.wast b/test/lit/passes/heap2local-desc.wast index caad93b0680..ba65bc900d5 100644 --- a/test/lit/passes/heap2local-desc.wast +++ b/test/lit/passes/heap2local-desc.wast @@ -36,12 +36,19 @@ ;; CHECK: (type $11 (func (param (ref null (exact $super.desc))))) - ;; CHECK: (type $12 (func (result (ref null $super.desc)))) + ;; CHECK: (type $12 (func (param (ref null (exact $super))))) - ;; CHECK: (type $13 (func (param (ref null (exact $super))))) + ;; CHECK: (type $13 (func (result (ref null $super.desc)))) ;; CHECK: (type $14 (func (param (ref null (exact $chain-descriptor))))) + ;; CHECK: (type $15 (func (result (ref (exact $super))))) + + ;; CHECK: (type $16 (func (result (ref (exact $super.desc))))) + + ;; CHECK: (import "" "" (func $effect (type $10))) + (import "" "" (func $effect)) + ;; CHECK: (global $desc (ref null (exact $descriptor)) (ref.null none)) (global $desc (ref null (exact $descriptor)) (ref.null none)) @@ -175,7 +182,7 @@ ) ) - ;; CHECK: (func $get-desc (type $12) (result (ref null $super.desc)) + ;; CHECK: (func $get-desc (type $13) (result (ref null $super.desc)) ;; CHECK-NEXT: (local $0 (ref (exact $super.desc))) ;; CHECK-NEXT: (local $1 (ref (exact $super.desc))) ;; CHECK-NEXT: (drop @@ -199,7 +206,7 @@ ) ) - ;; CHECK: (func $get-desc-refinalize (type $12) (result (ref null $super.desc)) + ;; CHECK: (func $get-desc-refinalize (type $13) (result (ref null $super.desc)) ;; CHECK-NEXT: (local $0 (ref (exact $sub.desc))) ;; CHECK-NEXT: (local $1 (ref (exact $sub.desc))) ;; CHECK-NEXT: (block (result (ref (exact $sub.desc))) @@ -373,7 +380,7 @@ ) ) - ;; CHECK: (func $cast-desc-fail-param (type $13) (param $ref (ref null (exact $super))) + ;; CHECK: (func $cast-desc-fail-param (type $12) (param $ref (ref null (exact $super))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop @@ -399,6 +406,111 @@ ) ) + ;; CHECK: (func $cast-desc-fail-param-effect (type $12) (param $ref (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null (exact $super))) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-fail-param-effect (param $ref (ref null (exact $super))) + ;; Same, but with effects we cannot drop. + (drop + (ref.cast_desc (ref (exact $super)) + (block (result (ref null (exact $super))) + (call $effect) + (local.get $ref) + ) + (block (result (ref (exact $super.desc))) + (call $effect) + (struct.new $super.desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-desc-fail-param-nullable (type $12) (param $ref (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-fail-param-nullable (param $ref (ref null (exact $super))) + ;; Now the cast admits nulls. + (drop + (ref.cast_desc (ref null (exact $super)) + (local.get $ref) + (struct.new $super.desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-fail-param-nullable-effect (type $12) (param $ref (ref null (exact $super))) + ;; CHECK-NEXT: (local $1 (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block (result (ref null (exact $super))) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-fail-param-nullable-effect (param $ref (ref null (exact $super))) + ;; Now the cast admits nulls and there are effects we cannot remove. + (drop + (ref.cast_desc (ref null (exact $super)) + (block (result (ref null (exact $super))) + (call $effect) + (local.get $ref) + ) + (block (result (ref (exact $super.desc))) + (call $effect) + (struct.new $super.desc) + ) + ) + ) + ) + ;; CHECK: (func $cast-no-desc (type $11) (param $desc (ref null (exact $super.desc))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -424,6 +536,107 @@ ) ) + ;; CHECK: (func $cast-no-desc-effect (type $11) (param $desc (ref null (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null (exact $super.desc))) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-no-desc-effect (param $desc (ref null (exact $super.desc))) + ;; Same, but with effects we cannot drop. + (drop + (ref.cast_desc (ref (exact $super)) + (block (result (ref (exact $no-desc))) + (call $effect) + (struct.new $no-desc) + ) + (block (result (ref null (exact $super.desc))) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + + ;; CHECK: (func $cast-no-desc-nullable (type $11) (param $desc (ref null (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-no-desc-nullable (param $desc (ref null (exact $super.desc))) + ;; The allocation does not have a descriptor, so we know the cast must fail. + ;; Although the cast admits nulls, we know we don't have a null here, so we + ;; don't need to preserve a null cast. + (drop + (ref.cast_desc (ref null (exact $super)) + (struct.new $no-desc) + (local.get $desc) + ) + ) + ) + + ;; CHECK: (func $cast-no-desc-nullable-effect (type $11) (param $desc (ref null (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null (exact $super.desc))) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-no-desc-nullable-effect (param $desc (ref null (exact $super.desc))) + ;; Same, but with effects we cannot drop. + (drop + (ref.cast_desc (ref null (exact $super)) + (block (result (ref (exact $no-desc))) + (call $effect) + (struct.new $no-desc) + ) + (block (result (ref null (exact $super.desc))) + (call $effect) + (local.get $desc) + ) + ) + ) + ) + ;; CHECK: (func $cast-desc-and-ref (type $14) (param $desc (ref null (exact $chain-descriptor))) ;; CHECK-NEXT: (local $middle (ref null (exact $chain-middle))) ;; CHECK-NEXT: (local $2 (ref null (exact $chain-descriptor))) @@ -471,4 +684,71 @@ ) ) ) + + ;; CHECK: (func $cast-desc-stale-parent (type $15) (result (ref (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $super.desc)) + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $cast-desc-stale-parent (result (ref (exact $super))) + (ref.cast_desc (ref (exact $super)) + ;; We will optimize this allocation first, causing the parent + ;; ref.cast_desc to be optimized out. The parent map will no longer be up + ;; to date when we optimize the second allocation, but we should sill be + ;; able to optimize successfully without crashing. + (struct.new_default $no-desc) + (block (result (ref (exact $super.desc))) + (call $effect) + (struct.new_default $super.desc) + ) + ) + ) + + ;; CHECK: (func $cast-desc-stale-parent-escape (type $16) (result (ref (exact $super.desc))) + ;; CHECK-NEXT: (local $desc (ref null (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-desc-stale-parent-escape (result (ref (exact $super.desc))) + (local $desc (ref (exact $super.desc))) + (drop + (ref.cast_desc (ref (exact $super)) + ;; Same as above, but now the second alloocation escapes. We should still + ;; optimize the first allocation and the cast, and we should still not + ;; crash. + (struct.new_default $no-desc) + (local.tee $desc + (struct.new_default $super.desc) + ) + ) + ) + (local.get $desc) + ) ) From fa18bdd20b114babdbad074cd7f5623d23c552e7 Mon Sep 17 00:00:00 2001 From: mason-lgtm <139171969+mason-lgtm@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:23:51 -0700 Subject: [PATCH 616/622] Update J2CLItableMerging to consider custom descriptors (#7729) Update J2CLItableMerging to consider types whose vtables are custom descriptors --- src/passes/J2CLItableMerging.cpp | 228 +++++++++++++++------ test/lit/passes/j2cl-merge-itables.wast | 259 +++++++++++++++++++++++- 2 files changed, 422 insertions(+), 65 deletions(-) diff --git a/src/passes/J2CLItableMerging.cpp b/src/passes/J2CLItableMerging.cpp index be8da22cefa..03f44fcdc53 100644 --- a/src/passes/J2CLItableMerging.cpp +++ b/src/passes/J2CLItableMerging.cpp @@ -26,18 +26,15 @@ // `Foo[vtable] = { i1, i2, ...., m1, m2, m3, ... }`, and fixes all accesses // and initializations accordingly. +#include +#include #include -#include -#include "ir/effects.h" -#include "ir/localize.h" -#include "ir/ordering.h" -#include "ir/struct-utils.h" -#include "ir/subtypes.h" #include "ir/type-updating.h" -#include "ir/utils.h" #include "pass.h" +#include "support/utilities.h" #include "wasm-builder.h" +#include "wasm-traversal.h" #include "wasm-type.h" #include "wasm.h" @@ -53,6 +50,11 @@ struct StructInfo { }; struct J2CLItableMerging : public Pass { + // Number of entries at the start of the descriptor that should not change + // index. If the vtable is a custom descriptor, itable fields are inserted at + // index 1. Index 0 is preserved for a possible JS prototype. + static const Index kPreservedDescriptorFields = 1; + // Keep track of all the structInfos so that they will be automatically // released after the pass is done. std::list structInfos; @@ -97,25 +99,48 @@ struct J2CLItableMerging : public Pass { // Collects all structs corresponding to Java classes, their vtables and // their itables. This is very tied to the way j2cl emits these constructs. void collectVtableAndItableTypes(Module& wasm) { + auto hasField = + [](TypeNames& typeNameInfo, int index, std::string_view name) { + auto it = typeNameInfo.fieldNames.find(index); + return it != typeNameInfo.fieldNames.end() && it->second.equals(name); + }; + // 1. Collect all structs that correspond that a Java type. for (auto [heapType, typeNameInfo] : wasm.typeNames) { - if (!heapType.isStruct()) { continue; } - auto type = heapType.getStruct(); - if (typeNameInfo.fieldNames.empty() || - !typeNameInfo.fieldNames[0].equals("vtable")) { - continue; - } - if (typeNameInfo.fieldNames.size() < 1 || - !typeNameInfo.fieldNames[1].equals("itable")) { - continue; - } + // The vtable may either be the first field or the custom descriptor. + HeapType vtabletype; + HeapType itabletype; + auto& type = heapType.getStruct(); + if (auto descriptor = heapType.getDescriptorType()) { + if (!hasField(typeNameInfo, 0, "itable")) { + continue; + } + + vtabletype = *descriptor; + // If the vtable is a descriptor, we enforce that it has at least 1 + // field for the possible JS prototype and simply assume this + // downstream. In practice, this is necessary anyway to allow vtables to + // subtype each other. + if (vtabletype.getStruct().fields.size() < kPreservedDescriptorFields) { + Fatal() << "--merge-j2cl-itables needs to be the first pass to run " + << "on j2cl output. (descriptor has fewer than expected " + << "fields)"; + } - auto vtabletype = type.fields[0].type.getHeapType(); - auto itabletype = type.fields[1].type.getHeapType(); + itabletype = type.fields[0].type.getHeapType(); + } else { + if (!hasField(typeNameInfo, 0, "vtable") || + !hasField(typeNameInfo, 1, "itable")) { + continue; + } + + vtabletype = type.fields[0].type.getHeapType(); + itabletype = type.fields[1].type.getHeapType(); + } auto structItableSize = itabletype.getStruct().fields.size(); @@ -170,33 +195,32 @@ struct J2CLItableMerging : public Pass { } void visitStructGet(StructGet* curr) { - if (curr->ref->type == Type::unreachable) { + auto* structInfo = getStructInfoByVtableType(curr->ref->type); + if (!structInfo) { return; } - if (!parent.structInfoByVtableType.count( - curr->ref->type.getHeapType())) { - return; - } // This is a struct.get on the vtable. // It is ok to just change the index since the field has moved but // the type is the same. - curr->index += parent.itableSize; + if (structInfo->javaClass.getDescriptorType()) { + if (curr->index >= kPreservedDescriptorFields) { + curr->index += parent.itableSize; + } + } else { + curr->index += parent.itableSize; + } } void visitStructNew(StructNew* curr) { - if (curr->type == Type::unreachable) { + auto* structInfo = getStructInfoByVtableType(curr->type); + if (!structInfo) { return; } - auto it = parent.structInfoByVtableType.find(curr->type.getHeapType()); - if (it == parent.structInfoByVtableType.end()) { - return; - } // The struct.new is for a vtable type and structInfo has the // information relating the struct types for the Java class, its vtable // and its itable. - auto structInfo = it->second; // Get the global that holds the corresponding itable instance. auto* itableGlobal = parent.tableGlobalsByType[structInfo->itable]; @@ -221,13 +245,18 @@ struct J2CLItableMerging : public Pass { } auto& itableFieldInitializers = itableStructNew->operands; + size_t insertIndex = + structInfo->javaClass.getDescriptorType().has_value() + ? kPreservedDescriptorFields + : 0; + // Add the initialization for the itable fields. for (Index i = parent.itableSize; i > 0; i--) { if (itableFieldInitializers.size() >= i) { // The itable was initialized with a struct.new, copy the // initialization values. curr->operands.insertAt( - 0, + insertIndex, ExpressionManipulator::copy(itableFieldInitializers[i - 1], *getModule())); } else { @@ -235,7 +264,7 @@ struct J2CLItableMerging : public Pass { // null values to initialize the itable fields. Builder builder(*getModule()); curr->operands.insertAt( - 0, + insertIndex, builder.makeRefNull(itableStructNew->type.getHeapType() .getStruct() .fields[i - 1] @@ -243,6 +272,17 @@ struct J2CLItableMerging : public Pass { } } } + + StructInfo* getStructInfoByVtableType(Type type) { + if (type == Type::unreachable) { + return nullptr; + } + if (auto it = parent.structInfoByVtableType.find(type.getHeapType()); + it != parent.structInfoByVtableType.end()) { + return it->second; + } + return nullptr; + } }; Reindexer reindexer(*this); @@ -265,17 +305,60 @@ struct J2CLItableMerging : public Pass { } void visitStructGet(StructGet* curr) { - if (curr->ref->type == Type::unreachable) { + // Determine if the struct.get is to get a field from the itable or the + // to get the itable itself. + + if (auto* structInfo = getStructInfoByItableType(curr->ref->type)) { + // This is a struct.get that returns an itable field. + updateGetItableField(curr, structInfo->javaClass); return; } - if (!curr->type.isStruct() || - !parent.structInfoByITableType.count(curr->type.getHeapType())) { + if (auto* structInfo = getStructInfoByItableType(curr->type)) { + // This is a struct.get that returns an itable type. + updateGetItable(curr, structInfo->javaClass); return; } + } - // This is a struct.get that returns an itable type; - // Change to return the corresponding vtable type. + StructInfo* getStructInfoByItableType(Type type) { + if (type == Type::unreachable || !type.isStruct()) { + return nullptr; + } + if (auto it = parent.structInfoByITableType.find(type.getHeapType()); + it != parent.structInfoByITableType.end()) { + return it->second; + } + return nullptr; + } + + void updateGetItableField(StructGet* curr, HeapType javaClass) { + if (!javaClass.getDescriptorType()) { + return; + } + + curr->index += kPreservedDescriptorFields; + if (auto childGet = curr->ref->dynCast()) { + // The reference is another struct.get. It is getting the itable for + // the type. + // Replace it with a ref.get_desc for the vtable, which is the + // descriptor. + Builder builder(*getModule()); + curr->ref = builder.makeRefGetDesc(childGet->ref); + return; + } + + // We expect the reference to be another struct.get. + Fatal() << "--merge-j2cl-itables needs to be the first pass to run " + << "on j2cl output. (itable getter not found) "; + } + + void updateGetItable(StructGet* curr, HeapType javaClass) { + if (javaClass.getDescriptorType()) { + return; + } + + // Change to return the corresponding vtable type (field 0). Builder builder(*getModule()); replaceCurrent(builder.makeStructGet( 0, @@ -304,32 +387,51 @@ struct J2CLItableMerging : public Pass { : GlobalTypeRewriter(wasm), parent(parent) {} void modifyStruct(HeapType oldStructType, Struct& struct_) override { - if (parent.structInfoByVtableType.count(oldStructType)) { - auto& newFields = struct_.fields; - - auto structInfo = parent.structInfoByVtableType[oldStructType]; - // Add the itable fields to the beginning of the vtable. - auto it = structInfo->itable.getStruct().fields.rbegin(); - while (it != structInfo->itable.getStruct().fields.rend()) { - newFields.insert(newFields.begin(), *it++); - newFields[0].type = getTempType(newFields[0].type); - } + auto structInfoIt = parent.structInfoByVtableType.find(oldStructType); + if (structInfoIt == parent.structInfoByVtableType.end()) { + return; + } + + auto& newFields = struct_.fields; - // Update field names as well. The Type Rewriter cannot do this for - // us, as it does not know which old fields map to which new ones - // (it just keeps the names in sequence). - auto& nameInfo = wasm.typeNames[oldStructType]; - - // Make a copy of the old ones before clearing them. - auto oldFieldNames = nameInfo.fieldNames; - - // Clear the old names and write the new ones. - nameInfo.fieldNames.clear(); - // Only need to preserve the field names for the vtable fields; the - // itable fields do not have names (in the original .wat file they - // are accessed by index). - for (Index i = 0; i < oldFieldNames.size(); i++) { - nameInfo.fieldNames[i + parent.itableSize] = oldFieldNames[i]; + auto* structInfo = structInfoIt->second; + + Index insertIndex = + structInfo->javaClass.getDescriptorType().has_value() + ? kPreservedDescriptorFields + : 0; + + // Add the itable fields to the beginning of the vtable. + auto& itableFields = structInfo->itable.getStruct().fields; + newFields.insert(newFields.begin() + insertIndex, + itableFields.begin(), + itableFields.end()); + for (Index i = 0; i < parent.itableSize; i++) { + newFields[insertIndex + i].type = + getTempType(newFields[insertIndex + i].type); + } + + // Update field names as well. The Type Rewriter cannot do this for + // us, as it does not know which old fields map to which new ones + // (it just keeps the names in sequence). + auto& nameInfo = wasm.typeNames[oldStructType]; + + // Make a copy of the old ones before clearing them. + auto oldFieldNames = nameInfo.fieldNames; + + // Clear the old names and write the new ones. + nameInfo.fieldNames.clear(); + // Only need to preserve the field names for the vtable fields; the + // itable fields do not have names (in the original .wat file they + // are accessed by index). + for (Index i = 0; i < insertIndex; i++) { + if (auto name = oldFieldNames[i]) { + nameInfo.fieldNames[i] = name; + } + } + for (Index i = insertIndex; i < oldFieldNames.size(); i++) { + if (auto name = oldFieldNames[i]) { + nameInfo.fieldNames[i + parent.itableSize] = name; } } } diff --git a/test/lit/passes/j2cl-merge-itables.wast b/test/lit/passes/j2cl-merge-itables.wast index 8506724fd1a..2765f873c32 100644 --- a/test/lit/passes/j2cl-merge-itables.wast +++ b/test/lit/passes/j2cl-merge-itables.wast @@ -100,7 +100,7 @@ (global.get $SubObject.vtable) (global.get $SubObject.itable))) (drop - ;; The access to vtable field 0 is offset but the itable size and + ;; The access to vtable field 0 is offset by the itable size and ;; will be an access to field 1. (struct.get $SubObject.vtable 0 (struct.get $SubObject $vtable @@ -214,7 +214,7 @@ (global.get $SubObject.vtable) (global.get $SubObject.itable))) (drop - ;; The access to vtable field 0 is offset but the itable size and + ;; The access to vtable field 0 is offset by the itable size and ;; will be an access to field 1. (struct.get $SubObject.vtable 0 (struct.get $SubObject $vtable @@ -227,3 +227,258 @@ (local.get $o)))) ) ) + +;; Custom descriptors - Shared itable instance. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $Object (sub (descriptor $Object.vtable (struct (field $itable (ref $Object.itable)))))) + (type $Object (sub (descriptor $Object.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct (field $itable (ref $Object.itable)))))) + (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $function (func)) + (type $function (func)) + + ;; The $Object.itable field (a structref) will be added as a field after + ;; the first field of this vtable. + ;; CHECK: (type $Object.vtable (sub (describes $Object (struct (field externref) (field structref))))) + (type $Object.vtable (sub (describes $Object (struct + (field externref))))) + + ;; The $Object.itable field (a structref) will be added as a field after + ;; the first field of this vtable. + ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct (field externref) (field structref) (field (ref $function)))))) + (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct + (field externref) + (field (ref $function)))))) + + ;; CHECK: (type $Object.itable (struct (field structref))) + (type $Object.itable (struct + (field (ref null struct)))) + ) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) + (global $Object.itable (ref $Object.itable) + (struct.new_default $Object.itable)) + + ;; CHECK: (global $SubObject.itable (ref $Object.itable) (global.get $Object.itable)) + (global $SubObject.itable (ref $Object.itable) + (global.get $Object.itable)) ;; uses shared empty itable instance. + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $SubObject.vtable (ref (exact $SubObject.vtable)) (struct.new $SubObject.vtable + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.func $SubObject.f) + ;; CHECK-NEXT: )) + (global $SubObject.vtable (ref (exact $SubObject.vtable)) + (struct.new $SubObject.vtable (ref.null extern) (ref.func $SubObject.f))) + + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $Object.vtable (ref (exact $Object.vtable)) (struct.new $Object.vtable + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $Object.vtable (ref (exact $Object.vtable)) + (struct.new $Object.vtable (ref.null extern))) + + ;; CHECK: (func $SubObject.f (type $function) + ;; CHECK-NEXT: ) + (func $SubObject.f + (type $function) + ) + + ;; CHECK: (func $usages (type $6) + ;; CHECK-NEXT: (local $o (ref null $SubObject)) + ;; CHECK-NEXT: (local.set $o + ;; CHECK-NEXT: (struct.new $SubObject + ;; CHECK-NEXT: (global.get $SubObject.itable) + ;; CHECK-NEXT: (global.get $SubObject.vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 2 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $usages + (local $o (ref null $SubObject)) + (local.set $o + (struct.new $SubObject + (global.get $SubObject.itable) + (global.get $SubObject.vtable))) + (drop + ;; The access to vtable field 0 is NOT offset and will remain an + ;; access to field 0. + (struct.get $SubObject.vtable 0 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to vtable field 1 is offset by the itable size and + ;; will be an access to field 2. + (struct.get $SubObject.vtable 1 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to itable field 0 will be rerouted to be an access to + ;; vtable field 1. + (struct.get $Object.itable 0 + (struct.get $SubObject $itable + (local.get $o)))) + ) +) + +;; Custom descriptors - Each type has its own itable. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $Object (sub (descriptor $Object.vtable (struct (field $itable (ref $Object.itable)))))) + (type $Object (sub (descriptor $Object.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct (field $itable (ref $SubObject.itable)))))) + (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct + (field $itable (ref $SubObject.itable)))))) + + ;; CHECK: (type $function (func)) + (type $function (func)) + + ;; CHECK: (type $Object.itable (sub (struct (field structref)))) + (type $Object.itable (sub (struct + (field (ref null struct))))) + + ;; CHECK: (type $SubObject.itable (sub $Object.itable (struct (field structref)))) + (type $SubObject.itable (sub $Object.itable + (struct (field (ref null struct))))) + + ;; The $Object.itable field (a structref) will be added as a field after + ;; the first field of this vtable. + ;; CHECK: (type $Object.vtable (sub (describes $Object (struct (field externref) (field structref))))) + (type $Object.vtable (sub (describes $Object (struct + (field externref))))) + + ;; The $SubObject.itable field (a structref) will be added as a field after + ;; the first field of this vtable. + ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct (field externref) (field structref) (field (ref $function)))))) + (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct + (field externref) + (field (ref $function)))))) + ) + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (type $7 (func)) + + ;; CHECK: (global $SubObject.vtable (ref (exact $SubObject.vtable)) (struct.new $SubObject.vtable + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.func $SubObject.f) + ;; CHECK-NEXT: )) + (global $SubObject.vtable (ref (exact $SubObject.vtable)) + (struct.new $SubObject.vtable (ref.null extern) (ref.func $SubObject.f))) + + ;; CHECK: (global $SubObject.itable (ref $SubObject.itable) (struct.new_default $SubObject.itable)) + (global $SubObject.itable (ref $SubObject.itable) + (struct.new_default $SubObject.itable)) + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $Object.vtable (ref (exact $Object.vtable)) (struct.new $Object.vtable + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $Object.vtable (ref (exact $Object.vtable)) + (struct.new $Object.vtable (ref.null extern))) + + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) + (global $Object.itable (ref $Object.itable) + (struct.new_default $Object.itable)) + + ;; CHECK: (func $SubObject.f (type $function) + ;; CHECK-NEXT: ) + (func $SubObject.f + (type $function) + ) + + ;; CHECK: (func $usages (type $7) + ;; CHECK-NEXT: (local $o (ref null $SubObject)) + ;; CHECK-NEXT: (local.set $o + ;; CHECK-NEXT: (struct.new $SubObject + ;; CHECK-NEXT: (global.get $SubObject.itable) + ;; CHECK-NEXT: (global.get $SubObject.vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 2 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $usages + (local $o (ref null $SubObject)) + (local.set $o + (struct.new $SubObject + (global.get $SubObject.itable) + (global.get $SubObject.vtable))) + (drop + ;; The access to vtable field 0 is NOT offset and will remain an + ;; access to field 0. + (struct.get $SubObject.vtable 0 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to vtable field 1 is offset by the itable size and + ;; will be an access to field 2. + (struct.get $SubObject.vtable 1 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to itable field 0 will be rerouted to be an access to + ;; vtable field 1. + (struct.get $Object.itable 0 + (struct.get $SubObject $itable + (local.get $o)))) + ) +) From 07860ffbdb514f6e27211b3720a63243d8cc12c4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Jul 2025 16:55:37 -0700 Subject: [PATCH 617/622] [NFC] Add comments to RemoveUnusedModuleElements + renamings (#7750) Followup to recent investigation, summarizing the reasons for the current separation of classes in that pass. Also rename methods for clarity. --- src/passes/RemoveUnusedModuleElements.cpp | 74 +++++++++++++---------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index c9b7a4474e0..52e9c6d3ddc 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -66,28 +66,38 @@ using ModuleElement = std::pair; // Information from an indirect call: the name of the table, and the heap type. using IndirectCall = std::pair; -// Visit or walk an expression to find what things are referenced. -struct ReferenceFinder - : public PostWalker> { - // Our findings are placed in these data structures, which the user of this - // code can then process. We mark both uses and references, and also note - // uses of specific things that require special handling, like refFuncs. +// Visit or walk an expression to find important things. We note them on data +// structures that the caller can then process. +// +// We could in theory merge this class in with Analyzer, allowing us to +// directly apply our findings - that is, rather than add to a data structure, +// then the caller iterates on it and calls a method on them all, we could call +// that method as we go. However, separating the classes is simpler as we need +// different handling in different cases (when scanning for references vs when +// scanning normally, see below). +// +// In theory we could parallelize this class, processing all functions in +// advance, rather than as we go (single-threaded). However, this turns out not +// to be faster in practice (perhaps because the single-threaded approach uses +// less memory). +struct Noter : public PostWalker> { + // We mark both uses and references, and also note specific things that + // require special handling, like refFuncs. std::vector used, referenced; std::vector callRefTypes; std::vector refFuncs; std::vector structFields; std::vector indirectCalls; - // Add an item to the output data structures. + // Note an item on our output data structures. void use(ModuleElement element) { used.push_back(element); } void reference(ModuleElement element) { referenced.push_back(element); } - void useCallRef(HeapType type) { callRefTypes.push_back(type); } - void useRefFunc(Name refFunc) { refFuncs.push_back(refFunc); } - void useStructField(StructField structField) { + void noteCallRef(HeapType type) { callRefTypes.push_back(type); } + void noteRefFunc(Name refFunc) { refFuncs.push_back(refFunc); } + void noteStructField(StructField structField) { structFields.push_back(structField); } - void useIndirectCall(Name table, HeapType type) { + void noteIndirectCall(Name table, HeapType type) { indirectCalls.push_back({table, type}); } @@ -154,12 +164,12 @@ struct ReferenceFinder // We refer to the table, but may not use all parts of it, that depends on // the heap type we call with. reference({ModuleElementKind::Table, curr->table}); - useIndirectCall(curr->table, curr->heapType); + noteIndirectCall(curr->table, curr->heapType); // Note a possible call of a function reference as well, as something might // be written into the table during runtime. With precise tracking of what // is written into the table we could do better here; we could also see // which tables are immutable. TODO - useCallRef(curr->heapType); + noteCallRef(curr->heapType); } void visitCallRef(CallRef* curr) { @@ -168,17 +178,17 @@ struct ReferenceFinder return; } - useCallRef(curr->target->type.getHeapType()); + noteCallRef(curr->target->type.getHeapType()); } - void visitRefFunc(RefFunc* curr) { useRefFunc(curr->func); } + void visitRefFunc(RefFunc* curr) { noteRefFunc(curr->func); } void visitStructGet(StructGet* curr) { if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) { return; } auto type = curr->ref->type.getHeapType(); - useStructField(StructField{type, curr->index}); + noteStructField(StructField{type, curr->index}); } }; @@ -276,25 +286,25 @@ struct Analyzer { // Find references in this expression, and apply them. Anything found here // is used. - ReferenceFinder finder; - finder.setModule(module); - finder.visit(curr); - for (auto element : finder.used) { + Noter noter; + noter.setModule(module); + noter.visit(curr); + for (auto element : noter.used) { use(element); } - for (auto element : finder.referenced) { + for (auto element : noter.referenced) { reference(element); } - for (auto type : finder.callRefTypes) { + for (auto type : noter.callRefTypes) { useCallRefType(type); } - for (auto func : finder.refFuncs) { + for (auto func : noter.refFuncs) { useRefFunc(func); } - for (auto structField : finder.structFields) { + for (auto structField : noter.structFields) { useStructField(structField); } - for (auto call : finder.indirectCalls) { + for (auto call : noter.indirectCalls) { useIndirectCall(call); } @@ -604,18 +614,18 @@ struct Analyzer { // here). void addReferences(Expression* curr) { // Find references anywhere in this expression so we can apply them. - ReferenceFinder finder; - finder.setModule(module); - finder.walk(curr); + Noter noter; + noter.setModule(module); + noter.walk(curr); - for (auto element : finder.used) { + for (auto element : noter.used) { reference(element); } - for (auto element : finder.referenced) { + for (auto element : noter.referenced) { reference(element); } - for (auto func : finder.refFuncs) { + for (auto func : noter.refFuncs) { // If a function ends up referenced but not used then later down we will // empty it out by replacing its body with an unreachable, which always // validates. For that reason all we need to do here is mark the function From a03f002c0a49432d17864790d5b7730344f0ad5b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 10:19:22 -0700 Subject: [PATCH 618/622] [BranchHints] Fuzz branch hints (#7704) Add two helper passes, one to delete specific branch hints by their instrumentation ID (as added by InstrumentBranchHints), and one to remove all instrumentation. The new fuzzer then * Adds random branch hints * Instruments them and runs that to see the output * Delete all incorrect hints * Remove all instrumentation, leaving a wasm with correct hints only * Optimize * Add new instrumentation and run that to see the output The idea is that once we have a wasm with only correct hints, the optimizer is allowed to remove some (e.g. in DCE), but it should never emit an invalid branch hint (e.g. by forgetting to flip a hint when it flips an if). We do need to avoid passes that reorder or unconditionalize code, or parts of them, so this is not quite that simple, but it still allows us to fuzz this. --- scripts/fuzz_opt.py | 223 +++++++++++++- scripts/fuzz_shell.js | 4 + src/passes/CodeFolding.cpp | 4 + src/passes/InstrumentBranchHints.cpp | 284 ++++++++++++++++-- src/passes/LocalCSE.cpp | 5 + src/passes/OptimizeInstructions.cpp | 33 +- src/passes/RandomizeBranchHints.cpp | 2 + src/passes/RemoveUnusedBrs.cpp | 37 ++- src/passes/pass.cpp | 18 +- src/passes/passes.h | 2 + src/tools/execution-results.h | 10 +- .../lit/passes/code-folding_branch-hints.wast | 141 +++++++++ .../lit/passes/deinstrument-branch-hints.wast | 167 ++++++++++ test/lit/passes/delete-branch-hints.wast | 163 ++++++++++ test/lit/passes/instrument-branch-hints.wast | 19 +- .../optimize-instructions-branch-hints.wast | 32 -- ...timize-instructions_branch-hints-fold.wast | 268 +++++++++++++++++ ...sed-brs_branch-hints-unconditionalize.wast | 213 +++++++++++++ 18 files changed, 1550 insertions(+), 75 deletions(-) create mode 100644 test/lit/passes/code-folding_branch-hints.wast create mode 100644 test/lit/passes/deinstrument-branch-hints.wast create mode 100644 test/lit/passes/delete-branch-hints.wast delete mode 100644 test/lit/passes/optimize-instructions-branch-hints.wast create mode 100644 test/lit/passes/optimize-instructions_branch-hints-fold.wast create mode 100644 test/lit/passes/remove-unused-brs_branch-hints-unconditionalize.wast diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 5c06ef33995..84359998182 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -515,10 +515,10 @@ def compare_between_vms(x, y, context): y_line = y_lines[i] if x_line != y_line: # this is different, but maybe it's a vm difference we can ignore - LEI_LOGGING = '[LoggingExternalInterface logging' - if x_line.startswith(LEI_LOGGING) and y_line.startswith(LEI_LOGGING): - x_val = x_line[len(LEI_LOGGING) + 1:-1] - y_val = y_line[len(LEI_LOGGING) + 1:-1] + LOGGING_PREFIX = '[LoggingExternalInterface logging' + if x_line.startswith(LOGGING_PREFIX) and y_line.startswith(LOGGING_PREFIX): + x_val = x_line[len(LOGGING_PREFIX) + 1:-1] + y_val = y_line[len(LOGGING_PREFIX) + 1:-1] if numbers_are_close_enough(x_val, y_val): continue if x_line.startswith(FUZZ_EXEC_NOTE_RESULT) and y_line.startswith(FUZZ_EXEC_NOTE_RESULT): @@ -1844,6 +1844,220 @@ def get_relevant_lines(wat): compare(get_relevant_lines(original), get_relevant_lines(processed), 'Preserve') +# Test that we preserve branch hints properly. The invariant that we test here +# is that, given correct branch hints (that is, the input wasm's branch hints +# are always correct: a branch is taken iff the hint is that it is taken), then +# the optimizer does not end up with incorrect branch hints. It is fine if the +# optimizer removes some hints (it may remove entire chunks of code in DCE, for +# example, and it may find ways to simplify code so fewer things execute), but +# it should not emit a branch hint that is wrong - if it is not certain, it +# should remove the branch hint. +# +# Note that bugs found by this fuzzer tend to require the following during +# reducing: BINARYEN_TRUST_GIVEN_WASM=1 in the env, and --text as a parameter. +class BranchHintPreservation(TestCaseHandler): + frequency = 0.1 + + def handle(self, wasm): + # Generate an instrumented wasm. + instrumented = wasm + '.inst.wasm' + run([ + in_bin('wasm-opt'), + wasm, + '-o', instrumented, + # Add random branch hints (so we have something to work with). + '--randomize-branch-hints', + # Instrument them with logging. + '--instrument-branch-hints', + '-g', + ] + FEATURE_OPTS) + + # Collect the logging. + out = run_bynterp(instrumented, ['--fuzz-exec-before', '-all']) + + # Process the output. We look at the lines like this: + # + # [LoggingExternalInterface log-branch 1 0 0] + # + # where the three integers are: ID, predicted, actual. + all_ids = set() + bad_ids = set() + LOG_BRANCH_PREFIX = '[LoggingExternalInterface log-branch' + for line in out.splitlines(): + if line.startswith(LOG_BRANCH_PREFIX): + # (1:-1 strips away the '[', ']' at the edges) + _, _, id_, hint, actual = line[1:-1].split(' ') + all_ids.add(id_) + if hint != actual: + # This hint was misleading. + bad_ids.add(id_) + + # If no good ids remain, there is nothing to test (no hints will remain + # later down, after we remove bad ones). + if bad_ids == all_ids: + note_ignored_vm_run('no good ids') + return + + # Generate proper hints for testing: A wasm file with 100% valid branch + # hints, and instrumentation to verify that. + de_instrumented = wasm + '.de_inst.wasm' + args = [ + in_bin('wasm-opt'), + instrumented, + '-o', de_instrumented, + ] + # Remove the bad ids (using the instrumentation to identify them by ID). + if bad_ids: + args += [ + '--delete-branch-hints=' + ','.join(bad_ids), + ] + args += [ + # Remove all prior instrumentation, so it does not confuse us later + # when we log our final hints, and also so it does not inhibit + # optimizations. + '--deinstrument-branch-hints', + '-g', + ] + FEATURE_OPTS + run(args) + + # Add optimizations to see if things break. + opted = wasm + '.opted.wasm' + args = [ + in_bin('wasm-opt'), + de_instrumented, + '-o', opted, + '-g', + + # Some passes are just skipped, as they do not modify ifs or brs, + # but they do break the invariant of not adding bad branch hints. + # There are two main issues here: + # * Moving code around, possibly causing it to start to execute if + # it previously was not reached due to a trap (a branch hint + # seems to have no effects in the optimizer, so it will do such + # movements). And if it starts to execute and is a wrong hint, we + # get an invalid fuzzer finding. + # * LICM moves code out of loops. + '--skip-pass=licm', + # * HeapStoreOptimization moves struct.sets closer to struct.news. + '--skip-pass=heap-store-optimization', + # * MergeBlocks moves code out of inner blocks to outer blocks. + '--skip-pass=merge-blocks', + # * Monomorphize can subtly reorder code: + # + # (call $foo + # (select + # (i32.div_s ..which will trap..) + # (if with branch hint) + # => + # (call $foo_1 + # (if with branch hint) + # + # where $foo_1 receives the if's result and uses it in the + # ("reverse-inlined") select. Now the if executes first, when + # previously the trap stopped it. + '--skip-pass=monomorphize', + '--skip-pass=monomorphize-always', + # SimplifyGlobals finds globals that are "read only to be written", + # and can remove the ifs that do so: + # + # if (foo) { foo = 1 } + # => + # if (0) {} + # + # This is valid if the global's value is never read otherwise, but + # it does alter the if's behavior. + '--skip-pass=simplify-globals', + '--skip-pass=simplify-globals-optimizing', + + # * Merging/folding code. When we do so, code identical in content + # but differing in metadata will end up with the metadata from one + # of the copies, which might be wrong (we follow LLVM here, see + # details in the passes). + # * CodeFolding merges code blocks inside functions. + '--skip-pass=code-folding', + # * DuplicateFunctionElimination merges functions. + '--skip-pass=duplicate-function-elimination', + + # Some passes break the invariant in some cases, but we do not want + # to skip them entirely, as they have other things we need to fuzz. + # We add pass-args for them: + # * Do not fold inside OptimizeInstructions. + '--pass-arg=optimize-instructions-never-fold-or-reorder', + # * Do not unconditionalize code in RemoveUnusedBrs. + '--pass-arg=remove-unused-brs-never-unconditionalize', + + ] + get_random_opts() + FEATURE_OPTS + run(args) + + # Add instrumentation, to see if any branch hints are wrong after + # optimizations. We must do this in a separate invocation from the + # optimizations due to flags like --converge (which would instrument + # multiple times). + final = wasm + '.final.wasm' + args = [ + in_bin('wasm-opt'), + opted, + '-o', final, + '--instrument-branch-hints', + '-g', + ] + FEATURE_OPTS + run(args) + + # Run the final wasm. + out = run_bynterp(final, ['--fuzz-exec-before', '-all']) + + # Preprocess the logging. We must discard all lines from functions that + # trap, because we are fuzzing branch hints, which are not an effect, + # and so they can be reordered with traps; consider this: + # + # (i32.add + # (block + # (if (X) (unreachable) + # (i32.const 10) + # ) + # (block + # (@metadata.code.branch_hint "\00") + # (if (Y) (unreachable) + # (i32.const 20) + # ) + # ) + # + # It is ok to reorder traps, so the optimizer might flip the arms of + # this add (imagine other code inside the arms justified that). That + # reordering is fine since the branch hint has no effect that the + # optimizer needs to care about. However, after we instrument, there + # *is* an effect, the visible logging, so if X is true we trap and do + # not log a branch hint, but if we reorder, we do log, then trap. + # + # Note that this problem is specific to traps, because the optimizer can + # reorder them, and does not care about identity. + # + # To handle this, gather lines for each call, and then see which groups + # end in traps. (Initialize the list of groups with an empty group, for + # any logging before the first call.) + line_groups = [['before calls']] + for line in out.splitlines(): + if line.startswith(FUZZ_EXEC_CALL_PREFIX): + line_groups.append([line]) + else: + line_groups[-1].append(line) + + # No bad hints should pop up after optimizations. + for group in line_groups: + if not group or group[-1] == '[trap unreachable]': + continue + for line in group: + if line.startswith(LOG_BRANCH_PREFIX): + _, _, id_, hint, actual = line[1:-1].split(' ') + hint = int(hint) + actual = int(actual) + assert hint in (0, 1) + # We do not care about the integer value of the condition, + # only if it was 0 or non-zero. + actual = (actual != 0) + assert hint == actual, 'Bad hint after optimizations' + + # The global list of all test case handlers testcase_handlers = [ FuzzExec(), @@ -1859,6 +2073,7 @@ def get_relevant_lines(wat): ClusterFuzz(), Two(), PreserveImportsExports(), + BranchHintPreservation(), ] diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 3f201b3c812..ba853a6e538 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -353,6 +353,10 @@ var imports = { // how many time units to wait). }); }, + + 'log-branch': (id, expected, actual) => { + console.log(`[LoggingExternalInterface log-branch ${id} ${expected} ${actual}]`); + }, }, // Emscripten support. 'env': { diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp index 305eb12784f..ee30d3e5080 100644 --- a/src/passes/CodeFolding.cpp +++ b/src/passes/CodeFolding.cpp @@ -249,6 +249,10 @@ struct CodeFolding // run the rest of the optimization mormally. auto maybeAddBlock = [this](Block* block, Expression*& other) -> Block* { // If other is a suffix of the block, wrap it in a block. + // + // Note that we do not consider metadata here. Like LLVM, we ignore + // metadata when trying to fold code together, preferring certain + // optimization over possible benefits of profiling data. if (block->list.empty() || !ExpressionAnalyzer::equal(other, block->list.back())) { return nullptr; diff --git a/src/passes/InstrumentBranchHints.cpp b/src/passes/InstrumentBranchHints.cpp index 35d2dbabb15..34af0953662 100644 --- a/src/passes/InstrumentBranchHints.cpp +++ b/src/passes/InstrumentBranchHints.cpp @@ -54,11 +54,56 @@ // if (expected != actual) throw `Bad branch hint! (${id})`; // }; // +// A pass to delete branch hints is also provided, which finds instrumentations +// and the IDs in those calls, and deletes branch hints that were listed. For +// example, +// +// --delete-branch-hints=10,20 +// +// would do this transformation: +// +// @metadata.branch.hint A +// if (temp = condition; log(10, A, temp); temp) { // 10 matches one of 10,20 +// X +// } +// @metadata.branch.hint B +// if (temp = condition; log(99, B, temp); temp) { // 99 does not match +// Y +// } +// +// => +// +// // Used to be a branch hint here, but it was deleted. +// if (temp = condition; log(10, A, temp); temp) { +// X +// } +// @metadata.branch.hint B // this one is unmodified. +// if (temp = condition; log(99, B, temp); temp) { +// Y +// } +// +// A pass to undo the instrumentation is also provided, which does +// +// if (temp = condition; log(123, A, temp); temp) { +// X +// } +// +// => +// +// if (condition) { +// X +// } +// +#include "ir/drop.h" #include "ir/eh-utils.h" +#include "ir/find_all.h" +#include "ir/local-graph.h" #include "ir/names.h" +#include "ir/parents.h" #include "ir/properties.h" #include "pass.h" +#include "support/string.h" #include "wasm-builder.h" #include "wasm.h" @@ -66,6 +111,20 @@ namespace wasm { namespace { +// The module and base names of our import. +const Name MODULE = "fuzzing-support"; +const Name BASE = "log-branch"; + +// Finds our import, if it exists. +Name getLogBranchImport(Module* module) { + for (auto& func : module->functions) { + if (func->module == MODULE && func->base == BASE) { + return func->name; + } + } + return Name(); +} + // The branch id, which increments as we go. int branchId = 1; @@ -74,10 +133,6 @@ struct InstrumentBranchHints using Super = WalkerPass>; - // The module and base names of our import. - const Name MODULE = "fuzzing-support"; - const Name BASE = "log-branch"; - // The internal name of our import. Name logBranch; @@ -132,31 +187,222 @@ struct InstrumentBranchHints } void doWalkModule(Module* module) { - // Find our import, if we were already run on this module. - for (auto& func : module->functions) { - if (func->module == MODULE && func->base == BASE) { - logBranch = func->name; - break; - } + if (auto existing = getLogBranchImport(module)) { + // This file already has our import. We nop it out, as whatever the + // current code does may be dangerous (it may log incorrect hints). + auto* func = module->getFunction(existing); + func->body = Builder(*module).makeNop(); + func->module = func->base = Name(); + } + + // Add our import. + auto* func = module->addFunction(Builder::makeFunction( + Names::getValidFunctionName(*module, BASE), + Signature({Type::i32, Type::i32, Type::i32}, Type::none), + {})); + func->module = MODULE; + func->base = BASE; + logBranch = func->name; + + // Walk normally, using logBranch as we go. + Super::doWalkModule(module); + } +}; + +// Helper class that provides basic utilities for identifying and processing +// instrumentation from InstrumentBranchHints. +template +struct InstrumentationProcessor : public WalkerPass> { + + using Super = WalkerPass>; + + // The internal name of our import. + Name logBranch; + + // A LocalGraph, so we can identify the pattern. + std::unique_ptr localGraph; + + // A map of expressions to their parents, so we can identify the pattern. + std::unique_ptr parents; + + Sub* self() { return static_cast(this); } + + void visitIf(If* curr) { self()->processCondition(curr); } + + void visitBreak(Break* curr) { + if (curr->condition) { + self()->processCondition(curr); } - // Otherwise, add it. + } + + // TODO: BrOn, but the condition there is not an i32 + + void doWalkFunction(Function* func) { + localGraph = std::make_unique(func, this->getModule()); + localGraph->computeSetInfluences(); + + parents = std::make_unique(func->body); + + Super::doWalkFunction(func); + } + + void doWalkModule(Module* module) { + logBranch = getLogBranchImport(module); if (!logBranch) { - auto* func = module->addFunction(Builder::makeFunction( - Names::getValidFunctionName(*module, BASE), - Signature({Type::i32, Type::i32, Type::i32}, Type::none), - {})); - func->module = MODULE; - func->base = BASE; - logBranch = func->name; + Fatal() + << "No branch hint logging import found. Was this code instrumented?"; } - // Walk normally, using logBranch as we go. Super::doWalkModule(module); } + + // Helpers + + // Instrumentation info for a chunk of code that is the result of the + // instrumentation pass. + struct Instrumentation { + // The condition before the instrumentation (a pointer to it, so we can + // replace it). + Expression** originalCondition; + // The call to the logging that the instrumentation added. + Call* call; + }; + + // Check if an expression's condition is an instrumentation, and return the + // info if so. + std::optional getInstrumentation(Expression* condition) { + // We must identify this pattern: + // + // (br_if + // (block + // (local.set $temp (condition)) + // (call $log (id, prediction, (local.get $temp))) + // (local.get $temp) + // ) + // + // The block may vanish during roundtrip though, so we just follow back from + // the last local.get, which appears in the condition: + // + // (local.set $temp (condition)) + // (call $log (id, prediction, (local.get $temp))) + // (br_if + // (local.get $temp) + // + auto* fallthrough = Properties::getFallthrough( + condition, this->getPassOptions(), *this->getModule()); + auto* get = fallthrough->template dynCast(); + if (!get) { + return {}; + } + auto& sets = localGraph->getSets(get); + if (sets.size() != 1) { + return {}; + } + auto* set = *sets.begin(); + if (!set) { + return {}; + } + auto& gets = localGraph->getSetInfluences(set); + if (gets.size() != 2) { + return {}; + } + // The set has two gets: the get in the condition we began at, and + // another. + LocalGet* otherGet = nullptr; + for (auto* get2 : gets) { + if (get2 != get) { + otherGet = get2; + } + } + assert(otherGet); + // See if that other get is used in a logging. The parent should be a + // logging call. + auto* call = parents->getParent(otherGet)->template dynCast(); + if (!call || call->target != logBranch) { + return {}; + } + // Great, this is indeed a prior instrumentation. + return Instrumentation{&set->value, call}; + } +}; + +struct DeleteBranchHints : public InstrumentationProcessor { + using Super = InstrumentationProcessor; + + // The set of IDs to delete. + std::unordered_set idsToDelete; + + template void processCondition(T* curr) { + if (auto info = getInstrumentation(curr->condition)) { + if (auto* c = info->call->operands[0]->template dynCast()) { + auto id = c->value.geti32(); + if (idsToDelete.count(id)) { + // Remove the branch hint. + getFunction()->codeAnnotations[curr].branchLikely = {}; + } + } + } + } + + void doWalkModule(Module* module) { + auto arg = getArgument( + "delete-branch-hints", + "DeleteBranchHints usage: wasm-opt --delete-branch-hints=10,20,30"); + for (auto& str : String::Split(arg, String::Split::NewLineOr(","))) { + idsToDelete.insert(std::stoi(str)); + } + + Super::doWalkModule(module); + } +}; + +struct DeInstrumentBranchHints + : public InstrumentationProcessor { + + template void processCondition(T* curr) { + if (auto info = getInstrumentation(curr->condition)) { + // Replace the instrumented condition with the original one (swap so that + // the IR remains valid: we cannot use the same expression twice in our + // IR, and the original condition is still used in another place, until + // we remove the logging calls; since we will remove the calls anyhow, we + // just need some valid IR there). + std::swap(curr->condition, *info->originalCondition); + } + } + + void visitFunction(Function* func) { + if (func->imported()) { + return; + } + // At the very end, remove all logging calls (we use them during the main + // walk to identify instrumentation). + for (auto** callp : FindAllPointers(func->body).list) { + auto* call = (*callp)->cast(); + if (call->target == logBranch) { + Builder builder(*getModule()); + Expression* last; + if (call->type == Type::none) { + last = builder.makeNop(); + } else { + last = builder.makeUnreachable(); + } + *callp = getDroppedChildrenAndAppend(call, + *getModule(), + getPassOptions(), + last, + // We know the call is removable. + DropMode::IgnoreParentEffects); + } + } + } }; } // anonymous namespace Pass* createInstrumentBranchHintsPass() { return new InstrumentBranchHints(); } +Pass* createDeleteBranchHintsPass() { return new DeleteBranchHints(); } +Pass* createDeInstrumentBranchHintsPass() { + return new DeInstrumentBranchHints(); +} } // namespace wasm diff --git a/src/passes/LocalCSE.cpp b/src/passes/LocalCSE.cpp index 78f722c7ae5..9c9a8198f89 100644 --- a/src/passes/LocalCSE.cpp +++ b/src/passes/LocalCSE.cpp @@ -157,6 +157,11 @@ struct HEComparer { if (a.digest != b.digest) { return false; } + // Note that we do not consider metadata here. That means we may replace two + // identical expressions with different metadata, say, different branch + // hints, but that is ok: we are only removing things from executing (by + // reusing the first computed value), so this will not cause new invalid + // branch hints to execute. return ExpressionAnalyzer::equal(a.expr, b.expr); } }; diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 9ad27dc67ab..b2b937dd2a3 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -228,11 +228,25 @@ struct OptimizeInstructions bool fastMath; + // If set, we never fold/merge code together. This is important when fuzzing + // branch hints, as if we allow folding, then we may fold code identical in + // all ways but for branch hints, leading to an invalid branch hint executing + // later (imagine one arm had the right hint and the other the wrong one; we + // leave one of the two arbitrarily, so we might get unlucky). + bool neverFold; + + // As neverFold, but for reordering code. If we move a branch hint around code + // that might trap, and the trap happens later, the branch hint might start to + // execute, and it could be wrong. + bool neverReorder; + // In rare cases we make a change to a type, and will do a refinalize. bool refinalize = false; void doWalkFunction(Function* func) { fastMath = getPassOptions().fastMath; + neverFold = neverReorder = + hasArgument("optimize-instructions-never-fold-or-reorder"); // First, scan locals. { @@ -312,6 +326,9 @@ struct OptimizeInstructions } bool canReorder(Expression* a, Expression* b) { + if (neverReorder) { + return false; + } return EffectAnalyzer::canReorder(getPassOptions(), *getModule(), a, b); } @@ -1176,7 +1193,10 @@ struct OptimizeInstructions BranchHints::flip(curr, getFunction()); } } - if (curr->condition->type != Type::unreachable && + // Note that we do not consider metadata here. Like LLVM, we ignore + // metadata when trying to fold code together, preferring certain + // optimization over possible benefits of profiling data. + if (!neverFold && curr->condition->type != Type::unreachable && ExpressionAnalyzer::equal(curr->ifTrue, curr->ifFalse)) { // The sides are identical, so fold. If we can replace the If with one // arm and there are no side effects in the condition, replace it. But @@ -2820,6 +2840,9 @@ struct OptimizeInstructions // write more concise pattern matching code elsewhere. void canonicalize(Binary* binary) { assert(shouldCanonicalize(binary)); + if (neverReorder) { + return; + } auto swap = [&]() { assert(canReorder(binary->left, binary->right)); if (binary->isRelational()) { @@ -3235,8 +3258,12 @@ struct OptimizeInstructions } } { - // Sides are identical, fold + // If sides are identical, fold. Expression *ifTrue, *ifFalse, *c; + // Note we do not compare metadata here: This is a select, so both arms + // execute anyhow, and things like branch hints were already being run. + // After optimization, we will only run fewer things, and run no risk of + // running new bad things. if (matches(curr, select(any(&ifTrue), any(&ifFalse), any(&c))) && ExpressionAnalyzer::equal(ifTrue, ifFalse)) { auto value = effects(ifTrue); @@ -5642,7 +5669,7 @@ struct OptimizeInstructions } } - { + if (!neverFold) { // Identical code on both arms can be folded out, e.g. // // (select diff --git a/src/passes/RandomizeBranchHints.cpp b/src/passes/RandomizeBranchHints.cpp index 7b013fc7ebf..b74d2e8a093 100644 --- a/src/passes/RandomizeBranchHints.cpp +++ b/src/passes/RandomizeBranchHints.cpp @@ -50,6 +50,8 @@ struct RandomizeBranchHints } } + // TODO: BrOn + template void processCondition(T* curr) { auto& likely = getFunction()->codeAnnotations[curr].branchLikely; switch (hash % 3) { diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index b2237dcb823..4ea8592c356 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -15,7 +15,7 @@ */ // -// Removes branches for which we go to where they go anyhow +// Removes branches for which we go to where they go anyhow. // #include "ir/branch-hints.h" @@ -161,6 +161,12 @@ struct RemoveUnusedBrs : public WalkerPass> { bool anotherCycle; + // Whether we are allowed to unconditionalize code, that is, make code run + // that previously might not have. Unconditionalizing code is a problem for + // fuzzing branch hints: a branch hint that never ran might be wrong, and if + // we start to run it, the fuzzer would report a finding. + bool neverUnconditionalize; + using Flows = std::vector; // list of breaks that are currently flowing. if they reach their target @@ -408,7 +414,12 @@ struct RemoveUnusedBrs : public WalkerPass> { // zero (also 3 bytes). The size is unchanged, but the select may // be further optimizable, and if select does not branch we also // avoid one branch. - // Multivalue selects are not supported + if (neverUnconditionalize) { + // Creating a select, below, would unconditionally run the + // select's condition. + return; + } + // Multivalue selects are not supported. if (br->value && br->value->type.isTuple()) { return; } @@ -449,6 +460,11 @@ struct RemoveUnusedBrs : public WalkerPass> { if (child->ifFalse) { return; } + if (neverUnconditionalize) { + // Creating a select, below, would unconditionally run the inner if's + // condition (condition-B, in the comment above). + return; + } // If running the child's condition unconditionally is too expensive, // give up. if (tooCostlyToRunUnconditionally(getPassOptions(), child->condition)) { @@ -1129,6 +1145,9 @@ struct RemoveUnusedBrs : public WalkerPass> { } void doWalkFunction(Function* func) { + neverUnconditionalize = + hasArgument("remove-unused-brs-never-unconditionalize"); + // multiple cycles may be needed do { anotherCycle = false; @@ -1252,9 +1271,11 @@ struct RemoveUnusedBrs : public WalkerPass> { // perform some final optimizations struct FinalOptimizer : public PostWalker { - bool shrink; PassOptions& passOptions; + bool shrink; + bool neverUnconditionalize; + bool needUniqify = false; bool refinalize = false; @@ -1520,6 +1541,11 @@ struct RemoveUnusedBrs : public WalkerPass> { // must not have side effects. // TODO: we can do this when there *are* other refs to $x, // with a larger refactoring here. + if (neverUnconditionalize) { + // If we optimize, we'd unconditionally execute the rest of + // the block. + return; + } // Test for the conditions with a temporary nop instead of the // br_if. @@ -1556,6 +1582,9 @@ struct RemoveUnusedBrs : public WalkerPass> { // Convert an if into a select, if possible and beneficial to do so. Select* selectify(If* iff) { + if (neverUnconditionalize) { + return nullptr; + } // Only an if-else can be turned into a select. if (!iff->ifFalse) { return nullptr; @@ -2011,6 +2040,8 @@ struct RemoveUnusedBrs : public WalkerPass> { FinalOptimizer finalOptimizer(getPassOptions()); finalOptimizer.setModule(getModule()); finalOptimizer.shrink = getPassRunner()->options.shrinkLevel > 0; + finalOptimizer.neverUnconditionalize = neverUnconditionalize; + finalOptimizer.walkFunction(func); if (finalOptimizer.needUniqify) { wasm::UniqueNameMapper::uniquify(func->body); diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index e0b7595b0b5..8db59b8ac0f 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -412,9 +412,6 @@ void PassRegistry::registerPasses() { registerPass("propagate-globals-globally", "propagate global values to other globals (useful for tests)", createPropagateGlobalsGloballyPass); - registerTestPass("randomize-branch-hints", - "randomize branch hints (for fuzzing)", - createRandomizeBranchHintsPass); registerPass("remove-non-js-ops", "removes operations incompatible with js", createRemoveNonJSOpsPass); @@ -451,9 +448,6 @@ void PassRegistry::registerPasses() { registerPass("reorder-globals", "sorts globals by access frequency", createReorderGlobalsPass); - registerTestPass("reorder-globals-always", - "sorts globals by access frequency (even if there are few)", - createReorderGlobalsAlwaysPass); registerPass("reorder-locals", "sorts locals by access frequency", createReorderLocalsPass); @@ -599,9 +593,21 @@ void PassRegistry::registerPasses() { registerTestPass("catch-pop-fixup", "fixup nested pops within catches", createCatchPopFixupPass); + registerTestPass("deinstrument-branch-hints", + "de-instrument branch hint instrumentation", + createDeInstrumentBranchHintsPass); + registerTestPass("delete-branch-hints", + "delete branch hints using a list of instrumented IDs", + createDeleteBranchHintsPass); registerTestPass("experimental-type-generalizing", "generalize types (not yet sound)", createTypeGeneralizingPass); + registerTestPass("randomize-branch-hints", + "randomize branch hints (for fuzzing)", + createRandomizeBranchHintsPass); + registerTestPass("reorder-globals-always", + "sorts globals by access frequency (even if there are few)", + createReorderGlobalsAlwaysPass); } void PassRunner::addIfNoDWARFIssues(std::string passName) { diff --git a/src/passes/passes.h b/src/passes/passes.h index e0c03bad8d7..92dcd3e4eb4 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -37,6 +37,8 @@ Pass* createDAEPass(); Pass* createDAEOptimizingPass(); Pass* createDataFlowOptsPass(); Pass* createDeadCodeEliminationPass(); +Pass* createDeInstrumentBranchHintsPass(); +Pass* createDeleteBranchHintsPass(); Pass* createDeNaNPass(); Pass* createDeAlignPass(); Pass* createDebugLocationPropagationPass(); diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 0dea839c839..03e9ccb47aa 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -79,7 +79,15 @@ struct LoggingExternalInterface : public ShellExternalInterface { if (import->module == "fuzzing-support") { if (import->base.startsWith("log")) { // This is a logging function like log-i32 or log-f64 - std::cout << "[LoggingExternalInterface logging"; + std::cout << "[LoggingExternalInterface "; + if (import->base == "log-branch") { + // Report this as a special logging, so we can differentiate it from + // the others in the fuzzer. + std::cout << "log-branch"; + } else { + // All others are just reported as loggings. + std::cout << "logging"; + } loggings.push_back(Literal()); // buffer with a None between calls for (auto argument : arguments) { if (argument.type == Type::i64) { diff --git a/test/lit/passes/code-folding_branch-hints.wast b/test/lit/passes/code-folding_branch-hints.wast new file mode 100644 index 00000000000..51601230865 --- /dev/null +++ b/test/lit/passes/code-folding_branch-hints.wast @@ -0,0 +1,141 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. + +;; RUN: wasm-opt %s -all --code-folding -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func (param i32 i32) (result f32))) + + ;; CHECK: (func $different (type $0) (param $x i32) (param $y i32) (result f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + (func $different (param $x i32) (param $y i32) (result f32) + ;; The branch hints differ, but we still optimize (like LLVM). + (if (result f32) + (local.get $x) + (then + (block (result f32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (nop) + ) + ) + (f32.const 0) + ) + ) + (else + (block (result f32) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (nop) + ) + ) + (f32.const 0) + ) + ) + ) + ) + + ;; CHECK: (func $different-flip (type $0) (param $x i32) (param $y i32) (result f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + (func $different-flip (param $x i32) (param $y i32) (result f32) + ;; As above, but flipped. We still optimize, still keeping the first branch + ;; hint (now "\01"). + (if (result f32) + (local.get $x) + (then + (block (result f32) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (nop) + ) + ) + (f32.const 0) + ) + ) + (else + (block (result f32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (nop) + ) + ) + (f32.const 0) + ) + ) + ) + ) + + ;; CHECK: (func $same (type $0) (param $x i32) (param $y i32) (result f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + (func $same (param $x i32) (param $y i32) (result f32) + ;; The branch hints are the same, so we definitely optimize. + (if (result f32) + (local.get $x) + (then + (block (result f32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (nop) + ) + ) + (f32.const 0) + ) + ) + (else + (block (result f32) + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (nop) + ) + ) + (f32.const 0) + ) + ) + ) + ) +) diff --git a/test/lit/passes/deinstrument-branch-hints.wast b/test/lit/passes/deinstrument-branch-hints.wast new file mode 100644 index 00000000000..6d619c4b28b --- /dev/null +++ b/test/lit/passes/deinstrument-branch-hints.wast @@ -0,0 +1,167 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --deinstrument-branch-hints -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32 i32 i32))) + + ;; CHECK: (import "fuzzing-support" "log-branch" (func $log (type $1) (param i32 i32 i32))) + (import "fuzzing-support" "log-branch" (func $log (param i32 i32 i32))) + + ;; CHECK: (func $if (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 99) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if + (local $temp i32) + ;; The instrumentation should be removed, and the if's condition should + ;; be 42. + (@metadata.code.branch_hint "\00") + (if + (block (result i32) + (local.set $temp + (i32.const 42) + ) + (call $log + (i32.const 1) + (i32.const 0) + (local.get $temp) + ) + (local.get $temp) + ) + (then + (drop + (i32.const 1337) + ) + ) + (else + (drop + (i32.const 99) + ) + ) + ) + ) + + ;; CHECK: (func $br (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br + ;; The same, with a br. + (local $temp i32) + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (block (result i32) + (local.set $temp + (i32.const 42) + ) + (call $log + (i32.const 4) + (i32.const 0) + (local.get $temp) + ) + (local.get $temp) + ) + ) + ) + ) + + ;; CHECK: (func $br-before (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-before + ;; As above, but the instrumentation is before us, leaving only a local.get + ;; in the br's condition. We should still identify the pattern and remove + ;; the logging (but we leave the local.set for other things to clean up). + (local $temp i32) + (block $out + (local.set $temp + (i32.const 42) + ) + (call $log + (i32.const 4) + (i32.const 0) + (local.get $temp) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $br-before-effects (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (local $other i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $other + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-before-effects + ;; As above, but there are effects in the call's children that we must + ;; keep. + (local $temp i32) + (local $other i32) + (block $out + (local.set $temp + (i32.const 42) + ) + (call $log + (i32.const 4) + (local.tee $other ;; this tee must be kept around + (i32.const 0) + ) + (local.get $temp) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $temp) + ) + ) + ) +) diff --git a/test/lit/passes/delete-branch-hints.wast b/test/lit/passes/delete-branch-hints.wast new file mode 100644 index 00000000000..375b10d16c2 --- /dev/null +++ b/test/lit/passes/delete-branch-hints.wast @@ -0,0 +1,163 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --delete-branch-hints=10,30 -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32 i32 i32))) + + ;; CHECK: (import "fuzzing-support" "log-branch" (func $log (type $1) (param i32 i32 i32))) + (import "fuzzing-support" "log-branch" (func $log (param i32 i32 i32))) + + ;; CHECK: (func $if-10 (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 99) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-10 + (local $temp i32) + ;; The branch hint should be removed, since the ID "10" is in the list of + ;; 10, 30. + (@metadata.code.branch_hint "\00") + (if + (block (result i32) + (local.set $temp + (i32.const 42) + ) + (call $log + (i32.const 10) + (i32.const 0) + (local.get $temp) + ) + (local.get $temp) + ) + (then + (drop + (i32.const 1337) + ) + ) + (else + (drop + (i32.const 99) + ) + ) + ) + ) + + ;; CHECK: (func $if-20 (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 99) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-20 + (local $temp i32) + ;; The branch hint should *not* be removed: 20 is not in the list. + (@metadata.code.branch_hint "\00") + (if + (block (result i32) + (local.set $temp + (i32.const 42) + ) + (call $log + (i32.const 20) + (i32.const 0) + (local.get $temp) + ) + (local.get $temp) + ) + (then + (drop + (i32.const 1337) + ) + ) + (else + (drop + (i32.const 99) + ) + ) + ) + ) + + ;; CHECK: (func $br-30 (type $0) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $log + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-30 + ;; The hint should be removed. + (local $temp i32) + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (block (result i32) + (local.set $temp + (i32.const 42) + ) + (call $log + (i32.const 30) + (i32.const 0) + (local.get $temp) + ) + (local.get $temp) + ) + ) + ) + ) +) diff --git a/test/lit/passes/instrument-branch-hints.wast b/test/lit/passes/instrument-branch-hints.wast index 565bcf78716..5a6d73f5e0e 100644 --- a/test/lit/passes/instrument-branch-hints.wast +++ b/test/lit/passes/instrument-branch-hints.wast @@ -433,15 +433,20 @@ ) ) -;; This module has our import, but with a minified internal name. We should use -;; that import. +;; This module has an existing import with our module and base names. We nop it +;; and create a fresh one, to avoid confusion. (module + (import "fuzzing-support" "log-branch" (func $existing (param i32 i32 i32))) + ;; CHECK: (type $0 (func (param i32 i32 i32))) ;; CHECK: (type $1 (func)) - ;; CHECK: (import "fuzzing-support" "log-branch" (func $min (type $0) (param i32 i32 i32))) - (import "fuzzing-support" "log-branch" (func $min (param i32 i32 i32))) + ;; CHECK: (import "fuzzing-support" "log-branch" (func $log-branch (type $0) (param i32 i32 i32))) + + ;; CHECK: (func $existing (type $0) (param $0 i32) (param $1 i32) (param $2 i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) ;; CHECK: (func $if (type $1) ;; CHECK-NEXT: (local $x i32) @@ -454,7 +459,7 @@ ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $min + ;; CHECK-NEXT: (call $existing ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (local.get $x) @@ -462,7 +467,7 @@ ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $min + ;; CHECK-NEXT: (call $log-branch ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (local.get $1) @@ -484,7 +489,7 @@ (local.set $x (i32.const 42) ) - (call $min + (call $existing (i32.const 42) (i32.const 1) (local.get $x) diff --git a/test/lit/passes/optimize-instructions-branch-hints.wast b/test/lit/passes/optimize-instructions-branch-hints.wast deleted file mode 100644 index 270bf179216..00000000000 --- a/test/lit/passes/optimize-instructions-branch-hints.wast +++ /dev/null @@ -1,32 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s --optimize-instructions -all -S -o - | filecheck %s - -(module - ;; CHECK: (func $conditionals (type $0) (param $x i32) (result i32) - ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") - ;; CHECK-NEXT: (if (result i32) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (i32.const 1337) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $conditionals (param $x i32) (result i32) - ;; When we flip the if, the hint should flip too. - (@metadata.code.branch_hint "\00") - (if (result i32) - (i32.eqz - (local.get $x) - ) - (then - (i32.const 42) - ) - (else - (i32.const 1337) - ) - ) - ) -) diff --git a/test/lit/passes/optimize-instructions_branch-hints-fold.wast b/test/lit/passes/optimize-instructions_branch-hints-fold.wast new file mode 100644 index 00000000000..6223817e59e --- /dev/null +++ b/test/lit/passes/optimize-instructions_branch-hints-fold.wast @@ -0,0 +1,268 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s --optimize-instructions -all -S -o - | filecheck %s + +;; Also verify that the "never-fold-or-reorder" flag is respected: when set, we +;; do not fold code together (important, as we keep one of the branch hints, and +;; it may be wrong, which can confuse the fuzzer), and we never reorder (which +;; can move a branch hint to execute before a trap, which can also cause the +;; fuzzer to alert). + +;; RUN: wasm-opt %s --optimize-instructions -all --pass-arg=optimize-instructions-never-fold-or-reorder -S -o - \ +;; RUN: | filecheck %s --check-prefix=NO_FO + +(module + ;; CHECK: (func $conditionals (type $1) (param $x i32) (result i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_FO: (func $conditionals (type $1) (param $x i32) (result i32) + ;; NO_FO-NEXT: (@metadata.code.branch_hint "\01") + ;; NO_FO-NEXT: (if (result i32) + ;; NO_FO-NEXT: (local.get $x) + ;; NO_FO-NEXT: (then + ;; NO_FO-NEXT: (i32.const 1337) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: (else + ;; NO_FO-NEXT: (i32.const 42) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + (func $conditionals (param $x i32) (result i32) + ;; When we flip the if, the hint should flip too. + (@metadata.code.branch_hint "\00") + (if (result i32) + (i32.eqz + (local.get $x) + ) + (then + (i32.const 42) + ) + (else + (i32.const 1337) + ) + ) + ) + + ;; CHECK: (func $still-fold (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_FO: (func $still-fold (type $0) (param $x i32) (param $y i32) + ;; NO_FO-NEXT: (if + ;; NO_FO-NEXT: (local.get $x) + ;; NO_FO-NEXT: (then + ;; NO_FO-NEXT: (@metadata.code.branch_hint "\00") + ;; NO_FO-NEXT: (if + ;; NO_FO-NEXT: (local.get $y) + ;; NO_FO-NEXT: (then + ;; NO_FO-NEXT: (unreachable) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: (else + ;; NO_FO-NEXT: (@metadata.code.branch_hint "\01") + ;; NO_FO-NEXT: (if + ;; NO_FO-NEXT: (local.get $y) + ;; NO_FO-NEXT: (then + ;; NO_FO-NEXT: (unreachable) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + (func $still-fold (param $x i32) (param $y i32) + ;; We fold if arms even if metadata differs (like LLVM). We do not fold if the + ;; flag was passed, however. + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\00") + (if + (local.get $y) + (then + (unreachable) + ) + ) + ) + (else + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (unreachable) + ) + ) + ) + ) + ) + + ;; CHECK: (func $yes-fold (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_FO: (func $yes-fold (type $0) (param $x i32) (param $y i32) + ;; NO_FO-NEXT: (if + ;; NO_FO-NEXT: (local.get $x) + ;; NO_FO-NEXT: (then + ;; NO_FO-NEXT: (@metadata.code.branch_hint "\01") + ;; NO_FO-NEXT: (if + ;; NO_FO-NEXT: (local.get $y) + ;; NO_FO-NEXT: (then + ;; NO_FO-NEXT: (unreachable) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: (else + ;; NO_FO-NEXT: (@metadata.code.branch_hint "\01") + ;; NO_FO-NEXT: (if + ;; NO_FO-NEXT: (local.get $y) + ;; NO_FO-NEXT: (then + ;; NO_FO-NEXT: (unreachable) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + (func $yes-fold (param $x i32) (param $y i32) + ;; Now the hints match, so we definitely fold (without the flag). + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (unreachable) + ) + ) + ) + (else + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (unreachable) + ) + ) + ) + ) + ) + + ;; CHECK: (func $always-fold-select (type $2) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_FO: (func $always-fold-select (type $2) (param $x i32) (param $y i32) (result i32) + ;; NO_FO-NEXT: (@metadata.code.branch_hint "\00") + ;; NO_FO-NEXT: (if (result i32) + ;; NO_FO-NEXT: (local.get $x) + ;; NO_FO-NEXT: (then + ;; NO_FO-NEXT: (i32.const 10) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: (else + ;; NO_FO-NEXT: (i32.const 20) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + (func $always-fold-select (param $x i32) (param $y i32) (result i32) + ;; A select with different metadata is still foldable: the code was executed + ;; anyhow, so it's fine if we execute just one of the two (we pick the first, + ;; arbitrarily). We do so even with the flag. + (select + (@metadata.code.branch_hint "\00") + (if (result i32) + (local.get $x) + (then + (i32.const 10) + ) + (else + (i32.const 20) + ) + ) + (@metadata.code.branch_hint "\01") + (if (result i32) + (local.get $x) + (then + (i32.const 10) + ) + (else + (i32.const 20) + ) + ) + (local.get $y) + ) + ) + + ;; CHECK: (func $ordering (type $3) (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_FO: (func $ordering (type $3) (param $x i32) + ;; NO_FO-NEXT: (drop + ;; NO_FO-NEXT: (i32.add + ;; NO_FO-NEXT: (local.get $x) + ;; NO_FO-NEXT: (i32.const 42) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: (drop + ;; NO_FO-NEXT: (i32.add + ;; NO_FO-NEXT: (i32.const 42) + ;; NO_FO-NEXT: (local.get $x) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + ;; NO_FO-NEXT: ) + (func $ordering (param $x i32) + ;; Normally we canonicalize the sides of a binary like this (so after the + ;; pass, both the below expressions would be identical), but we refrain from + ;; doing so with the flag. + (drop + (i32.add + (local.get $x) + (i32.const 42) + ) + ) + (drop + (i32.add + (i32.const 42) + (local.get $x) + ) + ) + ) +) diff --git a/test/lit/passes/remove-unused-brs_branch-hints-unconditionalize.wast b/test/lit/passes/remove-unused-brs_branch-hints-unconditionalize.wast new file mode 100644 index 00000000000..145a18deccb --- /dev/null +++ b/test/lit/passes/remove-unused-brs_branch-hints-unconditionalize.wast @@ -0,0 +1,213 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s --remove-unused-brs -all -S -o - \ +;; RUN: | filecheck %s +;; RUN: wasm-opt %s --remove-unused-brs --pass-arg=remove-unused-brs-never-unconditionalize -all -S -o - \ +;; RUN: | filecheck %s --check-prefix=NO_UN + +;; Verify that the "never-unconditionalize" flag is respected: when set, we do +;; not run code unconditionally that previously might not have run. This is +;; important as the branch hint in un-executed code may be right or wrong, which +;; can confuse the fuzzer. + +(module + ;; CHECK: (func $selectify (type $0) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (block $out (result i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_UN: (func $selectify (type $0) (param $x i32) (param $y i32) (result i32) + ;; NO_UN-NEXT: (if (result i32) + ;; NO_UN-NEXT: (local.get $x) + ;; NO_UN-NEXT: (then + ;; NO_UN-NEXT: (local.get $y) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: (else + ;; NO_UN-NEXT: (@metadata.code.branch_hint "\01") + ;; NO_UN-NEXT: (if (result i32) + ;; NO_UN-NEXT: (local.get $y) + ;; NO_UN-NEXT: (then + ;; NO_UN-NEXT: (i32.const 10) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: (else + ;; NO_UN-NEXT: (block $out (result i32) + ;; NO_UN-NEXT: (nop) + ;; NO_UN-NEXT: (i32.const 20) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + (func $selectify (param $x i32) (param $y i32) (result i32) + ;; This if can be a select, but the nested if's branch hint will then + ;; always execute, which we should avoid when the flag is passed. + (if (result i32) + (local.get $x) + (then + (local.get $y) + ) + (else + (block $out (result i32) + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (br $out + (i32.const 10) + ) + ) + ) + (i32.const 20) + ) + ) + ) + ) + + ;; CHECK: (func $if-select (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_UN: (func $if-select (type $1) (param $x i32) (param $y i32) + ;; NO_UN-NEXT: (if + ;; NO_UN-NEXT: (local.get $x) + ;; NO_UN-NEXT: (then + ;; NO_UN-NEXT: (block $out + ;; NO_UN-NEXT: (br_if $out + ;; NO_UN-NEXT: (local.get $y) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + (func $if-select (param $x i32) (param $y i32) + ;; The br_if can be combined with the if using a select, but not when the + ;; flag is passed. + (block $out + (if + (local.get $x) + (then + (br_if $out + (local.get $y) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-select-2 (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_UN: (func $if-select-2 (type $1) (param $x i32) (param $y i32) + ;; NO_UN-NEXT: (if + ;; NO_UN-NEXT: (local.get $x) + ;; NO_UN-NEXT: (then + ;; NO_UN-NEXT: (if + ;; NO_UN-NEXT: (local.get $y) + ;; NO_UN-NEXT: (then + ;; NO_UN-NEXT: (nop) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + (func $if-select-2 (param $x i32) (param $y i32) + ;; The if conditions can be combined into one if with a select, but not when + ;; the flag is passed. + (if + (local.get $x) + (then + (if + (local.get $y) + (then + (nop) + ) + ) + ) + ) + ) + + ;; CHECK: (func $nothing (type $2) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; NO_UN: (func $nothing (type $2) + ;; NO_UN-NEXT: (nop) + ;; NO_UN-NEXT: ) + (func $nothing + ;; Helper for below. + (nop) + ) + + ;; CHECK: (func $restructure-br_if-value-effectful (type $0) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $nothing) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $x (result i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NO_UN: (func $restructure-br_if-value-effectful (type $0) (param $x i32) (param $y i32) (result i32) + ;; NO_UN-NEXT: (block $x (result i32) + ;; NO_UN-NEXT: (drop + ;; NO_UN-NEXT: (br_if $x + ;; NO_UN-NEXT: (block (result i32) + ;; NO_UN-NEXT: (call $nothing) + ;; NO_UN-NEXT: (local.get $y) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: (local.get $x) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: (i32.const 0) + ;; NO_UN-NEXT: ) + ;; NO_UN-NEXT: ) + (func $restructure-br_if-value-effectful (param $x i32) (param $y i32) (result i32) + ;; We can restructure this to a select, but not when the flag is passed. + (block $x (result i32) + (drop + (br_if $x + (block (result i32) + (call $nothing) + (local.get $y) + ) + (local.get $x) + ) + ) + (i32.const 0) + ) + ) +) From 9b8259b567a3c562f70f8f1cbcc7bc8746f2a531 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 25 Jul 2025 11:43:23 -0700 Subject: [PATCH 619/622] [Custom Descriptors] Preserve traps in AbstractTypeRefining (#7752) When AbstractTypeRefining optimized out descriptor casts targeting uninstantiated types, it previously did not preserve traps on null descriptors. Preserve the traps by inserting a ref.as_non_null on nullable descriptor operands. --- src/passes/AbstractTypeRefining.cpp | 10 + .../passes/abstract-type-refining-desc.wast | 254 ++++++++++++++---- 2 files changed, 212 insertions(+), 52 deletions(-) diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index 0c78895f687..831464a1b1b 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -334,6 +334,11 @@ struct AbstractTypeRefining : public Pass { if (!curr->desc || !curr->type.isNull()) { return; } + // Preserve the trap on a null descriptor. + if (curr->desc->type.isNullable()) { + curr->desc = + Builder(*getModule()).makeRefAs(RefAsNonNull, curr->desc); + } Block* replacement = ChildLocalizer(curr, getFunction(), *getModule(), getPassOptions()) .getChildrenReplacement(); @@ -352,6 +357,11 @@ struct AbstractTypeRefining : public Pass { return; } bool isFail = curr->op == BrOnCastDescFail; + // Preserve the trap on a null descriptor. + if (curr->desc->type.isNullable()) { + curr->desc = + Builder(*getModule()).makeRefAs(RefAsNonNull, curr->desc); + } Block* replacement = ChildLocalizer(curr, getFunction(), *getModule(), getPassOptions()) .getChildrenReplacement(); diff --git a/test/lit/passes/abstract-type-refining-desc.wast b/test/lit/passes/abstract-type-refining-desc.wast index c08845cccd7..2c51023239d 100644 --- a/test/lit/passes/abstract-type-refining-desc.wast +++ b/test/lit/passes/abstract-type-refining-desc.wast @@ -87,14 +87,18 @@ ;; YESTNH: (type $2 (func (param anyref (ref null $desc)) (result nullref))) - ;; YESTNH: (type $3 (func)) + ;; YESTNH: (type $3 (func (param anyref (ref $desc)) (result nullref))) - ;; YESTNH: (import "" "" (func $effect (type $3))) + ;; YESTNH: (type $4 (func)) + + ;; YESTNH: (import "" "" (func $effect (type $4))) ;; NO_TNH: (type $2 (func (param anyref (ref null $desc)) (result nullref))) - ;; NO_TNH: (type $3 (func)) + ;; NO_TNH: (type $3 (func (param anyref (ref $desc)) (result nullref))) + + ;; NO_TNH: (type $4 (func)) - ;; NO_TNH: (import "" "" (func $effect (type $3))) + ;; NO_TNH: (import "" "" (func $effect (type $4))) (import "" "" (func $effect)) ;; YESTNH: (global $desc (ref $desc) (struct.new_default $desc)) @@ -107,6 +111,12 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $cast-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 (ref $desc)) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (ref.cast nullref ;; NO_TNH-NEXT: (local.get $ref) ;; NO_TNH-NEXT: ) @@ -124,7 +134,7 @@ ;; YESTNH: (func $cast-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) ;; YESTNH-NEXT: (local $2 anyref) - ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (local $3 (ref $desc)) ;; YESTNH-NEXT: (local.set $2 ;; YESTNH-NEXT: (block (result anyref) ;; YESTNH-NEXT: (call $effect) @@ -132,9 +142,11 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (local.set $3 - ;; YESTNH-NEXT: (block (result (ref null $desc)) - ;; YESTNH-NEXT: (call $effect) - ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: (ref.as_non_null + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (ref.cast nullref @@ -143,7 +155,7 @@ ;; YESTNH-NEXT: ) ;; NO_TNH: (func $cast-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) ;; NO_TNH-NEXT: (local $2 anyref) - ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (local $3 (ref $desc)) ;; NO_TNH-NEXT: (local.set $2 ;; NO_TNH-NEXT: (block (result anyref) ;; NO_TNH-NEXT: (call $effect) @@ -151,9 +163,11 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (local.set $3 - ;; NO_TNH-NEXT: (block (result (ref null $desc)) - ;; NO_TNH-NEXT: (call $effect) - ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (ref.cast nullref @@ -180,6 +194,12 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $cast-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 (ref $desc)) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (ref.cast (ref none) ;; NO_TNH-NEXT: (local.get $ref) ;; NO_TNH-NEXT: ) @@ -192,9 +212,27 @@ ) ) - ;; YESTNH: (func $cast-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH: (func $cast-non-nullable-desc (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) + ;; YESTNH-NEXT: (ref.cast (ref none) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $cast-non-nullable-desc (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) + ;; NO_TNH-NEXT: (ref.cast (ref none) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $cast-non-nullable-desc (param $ref anyref) (param $desc (ref $desc)) (result (ref null $struct)) + ;; Same, but now the descriptor is additionally non-null. + (ref.cast_desc (ref $struct) + (local.get $ref) + (local.get $desc) + ) + ) + + ;; YESTNH: (func $cast-non-nullable-effect (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) ;; YESTNH-NEXT: (local $2 anyref) - ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (local $3 (ref $desc)) ;; YESTNH-NEXT: (local.set $2 ;; YESTNH-NEXT: (block (result anyref) ;; YESTNH-NEXT: (call $effect) @@ -202,7 +240,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (local.set $3 - ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (block (result (ref $desc)) ;; YESTNH-NEXT: (call $effect) ;; YESTNH-NEXT: (local.get $desc) ;; YESTNH-NEXT: ) @@ -211,9 +249,9 @@ ;; YESTNH-NEXT: (local.get $2) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $cast-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH: (func $cast-non-nullable-effect (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) ;; NO_TNH-NEXT: (local $2 anyref) - ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (local $3 (ref $desc)) ;; NO_TNH-NEXT: (local.set $2 ;; NO_TNH-NEXT: (block (result anyref) ;; NO_TNH-NEXT: (call $effect) @@ -221,7 +259,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (local.set $3 - ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (block (result (ref $desc)) ;; NO_TNH-NEXT: (call $effect) ;; NO_TNH-NEXT: (local.get $desc) ;; NO_TNH-NEXT: ) @@ -230,14 +268,14 @@ ;; NO_TNH-NEXT: (local.get $2) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) - (func $cast-non-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (func $cast-non-nullable-effect (param $ref anyref) (param $desc (ref $desc)) (result (ref null $struct)) ;; Same, but with side effects we cannot drop. (ref.cast_desc (ref $struct) (block (result anyref) (call $effect) (local.get $ref) ) - (block (result (ref null $desc)) + (block (result (ref $desc)) (call $effect) (local.get $desc) ) @@ -257,9 +295,15 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $branch-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 (ref $desc)) ;; NO_TNH-NEXT: (block $block (result nullref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block (result (ref any)) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (br_on_cast $block anyref nullref ;; NO_TNH-NEXT: (local.get $ref) ;; NO_TNH-NEXT: ) @@ -283,7 +327,7 @@ ;; YESTNH: (func $branch-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) ;; YESTNH-NEXT: (local $2 anyref) - ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (local $3 (ref $desc)) ;; YESTNH-NEXT: (block $block (result nullref) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (block (result (ref any)) @@ -294,9 +338,11 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (local.set $3 - ;; YESTNH-NEXT: (block (result (ref null $desc)) - ;; YESTNH-NEXT: (call $effect) - ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: (ref.as_non_null + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (br_on_cast $block anyref nullref @@ -309,7 +355,7 @@ ;; YESTNH-NEXT: ) ;; NO_TNH: (func $branch-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) ;; NO_TNH-NEXT: (local $2 anyref) - ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (local $3 (ref $desc)) ;; NO_TNH-NEXT: (block $block (result nullref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block (result (ref any)) @@ -320,9 +366,11 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (local.set $3 - ;; NO_TNH-NEXT: (block (result (ref null $desc)) - ;; NO_TNH-NEXT: (call $effect) - ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (br_on_cast $block anyref nullref @@ -365,9 +413,15 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $branch-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 (ref $desc)) ;; NO_TNH-NEXT: (block $block (result (ref none)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (br_on_cast $block anyref (ref none) ;; NO_TNH-NEXT: (local.get $ref) ;; NO_TNH-NEXT: ) @@ -389,9 +443,46 @@ ) ) - ;; YESTNH: (func $branch-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH: (func $branch-non-nullable-desc (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) + ;; YESTNH-NEXT: (block $block (result (ref none)) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block (result anyref) + ;; YESTNH-NEXT: (br_on_cast $block anyref (ref none) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-non-nullable-desc (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) + ;; NO_TNH-NEXT: (block $block (result (ref none)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block (result anyref) + ;; NO_TNH-NEXT: (br_on_cast $block anyref (ref none) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $branch-non-nullable-desc (param $ref anyref) (param $desc (ref $desc)) (result (ref null $struct)) + (block $block (result (ref null $struct)) + (drop + ;; Same, but now the descriptor is additionally non-null. + (br_on_cast_desc $block anyref (ref $struct) + (local.get $ref) + (local.get $desc) + ) + ) + (unreachable) + ) + ) + + ;; YESTNH: (func $branch-non-nullable-effect (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) ;; YESTNH-NEXT: (local $2 anyref) - ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (local $3 (ref $desc)) ;; YESTNH-NEXT: (block $block (result (ref none)) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (block (result anyref) @@ -402,7 +493,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (local.set $3 - ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (block (result (ref $desc)) ;; YESTNH-NEXT: (call $effect) ;; YESTNH-NEXT: (local.get $desc) ;; YESTNH-NEXT: ) @@ -415,9 +506,9 @@ ;; YESTNH-NEXT: (unreachable) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $branch-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH: (func $branch-non-nullable-effect (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) ;; NO_TNH-NEXT: (local $2 anyref) - ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (local $3 (ref $desc)) ;; NO_TNH-NEXT: (block $block (result (ref none)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block (result anyref) @@ -428,7 +519,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (local.set $3 - ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (block (result (ref $desc)) ;; NO_TNH-NEXT: (call $effect) ;; NO_TNH-NEXT: (local.get $desc) ;; NO_TNH-NEXT: ) @@ -441,7 +532,7 @@ ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) - (func $branch-non-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (func $branch-non-nullable-effect (param $ref anyref) (param $desc (ref $desc)) (result (ref null $struct)) (block $block (result (ref null $struct)) (drop ;; Same, but with effects we cannot drop. @@ -450,7 +541,7 @@ (call $effect) (local.get $ref) ) - (block (result (ref null $desc)) + (block (result (ref $desc)) (call $effect) (local.get $desc) ) @@ -475,10 +566,16 @@ ;; YESTNH-NEXT: (unreachable) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $branch-fail-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 (ref $desc)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block $block (result (ref any)) ;; NO_TNH-NEXT: (return ;; NO_TNH-NEXT: (block (result nullref) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (br_on_cast_fail $block anyref nullref ;; NO_TNH-NEXT: (local.get $ref) ;; NO_TNH-NEXT: ) @@ -505,7 +602,7 @@ ;; YESTNH: (func $branch-fail-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) ;; YESTNH-NEXT: (local $2 anyref) - ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (local $3 (ref $desc)) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (block $block (result (ref any)) ;; YESTNH-NEXT: (return @@ -517,9 +614,11 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (local.set $3 - ;; YESTNH-NEXT: (block (result (ref null $desc)) - ;; YESTNH-NEXT: (call $effect) - ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: (ref.as_non_null + ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (call $effect) + ;; YESTNH-NEXT: (local.get $desc) + ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (br_on_cast_fail $block anyref nullref @@ -533,7 +632,7 @@ ;; YESTNH-NEXT: ) ;; NO_TNH: (func $branch-fail-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) ;; NO_TNH-NEXT: (local $2 anyref) - ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (local $3 (ref $desc)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block $block (result (ref any)) ;; NO_TNH-NEXT: (return @@ -545,9 +644,11 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (local.set $3 - ;; NO_TNH-NEXT: (block (result (ref null $desc)) - ;; NO_TNH-NEXT: (call $effect) - ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (call $effect) + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (br_on_cast_fail $block anyref nullref @@ -595,10 +696,16 @@ ;; YESTNH-NEXT: (unreachable) ;; YESTNH-NEXT: ) ;; NO_TNH: (func $branch-fail-non-nullable (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH-NEXT: (local $2 (ref $desc)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block $block (result anyref) ;; NO_TNH-NEXT: (return ;; NO_TNH-NEXT: (block (result (ref none)) + ;; NO_TNH-NEXT: (local.set $2 + ;; NO_TNH-NEXT: (ref.as_non_null + ;; NO_TNH-NEXT: (local.get $desc) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (br_on_cast_fail $block anyref (ref none) ;; NO_TNH-NEXT: (local.get $ref) ;; NO_TNH-NEXT: ) @@ -623,9 +730,52 @@ (unreachable) ) - ;; YESTNH: (func $branch-fail-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; YESTNH: (func $branch-fail-non-nullable-desc (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (block $block (result anyref) + ;; YESTNH-NEXT: (return + ;; YESTNH-NEXT: (block (result (ref none)) + ;; YESTNH-NEXT: (br_on_cast_fail $block anyref (ref none) + ;; YESTNH-NEXT: (local.get $ref) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $branch-fail-non-nullable-desc (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block $block (result anyref) + ;; NO_TNH-NEXT: (return + ;; NO_TNH-NEXT: (block (result (ref none)) + ;; NO_TNH-NEXT: (br_on_cast_fail $block anyref (ref none) + ;; NO_TNH-NEXT: (local.get $ref) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + (func $branch-fail-non-nullable-desc (param $ref anyref) (param $desc (ref $desc)) (result (ref null $struct)) + (drop + (block $block (result anyref) + (return + ;; Same, but now the descriptor is additionally non-null. + (br_on_cast_desc_fail $block anyref (ref $struct) + (local.get $ref) + (local.get $desc) + ) + ) + ) + ) + (unreachable) + ) + + ;; YESTNH: (func $branch-fail-non-nullable-effect (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) ;; YESTNH-NEXT: (local $2 anyref) - ;; YESTNH-NEXT: (local $3 (ref null $desc)) + ;; YESTNH-NEXT: (local $3 (ref $desc)) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (block $block (result anyref) ;; YESTNH-NEXT: (return @@ -637,7 +787,7 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (local.set $3 - ;; YESTNH-NEXT: (block (result (ref null $desc)) + ;; YESTNH-NEXT: (block (result (ref $desc)) ;; YESTNH-NEXT: (call $effect) ;; YESTNH-NEXT: (local.get $desc) ;; YESTNH-NEXT: ) @@ -651,9 +801,9 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (unreachable) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $branch-fail-non-nullable-effect (type $2) (param $ref anyref) (param $desc (ref null $desc)) (result nullref) + ;; NO_TNH: (func $branch-fail-non-nullable-effect (type $3) (param $ref anyref) (param $desc (ref $desc)) (result nullref) ;; NO_TNH-NEXT: (local $2 anyref) - ;; NO_TNH-NEXT: (local $3 (ref null $desc)) + ;; NO_TNH-NEXT: (local $3 (ref $desc)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (block $block (result anyref) ;; NO_TNH-NEXT: (return @@ -665,7 +815,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (local.set $3 - ;; NO_TNH-NEXT: (block (result (ref null $desc)) + ;; NO_TNH-NEXT: (block (result (ref $desc)) ;; NO_TNH-NEXT: (call $effect) ;; NO_TNH-NEXT: (local.get $desc) ;; NO_TNH-NEXT: ) @@ -679,7 +829,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) - (func $branch-fail-non-nullable-effect (param $ref anyref) (param $desc (ref null $desc)) (result (ref null $struct)) + (func $branch-fail-non-nullable-effect (param $ref anyref) (param $desc (ref $desc)) (result (ref null $struct)) (drop (block $block (result anyref) (return @@ -689,7 +839,7 @@ (call $effect) (local.get $ref) ) - (block (result (ref null $desc)) + (block (result (ref $desc)) (call $effect) (local.get $desc) ) From 7bee1a13991b6a34cb1f83174045264978611af3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 25 Jul 2025 11:44:08 -0700 Subject: [PATCH 620/622] Implement pause (#7751) The `pause` instruction is introduced by the shared-everything-threads proposal to make spinlocks more efficient. It has no observable semantics, so it is essentially a fancy nop. --- scripts/gen-s-parser.py | 1 + src/gen-s-parser.inc | 21 +++-- src/interpreter/interpreter.cpp | 1 + src/ir/ReFinalize.cpp | 1 + src/ir/child-typer.h | 2 + src/ir/cost.h | 5 ++ src/ir/effects.h | 5 ++ src/ir/possible-contents.cpp | 1 + src/ir/subtype-exprs.h | 1 + src/parser/contexts.h | 5 ++ src/parser/parsers.h | 8 ++ src/passes/Print.cpp | 1 + src/passes/TypeGeneralizing.cpp | 1 + src/wasm-binary.h | 1 + src/wasm-builder.h | 1 + src/wasm-delegations-fields.def | 3 + src/wasm-delegations.def | 1 + src/wasm-interpreter.h | 4 + src/wasm-ir-builder.h | 1 + src/wasm.h | 7 +- src/wasm/wasm-binary.cpp | 2 + src/wasm/wasm-ir-builder.cpp | 5 ++ src/wasm/wasm-stack.cpp | 4 + src/wasm/wasm.cpp | 2 - src/wasm2js.h | 5 ++ test/binaryen.js/exception-handling.js.txt | 8 +- test/binaryen.js/expressions.js | 3 - test/binaryen.js/kitchen-sink.js.txt | 98 +++++++++++----------- test/lit/basic/pause.wast | 32 +++++++ 29 files changed, 166 insertions(+), 64 deletions(-) create mode 100644 test/lit/basic/pause.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 7370b3c69bf..bc179122732 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -207,6 +207,7 @@ ("memory.atomic.wait32", "makeAtomicWait(Type::i32)"), ("memory.atomic.wait64", "makeAtomicWait(Type::i64)"), ("atomic.fence", "makeAtomicFence()"), + ("pause", "makePause()"), ("i32.atomic.load8_u", "makeLoad(Type::i32, /*signed=*/false, 1, /*isAtomic=*/true)"), ("i32.atomic.load16_u", "makeLoad(Type::i32, /*signed=*/false, 2, /*isAtomic=*/true)"), ("i32.atomic.load", "makeLoad(Type::i32, /*signed=*/false, 4, /*isAtomic=*/true)"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index de588f76118..a21d943b8ca 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -4859,12 +4859,23 @@ switch (buf[0]) { return Ok{}; } goto parse_error; - case 'p': - if (op == "pop"sv) { - CHECK_ERR(makePop(ctx, pos, annotations)); - return Ok{}; + case 'p': { + switch (buf[1]) { + case 'a': + if (op == "pause"sv) { + CHECK_ERR(makePause(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case 'o': + if (op == "pop"sv) { + CHECK_ERR(makePop(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } case 'r': { switch (buf[2]) { case 'f': { diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 0e97cf4f39e..8007f011dcc 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -78,6 +78,7 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitAtomicWait(AtomicWait* curr) { WASM_UNREACHABLE("TODO"); } Flow visitAtomicNotify(AtomicNotify* curr) { WASM_UNREACHABLE("TODO"); } Flow visitAtomicFence(AtomicFence* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitPause(Pause* curr) { WASM_UNREACHABLE("TODO"); } Flow visitSIMDExtract(SIMDExtract* curr) { WASM_UNREACHABLE("TODO"); } Flow visitSIMDReplace(SIMDReplace* curr) { WASM_UNREACHABLE("TODO"); } Flow visitSIMDShuffle(SIMDShuffle* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index cbb5c40f4f6..8636a7c98cb 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -91,6 +91,7 @@ void ReFinalize::visitAtomicCmpxchg(AtomicCmpxchg* curr) { curr->finalize(); } void ReFinalize::visitAtomicWait(AtomicWait* curr) { curr->finalize(); } void ReFinalize::visitAtomicNotify(AtomicNotify* curr) { curr->finalize(); } void ReFinalize::visitAtomicFence(AtomicFence* curr) { curr->finalize(); } +void ReFinalize::visitPause(Pause* curr) { curr->finalize(); } void ReFinalize::visitSIMDExtract(SIMDExtract* curr) { curr->finalize(); } void ReFinalize::visitSIMDReplace(SIMDReplace* curr) { curr->finalize(); } void ReFinalize::visitSIMDShuffle(SIMDShuffle* curr) { curr->finalize(); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index c3b34e7a256..d0b2ef4dc92 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -244,6 +244,8 @@ template struct ChildTyper : OverriddenVisitor { void visitAtomicFence(AtomicFence* curr) {} + void visitPause(Pause* curr) {} + void visitSIMDExtract(SIMDExtract* curr) { note(&curr->vec, Type::v128); } void visitSIMDReplace(SIMDReplace* curr) { diff --git a/src/ir/cost.h b/src/ir/cost.h index f68725d4bc8..6f15731f1aa 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -116,6 +116,11 @@ struct CostAnalyzer : public OverriddenVisitor { return AtomicCost + visit(curr->ptr) + visit(curr->notifyCount); } CostType visitAtomicFence(AtomicFence* curr) { return AtomicCost; } + CostType visitPause(Pause* curr) { + // When used properly, pause only makes things more efficient, so we do not + // model it as having any cost. + return 0; + } CostType visitConst(Const* curr) { return 1; } CostType visitUnary(Unary* curr) { CostType ret = 0; diff --git a/src/ir/effects.h b/src/ir/effects.h index 57a12720f71..0463a3bf686 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -643,6 +643,11 @@ class EffectAnalyzer { parent.writesMemory = true; parent.isAtomic = true; } + void visitPause(Pause* curr) { + // It's not much of a problem if pause gets reordered with anything, but + // we don't want it to be removed entirely. + parent.isAtomic = true; + } void visitSIMDExtract(SIMDExtract* curr) {} void visitSIMDReplace(SIMDReplace* curr) {} void visitSIMDShuffle(SIMDShuffle* curr) {} diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 86323801069..28d2560b2d4 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -593,6 +593,7 @@ struct InfoCollector void visitAtomicWait(AtomicWait* curr) { addRoot(curr); } void visitAtomicNotify(AtomicNotify* curr) { addRoot(curr); } void visitAtomicFence(AtomicFence* curr) {} + void visitPause(Pause* curr) {} void visitSIMDExtract(SIMDExtract* curr) { addRoot(curr); } void visitSIMDReplace(SIMDReplace* curr) { addRoot(curr); } void visitSIMDShuffle(SIMDShuffle* curr) { addRoot(curr); } diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 78d3ef5232c..b0c1a3838b8 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -188,6 +188,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitAtomicWait(AtomicWait* curr) {} void visitAtomicNotify(AtomicNotify* curr) {} void visitAtomicFence(AtomicFence* curr) {} + void visitPause(Pause* curr) {} void visitSIMDExtract(SIMDExtract* curr) {} void visitSIMDReplace(SIMDReplace* curr) {} void visitSIMDShuffle(SIMDShuffle* curr) {} diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 82f4d31ce63..ed8bebc02d8 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -593,6 +593,7 @@ struct NullInstrParserCtx { Result<> makeAtomicFence(Index, const std::vector&) { return Ok{}; } + Result<> makePause(Index, const std::vector&) { return Ok{}; } Result<> makeSIMDExtract(Index, const std::vector&, SIMDExtractOp, @@ -2301,6 +2302,10 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeAtomicFence()); } + Result<> makePause(Index pos, const std::vector& annotations) { + return withLoc(pos, irBuilder.makePause()); + } + Result<> makeSIMDExtract(Index pos, const std::vector& annotations, SIMDExtractOp op, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 7b9c28cef68..65753942864 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -142,6 +142,8 @@ Result<> makeAtomicNotify(Ctx&, Index, const std::vector&); template Result<> makeAtomicFence(Ctx&, Index, const std::vector&); template +Result<> makePause(Ctx&, Index, const std::vector&); +template Result<> makeSIMDExtract( Ctx&, Index, const std::vector&, SIMDExtractOp op, size_t lanes); template @@ -1815,6 +1817,12 @@ Result<> makeAtomicFence(Ctx& ctx, return ctx.makeAtomicFence(pos, annotations); } +template +Result<> +makePause(Ctx& ctx, Index pos, const std::vector& annotations) { + return ctx.makePause(pos, annotations); +} + template Result<> makeSIMDExtract(Ctx& ctx, Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 45233f7728e..17c2ea8e2bb 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -689,6 +689,7 @@ struct PrintExpressionContents } } void visitAtomicFence(AtomicFence* curr) { printMedium(o, "atomic.fence"); } + void visitPause(Pause* curr) { printMedium(o, "pause"); } void visitSIMDExtract(SIMDExtract* curr) { prepareColor(o); switch (curr->op) { diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 89b9796416a..7ed65c602c7 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -417,6 +417,7 @@ struct TransferFn : OverriddenVisitor { void visitAtomicWait(AtomicWait* curr) {} void visitAtomicNotify(AtomicNotify* curr) {} void visitAtomicFence(AtomicFence* curr) {} + void visitPause(Pause* curr) {} void visitSIMDExtract(SIMDExtract* curr) {} void visitSIMDReplace(SIMDReplace* curr) {} void visitSIMDShuffle(SIMDShuffle* curr) {} diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 8cadd5c0072..8219ffbc947 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -693,6 +693,7 @@ enum ASTNodes { I32AtomicWait = 0x01, I64AtomicWait = 0x02, AtomicFence = 0x03, + Pause = 0x04, I32AtomicLoad = 0x10, I64AtomicLoad = 0x11, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 134409bb4cf..b190b848fcd 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -399,6 +399,7 @@ class Builder { return notify; } AtomicFence* makeAtomicFence() { return wasm.allocator.alloc(); } + Pause* makePause() { return wasm.allocator.alloc(); } Store* makeStore(unsigned bytes, Address offset, unsigned align, diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 28f008d5593..376648311f3 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -407,6 +407,9 @@ DELEGATE_FIELD_CASE_START(AtomicFence) DELEGATE_FIELD_INT(AtomicFence, order) DELEGATE_FIELD_CASE_END(AtomicFence) +DELEGATE_FIELD_CASE_START(Pause) +DELEGATE_FIELD_CASE_END(Pause) + DELEGATE_FIELD_CASE_START(SIMDExtract) DELEGATE_FIELD_CHILD(SIMDExtract, vec) DELEGATE_FIELD_INT(SIMDExtract, op) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index ce50255b896..dcec0e6e938 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -33,6 +33,7 @@ DELEGATE(AtomicCmpxchg); DELEGATE(AtomicWait); DELEGATE(AtomicNotify); DELEGATE(AtomicFence); +DELEGATE(Pause); DELEGATE(SIMDExtract); DELEGATE(SIMDReplace); DELEGATE(SIMDShuffle); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 701a89199fe..5cb7f1eb0a2 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1469,6 +1469,10 @@ class ExpressionRunner : public OverriddenVisitor { NOTE_ENTER("AtomicFence"); return Flow(); } + Flow visitPause(Pause* curr) { + NOTE_ENTER("AtomicFence"); + return Flow(); + } Flow visitTupleMake(TupleMake* curr) { NOTE_ENTER("tuple.make"); Literals arguments; diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index b1b2ff9226c..c1f01e955a0 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -165,6 +165,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeAtomicWait(Type type, Address offset, Name mem); Result<> makeAtomicNotify(Address offset, Name mem); Result<> makeAtomicFence(); + Result<> makePause(); Result<> makeSIMDExtract(SIMDExtractOp op, uint8_t lane); Result<> makeSIMDReplace(SIMDReplaceOp op, uint8_t lane); Result<> makeSIMDShuffle(const std::array& lanes); diff --git a/src/wasm.h b/src/wasm.h index 42de5ce890d..4059a8356f3 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -685,6 +685,7 @@ class Expression { AtomicWaitId, AtomicNotifyId, AtomicFenceId, + PauseId, SIMDExtractId, SIMDReplaceId, SIMDShuffleId, @@ -1085,8 +1086,12 @@ class AtomicFence : public SpecificExpression { // other orderings may be added in the future. This field is reserved for // that, and currently set to 0. uint8_t order = 0; +}; - void finalize(); +class Pause : public SpecificExpression { +public: + Pause() = default; + Pause(MixedArena& allocator) {} }; class SIMDExtract : public SpecificExpression { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 1437f2a8219..3c0bb5e07ed 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3728,6 +3728,8 @@ Result<> WasmBinaryReader::readInst() { return Err{"expected 0x00 byte immediate on atomic.fence"}; } return builder.makeAtomicFence(); + case BinaryConsts::Pause: + return builder.makePause(); case BinaryConsts::StructAtomicGet: case BinaryConsts::StructAtomicGetS: case BinaryConsts::StructAtomicGetU: { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 8912610932a..cafe222e3fb 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1648,6 +1648,11 @@ Result<> IRBuilder::makeAtomicFence() { return Ok{}; } +Result<> IRBuilder::makePause() { + push(builder.makePause()); + return Ok{}; +} + Result<> IRBuilder::makeSIMDExtract(SIMDExtractOp op, uint8_t lane) { SIMDExtract curr; CHECK_ERR(visitSIMDExtract(&curr)); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 0991fc77eff..793d2816dbd 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -569,6 +569,10 @@ void BinaryInstWriter::visitAtomicFence(AtomicFence* curr) { << int8_t(curr->order); } +void BinaryInstWriter::visitPause(Pause* curr) { + o << int8_t(BinaryConsts::AtomicPrefix) << U32LEB(BinaryConsts::Pause); +} + void BinaryInstWriter::visitSIMDExtract(SIMDExtract* curr) { o << int8_t(BinaryConsts::SIMDPrefix); switch (curr->op) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ee28c14f97d..64fd97c5153 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -376,8 +376,6 @@ void AtomicNotify::finalize() { } } -void AtomicFence::finalize() { type = Type::none; } - void SIMDExtract::finalize() { assert(vec); switch (op) { diff --git a/src/wasm2js.h b/src/wasm2js.h index abfcdd99a46..d6e0e4d6560 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2141,6 +2141,11 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, // TODOs + Ref visitPause(Pause* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } + Ref visitSIMDExtract(SIMDExtract* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/binaryen.js/exception-handling.js.txt b/test/binaryen.js/exception-handling.js.txt index d0a6367cb29..a26860f3eff 100644 --- a/test/binaryen.js/exception-handling.js.txt +++ b/test/binaryen.js/exception-handling.js.txt @@ -34,7 +34,7 @@ ) ) -getExpressionInfo(throw) = {"id":55,"type":1,"tag":"e"} -getExpressionInfo(rethrow) = {"id":56,"type":1,"target":"l0"} -getExpressionInfo(try_catch) = {"id":53,"type":1,"name":"l0","hasCatchAll":false,"delegateTarget":null,"isDelegate":false} -getExpressionInfo(try_delegate) = {"id":53,"type":0,"name":"try_outer","hasCatchAll":true,"delegateTarget":null,"isDelegate":false} +getExpressionInfo(throw) = {"id":56,"type":1,"tag":"e"} +getExpressionInfo(rethrow) = {"id":57,"type":1,"target":"l0"} +getExpressionInfo(try_catch) = {"id":54,"type":1,"name":"l0","hasCatchAll":false,"delegateTarget":null,"isDelegate":false} +getExpressionInfo(try_delegate) = {"id":54,"type":0,"name":"try_outer","hasCatchAll":true,"delegateTarget":null,"isDelegate":false} diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index 3596e5e5f7a..c750047c85e 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -1324,9 +1324,6 @@ console.log("# AtomicFence"); theAtomicFence.order = 1; assert(theAtomicFence.order === 1); - theAtomicFence.type = binaryen.f64; - theAtomicFence.finalize(); - assert(theAtomicFence.type === binaryen.none); info = binaryen.getExpressionInfo(theAtomicFence); assert(info.type === theAtomicFence.type); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 47977a46e03..2802300c194 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -62,55 +62,55 @@ AtomicCmpxchgId: 25 AtomicRMWId: 24 AtomicWaitId: 26 AtomicNotifyId: 27 -SIMDExtractId: 29 -SIMDReplaceId: 30 -SIMDShuffleId: 31 -SIMDTernaryId: 32 -SIMDShiftId: 33 -SIMDLoadId: 34 -SIMDLoadStoreLaneId: 35 -MemoryInitId: 36 -DataDropId: 37 -MemoryCopyId: 38 -MemoryFillId: 39 -PopId: 40 -RefNullId: 41 -RefIsNullId: 42 -RefFuncId: 43 -RefEqId: 44 -TableGetId: 45 -TableSetId: 46 -TableSizeId: 47 -TableGrowId: 48 -TryId: 53 -ThrowId: 55 -RethrowId: 56 -TupleMakeId: 58 -TupleExtractId: 59 -RefI31Id: 60 -I31GetId: 61 -CallRefId: 62 -RefTestId: 63 -RefCastId: 64 -BrOnId: 66 -StructNewId: 67 -StructGetId: 68 -StructSetId: 69 -ArrayNewId: 72 -ArrayNewFixedId: 75 -ArrayGetId: 76 -ArraySetId: 77 -ArrayLenId: 78 -ArrayCopy: 79 -RefAs: 85 -StringNew: 86 -StringConst: 87 -StringMeasure: 88 -StringEncode: 89 -StringConcat: 90 -StringEq: 91 -StringWTF16Get: 93 -StringSliceWTF: 94 +SIMDExtractId: 30 +SIMDReplaceId: 31 +SIMDShuffleId: 32 +SIMDTernaryId: 33 +SIMDShiftId: 34 +SIMDLoadId: 35 +SIMDLoadStoreLaneId: 36 +MemoryInitId: 37 +DataDropId: 38 +MemoryCopyId: 39 +MemoryFillId: 40 +PopId: 41 +RefNullId: 42 +RefIsNullId: 43 +RefFuncId: 44 +RefEqId: 45 +TableGetId: 46 +TableSetId: 47 +TableSizeId: 48 +TableGrowId: 49 +TryId: 54 +ThrowId: 56 +RethrowId: 57 +TupleMakeId: 59 +TupleExtractId: 60 +RefI31Id: 61 +I31GetId: 62 +CallRefId: 63 +RefTestId: 64 +RefCastId: 65 +BrOnId: 67 +StructNewId: 68 +StructGetId: 69 +StructSetId: 70 +ArrayNewId: 73 +ArrayNewFixedId: 76 +ArrayGetId: 77 +ArraySetId: 78 +ArrayLenId: 79 +ArrayCopy: 80 +RefAs: 86 +StringNew: 87 +StringConst: 88 +StringMeasure: 89 +StringEncode: 90 +StringConcat: 91 +StringEq: 92 +StringWTF16Get: 94 +StringSliceWTF: 95 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/lit/basic/pause.wast b/test/lit/basic/pause.wast new file mode 100644 index 00000000000..1bd4a2c9513 --- /dev/null +++ b/test/lit/basic/pause.wast @@ -0,0 +1,32 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + ;; CHECK-TEXT: (type $0 (func)) + + ;; CHECK-TEXT: (func $pause (type $0) + ;; CHECK-TEXT-NEXT: (pause) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (type $0 (func)) + + ;; CHECK-BIN: (func $pause (type $0) + ;; CHECK-BIN-NEXT: (pause) + ;; CHECK-BIN-NEXT: ) + (func $pause + ;; CHECK: (pause) + (pause) + ) +) +;; CHECK-BIN-NODEBUG: (type $0 (func)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $0) +;; CHECK-BIN-NODEBUG-NEXT: (pause) +;; CHECK-BIN-NODEBUG-NEXT: ) From f04073da73959a204d7b9e1aa6334241627a5616 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 25 Jul 2025 12:31:42 -0700 Subject: [PATCH 621/622] [NFC] Move j2cl test with descriptors to new file (#7753) The fuzzer does not yet handle custom descriptors, so it was tripping over the tests added to the existing test file. Fix the problem by moving the tests that use custom descriptors to a new file and ignore that file in the fuzzer. --- scripts/test/fuzzing.py | 1 + test/lit/passes/j2cl-merge-itables-desc.wast | 259 +++++++++++++++++++ test/lit/passes/j2cl-merge-itables.wast | 255 ------------------ 3 files changed, 260 insertions(+), 255 deletions(-) create mode 100644 test/lit/passes/j2cl-merge-itables-desc.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index f061f2897b4..a6a84101d65 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -131,6 +131,7 @@ 'abstract-type-refining-desc.wast', 'remove-unused-brs-desc.wast', 'vacuum-desc.wast', + 'j2cl-merge-itables-desc.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/test/lit/passes/j2cl-merge-itables-desc.wast b/test/lit/passes/j2cl-merge-itables-desc.wast new file mode 100644 index 00000000000..ec32f9eab14 --- /dev/null +++ b/test/lit/passes/j2cl-merge-itables-desc.wast @@ -0,0 +1,259 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --merge-j2cl-itables -all -S -o - | filecheck %s + +;; Custom descriptors - Shared itable instance. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $Object (sub (descriptor $Object.vtable (struct (field $itable (ref $Object.itable)))))) + (type $Object (sub (descriptor $Object.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct (field $itable (ref $Object.itable)))))) + (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $function (func)) + (type $function (func)) + + ;; The $Object.itable field (a structref) will be added as a field after + ;; the first field of this vtable. + ;; CHECK: (type $Object.vtable (sub (describes $Object (struct (field externref) (field structref))))) + (type $Object.vtable (sub (describes $Object (struct + (field externref))))) + + ;; The $Object.itable field (a structref) will be added as a field after + ;; the first field of this vtable. + ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct (field externref) (field structref) (field (ref $function)))))) + (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct + (field externref) + (field (ref $function)))))) + + ;; CHECK: (type $Object.itable (struct (field structref))) + (type $Object.itable (struct + (field (ref null struct)))) + ) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) + (global $Object.itable (ref $Object.itable) + (struct.new_default $Object.itable)) + + ;; CHECK: (global $SubObject.itable (ref $Object.itable) (global.get $Object.itable)) + (global $SubObject.itable (ref $Object.itable) + (global.get $Object.itable)) ;; uses shared empty itable instance. + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $SubObject.vtable (ref (exact $SubObject.vtable)) (struct.new $SubObject.vtable + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.func $SubObject.f) + ;; CHECK-NEXT: )) + (global $SubObject.vtable (ref (exact $SubObject.vtable)) + (struct.new $SubObject.vtable (ref.null extern) (ref.func $SubObject.f))) + + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $Object.vtable (ref (exact $Object.vtable)) (struct.new $Object.vtable + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $Object.vtable (ref (exact $Object.vtable)) + (struct.new $Object.vtable (ref.null extern))) + + ;; CHECK: (func $SubObject.f (type $function) + ;; CHECK-NEXT: ) + (func $SubObject.f + (type $function) + ) + + ;; CHECK: (func $usages (type $6) + ;; CHECK-NEXT: (local $o (ref null $SubObject)) + ;; CHECK-NEXT: (local.set $o + ;; CHECK-NEXT: (struct.new $SubObject + ;; CHECK-NEXT: (global.get $SubObject.itable) + ;; CHECK-NEXT: (global.get $SubObject.vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 2 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $usages + (local $o (ref null $SubObject)) + (local.set $o + (struct.new $SubObject + (global.get $SubObject.itable) + (global.get $SubObject.vtable))) + (drop + ;; The access to vtable field 0 is NOT offset and will remain an + ;; access to field 0. + (struct.get $SubObject.vtable 0 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to vtable field 1 is offset by the itable size and + ;; will be an access to field 2. + (struct.get $SubObject.vtable 1 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to itable field 0 will be rerouted to be an access to + ;; vtable field 1. + (struct.get $Object.itable 0 + (struct.get $SubObject $itable + (local.get $o)))) + ) +) + +;; Custom descriptors - Each type has its own itable. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $Object (sub (descriptor $Object.vtable (struct (field $itable (ref $Object.itable)))))) + (type $Object (sub (descriptor $Object.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct (field $itable (ref $SubObject.itable)))))) + (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct + (field $itable (ref $SubObject.itable)))))) + + ;; CHECK: (type $function (func)) + (type $function (func)) + + ;; CHECK: (type $Object.itable (sub (struct (field structref)))) + (type $Object.itable (sub (struct + (field (ref null struct))))) + + ;; CHECK: (type $SubObject.itable (sub $Object.itable (struct (field structref)))) + (type $SubObject.itable (sub $Object.itable + (struct (field (ref null struct))))) + + ;; The $Object.itable field (a structref) will be added as a field after + ;; the first field of this vtable. + ;; CHECK: (type $Object.vtable (sub (describes $Object (struct (field externref) (field structref))))) + (type $Object.vtable (sub (describes $Object (struct + (field externref))))) + + ;; The $SubObject.itable field (a structref) will be added as a field after + ;; the first field of this vtable. + ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct (field externref) (field structref) (field (ref $function)))))) + (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct + (field externref) + (field (ref $function)))))) + ) + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (type $7 (func)) + + ;; CHECK: (global $SubObject.vtable (ref (exact $SubObject.vtable)) (struct.new $SubObject.vtable + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.func $SubObject.f) + ;; CHECK-NEXT: )) + (global $SubObject.vtable (ref (exact $SubObject.vtable)) + (struct.new $SubObject.vtable (ref.null extern) (ref.func $SubObject.f))) + + ;; CHECK: (global $SubObject.itable (ref $SubObject.itable) (struct.new_default $SubObject.itable)) + (global $SubObject.itable (ref $SubObject.itable) + (struct.new_default $SubObject.itable)) + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $Object.vtable (ref (exact $Object.vtable)) (struct.new $Object.vtable + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $Object.vtable (ref (exact $Object.vtable)) + (struct.new $Object.vtable (ref.null extern))) + + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) + (global $Object.itable (ref $Object.itable) + (struct.new_default $Object.itable)) + + ;; CHECK: (func $SubObject.f (type $function) + ;; CHECK-NEXT: ) + (func $SubObject.f + (type $function) + ) + + ;; CHECK: (func $usages (type $7) + ;; CHECK-NEXT: (local $o (ref null $SubObject)) + ;; CHECK-NEXT: (local.set $o + ;; CHECK-NEXT: (struct.new $SubObject + ;; CHECK-NEXT: (global.get $SubObject.itable) + ;; CHECK-NEXT: (global.get $SubObject.vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 2 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $usages + (local $o (ref null $SubObject)) + (local.set $o + (struct.new $SubObject + (global.get $SubObject.itable) + (global.get $SubObject.vtable))) + (drop + ;; The access to vtable field 0 is NOT offset and will remain an + ;; access to field 0. + (struct.get $SubObject.vtable 0 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to vtable field 1 is offset by the itable size and + ;; will be an access to field 2. + (struct.get $SubObject.vtable 1 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to itable field 0 will be rerouted to be an access to + ;; vtable field 1. + (struct.get $Object.itable 0 + (struct.get $SubObject $itable + (local.get $o)))) + ) +) diff --git a/test/lit/passes/j2cl-merge-itables.wast b/test/lit/passes/j2cl-merge-itables.wast index 2765f873c32..03d4f21afd6 100644 --- a/test/lit/passes/j2cl-merge-itables.wast +++ b/test/lit/passes/j2cl-merge-itables.wast @@ -227,258 +227,3 @@ (local.get $o)))) ) ) - -;; Custom descriptors - Shared itable instance. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $Object (sub (descriptor $Object.vtable (struct (field $itable (ref $Object.itable)))))) - (type $Object (sub (descriptor $Object.vtable (struct - (field $itable (ref $Object.itable)))))) - - ;; CHECK: (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct (field $itable (ref $Object.itable)))))) - (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct - (field $itable (ref $Object.itable)))))) - - ;; CHECK: (type $function (func)) - (type $function (func)) - - ;; The $Object.itable field (a structref) will be added as a field after - ;; the first field of this vtable. - ;; CHECK: (type $Object.vtable (sub (describes $Object (struct (field externref) (field structref))))) - (type $Object.vtable (sub (describes $Object (struct - (field externref))))) - - ;; The $Object.itable field (a structref) will be added as a field after - ;; the first field of this vtable. - ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct (field externref) (field structref) (field (ref $function)))))) - (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct - (field externref) - (field (ref $function)))))) - - ;; CHECK: (type $Object.itable (struct (field structref))) - (type $Object.itable (struct - (field (ref null struct)))) - ) - - ;; CHECK: (type $6 (func)) - - ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) - (global $Object.itable (ref $Object.itable) - (struct.new_default $Object.itable)) - - ;; CHECK: (global $SubObject.itable (ref $Object.itable) (global.get $Object.itable)) - (global $SubObject.itable (ref $Object.itable) - (global.get $Object.itable)) ;; uses shared empty itable instance. - - ;; The initialization for the itable field (null struct) will be added to this - ;; vtable instance. - ;; CHECK: (global $SubObject.vtable (ref (exact $SubObject.vtable)) (struct.new $SubObject.vtable - ;; CHECK-NEXT: (ref.null noextern) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (ref.func $SubObject.f) - ;; CHECK-NEXT: )) - (global $SubObject.vtable (ref (exact $SubObject.vtable)) - (struct.new $SubObject.vtable (ref.null extern) (ref.func $SubObject.f))) - - - ;; The initialization for the itable field (null struct) will be added to this - ;; vtable instance. - ;; CHECK: (global $Object.vtable (ref (exact $Object.vtable)) (struct.new $Object.vtable - ;; CHECK-NEXT: (ref.null noextern) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: )) - (global $Object.vtable (ref (exact $Object.vtable)) - (struct.new $Object.vtable (ref.null extern))) - - ;; CHECK: (func $SubObject.f (type $function) - ;; CHECK-NEXT: ) - (func $SubObject.f - (type $function) - ) - - ;; CHECK: (func $usages (type $6) - ;; CHECK-NEXT: (local $o (ref null $SubObject)) - ;; CHECK-NEXT: (local.set $o - ;; CHECK-NEXT: (struct.new $SubObject - ;; CHECK-NEXT: (global.get $SubObject.itable) - ;; CHECK-NEXT: (global.get $SubObject.vtable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 - ;; CHECK-NEXT: (ref.get_desc $SubObject - ;; CHECK-NEXT: (local.get $o) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $SubObject.vtable 2 - ;; CHECK-NEXT: (ref.get_desc $SubObject - ;; CHECK-NEXT: (local.get $o) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 - ;; CHECK-NEXT: (ref.get_desc $SubObject - ;; CHECK-NEXT: (local.get $o) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $usages - (local $o (ref null $SubObject)) - (local.set $o - (struct.new $SubObject - (global.get $SubObject.itable) - (global.get $SubObject.vtable))) - (drop - ;; The access to vtable field 0 is NOT offset and will remain an - ;; access to field 0. - (struct.get $SubObject.vtable 0 - (ref.get_desc $SubObject - (local.get $o)))) - (drop - ;; The access to vtable field 1 is offset by the itable size and - ;; will be an access to field 2. - (struct.get $SubObject.vtable 1 - (ref.get_desc $SubObject - (local.get $o)))) - (drop - ;; The access to itable field 0 will be rerouted to be an access to - ;; vtable field 1. - (struct.get $Object.itable 0 - (struct.get $SubObject $itable - (local.get $o)))) - ) -) - -;; Custom descriptors - Each type has its own itable. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $Object (sub (descriptor $Object.vtable (struct (field $itable (ref $Object.itable)))))) - (type $Object (sub (descriptor $Object.vtable (struct - (field $itable (ref $Object.itable)))))) - - ;; CHECK: (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct (field $itable (ref $SubObject.itable)))))) - (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct - (field $itable (ref $SubObject.itable)))))) - - ;; CHECK: (type $function (func)) - (type $function (func)) - - ;; CHECK: (type $Object.itable (sub (struct (field structref)))) - (type $Object.itable (sub (struct - (field (ref null struct))))) - - ;; CHECK: (type $SubObject.itable (sub $Object.itable (struct (field structref)))) - (type $SubObject.itable (sub $Object.itable - (struct (field (ref null struct))))) - - ;; The $Object.itable field (a structref) will be added as a field after - ;; the first field of this vtable. - ;; CHECK: (type $Object.vtable (sub (describes $Object (struct (field externref) (field structref))))) - (type $Object.vtable (sub (describes $Object (struct - (field externref))))) - - ;; The $SubObject.itable field (a structref) will be added as a field after - ;; the first field of this vtable. - ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct (field externref) (field structref) (field (ref $function)))))) - (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct - (field externref) - (field (ref $function)))))) - ) - - ;; The initialization for the itable field (null struct) will be added to this - ;; vtable instance. - ;; CHECK: (type $7 (func)) - - ;; CHECK: (global $SubObject.vtable (ref (exact $SubObject.vtable)) (struct.new $SubObject.vtable - ;; CHECK-NEXT: (ref.null noextern) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (ref.func $SubObject.f) - ;; CHECK-NEXT: )) - (global $SubObject.vtable (ref (exact $SubObject.vtable)) - (struct.new $SubObject.vtable (ref.null extern) (ref.func $SubObject.f))) - - ;; CHECK: (global $SubObject.itable (ref $SubObject.itable) (struct.new_default $SubObject.itable)) - (global $SubObject.itable (ref $SubObject.itable) - (struct.new_default $SubObject.itable)) - - ;; The initialization for the itable field (null struct) will be added to this - ;; vtable instance. - ;; CHECK: (global $Object.vtable (ref (exact $Object.vtable)) (struct.new $Object.vtable - ;; CHECK-NEXT: (ref.null noextern) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: )) - (global $Object.vtable (ref (exact $Object.vtable)) - (struct.new $Object.vtable (ref.null extern))) - - ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) - (global $Object.itable (ref $Object.itable) - (struct.new_default $Object.itable)) - - ;; CHECK: (func $SubObject.f (type $function) - ;; CHECK-NEXT: ) - (func $SubObject.f - (type $function) - ) - - ;; CHECK: (func $usages (type $7) - ;; CHECK-NEXT: (local $o (ref null $SubObject)) - ;; CHECK-NEXT: (local.set $o - ;; CHECK-NEXT: (struct.new $SubObject - ;; CHECK-NEXT: (global.get $SubObject.itable) - ;; CHECK-NEXT: (global.get $SubObject.vtable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 - ;; CHECK-NEXT: (ref.get_desc $SubObject - ;; CHECK-NEXT: (local.get $o) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $SubObject.vtable 2 - ;; CHECK-NEXT: (ref.get_desc $SubObject - ;; CHECK-NEXT: (local.get $o) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 - ;; CHECK-NEXT: (ref.get_desc $SubObject - ;; CHECK-NEXT: (local.get $o) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $usages - (local $o (ref null $SubObject)) - (local.set $o - (struct.new $SubObject - (global.get $SubObject.itable) - (global.get $SubObject.vtable))) - (drop - ;; The access to vtable field 0 is NOT offset and will remain an - ;; access to field 0. - (struct.get $SubObject.vtable 0 - (ref.get_desc $SubObject - (local.get $o)))) - (drop - ;; The access to vtable field 1 is offset by the itable size and - ;; will be an access to field 2. - (struct.get $SubObject.vtable 1 - (ref.get_desc $SubObject - (local.get $o)))) - (drop - ;; The access to itable field 0 will be rerouted to be an access to - ;; vtable field 1. - (struct.get $Object.itable 0 - (struct.get $SubObject $itable - (local.get $o)))) - ) -) From 70fca9253666eee20d63e38c6e19c81587989193 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 25 Jul 2025 14:52:37 -0700 Subject: [PATCH 622/622] Pause requires shared-everything (#7754) Validate that shared-everything is enabled when the module contains a pause instruction. This will keep the fuzzer from trying to execute pauses on V8 and failing because we don't yet enable shared-everything on V8. --- src/wasm/wasm-validator.cpp | 7 +++++++ test/lit/validation/pause.wast | 11 +++++++++++ 2 files changed, 18 insertions(+) create mode 100644 test/lit/validation/pause.wast diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index c4416a9b39e..609dc57937c 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -451,6 +451,7 @@ struct FunctionValidator : public WalkerPass> { void visitAtomicWait(AtomicWait* curr); void visitAtomicNotify(AtomicNotify* curr); void visitAtomicFence(AtomicFence* curr); + void visitPause(Pause* curr); void visitSIMDExtract(SIMDExtract* curr); void visitSIMDReplace(SIMDReplace* curr); void visitSIMDShuffle(SIMDShuffle* curr); @@ -1269,6 +1270,12 @@ void FunctionValidator::visitAtomicFence(AtomicFence* curr) { "so AtomicFence's order should be 0"); } +void FunctionValidator::visitPause(Pause* curr) { + shouldBeTrue(getModule()->features.hasSharedEverything(), + curr, + "pause requires shared-everything [--enable-shared-everything]"); +} + void FunctionValidator::visitSIMDExtract(SIMDExtract* curr) { shouldBeTrue(getModule()->features.hasSIMD(), curr, diff --git a/test/lit/validation/pause.wast b/test/lit/validation/pause.wast new file mode 100644 index 00000000000..1a394d66c62 --- /dev/null +++ b/test/lit/validation/pause.wast @@ -0,0 +1,11 @@ +;; Test that shared pause requires shared-everything threads + +;; RUN: not wasm-opt %s 2>&1 | filecheck %s --check-prefix NO-SHARED +;; RUN: wasm-opt %s --enable-reference-types --enable-gc --enable-shared-everything -o - -S | filecheck %s --check-prefix SHARED + +;; NO-SHARED: pause requires shared-everything [--enable-shared-everything] +;; SHARED: (pause) + +(module + (func (pause)) +) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy